├── .editorconfig ├── .eslintrc ├── .gitignore ├── .travis.yml ├── README.md ├── bin └── install-wp-tests.sh ├── css ├── simplechart-insert.css ├── simplechart-insert.scss ├── style.css └── style.scss ├── js ├── README.md ├── app │ ├── 2.83926e3.chunk.js │ ├── 2.83926e3.chunk.js.map │ ├── app.83926e3.js │ ├── app.83926e3.js.map │ ├── widget.83926e3.js │ └── widget.83926e3.js.map └── plugin │ ├── build │ ├── plugin.js │ ├── plugin.js.map │ ├── postEdit.js │ ├── postEdit.js.map │ ├── simplechartInsert.js │ └── simplechartInsert.js.map │ └── src │ ├── plugin.js │ ├── post-edit.js │ ├── simplechart-controller.js │ ├── simplechart-insert.js │ ├── simplechart-item.js │ ├── simplechart-post-frame.js │ ├── simplechart-toolbar.js │ └── simplechart-view.js ├── license.txt ├── modules ├── class-simplechart-insert-template.php ├── class-simplechart-insert.php ├── class-simplechart-plugin-versions.php ├── class-simplechart-post-type.php ├── class-simplechart-request-handler.php ├── class-simplechart-save.php ├── class-simplechart-template.php └── class-simplechart-wp-cli.php ├── package.json ├── phpcs.xml ├── phpunit.xml ├── simplechart.php ├── templates ├── amp-iframe-source.php ├── amp-iframe.php ├── embed.php ├── iframe.php └── meta-box.php ├── tests ├── bootstrap.php ├── data │ ├── testcsv.txt │ ├── testjson.txt │ ├── testjson2.txt │ └── testpngdata.txt └── test-save-post-meta.php └── webpack.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | # WordPress Coding Standards 5 | # https://make.wordpress.org/core/handbook/coding-standards/ 6 | 7 | root = true 8 | 9 | [*] 10 | charset = utf-8 11 | end_of_line = lf 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | indent_style = tab 15 | 16 | [{.jshintrc,*.json,*.yml}] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [{*.txt,wp-config-sample.php}] 21 | end_of_line = crlf -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | // Extend the AirBnb lint config 3 | "extends": "airbnb", 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "ecmaFeatures": { 7 | "globalReturn": true, 8 | "impliedStrict": true, 9 | "jsx": true 10 | }, 11 | "sourceType": "module", 12 | }, 13 | "env": { 14 | "es6": true, 15 | "browser": true, 16 | "node": true, 17 | "jquery": true, 18 | // Optional Enables 19 | "webextensions": false, // Enable if using Web Extensions 20 | // Optional Testing Frameworks 21 | "jasmine": false, // Enable if using Jasmine testing framework 22 | "protractor": false, // Enable if using Protractor testing framework 23 | "mocha": false // Enable if using Mocha testing framework 24 | }, 25 | "globals": { 26 | "jQuery": true, 27 | "wp": true, 28 | "_": true, 29 | "angular": false, // Enable if using Angular 30 | }, 31 | // Do NOT change these rules 32 | "rules": { 33 | "indent": [2, 2, {"SwitchCase": 1}], 34 | "max-len": [2, 80, 4, { 35 | "ignoreComments": true, 36 | "ignoreUrls": true, 37 | }], 38 | "quotes": [2, "single"], // Allows template literals if they have substitutions or line breaks 39 | "semi": [2, "always"], 40 | "no-multiple-empty-lines": [2, {"max": 1}], 41 | "comma-dangle": [2, "always-multiline"], 42 | "dot-location": [2, "property"], 43 | "one-var": [2, "never"], 44 | "no-var": [2], // Stop using var, use const or let instead 45 | "prefer-const": ["error"], 46 | "no-bitwise": [2], 47 | "id-length": ["error", { 48 | "properties": "never", 49 | "exceptions": ["a", "b", "x", "y", "i", "e", "n", "k", "$"] 50 | }], 51 | "func-names": [1, "always"], // This aids in debugging 52 | "no-use-before-define": [2, "nofunc"], 53 | "yoda": [2, "always"], 54 | "object-curly-spacing": [2, "always"], 55 | "array-bracket-spacing": [2, "never"], 56 | "space-unary-ops": [2, {"words": true, "nonwords": true}], 57 | "keyword-spacing": ["error", {"after": true}], 58 | "space-before-blocks": [2, "always"], 59 | "space-in-parens": [2, "never"], 60 | "spaced-comment": [2, "always"], 61 | "no-confusing-arrow": ["error", {"allowParens": true}], // See eslint config for reasons 62 | "no-constant-condition": ["error"], 63 | "arrow-parens": ["error", "always"], 64 | "react/sort-comp": [0] 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .svn 3 | *.DS_Store* 4 | *.css.map 5 | css/.sass-cache/ 6 | github_token.txt 7 | node_modules 8 | updateAppFilenames.js 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Travis CI (MIT License) configuration file for WP Starter Theme 2 | # @link https://travis-ci.org/ 3 | 4 | # Declare project language. 5 | # @link http://about.travis-ci.org/docs/user/languages/php/ 6 | language: php 7 | 8 | # Specify when Travis should build. 9 | branches: 10 | only: 11 | - master 12 | 13 | cache: 14 | directories: 15 | - $HOME/.composer/cache 16 | - ./vendor 17 | 18 | # Git clone depth. 19 | git: 20 | depth: 1 21 | 22 | matrix: 23 | fast_finish: true 24 | 25 | include: 26 | - php: '5.6' 27 | env: WP_VERSION=latest PHP_LINT=1 28 | 29 | - php: '7.2' 30 | env: WP_VERSION=latest PHP_LINT=1 31 | - php: '7.2' 32 | env: WP_VERSION=trunk 33 | - php: '7.2' 34 | env: WP_VERSION=latest WP_PHPCS=1 WP_TRAVIS_OBJECT_CACHE=1 35 | 36 | - php: 'nightly' 37 | env: WP_VERSION=latest PHP_LINT=1 38 | 39 | allow_failures: 40 | - php: 'nightly' 41 | 42 | # Prepare your build for testing. 43 | # Failures in this section will result in build status 'errored'. 44 | before_script: 45 | # Turn off Xdebug. See https://core.trac.wordpress.org/changeset/40138. 46 | - phpenv config-rm xdebug.ini || echo "Xdebug not available" 47 | 48 | - export PATH="$HOME/.composer/vendor/bin:$PATH" 49 | 50 | # Couple the PHPUnit version to the PHP version. 51 | - | 52 | case "$TRAVIS_PHP_VERSION" in 53 | 7.2|7.0|nightly) 54 | echo "Using PHPUnit 6.1" 55 | composer global require "phpunit/phpunit=6.1.*" 56 | ;; 57 | 5.6) 58 | echo "Using PHPUnit 4.8" 59 | composer global require "phpunit/phpunit=4.8.*" 60 | ;; 61 | *) 62 | echo "No PHPUnit version handling for PHP version $TRAVIS_PHP_VERSION" 63 | exit 1 64 | ;; 65 | esac 66 | - og_dir="$(pwd)" 67 | - plugin_slug="$(basename $(pwd))" 68 | 69 | - | 70 | if [[ ! -z "$WP_VERSION" ]] ; then 71 | # Set up the WordPress installation. 72 | export WP_CORE_DIR=/tmp/wordpress/ 73 | bash bin/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION 74 | echo "define( 'JETPACK_DEV_DEBUG', true );" >> $WP_CORE_DIR/wp-tests-config.php 75 | # Maybe install memcached. 76 | if [[ "$WP_TRAVIS_OBJECT_CACHE" == "1" ]]; then 77 | curl https://raw.githubusercontent.com/tollmanz/wordpress-pecl-memcached-object-cache/584392b56dc4adbe52bd2c7b86f875e23a3e5f75/object-cache.php > $WP_CORE_DIR/wp-content/object-cache.php 78 | echo "extension = memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini 79 | fi 80 | # Set up the plugin. This assumes that this repo name matches the plugin name. 81 | mkdir -p "${WP_CORE_DIR}wp-content/plugins/$plugin_slug" 82 | cp -R . "${WP_CORE_DIR}wp-content/plugins/$plugin_slug/" 83 | # Hop into plugin's directory. 84 | cd ${WP_CORE_DIR}wp-content/plugins/$plugin_slug/ 85 | # For debugging. 86 | which phpunit 87 | phpunit --version 88 | fi 89 | # Set up phpcs. 90 | - | 91 | if [[ "$WP_PHPCS" == "1" ]] ; then 92 | composer global require automattic/vipwpcs 93 | phpcs --config-set installed_paths $HOME/.composer/vendor/wp-coding-standards/wpcs,$HOME/.composer/vendor/automattic/vipwpcs 94 | fi 95 | - pwd 96 | 97 | # Run test script commands. 98 | # Default is specific to project language. 99 | # All commands must exit with code 0 on success. Anything else is considered failure. 100 | script: 101 | # Search for PHP syntax errors. 102 | # 103 | # Only need to run this once per PHP version. 104 | - | 105 | if [[ "$PHP_LINT" == "1" ]] ; then 106 | find . -type "f" -iname "*.php" -not -path "./vendor/*" | xargs -L "1" php -l 107 | fi 108 | # WordPress Coding Standards. 109 | # 110 | # These are the same across PHP and WordPress, so we need to run them only once. 111 | # 112 | # @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards 113 | # @link http://pear.php.net/package/PHP_CodeSniffer/ 114 | - | 115 | if [[ "$WP_PHPCS" == "1" ]] ; then 116 | phpcs -n 117 | fi 118 | # Run the theme's unit tests, both in single and multisite. 119 | - | 120 | if [[ ! -z "$WP_VERSION" ]] ; then 121 | phpunit 122 | WP_MULTISITE=1 phpunit 123 | fi 124 | # Receive notifications for build results. 125 | # @link http://docs.travis-ci.com/user/notifications/#Email-notifications 126 | notifications: 127 | email: false 128 | 129 | # Xenial image has PHP versions 5.6,7.1,7.2 pre-installed 130 | dist: xenial 131 | 132 | # Xenial does not start mysql by default 133 | services: 134 | - mysql 135 | - memcached 136 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simplechart for WordPress 2 | 3 | Simplechart lets anyone quickly create interactive data visualizations that are easy to embed in any webpage. 4 | 5 | ### Technical overview 6 | 7 | The plugin sets up a custom post type for Charts and launches the [Simplechart JS app](https://github.com/alleyinteractive/simplechart) inside an iframe. After the user creates a chart through the JS app in the iframe, all the info needed to recreate it (data and settings/options) is sent via postMessage back to the parent page. Then it gets saved in postmeta when the WordPress post is saved. 8 | 9 | When the post is rendered on the front end, this same data and settings/options are used to bootstrap redrawing the same chart. 10 | 11 | ### Installation 12 | 13 | 1. Install and activate the Simplechart plugin 14 | 1. See [theme setup](https://github.com/alleyinteractive/wordpress-simplechart/wiki/Theme-Setup) tips 15 | 16 | ### Usage 17 | 18 | 1. Your WP Admin area should now have a custom post type for Charts. 19 | 1. Click the "Launch Simplechart App" button to create a new chart. 20 | 1. When you're happy with your new chart, click the green "Publish" button to store the chart in WordPress 21 | 1. You can now embed the Chart in any post by selecting it from the Charts section in the Media Manager, which will drop a shortcode into the post content. 22 | 23 | ### Local Development 24 | 25 | The Simplechart Dev Mode plugin in the [dev site repo](https://github.com/alleyinteractive/simplechart-dev-site) is the easiest way to work locally. 26 | 27 | There are instructions for local development on the [JS application](https://github.com/alleyinteractive/simplechart-dev-site#local-js-app-development) or the [WordPress plugin](https://github.com/alleyinteractive/simplechart-dev-site#local-wordpress-plugin-development). 28 | 29 | Additionally, you can load the Simplechart JS files from `localhost:8080` by: 30 | 31 | 1. Adding `define( 'SIMPLECHART_USE_LOCALHOST', true );` to your `wp-config.php` file. 32 | 1. Setting the `simplechart_use_localhost` filter to `true` 33 | 34 | ### AMP Considerations 35 | 36 | The plugin is compatible with [AMP](https://www.ampproject.org/) pages using the `amp-iframe` [element](https://github.com/ampproject/amphtml/blob/master/extensions/amp-iframe/amp-iframe.md). 37 | 38 | Determining when to render the AMP version is handled automatically if you're using the offical [WP AMP plugin](https://wordpress.org/plugins/amp/). If not, you'll need to use the `simplechart_is_amp_page` filter, like: 39 | 40 | ``` 41 | add_action( 'wp', function() { 42 | if ( my_check_if_this_is_an_amp_page() ) { 43 | add_filter( 'simplechart_is_amp_page', '__return_true' ); 44 | } 45 | } ); 46 | ``` 47 | 48 | AMP requires that the source document of the `amp-iframe` be served over https. _If your site does not support https_, you should disable Simplechart embeds on AMP pages with this snippet: 49 | 50 | ``` 51 | add_filter( 'simplechart_disable_amp', '__return_true' ); 52 | ``` 53 | 54 | Additionally, two actions fire while rendering the source document of the ``. 55 | 56 | `simplechart_iframe_head` and `simplechart_iframe_footer` fire inside the `` and before the closing `` tag, and take the chart's WordPress ID as their only parameter. You can use these actions to add your own custom CSS or JS to the AMP embed. 57 | 58 | Use the `simplechart_amp_iframe_placeholder` action to render any markup you need inside the AMP `placeholder` [element](https://github.com/ampproject/amphtml/blob/master/extensions/amp-iframe/amp-iframe.md#iframe-with-placeholder). 59 | 60 | ### Available WordPress actions and filters 61 | 62 | ##### simplechart_web_app_iframe_src 63 | 64 | Set the `src` attribute of the iframe for creating/editing charts in wp-admin. Defaults to menu page set up by `Simplechart_Post_Type::setup_iframe_page()` 65 | 66 | ##### simplechart_webpack_public_path 67 | 68 | URL of the directory where Webpack assets live. Used for loading chunks and other assets. [More info](https://webpack.github.io/docs/configuration.html#output-publicpath). 69 | 70 | ##### simplechart_vendor_js_url 71 | 72 | Set the URL of the JS bundle container vendor libraries. Defaults to the local static file. 73 | 74 | ##### simplechart_web_app_js_url 75 | 76 | Set the URL of the main JS app for building a chart. Defaults to the local static file. 77 | 78 | ##### simplechart_widget_loader_url 79 | 80 | Set the URL of the chart rendering widget. Defaults to the local static file. 81 | 82 | ##### simplechart_show_debug_messages 83 | 84 | Defaults to `false`. If `true`, the plugin will display extra debugging notices after you save a chart in WordPress admin. 85 | 86 | ##### simplechart_api_http_headers 87 | 88 | Apply any headers to the request made to Simplechart's API before rendering a chart in a front-end template. Useful for dev sites protected by `.htaccess` passwords. 89 | 90 | ##### simplechart_widget_template 91 | 92 | Use different markup structure when rendering a chart in a frontend template. The `.simplechart-*` classes are required to render the chart, title, subtitle, caption, and credit. 93 | 94 | ##### simplechart_widget_placeholder_text 95 | 96 | Text string to use while chart data is loading. If none is provided, will use the JS app's default `Loading`. 97 | 98 | ##### simplechart_use_localhost 99 | 100 | Defaults to false. If true, will load the app and widget from `localhost:8080` 101 | 102 | ##### simplechart_chart_options_override 103 | 104 | Set defaults for NVD3. This is where you'd set a custom palette using the `color` as an array key. 105 | 106 | ##### simplechart_chart_default_metadata 107 | 108 | Set default values for the title, subtitle, caption, or credit. 109 | 110 | The `subtitle` field is **disabled** by default. You can use this filter to enable the field and set a default string value as with any of the other metadata. 111 | 112 | ##### simplechart_enable_subtitle_field 113 | 114 | Returning a truthy value for this filter will enable the subtitle field (which is disabled by default) without setting a default value. 115 | 116 | ##### simplechart_is_amp_page 117 | 118 | See [AMP considerations](#amp-considerations). 119 | 120 | ##### simplechart_disable_amp 121 | 122 | See [AMP considerations](#amp-considerations). 123 | 124 | ##### simplechart_iframe_head 125 | 126 | See [AMP considerations](#amp-considerations). 127 | 128 | ##### simplechart_iframe_footer 129 | 130 | See [AMP considerations](#amp-considerations). 131 | 132 | ##### simplechart_amp_iframe_placeholder 133 | 134 | See [AMP considerations](#amp-considerations). 135 | -------------------------------------------------------------------------------- /bin/install-wp-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ $# -lt 3 ]; then 4 | echo "usage: $0 [db-host] [wp-version] [skip-database-creation]" 5 | exit 1 6 | fi 7 | 8 | DB_NAME=$1 9 | DB_USER=$2 10 | DB_PASS=$3 11 | DB_HOST=${4-localhost} 12 | WP_VERSION=${5-latest} 13 | SKIP_DB_CREATE=${6-false} 14 | 15 | TMPDIR=${TMPDIR-/tmp} 16 | TMPDIR=$(echo $TMPDIR | sed -e "s/\/$//") 17 | WP_TESTS_DIR=${WP_TESTS_DIR-$TMPDIR/wordpress-tests-lib} 18 | WP_CORE_DIR=${WP_CORE_DIR-$TMPDIR/wordpress/} 19 | 20 | download() { 21 | if [ `which curl` ]; then 22 | curl -s "$1" > "$2"; 23 | elif [ `which wget` ]; then 24 | wget -nv -O "$2" "$1" 25 | fi 26 | } 27 | 28 | if [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+$ ]]; then 29 | WP_TESTS_TAG="branches/$WP_VERSION" 30 | elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then 31 | if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then 32 | # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x 33 | WP_TESTS_TAG="tags/${WP_VERSION%??}" 34 | else 35 | WP_TESTS_TAG="tags/$WP_VERSION" 36 | fi 37 | elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then 38 | WP_TESTS_TAG="trunk" 39 | else 40 | # http serves a single offer, whereas https serves multiple. we only want one 41 | download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json 42 | grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json 43 | LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//') 44 | if [[ -z "$LATEST_VERSION" ]]; then 45 | echo "Latest WordPress version could not be found" 46 | exit 1 47 | fi 48 | WP_TESTS_TAG="tags/$LATEST_VERSION" 49 | fi 50 | 51 | set -ex 52 | 53 | install_wp() { 54 | 55 | if [ -d $WP_CORE_DIR ]; then 56 | return; 57 | fi 58 | 59 | mkdir -p $WP_CORE_DIR 60 | 61 | if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then 62 | mkdir -p $TMPDIR/wordpress-nightly 63 | download https://wordpress.org/nightly-builds/wordpress-latest.zip $TMPDIR/wordpress-nightly/wordpress-nightly.zip 64 | unzip -q $TMPDIR/wordpress-nightly/wordpress-nightly.zip -d $TMPDIR/wordpress-nightly/ 65 | mv $TMPDIR/wordpress-nightly/wordpress/* $WP_CORE_DIR 66 | else 67 | if [ $WP_VERSION == 'latest' ]; then 68 | local ARCHIVE_NAME='latest' 69 | elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+ ]]; then 70 | # https serves multiple offers, whereas http serves single. 71 | download https://api.wordpress.org/core/version-check/1.7/ $TMPDIR/wp-latest.json 72 | if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then 73 | # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x 74 | LATEST_VERSION=${WP_VERSION%??} 75 | else 76 | # otherwise, scan the releases and get the most up to date minor version of the major release 77 | local VERSION_ESCAPED=`echo $WP_VERSION | sed 's/\./\\\\./g'` 78 | LATEST_VERSION=$(grep -o '"version":"'$VERSION_ESCAPED'[^"]*' $TMPDIR/wp-latest.json | sed 's/"version":"//' | head -1) 79 | fi 80 | if [[ -z "$LATEST_VERSION" ]]; then 81 | local ARCHIVE_NAME="wordpress-$WP_VERSION" 82 | else 83 | local ARCHIVE_NAME="wordpress-$LATEST_VERSION" 84 | fi 85 | else 86 | local ARCHIVE_NAME="wordpress-$WP_VERSION" 87 | fi 88 | download https://wordpress.org/${ARCHIVE_NAME}.tar.gz $TMPDIR/wordpress.tar.gz 89 | tar --strip-components=1 -zxmf $TMPDIR/wordpress.tar.gz -C $WP_CORE_DIR 90 | fi 91 | 92 | download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php 93 | } 94 | 95 | install_test_suite() { 96 | # portable in-place argument for both GNU sed and Mac OSX sed 97 | if [[ $(uname -s) == 'Darwin' ]]; then 98 | local ioption='-i.bak' 99 | else 100 | local ioption='-i' 101 | fi 102 | 103 | # set up testing suite if it doesn't yet exist 104 | if [ ! -d $WP_TESTS_DIR ]; then 105 | # set up testing suite 106 | mkdir -p $WP_TESTS_DIR 107 | svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes 108 | svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data 109 | fi 110 | 111 | if [ ! -f wp-tests-config.php ]; then 112 | download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php 113 | # remove all forward slashes in the end 114 | WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::") 115 | sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php 116 | sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php 117 | sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php 118 | sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php 119 | sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php 120 | fi 121 | 122 | } 123 | 124 | install_db() { 125 | 126 | if [ ${SKIP_DB_CREATE} = "true" ]; then 127 | return 0 128 | fi 129 | 130 | # parse DB_HOST for port or socket references 131 | local PARTS=(${DB_HOST//\:/ }) 132 | local DB_HOSTNAME=${PARTS[0]}; 133 | local DB_SOCK_OR_PORT=${PARTS[1]}; 134 | local EXTRA="" 135 | 136 | if ! [ -z $DB_HOSTNAME ] ; then 137 | if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then 138 | EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp" 139 | elif ! [ -z $DB_SOCK_OR_PORT ] ; then 140 | EXTRA=" --socket=$DB_SOCK_OR_PORT" 141 | elif ! [ -z $DB_HOSTNAME ] ; then 142 | EXTRA=" --host=$DB_HOSTNAME --protocol=tcp" 143 | fi 144 | fi 145 | 146 | # create database 147 | mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA 148 | } 149 | 150 | install_wp 151 | install_test_suite 152 | install_db 153 | -------------------------------------------------------------------------------- /css/simplechart-insert.css: -------------------------------------------------------------------------------- 1 | .simplechart-all { 2 | background: #fff; 3 | } 4 | .simplechart-all .simplechart-items { 5 | padding-top: 10px; 6 | } 7 | .simplechart-all .simplechart-item-area { 8 | padding: 10px; 9 | border: none !important; 10 | } 11 | .simplechart-all .simplechart-item-area.selected { 12 | webkit-box-shadow: inset 0 0 0 3px #fff, inset 0 0 0 7px #1e8cbe; 13 | box-shadow: inset 0 0 0 3px #fff, inset 0 0 0 7px #1e8cbe; 14 | } 15 | .simplechart-all .simplechart-item-area.selected a.check { 16 | display: block; 17 | } 18 | .simplechart-all .simplechart-item-container { 19 | max-width: 150px; 20 | } 21 | .simplechart-all .simplechart-item-container h3 { 22 | margin: .25em 0; 23 | } 24 | .simplechart-all .simplechart-item { 25 | cursor: pointer; 26 | border: 1px solid #fff; 27 | } 28 | .simplechart-all .simplechart-item:hover { 29 | border-color: #999; 30 | } 31 | .simplechart-all .simplechart-item.selected { 32 | border-color: transparent; 33 | } 34 | .simplechart-all li.simplechart-item { 35 | max-height: 250px !important; 36 | overflow-y: hidden !important; 37 | border: none !important; 38 | } 39 | .simplechart-all .simplechart-error { 40 | background: #fdd; 41 | padding: 12px 16px; 42 | margin: 0 0 10px 0; 43 | bottom: auto; 44 | display: none; 45 | } 46 | .simplechart-all .simplechart-empty { 47 | background: #ffd; 48 | padding: 12px 16px; 49 | margin: 0 0 10px 0; 50 | bottom: auto; 51 | display: none; 52 | } 53 | .simplechart-all .simplechart-toolbar { 54 | background: #f5f5f5; 55 | padding: 0; 56 | right: 0; 57 | } 58 | .simplechart-all .simplechart-toolbar .simplechart-input-search { 59 | margin-left: 10px; 60 | } 61 | .simplechart-all .simplechart-toolbar label { 62 | margin: 7px; 63 | color: #555; 64 | } 65 | .simplechart-all .simplechart-toolbar select { 66 | margin: 1px; 67 | padding: 6px 6px 5px; 68 | height: 2.5em; 69 | } 70 | .simplechart-all .simplechart-toolbar .button { 71 | margin: 1px; 72 | } 73 | .simplechart-all .simplechart-toolbar .spinner { 74 | float: left; 75 | margin: 7px 10px; 76 | } 77 | .simplechart-all .simplechart-toolbar-container { 78 | padding: 10px 9px; 79 | } 80 | .simplechart-all .attachments { 81 | right: 0; 82 | } 83 | .simplechart-all .simplechart-toolbar label, 84 | .simplechart-all .simplechart-toolbar select, 85 | .simplechart-all .simplechart-toolbar input { 86 | float: left; 87 | } 88 | .simplechart-all .simplechart-pagination .spinner { 89 | float: left; 90 | margin: 3px 10px; 91 | } 92 | .simplechart-all .clearfix:after { 93 | clear: both; 94 | content: "."; 95 | display: block; 96 | height: 0; 97 | visibility: hidden; 98 | } 99 | .simplechart-all .button.simplechart-pagination { 100 | margin-top: 15px; 101 | } 102 | 103 | html.ie8 .simplechart-content .simplechart-item.selected { 104 | border-color: #999; 105 | } 106 | 107 | body.mp6 .simplechart-content .simplechart-toolbar { 108 | background: #fff; 109 | border-bottom: 1px solid #ddd; 110 | } 111 | -------------------------------------------------------------------------------- /css/simplechart-insert.scss: -------------------------------------------------------------------------------- 1 | .simplechart-all { 2 | 3 | background: #fff; 4 | 5 | .simplechart-items { 6 | padding-top: 10px; 7 | } 8 | 9 | .simplechart-item-area { 10 | padding: 10px; 11 | border: none !important; 12 | 13 | &.selected { 14 | webkit-box-shadow: inset 0 0 0 3px #fff, inset 0 0 0 7px #1e8cbe; 15 | box-shadow: inset 0 0 0 3px #fff, inset 0 0 0 7px #1e8cbe; 16 | 17 | a.check { 18 | display: block; 19 | } 20 | } 21 | } 22 | 23 | .simplechart-item-container { 24 | max-width: 150px; 25 | 26 | h3 { 27 | margin: .25em 0; 28 | } 29 | } 30 | 31 | .simplechart-item { 32 | cursor: pointer; 33 | border: 1px solid #fff; 34 | } 35 | 36 | .simplechart-item:hover { 37 | border-color: #999; 38 | } 39 | 40 | .simplechart-item.selected { 41 | border-color: transparent; 42 | } 43 | 44 | li.simplechart-item { 45 | max-height: 250px !important; 46 | overflow-y: hidden !important; 47 | border: none !important; 48 | } 49 | 50 | .simplechart-error { 51 | background: #fdd; 52 | padding: 12px 16px; 53 | margin: 0 0 10px 0; 54 | bottom: auto; 55 | display: none; 56 | } 57 | 58 | .simplechart-empty { 59 | background: #ffd; 60 | padding: 12px 16px; 61 | margin: 0 0 10px 0; 62 | bottom: auto; 63 | display: none; 64 | } 65 | 66 | .simplechart-toolbar { 67 | background: #f5f5f5; 68 | padding: 0; 69 | right: 0; 70 | 71 | .simplechart-input-search { 72 | margin-left: 10px; 73 | } 74 | label{ 75 | margin: 7px; 76 | color: #555; 77 | } 78 | select { 79 | margin: 1px; 80 | padding: 6px 6px 5px; 81 | height: 2.5em; 82 | } 83 | .button { 84 | margin: 1px; 85 | } 86 | .spinner { 87 | float: left; 88 | margin: 7px 10px; 89 | } 90 | } 91 | 92 | .simplechart-toolbar-container { 93 | padding: 10px 9px; 94 | } 95 | 96 | .attachments { 97 | right: 0; 98 | } 99 | 100 | .simplechart-toolbar label, 101 | .simplechart-toolbar select, 102 | .simplechart-toolbar input { 103 | float: left; 104 | } 105 | 106 | .simplechart-pagination .spinner { 107 | float: left; 108 | margin: 3px 10px; 109 | } 110 | 111 | .clearfix:after { 112 | clear: both; 113 | content: "."; 114 | display: block; 115 | height: 0; 116 | visibility: hidden; 117 | } 118 | 119 | .button.simplechart-pagination { 120 | margin-top: 15px; 121 | } 122 | } 123 | 124 | html.ie8 .simplechart-content .simplechart-item.selected { 125 | border-color: #999; 126 | } 127 | 128 | body.mp6 .simplechart-content .simplechart-toolbar { 129 | background: #fff; 130 | border-bottom: 1px solid #ddd; 131 | } -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | #simplechart-modal { 2 | position: fixed; 3 | top: 30px; 4 | left: 30px; 5 | right: 30px; 6 | bottom: 30px; 7 | z-index: 160000; 8 | background: white; 9 | overflow-y: scroll; 10 | display: none; } 11 | #simplechart-modal #simplechart-close { 12 | position: absolute; 13 | text-decoration: none; 14 | top: 5px; 15 | right: 10px; 16 | z-index: 1000; } 17 | #simplechart-modal iframe { 18 | width: 100%; 19 | height: 100%; 20 | box-sizing: border-box; 21 | padding-top: 30px; } 22 | 23 | #simplechart-backdrop { 24 | position: fixed; 25 | top: 0; 26 | left: 0; 27 | right: 0; 28 | bottom: 0; 29 | min-height: 360px; 30 | background: #000; 31 | opacity: .7; 32 | z-index: 159900; 33 | display: none; } 34 | 35 | .simplechart-preview-heading { 36 | padding-bottom: 10px; 37 | border-bottom: 1px solid black; 38 | margin-bottom: 10px; } 39 | 40 | /*# sourceMappingURL=style.css.map */ 41 | -------------------------------------------------------------------------------- /css/style.scss: -------------------------------------------------------------------------------- 1 | #simplechart-modal { 2 | position: fixed; 3 | top: 30px; 4 | left: 30px; 5 | right: 30px; 6 | bottom: 30px; 7 | z-index: 160000; 8 | background: white; 9 | overflow-y: scroll; 10 | display: none; // hide on init 11 | 12 | #simplechart-close { 13 | position: absolute; 14 | text-decoration: none; 15 | top: 5px; 16 | right: 10px; 17 | z-index: 1000; 18 | } 19 | 20 | iframe { 21 | width: 100%; 22 | height: 100%; 23 | box-sizing: border-box; 24 | padding-top: 30px; 25 | } 26 | } 27 | 28 | #simplechart-backdrop { 29 | position: fixed; 30 | top: 0; 31 | left: 0; 32 | right: 0; 33 | bottom: 0; 34 | min-height: 360px; 35 | background: #000; 36 | opacity: .7; 37 | z-index: 159900; 38 | display: none; // hide on init 39 | } 40 | 41 | .simplechart-preview-heading { 42 | padding-bottom: 10px; 43 | border-bottom: 1px solid black; 44 | margin-bottom: 10px; 45 | } 46 | -------------------------------------------------------------------------------- /js/README.md: -------------------------------------------------------------------------------- 1 | # Simplechart JS app 2 | 3 | This directory contains two compiled JavaScript files: 4 | 5 | 1. `app/app.js` is the chart-building JS app. 6 | 2. `app/widget.js` renders charts in front-end templates. 7 | 8 | The source code for these files is in the [Simplechart](https://github.com/alleyinteractive/simplechart) repository, which is also licensed under GPL v2. -------------------------------------------------------------------------------- /js/plugin/build/plugin.js: -------------------------------------------------------------------------------- 1 | /******/ (function(modules) { // webpackBootstrap 2 | /******/ // The module cache 3 | /******/ var installedModules = {}; 4 | /******/ 5 | /******/ // The require function 6 | /******/ function __webpack_require__(moduleId) { 7 | /******/ 8 | /******/ // Check if module is in cache 9 | /******/ if(installedModules[moduleId]) { 10 | /******/ return installedModules[moduleId].exports; 11 | /******/ } 12 | /******/ // Create a new module (and put it into the cache) 13 | /******/ var module = installedModules[moduleId] = { 14 | /******/ i: moduleId, 15 | /******/ l: false, 16 | /******/ exports: {} 17 | /******/ }; 18 | /******/ 19 | /******/ // Execute the module function 20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 21 | /******/ 22 | /******/ // Flag the module as loaded 23 | /******/ module.l = true; 24 | /******/ 25 | /******/ // Return the exports of the module 26 | /******/ return module.exports; 27 | /******/ } 28 | /******/ 29 | /******/ 30 | /******/ // expose the modules object (__webpack_modules__) 31 | /******/ __webpack_require__.m = modules; 32 | /******/ 33 | /******/ // expose the module cache 34 | /******/ __webpack_require__.c = installedModules; 35 | /******/ 36 | /******/ // identity function for calling harmony imports with the correct context 37 | /******/ __webpack_require__.i = function(value) { return value; }; 38 | /******/ 39 | /******/ // define getter function for harmony exports 40 | /******/ __webpack_require__.d = function(exports, name, getter) { 41 | /******/ if(!__webpack_require__.o(exports, name)) { 42 | /******/ Object.defineProperty(exports, name, { 43 | /******/ configurable: false, 44 | /******/ enumerable: true, 45 | /******/ get: getter 46 | /******/ }); 47 | /******/ } 48 | /******/ }; 49 | /******/ 50 | /******/ // getDefaultExport function for compatibility with non-harmony modules 51 | /******/ __webpack_require__.n = function(module) { 52 | /******/ var getter = module && module.__esModule ? 53 | /******/ function getDefault() { return module['default']; } : 54 | /******/ function getModuleExports() { return module; }; 55 | /******/ __webpack_require__.d(getter, 'a', getter); 56 | /******/ return getter; 57 | /******/ }; 58 | /******/ 59 | /******/ // Object.prototype.hasOwnProperty.call 60 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 61 | /******/ 62 | /******/ // __webpack_public_path__ 63 | /******/ __webpack_require__.p = ""; 64 | /******/ 65 | /******/ // Load entry module and return exports 66 | /******/ return __webpack_require__(__webpack_require__.s = 5); 67 | /******/ }) 68 | /************************************************************************/ 69 | /******/ ({ 70 | 71 | /***/ 5: 72 | /***/ (function(module, exports, __webpack_require__) { 73 | 74 | "use strict"; 75 | 76 | 77 | /* eslint-disable */ 78 | /** 79 | * On the plugins page, make sure we don't accidentally 80 | * try to update Simplechart as if it was hosted on WP.org 81 | */ 82 | jQuery(document).ready(function ($) { 83 | if ('plugins' !== window.pagenow) { 84 | return; 85 | } 86 | 87 | var simplechartInput = $('input[value="wordpress-simplechart/simplechart.php"]'); 88 | var disabledForBulkActions = false; 89 | 90 | /** 91 | * Uncheck and disable Simplechart for Bulk Actions on Plugins page 92 | */ 93 | function _disableForBulkActions() { 94 | if (simplechartInput.length) { 95 | simplechartInput.attr({ disabled: true, checked: false }).css('cursor', 'default'); 96 | disabledForBulkActions = true; 97 | } 98 | } 99 | 100 | /** 101 | * Enable Simplechart for Bulk Actions on Plugins page 102 | * 103 | * @param bool checked Whether it should be checked 104 | */ 105 | function _enableForBulkActions(checked) { 106 | if (simplechartInput.length) { 107 | simplechartInput.attr({ disabled: false, checked: checked }).css('cursor', 'pointer'); 108 | disabledForBulkActions = false; 109 | } 110 | } 111 | 112 | if (simplechartInput.length) { 113 | 114 | // Make sure Simplechart is unchecked if Update is selected as Bulk Action 115 | $('.bulkactions [name^="action"]').on('change', function (evt) { 116 | if ('update-selected' === $(evt.target).val()) { 117 | // If Update is selected, uncheck and disable 118 | _disableForBulkActions(); 119 | } else if (disabledForBulkActions) { 120 | // If changing from Update to another Bulk Action, 121 | // un-disable and fallback to "check all" checkbox value 122 | var allChecked = 'undefined' !== typeof $('#cb-select-all-1').attr('checked'); 123 | _enableForBulkActions(allChecked); 124 | } 125 | }); 126 | 127 | // Make extra sure Simplechart is unchecked when applying bulk action to Update 128 | $('.bulkactions [type="submit"]').on('click', function (evt) { 129 | var select = $(evt.target).siblings('select').first(); 130 | if (select.length && 'update-selected' === select.val()) { 131 | _disableForBulkActions(); 132 | } 133 | }); 134 | } 135 | }); 136 | 137 | /***/ }) 138 | 139 | /******/ }); 140 | //# sourceMappingURL=plugin.js.map -------------------------------------------------------------------------------- /js/plugin/build/plugin.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack:///webpack/bootstrap e7c8b2ca5f14e16cc310?8ebb*","webpack:///./js/plugin/src/plugin.js"],"names":["jQuery","document","ready","$","window","pagenow","simplechartInput","disabledForBulkActions","_disableForBulkActions","length","attr","disabled","checked","css","_enableForBulkActions","on","evt","target","val","allChecked","select","siblings","first"],"mappings":";AAAA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;;AAGA;AACA;;AAEA;AACA;;AAEA;AACA,mDAA2C,cAAc;;AAEzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAK;AACL;AACA;;AAEA;AACA;AACA;AACA,mCAA2B,0BAA0B,EAAE;AACvD,yCAAiC,eAAe;AAChD;AACA;AACA;;AAEA;AACA,8DAAsD,+DAA+D;;AAErH;AACA;;AAEA;AACA;;;;;;;;;;;AChEA;AACA;;;;AAIAA,OAAQC,QAAR,EAAmBC,KAAnB,CAA0B,UAAUC,CAAV,EAAc;AACvC,KAAK,cAAcC,OAAOC,OAA1B,EAAoC;AACnC;AACA;;AAED,KAAIC,mBAAmBH,EAAG,sDAAH,CAAvB;AACA,KAAII,yBAAyB,KAA7B;;AAEA;;;AAGA,UAASC,sBAAT,GAAkC;AACjC,MAAKF,iBAAiBG,MAAtB,EAA+B;AAC9BH,oBAAiBI,IAAjB,CAAuB,EAAEC,UAAU,IAAZ,EAAkBC,SAAS,KAA3B,EAAvB,EAA4DC,GAA5D,CAAiE,QAAjE,EAA2E,SAA3E;AACAN,4BAAyB,IAAzB;AACA;AACD;;AAED;;;;;AAKA,UAASO,qBAAT,CAAgCF,OAAhC,EAA0C;AACzC,MAAKN,iBAAiBG,MAAtB,EAA+B;AAC9BH,oBAAiBI,IAAjB,CAAuB,EAAEC,UAAU,KAAZ,EAAmBC,SAASA,OAA5B,EAAvB,EAA+DC,GAA/D,CAAoE,QAApE,EAA8E,SAA9E;AACAN,4BAAyB,KAAzB;AACA;AACD;;AAED,KAAKD,iBAAiBG,MAAtB,EAA+B;;AAE9B;AACAN,IAAG,+BAAH,EAAqCY,EAArC,CAAyC,QAAzC,EAAmD,UAAUC,GAAV,EAAgB;AAClE,OAAK,sBAAsBb,EAAGa,IAAIC,MAAP,EAAgBC,GAAhB,EAA3B,EAAmD;AAClD;AACAV;AACA,IAHD,MAGO,IAAKD,sBAAL,EAA8B;AACpC;AACA;AACA,QAAIY,aAAa,gBAAgB,OAAOhB,EAAG,kBAAH,EAAwBO,IAAxB,CAA8B,SAA9B,CAAxC;AACAI,0BAAuBK,UAAvB;AACA;AACD,GAVD;;AAYA;AACAhB,IAAG,8BAAH,EAAoCY,EAApC,CAAwC,OAAxC,EAAiD,UAAUC,GAAV,EAAgB;AAChE,OAAII,SAASjB,EAAGa,IAAIC,MAAP,EAAgBI,QAAhB,CAA0B,QAA1B,EAAqCC,KAArC,EAAb;AACA,OAAKF,OAAOX,MAAP,IAAiB,sBAAsBW,OAAOF,GAAP,EAA5C,EAA2D;AAC1DV;AACA;AACD,GALD;AAMA;AACD,CArDD,E","file":"plugin.js","sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// identity function for calling harmony imports with the correct context\n \t__webpack_require__.i = function(value) { return value; };\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 5);\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap e7c8b2ca5f14e16cc310","/* eslint-disable */\n/**\n * On the plugins page, make sure we don't accidentally\n * try to update Simplechart as if it was hosted on WP.org\n */\njQuery( document ).ready( function( $ ) {\n\tif ( 'plugins' !== window.pagenow ) {\n\t\treturn;\n\t}\n\n\tvar simplechartInput = $( 'input[value=\"wordpress-simplechart/simplechart.php\"]' );\n\tvar disabledForBulkActions = false;\n\n\t/**\n\t * Uncheck and disable Simplechart for Bulk Actions on Plugins page\n\t */\n\tfunction _disableForBulkActions() {\n\t\tif ( simplechartInput.length ) {\n\t\t\tsimplechartInput.attr( { disabled: true, checked: false } ).css( 'cursor', 'default' );\n\t\t\tdisabledForBulkActions = true;\n\t\t}\n\t}\n\n\t/**\n\t * Enable Simplechart for Bulk Actions on Plugins page\n\t *\n\t * @param bool checked Whether it should be checked\n\t */\n\tfunction _enableForBulkActions( checked ) {\n\t\tif ( simplechartInput.length ) {\n\t\t\tsimplechartInput.attr( { disabled: false, checked: checked } ).css( 'cursor', 'pointer' );\n\t\t\tdisabledForBulkActions = false;\n\t\t}\n\t}\n\n\tif ( simplechartInput.length ) {\n\n\t\t// Make sure Simplechart is unchecked if Update is selected as Bulk Action\n\t\t$( '.bulkactions [name^=\"action\"]' ).on( 'change', function( evt ) {\n\t\t\tif ( 'update-selected' === $( evt.target ).val() ) {\n\t\t\t\t// If Update is selected, uncheck and disable\n\t\t\t\t_disableForBulkActions()\n\t\t\t} else if ( disabledForBulkActions ) {\n\t\t\t\t// If changing from Update to another Bulk Action,\n\t\t\t\t// un-disable and fallback to \"check all\" checkbox value\n\t\t\t\tvar allChecked = 'undefined' !== typeof $( '#cb-select-all-1' ).attr( 'checked' );\n\t\t\t\t_enableForBulkActions( allChecked );\n\t\t\t}\n\t\t} );\n\n\t\t// Make extra sure Simplechart is unchecked when applying bulk action to Update\n\t\t$( '.bulkactions [type=\"submit\"]' ).on( 'click', function( evt ) {\n\t\t\tvar select = $( evt.target ).siblings( 'select' ).first();\n\t\t\tif ( select.length && 'update-selected' === select.val() ) {\n\t\t\t\t_disableForBulkActions();\n\t\t\t}\n\t\t} );\n\t}\n});\n\n\n\n// WEBPACK FOOTER //\n// ./js/plugin/src/plugin.js"],"sourceRoot":""} -------------------------------------------------------------------------------- /js/plugin/build/postEdit.js: -------------------------------------------------------------------------------- 1 | /******/ (function(modules) { // webpackBootstrap 2 | /******/ // The module cache 3 | /******/ var installedModules = {}; 4 | /******/ 5 | /******/ // The require function 6 | /******/ function __webpack_require__(moduleId) { 7 | /******/ 8 | /******/ // Check if module is in cache 9 | /******/ if(installedModules[moduleId]) { 10 | /******/ return installedModules[moduleId].exports; 11 | /******/ } 12 | /******/ // Create a new module (and put it into the cache) 13 | /******/ var module = installedModules[moduleId] = { 14 | /******/ i: moduleId, 15 | /******/ l: false, 16 | /******/ exports: {} 17 | /******/ }; 18 | /******/ 19 | /******/ // Execute the module function 20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 21 | /******/ 22 | /******/ // Flag the module as loaded 23 | /******/ module.l = true; 24 | /******/ 25 | /******/ // Return the exports of the module 26 | /******/ return module.exports; 27 | /******/ } 28 | /******/ 29 | /******/ 30 | /******/ // expose the modules object (__webpack_modules__) 31 | /******/ __webpack_require__.m = modules; 32 | /******/ 33 | /******/ // expose the module cache 34 | /******/ __webpack_require__.c = installedModules; 35 | /******/ 36 | /******/ // identity function for calling harmony imports with the correct context 37 | /******/ __webpack_require__.i = function(value) { return value; }; 38 | /******/ 39 | /******/ // define getter function for harmony exports 40 | /******/ __webpack_require__.d = function(exports, name, getter) { 41 | /******/ if(!__webpack_require__.o(exports, name)) { 42 | /******/ Object.defineProperty(exports, name, { 43 | /******/ configurable: false, 44 | /******/ enumerable: true, 45 | /******/ get: getter 46 | /******/ }); 47 | /******/ } 48 | /******/ }; 49 | /******/ 50 | /******/ // getDefaultExport function for compatibility with non-harmony modules 51 | /******/ __webpack_require__.n = function(module) { 52 | /******/ var getter = module && module.__esModule ? 53 | /******/ function getDefault() { return module['default']; } : 54 | /******/ function getModuleExports() { return module; }; 55 | /******/ __webpack_require__.d(getter, 'a', getter); 56 | /******/ return getter; 57 | /******/ }; 58 | /******/ 59 | /******/ // Object.prototype.hasOwnProperty.call 60 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 61 | /******/ 62 | /******/ // __webpack_public_path__ 63 | /******/ __webpack_require__.p = ""; 64 | /******/ 65 | /******/ // Load entry module and return exports 66 | /******/ return __webpack_require__(__webpack_require__.s = 6); 67 | /******/ }) 68 | /************************************************************************/ 69 | /******/ ({ 70 | 71 | /***/ 6: 72 | /***/ (function(module, exports, __webpack_require__) { 73 | 74 | "use strict"; 75 | 76 | 77 | /* eslint-disable */ 78 | /** 79 | * Open, close, and communicate with the iframe containing the app 80 | */ 81 | 82 | function WPSimplechartApp($) { 83 | // setup scoped vars 84 | var appUrl, 85 | modalElements = { 86 | container: '
', 87 | backdrop: '
' 88 | }, 89 | confirmNoDataMessage = '', 90 | closeModalMessage = '', 91 | savedChart = false; 92 | 93 | // do not update directly! 94 | var __postShouldPublish = true; 95 | 96 | function setPostShouldPublish(newValue) { 97 | __postShouldPublish = newValue; 98 | } 99 | 100 | function shouldPostPublish() { 101 | // use !! to always return boolean 102 | return !!__postShouldPublish; 103 | } 104 | 105 | /** 106 | * True if adding a new chart; false if editing a chart 107 | */ 108 | function addingNewChart() { 109 | return 'post-new.php' === window.location.pathname.split('/').pop(); 110 | } 111 | 112 | /** 113 | * Set scope var values and build modal element 114 | */ 115 | function init() { 116 | appUrl = window.WPSimplechartContainer.appUrl.toString(); 117 | confirmNoDataMessage = window.WPSimplechartContainer.confirmNoDataMessage.toString(); 118 | closeModalMessage = window.WPSimplechartContainer.closeModalMessage.toString(); 119 | window.addEventListener('message', onReceiveMessage); 120 | renderModal(); 121 | } 122 | 123 | /** 124 | * Renders the app iframe modal in its open state 125 | */ 126 | function renderModal() { 127 | // create modal elements and append to 128 | modalElements.container = modalElements.container.replace('{{iframeSrc}}', appUrl); 129 | modalElements.container = modalElements.container.replace('{{closeModal}}', closeModalMessage); 130 | $('body').append(modalElements.container + modalElements.backdrop); 131 | 132 | // Listen for click to open modal 133 | $('#simplechart-launch').click(openModal); 134 | 135 | // Open modal if creating new chart 136 | if (addingNewChart()) { 137 | openModal(); 138 | } 139 | } 140 | 141 | /** 142 | * Reopens already-rendered modal 143 | */ 144 | function openModal() { 145 | $('#simplechart-backdrop, #simplechart-modal').show(); 146 | } 147 | 148 | /** 149 | * Hide modal 150 | */ 151 | function hideModal() { 152 | $('#simplechart-backdrop, #simplechart-modal').hide(); 153 | } 154 | 155 | /** 156 | * Extract messageType string when a postMessage is received 157 | */ 158 | function getMessageType(evt) { 159 | // confirm same-origin or http(s)://localhost:8080 160 | if (evt.origin !== window.location.origin && !/https?:\/\/localhost:8080/.test(evt.origin)) { 161 | return false; 162 | } 163 | 164 | var messageType; 165 | try { 166 | messageType = evt.data.messageType; 167 | } catch (err) { 168 | throw err; 169 | } 170 | 171 | return messageType; 172 | } 173 | 174 | /** 175 | * adds listeners for specific messageType from child window 176 | */ 177 | function onReceiveMessage(evt) { 178 | var messageType = getMessageType(evt); 179 | if (!messageType) { 180 | return; 181 | } 182 | 183 | switch (messageType) { 184 | case 'appReady': 185 | sendToApp(WPSimplechartBootstrap.isNewChart ? 'bootstrap.new' : 'bootstrap.edit', parseBootstrapData()); 186 | break; 187 | 188 | case 'closeApp': 189 | hideModal(); 190 | break; 191 | 192 | case 'saveChart': 193 | saveChart(evt.data.data); 194 | break; 195 | 196 | default: 197 | // nothing 198 | } 199 | } 200 | 201 | /** 202 | * Send previously saved data to child window 203 | */ 204 | function sendToApp(messageType, messageData) { 205 | var childWindow = document.getElementById('simplechart-frame'); 206 | if (!childWindow || !childWindow.contentWindow) { 207 | throw new Error('Missing iframe#simplechart-frame'); 208 | } 209 | 210 | childWindow.contentWindow.postMessage({ 211 | messageType: messageType, 212 | data: messageData 213 | }, '*'); 214 | } 215 | 216 | /** 217 | * Build data object to send to chart editor window, parsing stringified JSON as needed 218 | */ 219 | function parseBootstrapData() { 220 | // WPSimplechartBootstrap defined in meta-box.php 221 | if (!window.WPSimplechartBootstrap) { 222 | throw new Error('Missing window.WPSimplechartBootstrap'); 223 | } 224 | 225 | return Object.keys(window.WPSimplechartBootstrap).reduce(function (data, key) { 226 | var toSend; 227 | try { 228 | toSend = JSON.parse(window.WPSimplechartBootstrap[key]); 229 | } catch (e) { 230 | toSend = window.WPSimplechartBootstrap[key]; 231 | } 232 | data[key] = toSend; 233 | return data; 234 | }, {}); 235 | } 236 | 237 | /** 238 | * save individual elements of data from chart editor 239 | */ 240 | function saveChart(data) { 241 | var newSubtitle; 242 | Object.keys(data).forEach(function (key) { 243 | // If subtitle is set, rip it out and save it separately 244 | if ('chartMetadata' === key) { 245 | if ('undefined' !== typeof data[key].subtitle) { 246 | newSubtitle = data[key].subtitle; 247 | saveToField('save-chartSubtitle', newSubtitle); 248 | delete data[key].subtitle; 249 | } else { 250 | saveToField('save-chartSubtitle', false); 251 | } 252 | } 253 | saveToField('save-' + key, data[key]); 254 | }); 255 | 256 | // Save height to its own custom field 257 | document.getElementById('save-embedHeight').value = data.chartOptions.embedHeight; 258 | 259 | setPostTitleField(data); 260 | 261 | // auto-publish if we are creating a new chart 262 | if (addingNewChart()) { 263 | publishPost(); 264 | } else { 265 | // Re-apply subtitle to chartMetadata, so new values are reflected in chart preview. 266 | data.chartMetadata.subtitle = newSubtitle; 267 | updateWidget(data); 268 | hideModal(); 269 | } 270 | } 271 | 272 | /** 273 | * Receive new data from child window and set value of hidden input field 274 | */ 275 | function saveToField(fieldId, data) { 276 | if ('string' !== typeof data) { 277 | data = JSON.stringify(data); 278 | } 279 | document.getElementById(fieldId).value = data; 280 | } 281 | 282 | /** 283 | * Handle any special exceptions when receiving data from app 284 | */ 285 | function setPostTitleField(data) { 286 | // Update post_title field if needed 287 | var postTitleField = document.querySelector('input[name="post_title"]'); 288 | if (data.chartMetadata.title) { 289 | postTitleField.value = data.chartMetadata.title; 290 | // hides placeholder text 291 | document.getElementById('title-prompt-text').className = 'screen-reader-text'; 292 | setPostShouldPublish(true); 293 | } else if (!postTitleField.value) { 294 | addNotice('error', 'Please enter a WordPress internal identifier.'); 295 | setPostShouldPublish(false); 296 | } 297 | } 298 | 299 | /** 300 | * Add a notification in side the container created during the admin_notices hook 301 | * https://codex.wordpress.org/Plugin_API/Action_Reference/admin_notices 302 | * 303 | * @param string noticeType Should notice-error, notice-warning, notice-success, or notice-info 304 | * @param string message Text-only, no HTML 305 | * @return none 306 | */ 307 | function addNotice(noticeType, message) { 308 | var container = document.getElementById('simplechart-admin-notices'); 309 | if (!container) { 310 | return; 311 | } 312 | 313 | var notice = document.createElement('div'); 314 | notice.className = 'notice is-dismissable notice-' + noticeType; 315 | var content = document.createElement('p'); 316 | content.innerText = message; 317 | notice.appendChild(content); 318 | container.appendChild(notice); 319 | } 320 | 321 | /** 322 | * Trigger publishing when data is received for a new post 323 | */ 324 | function publishPost() { 325 | if (shouldPostPublish()) { 326 | // make extra super sure publish button exists as expected 327 | var publishButton = document.querySelector('#simplechart-save input#publish'); 328 | if (publishButton) { 329 | $(publishButton).click(); 330 | sendToApp('cms.isSaving', null); 331 | } else { 332 | hideModal(); 333 | addNotice('success', 'Your chart is ready! Click Publish to continue.'); 334 | } 335 | } else { 336 | // Only hide the modal if publishing is blocked for some reason, 337 | // e.g. a missing post_title 338 | hideModal(); 339 | } 340 | } 341 | 342 | /** 343 | * Trigger an update on the embedded chart widget 344 | * 345 | * @param obj data Data received from app 346 | */ 347 | function updateWidget(data) { 348 | var widgetUpdate = new CustomEvent('widgetData', { 349 | detail: { 350 | data: data.chartData, 351 | options: data.chartOptions, 352 | metadata: data.chartMetadata, 353 | annotations: data.chartAnnotations 354 | } 355 | }); 356 | document.getElementById(getWidgetId()).dispatchEvent(widgetUpdate); 357 | } 358 | 359 | /** 360 | * Get expected widget ID 361 | */ 362 | function getWidgetId() { 363 | var postId = /post=(\d+)/.exec(window.location.search)[1]; 364 | return 'simplechart-widget-' + postId; 365 | } 366 | 367 | // GO GO GO 368 | init(); 369 | } 370 | 371 | if ('undefined' !== typeof pagenow && 'simplechart' === pagenow) { 372 | jQuery(document).ready(function () { 373 | WPSimplechartApp(jQuery); 374 | }); 375 | } 376 | 377 | /***/ }) 378 | 379 | /******/ }); 380 | //# sourceMappingURL=postEdit.js.map -------------------------------------------------------------------------------- /js/plugin/build/postEdit.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack:///webpack/bootstrap e7c8b2ca5f14e16cc310?8ebb","webpack:///./js/plugin/src/post-edit.js"],"names":["WPSimplechartApp","$","appUrl","modalElements","container","backdrop","confirmNoDataMessage","closeModalMessage","savedChart","__postShouldPublish","setPostShouldPublish","newValue","shouldPostPublish","addingNewChart","window","location","pathname","split","pop","init","WPSimplechartContainer","toString","addEventListener","onReceiveMessage","renderModal","replace","append","click","openModal","show","hideModal","hide","getMessageType","evt","origin","test","messageType","data","err","sendToApp","WPSimplechartBootstrap","isNewChart","parseBootstrapData","saveChart","messageData","childWindow","document","getElementById","contentWindow","Error","postMessage","Object","keys","reduce","key","toSend","JSON","parse","e","newSubtitle","forEach","subtitle","saveToField","value","chartOptions","embedHeight","setPostTitleField","publishPost","chartMetadata","updateWidget","fieldId","stringify","postTitleField","querySelector","title","className","addNotice","noticeType","message","notice","createElement","content","innerText","appendChild","publishButton","widgetUpdate","CustomEvent","detail","chartData","options","metadata","annotations","chartAnnotations","getWidgetId","dispatchEvent","postId","exec","search","pagenow","jQuery","ready"],"mappings":";AAAA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;;AAGA;AACA;;AAEA;AACA;;AAEA;AACA,mDAA2C,cAAc;;AAEzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAK;AACL;AACA;;AAEA;AACA;AACA;AACA,mCAA2B,0BAA0B,EAAE;AACvD,yCAAiC,eAAe;AAChD;AACA;AACA;;AAEA;AACA,8DAAsD,+DAA+D;;AAErH;AACA;;AAEA;AACA;;;;;;;;;;;AChEA;AACA;;;;AAIA,SAASA,gBAAT,CAA2BC,CAA3B,EAA+B;AAC9B;AACA,KAAIC,MAAJ;AAAA,KACCC,gBAAgB;AACfC,aAAY,gGADG;AAEfC,YAAW;AAFI,EADjB;AAAA,KAKCC,uBAAuB,EALxB;AAAA,KAMCC,oBAAoB,EANrB;AAAA,KAOCC,aAAa,KAPd;;AASA;AACA,KAAIC,sBAAsB,IAA1B;;AAEA,UAASC,oBAAT,CAA+BC,QAA/B,EAA0C;AACzCF,wBAAsBE,QAAtB;AACA;;AAED,UAASC,iBAAT,GAA6B;AAC5B;AACA,SAAO,CAAC,CAAEH,mBAAV;AACA;;AAED;;;AAGA,UAASI,cAAT,GAA0B;AACzB,SAAO,mBAAmBC,OAAOC,QAAP,CAAgBC,QAAhB,CAAyBC,KAAzB,CAAgC,GAAhC,EAAsCC,GAAtC,EAA1B;AACA;;AAED;;;AAGA,UAASC,IAAT,GAAgB;AACfjB,WAASY,OAAOM,sBAAP,CAA8BlB,MAA9B,CAAqCmB,QAArC,EAAT;AACAf,yBAAuBQ,OAAOM,sBAAP,CAA8Bd,oBAA9B,CAAmDe,QAAnD,EAAvB;AACAd,sBAAoBO,OAAOM,sBAAP,CAA8Bb,iBAA9B,CAAgDc,QAAhD,EAApB;AACAP,SAAOQ,gBAAP,CAAyB,SAAzB,EAAoCC,gBAApC;AACAC;AACA;;AAED;;;AAGA,UAASA,WAAT,GAAuB;AACtB;AACArB,gBAAcC,SAAd,GAA0BD,cAAcC,SAAd,CAAwBqB,OAAxB,CAAiC,eAAjC,EAAkDvB,MAAlD,CAA1B;AACAC,gBAAcC,SAAd,GAA0BD,cAAcC,SAAd,CAAwBqB,OAAxB,CAAiC,gBAAjC,EAAmDlB,iBAAnD,CAA1B;AACAN,IAAG,MAAH,EAAYyB,MAAZ,CAAoBvB,cAAcC,SAAd,GAA0BD,cAAcE,QAA5D;;AAEA;AACAJ,IAAG,qBAAH,EAA2B0B,KAA3B,CAAkCC,SAAlC;;AAEA;AACA,MAAKf,gBAAL,EAAwB;AACvBe;AACA;AACD;;AAED;;;AAGA,UAASA,SAAT,GAAqB;AACpB3B,IAAG,2CAAH,EAAiD4B,IAAjD;AACA;;AAED;;;AAGA,UAASC,SAAT,GAAqB;AACpB7B,IAAG,2CAAH,EAAiD8B,IAAjD;AACA;;AAED;;;AAGA,UAASC,cAAT,CAAyBC,GAAzB,EAA+B;AAC9B;AACA,MAAKA,IAAIC,MAAJ,KAAepB,OAAOC,QAAP,CAAgBmB,MAA/B,IAAyC,CAAC,4BAA4BC,IAA5B,CAAkCF,IAAIC,MAAtC,CAA/C,EAAgG;AAC/F,UAAO,KAAP;AACA;;AAED,MAAIE,WAAJ;AACA,MAAI;AACHA,iBAAcH,IAAII,IAAJ,CAASD,WAAvB;AACA,GAFD,CAEE,OAAOE,GAAP,EAAa;AACd,SAAMA,GAAN;AACA;;AAED,SAAOF,WAAP;AACA;;AAED;;;AAGA,UAASb,gBAAT,CAA2BU,GAA3B,EAAiC;AAChC,MAAIG,cAAcJ,eAAgBC,GAAhB,CAAlB;AACA,MAAK,CAAEG,WAAP,EAAqB;AACpB;AACA;;AAED,UAAQA,WAAR;AACC,QAAK,UAAL;AACCG,cACCC,uBAAuBC,UAAvB,GAAoC,eAApC,GAAsD,gBADvD,EAECC,oBAFD;AAIA;;AAED,QAAK,UAAL;AACCZ;AACA;;AAED,QAAK,WAAL;AACCa,cAAWV,IAAII,IAAJ,CAASA,IAApB;AACA;;AAED;AACC;AAjBF;AAmBA;;AAED;;;AAGA,UAASE,SAAT,CAAoBH,WAApB,EAAiCQ,WAAjC,EAA+C;AAC9C,MAAIC,cAAcC,SAASC,cAAT,CAAyB,mBAAzB,CAAlB;AACA,MAAK,CAAEF,WAAF,IAAiB,CAAEA,YAAYG,aAApC,EAAoD;AACnD,SAAM,IAAIC,KAAJ,CAAW,kCAAX,CAAN;AACA;;AAEDJ,cAAYG,aAAZ,CAA0BE,WAA1B,CAAuC;AACtCd,gBAAaA,WADyB;AAEtCC,SAAMO;AAFgC,GAAvC,EAGG,GAHH;AAIA;;AAED;;;AAGA,UAASF,kBAAT,GAA8B;AAC7B;AACA,MAAK,CAAE5B,OAAO0B,sBAAd,EAAuC;AACtC,SAAM,IAAIS,KAAJ,CAAW,uCAAX,CAAN;AACA;;AAED,SAAOE,OAAOC,IAAP,CAAatC,OAAO0B,sBAApB,EAA6Ca,MAA7C,CAAoD,UAAShB,IAAT,EAAeiB,GAAf,EAAoB;AAC9E,OAAIC,MAAJ;AACA,OAAI;AACHA,aAASC,KAAKC,KAAL,CAAY3C,OAAO0B,sBAAP,CAA+Bc,GAA/B,CAAZ,CAAT;AACA,IAFD,CAEE,OAAOI,CAAP,EAAW;AACZH,aAASzC,OAAO0B,sBAAP,CAA+Bc,GAA/B,CAAT;AACA;AACDjB,QAAMiB,GAAN,IAAcC,MAAd;AACA,UAAOlB,IAAP;AACA,GATM,EASJ,EATI,CAAP;AAUA;;AAED;;;AAGA,UAASM,SAAT,CAAoBN,IAApB,EAA2B;AAC1B,MAAIsB,WAAJ;AACAR,SAAOC,IAAP,CAAaf,IAAb,EAAoBuB,OAApB,CAA6B,UAAUN,GAAV,EAAgB;AAC5C;AACA,OAAI,oBAAoBA,GAAxB,EAA6B;AAC5B,QAAI,gBAAgB,OAAOjB,KAAKiB,GAAL,EAAUO,QAArC,EAA+C;AAC9CF,mBAActB,KAAKiB,GAAL,EAAUO,QAAxB;AACAC,iBAAY,oBAAZ,EAAkCH,WAAlC;AACA,YAAOtB,KAAKiB,GAAL,EAAUO,QAAjB;AACA,KAJD,MAIO;AACNC,iBAAY,oBAAZ,EAAkC,KAAlC;AACA;AACD;AACDA,eAAa,UAAUR,GAAvB,EAA4BjB,KAAKiB,GAAL,CAA5B;AACA,GAZD;;AAcA;AACAR,WAASC,cAAT,CAAwB,kBAAxB,EAA4CgB,KAA5C,GAAoD1B,KAAK2B,YAAL,CAAkBC,WAAtE;;AAEAC,oBAAmB7B,IAAnB;;AAEA;AACA,MAAKxB,gBAAL,EAAwB;AACvBsD;AACA,GAFD,MAEO;AACN;AACA9B,QAAK+B,aAAL,CAAmBP,QAAnB,GAA8BF,WAA9B;AACAU,gBAAchC,IAAd;AACAP;AACA;AACD;;AAED;;;AAGA,UAASgC,WAAT,CAAsBQ,OAAtB,EAA+BjC,IAA/B,EAAsC;AACrC,MAAK,aAAa,OAAOA,IAAzB,EAAgC;AAC/BA,UAAOmB,KAAKe,SAAL,CAAgBlC,IAAhB,CAAP;AACA;AACDS,WAASC,cAAT,CAAyBuB,OAAzB,EAAmCP,KAAnC,GAA2C1B,IAA3C;AACA;;AAED;;;AAGA,UAAS6B,iBAAT,CAA4B7B,IAA5B,EAAmC;AAClC;AACA,MAAImC,iBAAiB1B,SAAS2B,aAAT,CAAwB,0BAAxB,CAArB;AACA,MAAKpC,KAAK+B,aAAL,CAAmBM,KAAxB,EAAgC;AAC/BF,kBAAeT,KAAf,GAAuB1B,KAAK+B,aAAL,CAAmBM,KAA1C;AACA;AACA5B,YAASC,cAAT,CAAyB,mBAAzB,EAA+C4B,SAA/C,GAA2D,oBAA3D;AACAjE,wBAAsB,IAAtB;AACA,GALD,MAKO,IAAK,CAAE8D,eAAeT,KAAtB,EAA8B;AACpCa,aAAW,OAAX,EAAoB,+CAApB;AACAlE,wBAAsB,KAAtB;AACA;AACD;;AAED;;;;;;;;AAQA,UAASkE,SAAT,CAAoBC,UAApB,EAAgCC,OAAhC,EAA0C;AACzC,MAAI1E,YAAY0C,SAASC,cAAT,CAAyB,2BAAzB,CAAhB;AACA,MAAK,CAAE3C,SAAP,EAAmB;AAClB;AACA;;AAED,MAAI2E,SAASjC,SAASkC,aAAT,CAAwB,KAAxB,CAAb;AACAD,SAAOJ,SAAP,GAAmB,kCAAkCE,UAArD;AACA,MAAII,UAAUnC,SAASkC,aAAT,CAAwB,GAAxB,CAAd;AACAC,UAAQC,SAAR,GAAoBJ,OAApB;AACAC,SAAOI,WAAP,CAAoBF,OAApB;AACA7E,YAAU+E,WAAV,CAAuBJ,MAAvB;AACA;;AAED;;;AAGA,UAASZ,WAAT,GAAuB;AACtB,MAAKvD,mBAAL,EAA2B;AAC1B;AACA,OAAIwE,gBAAgBtC,SAAS2B,aAAT,CAAwB,iCAAxB,CAApB;AACA,OAAKW,aAAL,EAAqB;AACpBnF,MAAGmF,aAAH,EAAmBzD,KAAnB;AACAY,cAAW,cAAX,EAA2B,IAA3B;AACA,IAHD,MAGO;AACNT;AACA8C,cAAW,SAAX,EAAsB,iDAAtB;AACA;AACD,GAVD,MAUO;AACN;AACA;AACA9C;AACA;AACD;;AAED;;;;;AAKA,UAASuC,YAAT,CAAuBhC,IAAvB,EAA8B;AAC7B,MAAIgD,eAAe,IAAIC,WAAJ,CAAiB,YAAjB,EAA+B;AACjDC,WAAQ;AACPlD,UAAMA,KAAKmD,SADJ;AAEPC,aAASpD,KAAK2B,YAFP;AAGP0B,cAAUrD,KAAK+B,aAHR;AAIPuB,iBAAatD,KAAKuD;AAJX;AADyC,GAA/B,CAAnB;AAQA9C,WAASC,cAAT,CAAyB8C,aAAzB,EAAyCC,aAAzC,CAAwDT,YAAxD;AACA;;AAED;;;AAGA,UAASQ,WAAT,GAAuB;AACtB,MAAIE,SAAS,aAAaC,IAAb,CAAkBlF,OAAOC,QAAP,CAAgBkF,MAAlC,EAA0C,CAA1C,CAAb;AACA,SAAO,wBAAwBF,MAA/B;AACA;;AAED;AACA5E;AACA;;AAED,IAAK,gBAAgB,OAAO+E,OAAvB,IAAkC,kBAAkBA,OAAzD,EAAkE;AACjEC,QAAQrD,QAAR,EAAmBsD,KAAnB,CAA0B,YAAW;AACpCpG,mBAAkBmG,MAAlB;AACA,EAFD;AAGA,C","file":"postEdit.js","sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// identity function for calling harmony imports with the correct context\n \t__webpack_require__.i = function(value) { return value; };\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 6);\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap e7c8b2ca5f14e16cc310","/* eslint-disable */\n/**\n * Open, close, and communicate with the iframe containing the app\n */\n\nfunction WPSimplechartApp( $ ) {\n\t// setup scoped vars\n\tvar appUrl,\n\t\tmodalElements = {\n\t\t\tcontainer : '
',\n\t\t\tbackdrop : '
'\n\t\t},\n\t\tconfirmNoDataMessage = '',\n\t\tcloseModalMessage = '',\n\t\tsavedChart = false;\n\n\t// do not update directly!\n\tvar __postShouldPublish = true;\n\n\tfunction setPostShouldPublish( newValue ) {\n\t\t__postShouldPublish = newValue;\n\t}\n\n\tfunction shouldPostPublish() {\n\t\t// use !! to always return boolean\n\t\treturn !! __postShouldPublish;\n\t}\n\n\t/**\n\t * True if adding a new chart; false if editing a chart\n\t */\n\tfunction addingNewChart() {\n\t\treturn 'post-new.php' === window.location.pathname.split( '/' ).pop();\n\t}\n\n\t/**\n\t * Set scope var values and build modal element\n\t */\n\tfunction init() {\n\t\tappUrl = window.WPSimplechartContainer.appUrl.toString();\n\t\tconfirmNoDataMessage = window.WPSimplechartContainer.confirmNoDataMessage.toString();\n\t\tcloseModalMessage = window.WPSimplechartContainer.closeModalMessage.toString();\n\t\twindow.addEventListener( 'message', onReceiveMessage );\n\t\trenderModal();\n\t}\n\n\t/**\n\t * Renders the app iframe modal in its open state\n\t */\n\tfunction renderModal() {\n\t\t// create modal elements and append to \n\t\tmodalElements.container = modalElements.container.replace( '{{iframeSrc}}', appUrl);\n\t\tmodalElements.container = modalElements.container.replace( '{{closeModal}}', closeModalMessage);\n\t\t$( 'body' ).append( modalElements.container + modalElements.backdrop );\n\n\t\t// Listen for click to open modal\n\t\t$( '#simplechart-launch' ).click( openModal );\n\n\t\t// Open modal if creating new chart\n\t\tif ( addingNewChart() ) {\n\t\t\topenModal();\n\t\t}\n\t}\n\n\t/**\n\t * Reopens already-rendered modal\n\t */\n\tfunction openModal() {\n\t\t$( '#simplechart-backdrop, #simplechart-modal' ).show();\n\t}\n\n\t/**\n\t * Hide modal\n\t */\n\tfunction hideModal() {\n\t\t$( '#simplechart-backdrop, #simplechart-modal' ).hide();\n\t}\n\n\t/**\n\t * Extract messageType string when a postMessage is received\n\t */\n\tfunction getMessageType( evt ) {\n\t\t// confirm same-origin or http(s)://localhost:8080\n\t\tif ( evt.origin !== window.location.origin && !/https?:\\/\\/localhost:8080/.test( evt.origin ) ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tvar messageType;\n\t\ttry {\n\t\t\tmessageType = evt.data.messageType;\n\t\t} catch( err ) {\n\t\t\tthrow err;\n\t\t}\n\n\t\treturn messageType;\n\t}\n\n\t/**\n\t * adds listeners for specific messageType from child window\n\t */\n\tfunction onReceiveMessage( evt ) {\n\t\tvar messageType = getMessageType( evt );\n\t\tif ( ! messageType ) {\n\t\t\treturn;\n\t\t}\n\n\t\tswitch( messageType ) {\n\t\t\tcase 'appReady':\n\t\t\t\tsendToApp(\n\t\t\t\t\tWPSimplechartBootstrap.isNewChart ? 'bootstrap.new' : 'bootstrap.edit',\n\t\t\t\t\tparseBootstrapData()\n\t\t\t\t);\n\t\t\t\tbreak;\n\n\t\t\tcase 'closeApp':\n\t\t\t\thideModal();\n\t\t\t\tbreak;\n\n\t\t\tcase 'saveChart':\n\t\t\t\tsaveChart( evt.data.data );\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\t// nothing\n\t\t}\n\t}\n\n\t/**\n\t * Send previously saved data to child window\n\t */\n\tfunction sendToApp( messageType, messageData ) {\n\t\tvar childWindow = document.getElementById( 'simplechart-frame' );\n\t\tif ( ! childWindow || ! childWindow.contentWindow ) {\n\t\t\tthrow new Error( 'Missing iframe#simplechart-frame' );\n\t\t}\n\n\t\tchildWindow.contentWindow.postMessage( {\n\t\t\tmessageType: messageType,\n\t\t\tdata: messageData\n\t\t}, '*' );\n\t}\n\n\t/**\n\t * Build data object to send to chart editor window, parsing stringified JSON as needed\n\t */\n\tfunction parseBootstrapData() {\n\t\t// WPSimplechartBootstrap defined in meta-box.php\n\t\tif ( ! window.WPSimplechartBootstrap ) {\n\t\t\tthrow new Error( 'Missing window.WPSimplechartBootstrap' );\n\t\t}\n\n\t\treturn Object.keys( window.WPSimplechartBootstrap ).reduce(function(data, key) {\n\t\t\tvar toSend;\n\t\t\ttry {\n\t\t\t\ttoSend = JSON.parse( window.WPSimplechartBootstrap[ key ] );\n\t\t\t} catch( e ) {\n\t\t\t\ttoSend = window.WPSimplechartBootstrap[ key ];\n\t\t\t}\n\t\t\tdata[ key ] = toSend;\n\t\t\treturn data;\n\t\t}, {} )\n\t}\n\n\t/**\n\t * save individual elements of data from chart editor\n\t */\n\tfunction saveChart( data ) {\n\t\tvar newSubtitle;\n\t\tObject.keys( data ).forEach( function( key ) {\n\t\t\t// If subtitle is set, rip it out and save it separately\n\t\t\tif ('chartMetadata' === key) {\n\t\t\t\tif ('undefined' !== typeof data[key].subtitle) {\n\t\t\t\t\tnewSubtitle = data[key].subtitle;\n\t\t\t\t\tsaveToField('save-chartSubtitle', newSubtitle);\n\t\t\t\t\tdelete data[key].subtitle;\n\t\t\t\t} else {\n\t\t\t\t\tsaveToField('save-chartSubtitle', false);\n\t\t\t\t}\n\t\t\t}\n\t\t\tsaveToField( 'save-' + key, data[key] );\n\t\t} );\n\n\t\t// Save height to its own custom field\n\t\tdocument.getElementById('save-embedHeight').value = data.chartOptions.embedHeight;\n\n\t\tsetPostTitleField( data );\n\n\t\t// auto-publish if we are creating a new chart\n\t\tif ( addingNewChart() ) {\n\t\t\tpublishPost();\n\t\t} else {\n\t\t\t// Re-apply subtitle to chartMetadata, so new values are reflected in chart preview.\n\t\t\tdata.chartMetadata.subtitle = newSubtitle;\n\t\t\tupdateWidget( data );\n\t\t\thideModal();\n\t\t}\n\t}\n\n\t/**\n\t * Receive new data from child window and set value of hidden input field\n\t */\n\tfunction saveToField( fieldId, data ) {\n\t\tif ( 'string' !== typeof data ) {\n\t\t\tdata = JSON.stringify( data );\n\t\t}\n\t\tdocument.getElementById( fieldId ).value = data;\n\t}\n\n\t/**\n\t * Handle any special exceptions when receiving data from app\n\t */\n\tfunction setPostTitleField( data ) {\n\t\t// Update post_title field if needed\n\t\tvar postTitleField = document.querySelector( 'input[name=\"post_title\"]' );\n\t\tif ( data.chartMetadata.title ) {\n\t\t\tpostTitleField.value = data.chartMetadata.title;\n\t\t\t// hides placeholder text\n\t\t\tdocument.getElementById( 'title-prompt-text' ).className = 'screen-reader-text';\n\t\t\tsetPostShouldPublish( true );\n\t\t} else if ( ! postTitleField.value ) {\n\t\t\taddNotice( 'error', 'Please enter a WordPress internal identifier.' );\n\t\t\tsetPostShouldPublish( false );\n\t\t}\n\t}\n\n\t/**\n\t * Add a notification in side the container created during the admin_notices hook\n\t * https://codex.wordpress.org/Plugin_API/Action_Reference/admin_notices\n\t *\n\t * @param string noticeType Should notice-error, notice-warning, notice-success, or notice-info\n\t * @param string message Text-only, no HTML\n\t * @return none\n\t */\n\tfunction addNotice( noticeType, message ) {\n\t\tvar container = document.getElementById( 'simplechart-admin-notices' );\n\t\tif ( ! container ) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar notice = document.createElement( 'div' );\n\t\tnotice.className = 'notice is-dismissable notice-' + noticeType;\n\t\tvar content = document.createElement( 'p' );\n\t\tcontent.innerText = message;\n\t\tnotice.appendChild( content );\n\t\tcontainer.appendChild( notice );\n\t}\n\n\t/**\n\t * Trigger publishing when data is received for a new post\n\t */\n\tfunction publishPost() {\n\t\tif ( shouldPostPublish() ) {\n\t\t\t// make extra super sure publish button exists as expected\n\t\t\tvar publishButton = document.querySelector( '#simplechart-save input#publish' );\n\t\t\tif ( publishButton ) {\n\t\t\t\t$( publishButton ).click();\n\t\t\t\tsendToApp( 'cms.isSaving', null );\n\t\t\t} else {\n\t\t\t\thideModal();\n\t\t\t\taddNotice( 'success', 'Your chart is ready! Click Publish to continue.' );\n\t\t\t}\n\t\t} else {\n\t\t\t// Only hide the modal if publishing is blocked for some reason,\n\t\t\t// e.g. a missing post_title\n\t\t\thideModal();\n\t\t}\n\t}\n\n\t/**\n\t * Trigger an update on the embedded chart widget\n\t *\n\t * @param obj data Data received from app\n\t */\n\tfunction updateWidget( data ) {\n\t\tvar widgetUpdate = new CustomEvent( 'widgetData', {\n\t\t\tdetail: {\n\t\t\t\tdata: data.chartData,\n\t\t\t\toptions: data.chartOptions,\n\t\t\t\tmetadata: data.chartMetadata,\n\t\t\t\tannotations: data.chartAnnotations,\n\t\t\t}\n\t\t} );\n\t\tdocument.getElementById( getWidgetId() ).dispatchEvent( widgetUpdate );\n\t}\n\n\t/**\n\t * Get expected widget ID\n\t */\n\tfunction getWidgetId() {\n\t\tvar postId = /post=(\\d+)/.exec(window.location.search)[1];\n\t\treturn 'simplechart-widget-' + postId;\n\t}\n\n\t// GO GO GO\n\tinit();\n}\n\nif ( 'undefined' !== typeof pagenow && 'simplechart' === pagenow ){\n\tjQuery( document ).ready( function() {\n\t\tWPSimplechartApp( jQuery );\n\t} );\n}\n\n\n\n// WEBPACK FOOTER //\n// ./js/plugin/src/post-edit.js"],"sourceRoot":""} -------------------------------------------------------------------------------- /js/plugin/src/plugin.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * On the plugins page, make sure we don't accidentally 4 | * try to update Simplechart as if it was hosted on WP.org 5 | */ 6 | jQuery( document ).ready( function( $ ) { 7 | if ( 'plugins' !== window.pagenow ) { 8 | return; 9 | } 10 | 11 | var simplechartInput = $( 'input[value="wordpress-simplechart/simplechart.php"]' ); 12 | var disabledForBulkActions = false; 13 | 14 | /** 15 | * Uncheck and disable Simplechart for Bulk Actions on Plugins page 16 | */ 17 | function _disableForBulkActions() { 18 | if ( simplechartInput.length ) { 19 | simplechartInput.attr( { disabled: true, checked: false } ).css( 'cursor', 'default' ); 20 | disabledForBulkActions = true; 21 | } 22 | } 23 | 24 | /** 25 | * Enable Simplechart for Bulk Actions on Plugins page 26 | * 27 | * @param bool checked Whether it should be checked 28 | */ 29 | function _enableForBulkActions( checked ) { 30 | if ( simplechartInput.length ) { 31 | simplechartInput.attr( { disabled: false, checked: checked } ).css( 'cursor', 'pointer' ); 32 | disabledForBulkActions = false; 33 | } 34 | } 35 | 36 | if ( simplechartInput.length ) { 37 | 38 | // Make sure Simplechart is unchecked if Update is selected as Bulk Action 39 | $( '.bulkactions [name^="action"]' ).on( 'change', function( evt ) { 40 | if ( 'update-selected' === $( evt.target ).val() ) { 41 | // If Update is selected, uncheck and disable 42 | _disableForBulkActions() 43 | } else if ( disabledForBulkActions ) { 44 | // If changing from Update to another Bulk Action, 45 | // un-disable and fallback to "check all" checkbox value 46 | var allChecked = 'undefined' !== typeof $( '#cb-select-all-1' ).attr( 'checked' ); 47 | _enableForBulkActions( allChecked ); 48 | } 49 | } ); 50 | 51 | // Make extra sure Simplechart is unchecked when applying bulk action to Update 52 | $( '.bulkactions [type="submit"]' ).on( 'click', function( evt ) { 53 | var select = $( evt.target ).siblings( 'select' ).first(); 54 | if ( select.length && 'update-selected' === select.val() ) { 55 | _disableForBulkActions(); 56 | } 57 | } ); 58 | } 59 | }); 60 | -------------------------------------------------------------------------------- /js/plugin/src/post-edit.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * Open, close, and communicate with the iframe containing the app 4 | */ 5 | 6 | function WPSimplechartApp( $ ) { 7 | // setup scoped vars 8 | var appUrl, 9 | modalElements = { 10 | container : '
', 11 | backdrop : '
' 12 | }, 13 | confirmNoDataMessage = '', 14 | closeModalMessage = '', 15 | savedChart = false; 16 | 17 | // do not update directly! 18 | var __postShouldPublish = true; 19 | 20 | function setPostShouldPublish( newValue ) { 21 | __postShouldPublish = newValue; 22 | } 23 | 24 | function shouldPostPublish() { 25 | // use !! to always return boolean 26 | return !! __postShouldPublish; 27 | } 28 | 29 | /** 30 | * True if adding a new chart; false if editing a chart 31 | */ 32 | function addingNewChart() { 33 | return 'post-new.php' === window.location.pathname.split( '/' ).pop(); 34 | } 35 | 36 | /** 37 | * Set scope var values and build modal element 38 | */ 39 | function init() { 40 | appUrl = window.WPSimplechartContainer.appUrl.toString(); 41 | confirmNoDataMessage = window.WPSimplechartContainer.confirmNoDataMessage.toString(); 42 | closeModalMessage = window.WPSimplechartContainer.closeModalMessage.toString(); 43 | window.addEventListener( 'message', onReceiveMessage ); 44 | renderModal(); 45 | } 46 | 47 | /** 48 | * Renders the app iframe modal in its open state 49 | */ 50 | function renderModal() { 51 | // create modal elements and append to 52 | modalElements.container = modalElements.container.replace( '{{iframeSrc}}', appUrl); 53 | modalElements.container = modalElements.container.replace( '{{closeModal}}', closeModalMessage); 54 | $( 'body' ).append( modalElements.container + modalElements.backdrop ); 55 | 56 | // Listen for click to open modal 57 | $( '#simplechart-launch' ).click( openModal ); 58 | 59 | // Open modal if creating new chart 60 | if ( addingNewChart() ) { 61 | openModal(); 62 | } 63 | } 64 | 65 | /** 66 | * Reopens already-rendered modal 67 | */ 68 | function openModal() { 69 | $( '#simplechart-backdrop, #simplechart-modal' ).show(); 70 | } 71 | 72 | /** 73 | * Hide modal 74 | */ 75 | function hideModal() { 76 | $( '#simplechart-backdrop, #simplechart-modal' ).hide(); 77 | } 78 | 79 | /** 80 | * Extract messageType string when a postMessage is received 81 | */ 82 | function getMessageType( evt ) { 83 | // confirm same-origin or http(s)://localhost:8080 84 | if ( evt.origin !== window.location.origin && !/https?:\/\/localhost:8080/.test( evt.origin ) ) { 85 | return false; 86 | } 87 | 88 | var messageType; 89 | try { 90 | messageType = evt.data.messageType; 91 | } catch( err ) { 92 | throw err; 93 | } 94 | 95 | return messageType; 96 | } 97 | 98 | /** 99 | * adds listeners for specific messageType from child window 100 | */ 101 | function onReceiveMessage( evt ) { 102 | var messageType = getMessageType( evt ); 103 | if ( ! messageType ) { 104 | return; 105 | } 106 | 107 | switch( messageType ) { 108 | case 'appReady': 109 | sendToApp( 110 | WPSimplechartBootstrap.isNewChart ? 'bootstrap.new' : 'bootstrap.edit', 111 | parseBootstrapData() 112 | ); 113 | break; 114 | 115 | case 'closeApp': 116 | hideModal(); 117 | break; 118 | 119 | case 'saveChart': 120 | saveChart( evt.data.data ); 121 | break; 122 | 123 | default: 124 | // nothing 125 | } 126 | } 127 | 128 | /** 129 | * Send previously saved data to child window 130 | */ 131 | function sendToApp( messageType, messageData ) { 132 | var childWindow = document.getElementById( 'simplechart-frame' ); 133 | if ( ! childWindow || ! childWindow.contentWindow ) { 134 | throw new Error( 'Missing iframe#simplechart-frame' ); 135 | } 136 | 137 | childWindow.contentWindow.postMessage( { 138 | messageType: messageType, 139 | data: messageData 140 | }, '*' ); 141 | } 142 | 143 | /** 144 | * Build data object to send to chart editor window, parsing stringified JSON as needed 145 | */ 146 | function parseBootstrapData() { 147 | // WPSimplechartBootstrap defined in meta-box.php 148 | if ( ! window.WPSimplechartBootstrap ) { 149 | throw new Error( 'Missing window.WPSimplechartBootstrap' ); 150 | } 151 | 152 | return Object.keys( window.WPSimplechartBootstrap ).reduce(function(data, key) { 153 | var toSend; 154 | try { 155 | toSend = JSON.parse( window.WPSimplechartBootstrap[ key ] ); 156 | } catch( e ) { 157 | toSend = window.WPSimplechartBootstrap[ key ]; 158 | } 159 | data[ key ] = toSend; 160 | return data; 161 | }, {} ) 162 | } 163 | 164 | /** 165 | * save individual elements of data from chart editor 166 | */ 167 | function saveChart( data ) { 168 | var newSubtitle; 169 | Object.keys( data ).forEach( function( key ) { 170 | // If subtitle is set, rip it out and save it separately 171 | if ('chartMetadata' === key) { 172 | if ('undefined' !== typeof data[key].subtitle) { 173 | newSubtitle = data[key].subtitle; 174 | saveToField('save-chartSubtitle', newSubtitle); 175 | delete data[key].subtitle; 176 | } else { 177 | saveToField('save-chartSubtitle', false); 178 | } 179 | } 180 | saveToField( 'save-' + key, data[key] ); 181 | } ); 182 | 183 | // Save height to its own custom field 184 | document.getElementById('save-embedHeight').value = data.chartOptions.embedHeight; 185 | 186 | setPostTitleField( data ); 187 | 188 | // auto-publish if we are creating a new chart 189 | if ( addingNewChart() ) { 190 | publishPost(); 191 | } else { 192 | // Re-apply subtitle to chartMetadata, so new values are reflected in chart preview. 193 | data.chartMetadata.subtitle = newSubtitle; 194 | updateWidget( data ); 195 | hideModal(); 196 | } 197 | } 198 | 199 | /** 200 | * Receive new data from child window and set value of hidden input field 201 | */ 202 | function saveToField( fieldId, data ) { 203 | if ( 'string' !== typeof data ) { 204 | data = JSON.stringify( data ); 205 | } 206 | document.getElementById( fieldId ).value = data; 207 | } 208 | 209 | /** 210 | * Handle any special exceptions when receiving data from app 211 | */ 212 | function setPostTitleField( data ) { 213 | // Update post_title field if needed 214 | var postTitleField = document.querySelector( 'input[name="post_title"]' ); 215 | if ( data.chartMetadata.title ) { 216 | postTitleField.value = data.chartMetadata.title; 217 | // hides placeholder text 218 | document.getElementById( 'title-prompt-text' ).className = 'screen-reader-text'; 219 | } else if ( ! postTitleField.value ) { 220 | addNotice( 'error', 'Please enter a WordPress internal identifier.' ); 221 | setPostShouldPublish( false ); 222 | } 223 | } 224 | 225 | /** 226 | * Add a notification in side the container created during the admin_notices hook 227 | * https://codex.wordpress.org/Plugin_API/Action_Reference/admin_notices 228 | * 229 | * @param string noticeType Should notice-error, notice-warning, notice-success, or notice-info 230 | * @param string message Text-only, no HTML 231 | * @return none 232 | */ 233 | function addNotice( noticeType, message ) { 234 | var container = document.getElementById( 'simplechart-admin-notices' ); 235 | if ( ! container ) { 236 | return; 237 | } 238 | 239 | var notice = document.createElement( 'div' ); 240 | notice.className = 'notice is-dismissable notice-' + noticeType; 241 | var content = document.createElement( 'p' ); 242 | content.innerText = message; 243 | notice.appendChild( content ); 244 | container.appendChild( notice ); 245 | } 246 | 247 | /** 248 | * Trigger publishing when data is received for a new post 249 | */ 250 | function publishPost() { 251 | if ( shouldPostPublish() ) { 252 | // make extra super sure publish button exists as expected 253 | var publishButton = document.querySelector( '#simplechart-save input#publish' ); 254 | if ( publishButton ) { 255 | $( publishButton ).click(); 256 | sendToApp( 'cms.isSaving', null ); 257 | } else { 258 | hideModal(); 259 | addNotice( 'success', 'Your chart is ready! Click Publish to continue.' ); 260 | } 261 | } else { 262 | // Only hide the modal if publishing is blocked for some reason, 263 | // e.g. a missing post_title 264 | hideModal(); 265 | } 266 | } 267 | 268 | /** 269 | * Trigger an update on the embedded chart widget 270 | * 271 | * @param obj data Data received from app 272 | */ 273 | function updateWidget( data ) { 274 | var widgetUpdate = new CustomEvent( 'widgetData', { 275 | detail: { 276 | data: data.chartData, 277 | options: data.chartOptions, 278 | metadata: data.chartMetadata, 279 | annotations: data.chartAnnotations, 280 | } 281 | } ); 282 | document.getElementById( getWidgetId() ).dispatchEvent( widgetUpdate ); 283 | } 284 | 285 | /** 286 | * Get expected widget ID 287 | */ 288 | function getWidgetId() { 289 | var postId = /post=(\d+)/.exec(window.location.search)[1]; 290 | return 'simplechart-widget-' + postId; 291 | } 292 | 293 | // GO GO GO 294 | init(); 295 | } 296 | 297 | if ( 'undefined' !== typeof pagenow && 'simplechart' === pagenow ){ 298 | jQuery( document ).ready( function() { 299 | WPSimplechartApp( jQuery ); 300 | } ); 301 | } 302 | -------------------------------------------------------------------------------- /js/plugin/src/simplechart-controller.js: -------------------------------------------------------------------------------- 1 | /* globals Backbone, tinymce */ 2 | // Controller 3 | export default function () { 4 | const SimplechartController = wp.media.controller.State.extend({ 5 | initialize() { 6 | this.props = new Backbone.Collection(); 7 | 8 | this.props.add(new Backbone.Model({ 9 | id: 'all', 10 | params: {}, 11 | page: null, 12 | min_id: null, 13 | max_id: null, 14 | fetchOnRender: true, 15 | })); 16 | 17 | this.props.add(new Backbone.Model({ 18 | id: '_all', 19 | selection: new Backbone.Collection(), 20 | })); 21 | 22 | this.props.on('change:selection', this.refresh, this); 23 | }, 24 | 25 | refresh() { 26 | this.frame.toolbar.get().refresh(); 27 | }, 28 | 29 | /** 30 | * Replicate MEXP function except with shortcodes intead of URLs. 31 | */ 32 | doInsert() { 33 | const selection = this.frame.content.get().getSelection(); 34 | const shortcodes = []; 35 | 36 | selection.each((model) => { 37 | shortcodes.push(`[simplechart id="${model.get('id')}"]`); 38 | }, this); 39 | 40 | if ('undefined' === typeof tinymce 41 | || null === tinymce.activeEditor 42 | || tinymce.activeEditor.isHidden()) { 43 | wp.media.editor.insert(_.toArray(shortcodes).join('\n\n')); 44 | } else { 45 | wp.media.editor 46 | .insert(`

${_.toArray(shortcodes).join('

')}

`); 47 | } 48 | 49 | selection.reset(); 50 | this.frame.close(); 51 | }, 52 | }); 53 | return SimplechartController; 54 | } 55 | -------------------------------------------------------------------------------- /js/plugin/src/simplechart-insert.js: -------------------------------------------------------------------------------- 1 | import SimplechartController from './simplechart-controller'; 2 | import SimplechartItem from './simplechart-item'; 3 | import SimplechartPostFrame from './simplechart-post-frame'; 4 | import SimplechartToolbar from './simplechart-toolbar'; 5 | import SimplechartView from './simplechart-view'; 6 | 7 | document.addEventListener('DOMContentLoaded', () => { 8 | wp.media.controller.Simplechart = SimplechartController(); 9 | wp.media.view.Toolbar.Simplechart = SimplechartToolbar(); 10 | wp.media.view.SimplechartItem = SimplechartItem(); 11 | wp.media.view.Simplechart = SimplechartView(); 12 | wp.media.view.MediaFrame.Post = SimplechartPostFrame(); 13 | }); 14 | -------------------------------------------------------------------------------- /js/plugin/src/simplechart-item.js: -------------------------------------------------------------------------------- 1 | // Item 2 | export default function () { 3 | const SimplechartItem = wp.Backbone.View.extend({ 4 | tagName: 'li', 5 | className: 'simplechart-item attachment', 6 | 7 | render() { 8 | this.template = wp.media.template('simplechart-insert-item-all'); 9 | this.$el.html(this.template(this.model.toJSON())); 10 | 11 | return this; 12 | }, 13 | }); 14 | return SimplechartItem; 15 | } 16 | -------------------------------------------------------------------------------- /js/plugin/src/simplechart-post-frame.js: -------------------------------------------------------------------------------- 1 | // Post Frame 2 | export default function () { 3 | const postFrame = wp.media.view.MediaFrame.Post; 4 | 5 | const SimplechartPostFrame = postFrame.extend({ 6 | initialize(...args) { 7 | postFrame.prototype.initialize.apply(this, args); 8 | 9 | const id = 'simplechart'; 10 | const controller = { 11 | id, 12 | toolbar: `${id}-toolbar`, 13 | menu: 'default', 14 | title: 'Insert Chart', 15 | priority: 100, 16 | content: 'simplechart-content-all', 17 | }; 18 | 19 | this.on( 20 | 'content:render:simplechart-content-all', 21 | this.simplechartContentRender, 22 | this 23 | ); 24 | 25 | this.states.add([ 26 | new wp.media.controller.Simplechart(controller), 27 | ]); 28 | 29 | this.on( 30 | 'toolbar:create:simplechart-toolbar', 31 | this.simplechartToolbarCreate, 32 | this 33 | ); 34 | }, 35 | 36 | simplechartContentRender() { 37 | this.content.set(new wp.media.view.Simplechart({ 38 | controller: this, 39 | model: this.state().props.get('all'), 40 | className: 'clearfix attachments-browser simplechart-all', 41 | })); 42 | }, 43 | 44 | simplechartToolbarCreate(toolbar) { 45 | // eslint-disable-next-line no-param-reassign 46 | toolbar.view = new wp.media.view.Toolbar.Simplechart({ 47 | controller: this, 48 | }); 49 | }, 50 | }); 51 | 52 | return SimplechartPostFrame; 53 | } 54 | -------------------------------------------------------------------------------- /js/plugin/src/simplechart-toolbar.js: -------------------------------------------------------------------------------- 1 | // Toolbar 2 | export default function () { 3 | const { 4 | initialize: parentInitialize, 5 | } = wp.media.view.Toolbar.prototype; 6 | 7 | const SimplechartToolbar = wp.media.view.Toolbar.extend({ 8 | initialize(...args) { 9 | _.defaults(this.options, { 10 | event: 'inserter', 11 | close: false, 12 | items: { 13 | // See wp.media.view.Button 14 | inserter: { 15 | id: 'simplechart-button', 16 | style: 'primary', 17 | text: 'Insert into post', 18 | priority: 80, 19 | click: () => { 20 | this.controller.state().doInsert(); 21 | }, 22 | }, 23 | }, 24 | }); 25 | 26 | parentInitialize.apply(this, args); 27 | 28 | this.set('pagination', new wp.media.view.Button({ 29 | tagName: 'button', 30 | classes: 'simplechart-pagination button button-secondary', 31 | id: 'simplechart-loadmore', 32 | text: 'Load More', 33 | priority: - 20, 34 | })); 35 | }, 36 | 37 | refresh(...args) { 38 | const selection = this 39 | .controller 40 | .state() 41 | .props 42 | .get('_all') 43 | .get('selection'); 44 | 45 | // @TODO i think this is redundant 46 | this.get('inserter').model.set('disabled', ! selection.length); 47 | 48 | wp.media.view.Toolbar.prototype.refresh.apply(this, args); 49 | }, 50 | }); 51 | 52 | return SimplechartToolbar; 53 | } 54 | -------------------------------------------------------------------------------- /js/plugin/src/simplechart-view.js: -------------------------------------------------------------------------------- 1 | /* globals Backbone, simplechart */ 2 | // View 3 | export default function () { 4 | const SimplechartView = wp.media.View.extend({ 5 | events: { 6 | 'click .simplechart-item-area': 'toggleSelectionHandler', 7 | 'click .simplechart-item .check': 'removeSelectionHandler', 8 | 'submit .simplechart-toolbar form': 'updateInput', 9 | }, 10 | 11 | initialize() { 12 | /* fired when you switch router tabs */ 13 | 14 | this.collection = new Backbone.Collection(); 15 | 16 | this.createToolbar(); 17 | this.clearItems(); 18 | 19 | if (this.model.get('items')) { 20 | this.collection = new Backbone.Collection(); 21 | this.collection.reset(this.model.get('items')); 22 | 23 | jQuery('#simplechart-loadmore').attr('disabled', false).show(); 24 | } else { 25 | jQuery('#simplechart-loadmore').hide(); 26 | } 27 | 28 | this.collection.on('reset', this.render, this); 29 | 30 | this.model.on('change:params', this.changedParams, this); 31 | 32 | this.on('loading', this.loading, this); 33 | this.on('loaded', this.loaded, this); 34 | this.on('change:params', this.changedParams, this); 35 | this.on('change:page', this.changedPage, this); 36 | 37 | jQuery('.simplechart-pagination').click((event) => { 38 | this.paginate(event); 39 | }); 40 | 41 | if (this.model.get('fetchOnRender')) { 42 | this.model.set('fetchOnRender', false); 43 | this.fetchItems(); 44 | } 45 | }, 46 | 47 | render() { 48 | /* fired when you switch router tabs */ 49 | 50 | const selection = this.getSelection(); 51 | 52 | if (this.collection && this.collection.models.length) { 53 | this.clearItems(); 54 | 55 | const container = document.createDocumentFragment(); 56 | 57 | this.collection.each((model) => { 58 | container.appendChild(this.renderItem(model)); 59 | }, this); 60 | 61 | this.$el.find('.simplechart-items').append(container); 62 | } 63 | 64 | selection.each((model) => { 65 | const id = `#simplechart-item-simplechart-all-${model.get('id')}`; 66 | this 67 | .$el 68 | .find(id) 69 | .closest('.simplechart-item') 70 | .addClass('selected details'); 71 | }, this); 72 | 73 | jQuery('#simplechart-button').prop('disabled', ! selection.length); 74 | 75 | return this; 76 | }, 77 | 78 | renderItem(model) { 79 | const view = new wp.media.view.SimplechartItem({ 80 | model, 81 | }); 82 | 83 | return view.render().el; 84 | }, 85 | 86 | createToolbar() { 87 | let html; 88 | // @TODO this could be a separate view: 89 | html = '
'; 90 | this.$el.prepend(html); 91 | 92 | // @TODO this could be a separate view: 93 | html = '
'; 94 | this.$el.prepend(html); 95 | 96 | // @TODO this could be a separate view: 97 | html = '
    '; 98 | this.$el.append(html); 99 | 100 | // @TODO this could be a separate view: 101 | // eslint-disable-next-line max-len 102 | const toolbarTemplate = wp.media.template('simplechart-insert-search-all'); 103 | // eslint-disable-next-line max-len 104 | html = `
    ${toolbarTemplate(this.model.toJSON())}
    `; 105 | this.$el.prepend(html); 106 | }, 107 | 108 | removeSelectionHandler(event) { 109 | const target = jQuery(`#${event.currentTarget.id}`); 110 | const id = target.attr('data-id'); 111 | 112 | this.removeFromSelection(target, id); 113 | 114 | event.preventDefault(); 115 | }, 116 | 117 | toggleSelectionHandler(event) { 118 | if (event.target.href) { 119 | return; 120 | } 121 | 122 | const target = jQuery(`#${event.currentTarget.id}`); 123 | const id = target.attr('data-id'); 124 | 125 | if (this.getSelection().get(id)) { 126 | this.removeFromSelection(target, id); 127 | } else { 128 | this.addToSelection(target, id); 129 | } 130 | }, 131 | 132 | addToSelection(target, id) { 133 | target.closest('.simplechart-item').addClass('selected details'); 134 | 135 | // eslint-disable-next-line no-underscore-dangle 136 | this.getSelection().add(this.collection._byId[id]); 137 | 138 | // @TODO why isn't this triggered by the above line? 139 | this.controller.state().props.trigger('change:selection'); 140 | }, 141 | 142 | removeFromSelection(target, id) { 143 | target.closest('.simplechart-item').removeClass('selected details'); 144 | 145 | // eslint-disable-next-line no-underscore-dangle 146 | this.getSelection().remove(this.collection._byId[id]); 147 | 148 | // @TODO why isn't this triggered by the above line? 149 | this.controller.state().props.trigger('change:selection'); 150 | }, 151 | 152 | clearSelection() { 153 | this.getSelection().reset(); 154 | }, 155 | 156 | getSelection() { 157 | return this.controller.state().props.get('_all').get('selection'); 158 | }, 159 | 160 | clearItems() { 161 | this.$el.find('.simplechart-item').removeClass('selected details'); 162 | this.$el.find('.simplechart-items').empty(); 163 | this.$el.find('.simplechart-pagination').hide(); 164 | }, 165 | 166 | loading() { 167 | // show spinner 168 | this.$el.find('.spinner').addClass('is-active'); 169 | 170 | // hide messages 171 | this.$el.find('.simplechart-error').hide().text(''); 172 | this.$el.find('.simplechart-empty').hide().text(''); 173 | 174 | // disable 'load more' button 175 | jQuery('#simplechart-loadmore').attr('disabled', true); 176 | }, 177 | 178 | loaded() { 179 | // hide spinner 180 | this.$el.find('.spinner').removeClass('is-active'); 181 | }, 182 | 183 | fetchItems() { 184 | this.trigger('loading'); 185 | 186 | const date = new Date(); 187 | const tzOffsetSeconds = date.getTimezoneOffset() * 60; 188 | 189 | /* eslint-disable no-underscore-dangle */ 190 | const data = { 191 | _nonce: simplechart._nonce, 192 | service: 'simplechart', 193 | tab: 'all', 194 | params: this.model.get('params'), 195 | page: this.model.get('page'), 196 | max_id: this.model.get('max_id'), 197 | tz_off: tzOffsetSeconds, 198 | }; 199 | /* eslint-enable no-underscore-dangle */ 200 | 201 | wp.media.ajax('simplechart_request', { 202 | context: this, 203 | success: this.fetchedSuccess, 204 | error: this.fetchedError, 205 | data, 206 | }); 207 | }, 208 | 209 | fetchedSuccess(response) { 210 | if (! this.model.get('page')) { 211 | if (! response.items) { 212 | this.fetchedEmpty(response); 213 | return; 214 | } 215 | 216 | this.model.set('min_id', response.meta.min_id); 217 | this.model.set('items', response.items); 218 | 219 | this.collection.reset(response.items); 220 | } else { 221 | if (! response.items) { 222 | this.moreEmpty(response); 223 | return; 224 | } 225 | 226 | this.model.set('items', this.model.get('items').concat(response.items)); 227 | 228 | const collection = new Backbone.Collection(response.items); 229 | const container = document.createDocumentFragment(); 230 | 231 | this.collection.add(collection.models); 232 | 233 | collection.each((model) => { 234 | container.appendChild(this.renderItem(model)); 235 | }, this); 236 | 237 | this.$el.find('.simplechart-items').append(container); 238 | } 239 | 240 | jQuery('#simplechart-loadmore').attr('disabled', false).show(); 241 | this.model.set('max_id', response.meta.max_id); 242 | 243 | this.trigger('loaded loaded:success', response); 244 | }, 245 | 246 | fetchedEmpty(response) { 247 | this 248 | .$el 249 | .find('.simplechart-empty') 250 | .text('No charts matched your search query.') 251 | .show(); 252 | this.$el.find('.simplechart-pagination').hide(); 253 | 254 | this.trigger('loaded loaded:noresults', response); 255 | }, 256 | 257 | fetchedError(response) { 258 | this.$el.find('.simplechart-error').text(response.error_message).show(); 259 | jQuery('#simplechart-loadmore').attr('disabled', false).show(); 260 | this.trigger('loaded loaded:error', response); 261 | }, 262 | 263 | moreEmpty(response) { 264 | this.$el.find('.simplechart-pagination').hide(); 265 | 266 | this.trigger('loaded loaded:noresults', response); 267 | }, 268 | 269 | updateInput(event) { 270 | // triggered when a search is submitted 271 | 272 | const params = this.model.get('params'); 273 | this.$el.find('.simplechart-toolbar').find(':input').each((idx, el) => { 274 | const n = jQuery(el).attr('name'); 275 | if (n) { 276 | params[n] = jQuery(el).val(); 277 | } 278 | }); 279 | 280 | this.clearSelection(); 281 | jQuery('#simplechart-button').attr('disabled', 'disabled'); 282 | this.model.set('params', params); 283 | this.trigger('change:params'); // why isn't this triggering automatically? might be because params is an object 284 | 285 | event.preventDefault(); 286 | }, 287 | 288 | paginate(event) { 289 | if (0 === this.collection.length) { 290 | return; 291 | } 292 | 293 | const page = this.model.get('page') || 1; 294 | 295 | this.model.set('page', page + 1); 296 | this.trigger('change:page'); 297 | 298 | event.preventDefault(); 299 | }, 300 | 301 | changedPage() { 302 | // triggered when the pagination is changed 303 | this.fetchItems(); 304 | }, 305 | 306 | changedParams() { 307 | // triggered when the search parameters are changed 308 | this.model.set('page', null); 309 | this.model.set('min_id', null); 310 | this.model.set('max_id', null); 311 | 312 | this.clearItems(); 313 | this.fetchItems(); 314 | }, 315 | }); 316 | 317 | return SimplechartView; 318 | } 319 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Simplechart, a WordPress plugin for building and embedding charts. 2 | 3 | Source code for the js/app directory is available under the same license at: 4 | https://github.com/alleyinteractive/simplechart 5 | 6 | Copyright 2016 Alley Interactive LLC 7 | 8 | This file is part of Simplechart. 9 | 10 | Simplechart is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 2 of the License, or 13 | (at your option) any later version. 14 | 15 | Simplechart is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with Simplechart. If not, see . 22 | 23 | GNU GENERAL PUBLIC LICENSE 24 | Version 2, June 1991 25 | 26 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 27 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 28 | Everyone is permitted to copy and distribute verbatim copies 29 | of this license document, but changing it is not allowed. 30 | 31 | Preamble 32 | 33 | The licenses for most software are designed to take away your 34 | freedom to share and change it. By contrast, the GNU General Public 35 | License is intended to guarantee your freedom to share and change free 36 | software--to make sure the software is free for all its users. This 37 | General Public License applies to most of the Free Software 38 | Foundation's software and to any other program whose authors commit to 39 | using it. (Some other Free Software Foundation software is covered by 40 | the GNU Lesser General Public License instead.) You can apply it to 41 | your programs, too. 42 | 43 | When we speak of free software, we are referring to freedom, not 44 | price. Our General Public Licenses are designed to make sure that you 45 | have the freedom to distribute copies of free software (and charge for 46 | this service if you wish), that you receive source code or can get it 47 | if you want it, that you can change the software or use pieces of it 48 | in new free programs; and that you know you can do these things. 49 | 50 | To protect your rights, we need to make restrictions that forbid 51 | anyone to deny you these rights or to ask you to surrender the rights. 52 | These restrictions translate to certain responsibilities for you if you 53 | distribute copies of the software, or if you modify it. 54 | 55 | For example, if you distribute copies of such a program, whether 56 | gratis or for a fee, you must give the recipients all the rights that 57 | you have. You must make sure that they, too, receive or can get the 58 | source code. And you must show them these terms so they know their 59 | rights. 60 | 61 | We protect your rights with two steps: (1) copyright the software, and 62 | (2) offer you this license which gives you legal permission to copy, 63 | distribute and/or modify the software. 64 | 65 | Also, for each author's protection and ours, we want to make certain 66 | that everyone understands that there is no warranty for this free 67 | software. If the software is modified by someone else and passed on, we 68 | want its recipients to know that what they have is not the original, so 69 | that any problems introduced by others will not reflect on the original 70 | authors' reputations. 71 | 72 | Finally, any free program is threatened constantly by software 73 | patents. We wish to avoid the danger that redistributors of a free 74 | program will individually obtain patent licenses, in effect making the 75 | program proprietary. To prevent this, we have made it clear that any 76 | patent must be licensed for everyone's free use or not licensed at all. 77 | 78 | The precise terms and conditions for copying, distribution and 79 | modification follow. 80 | 81 | GNU GENERAL PUBLIC LICENSE 82 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 83 | 84 | 0. This License applies to any program or other work which contains 85 | a notice placed by the copyright holder saying it may be distributed 86 | under the terms of this General Public License. The "Program", below, 87 | refers to any such program or work, and a "work based on the Program" 88 | means either the Program or any derivative work under copyright law: 89 | that is to say, a work containing the Program or a portion of it, 90 | either verbatim or with modifications and/or translated into another 91 | language. (Hereinafter, translation is included without limitation in 92 | the term "modification".) Each licensee is addressed as "you". 93 | 94 | Activities other than copying, distribution and modification are not 95 | covered by this License; they are outside its scope. The act of 96 | running the Program is not restricted, and the output from the Program 97 | is covered only if its contents constitute a work based on the 98 | Program (independent of having been made by running the Program). 99 | Whether that is true depends on what the Program does. 100 | 101 | 1. You may copy and distribute verbatim copies of the Program's 102 | source code as you receive it, in any medium, provided that you 103 | conspicuously and appropriately publish on each copy an appropriate 104 | copyright notice and disclaimer of warranty; keep intact all the 105 | notices that refer to this License and to the absence of any warranty; 106 | and give any other recipients of the Program a copy of this License 107 | along with the Program. 108 | 109 | You may charge a fee for the physical act of transferring a copy, and 110 | you may at your option offer warranty protection in exchange for a fee. 111 | 112 | 2. You may modify your copy or copies of the Program or any portion 113 | of it, thus forming a work based on the Program, and copy and 114 | distribute such modifications or work under the terms of Section 1 115 | above, provided that you also meet all of these conditions: 116 | 117 | a) You must cause the modified files to carry prominent notices 118 | stating that you changed the files and the date of any change. 119 | 120 | b) You must cause any work that you distribute or publish, that in 121 | whole or in part contains or is derived from the Program or any 122 | part thereof, to be licensed as a whole at no charge to all third 123 | parties under the terms of this License. 124 | 125 | c) If the modified program normally reads commands interactively 126 | when run, you must cause it, when started running for such 127 | interactive use in the most ordinary way, to print or display an 128 | announcement including an appropriate copyright notice and a 129 | notice that there is no warranty (or else, saying that you provide 130 | a warranty) and that users may redistribute the program under 131 | these conditions, and telling the user how to view a copy of this 132 | License. (Exception: if the Program itself is interactive but 133 | does not normally print such an announcement, your work based on 134 | the Program is not required to print an announcement.) 135 | 136 | These requirements apply to the modified work as a whole. If 137 | identifiable sections of that work are not derived from the Program, 138 | and can be reasonably considered independent and separate works in 139 | themselves, then this License, and its terms, do not apply to those 140 | sections when you distribute them as separate works. But when you 141 | distribute the same sections as part of a whole which is a work based 142 | on the Program, the distribution of the whole must be on the terms of 143 | this License, whose permissions for other licensees extend to the 144 | entire whole, and thus to each and every part regardless of who wrote it. 145 | 146 | Thus, it is not the intent of this section to claim rights or contest 147 | your rights to work written entirely by you; rather, the intent is to 148 | exercise the right to control the distribution of derivative or 149 | collective works based on the Program. 150 | 151 | In addition, mere aggregation of another work not based on the Program 152 | with the Program (or with a work based on the Program) on a volume of 153 | a storage or distribution medium does not bring the other work under 154 | the scope of this License. 155 | 156 | 3. You may copy and distribute the Program (or a work based on it, 157 | under Section 2) in object code or executable form under the terms of 158 | Sections 1 and 2 above provided that you also do one of the following: 159 | 160 | a) Accompany it with the complete corresponding machine-readable 161 | source code, which must be distributed under the terms of Sections 162 | 1 and 2 above on a medium customarily used for software interchange; or, 163 | 164 | b) Accompany it with a written offer, valid for at least three 165 | years, to give any third party, for a charge no more than your 166 | cost of physically performing source distribution, a complete 167 | machine-readable copy of the corresponding source code, to be 168 | distributed under the terms of Sections 1 and 2 above on a medium 169 | customarily used for software interchange; or, 170 | 171 | c) Accompany it with the information you received as to the offer 172 | to distribute corresponding source code. (This alternative is 173 | allowed only for noncommercial distribution and only if you 174 | received the program in object code or executable form with such 175 | an offer, in accord with Subsection b above.) 176 | 177 | The source code for a work means the preferred form of the work for 178 | making modifications to it. For an executable work, complete source 179 | code means all the source code for all modules it contains, plus any 180 | associated interface definition files, plus the scripts used to 181 | control compilation and installation of the executable. However, as a 182 | special exception, the source code distributed need not include 183 | anything that is normally distributed (in either source or binary 184 | form) with the major components (compiler, kernel, and so on) of the 185 | operating system on which the executable runs, unless that component 186 | itself accompanies the executable. 187 | 188 | If distribution of executable or object code is made by offering 189 | access to copy from a designated place, then offering equivalent 190 | access to copy the source code from the same place counts as 191 | distribution of the source code, even though third parties are not 192 | compelled to copy the source along with the object code. 193 | 194 | 4. You may not copy, modify, sublicense, or distribute the Program 195 | except as expressly provided under this License. Any attempt 196 | otherwise to copy, modify, sublicense or distribute the Program is 197 | void, and will automatically terminate your rights under this License. 198 | However, parties who have received copies, or rights, from you under 199 | this License will not have their licenses terminated so long as such 200 | parties remain in full compliance. 201 | 202 | 5. You are not required to accept this License, since you have not 203 | signed it. However, nothing else grants you permission to modify or 204 | distribute the Program or its derivative works. These actions are 205 | prohibited by law if you do not accept this License. Therefore, by 206 | modifying or distributing the Program (or any work based on the 207 | Program), you indicate your acceptance of this License to do so, and 208 | all its terms and conditions for copying, distributing or modifying 209 | the Program or works based on it. 210 | 211 | 6. Each time you redistribute the Program (or any work based on the 212 | Program), the recipient automatically receives a license from the 213 | original licensor to copy, distribute or modify the Program subject to 214 | these terms and conditions. You may not impose any further 215 | restrictions on the recipients' exercise of the rights granted herein. 216 | You are not responsible for enforcing compliance by third parties to 217 | this License. 218 | 219 | 7. If, as a consequence of a court judgment or allegation of patent 220 | infringement or for any other reason (not limited to patent issues), 221 | conditions are imposed on you (whether by court order, agreement or 222 | otherwise) that contradict the conditions of this License, they do not 223 | excuse you from the conditions of this License. If you cannot 224 | distribute so as to satisfy simultaneously your obligations under this 225 | License and any other pertinent obligations, then as a consequence you 226 | may not distribute the Program at all. For example, if a patent 227 | license would not permit royalty-free redistribution of the Program by 228 | all those who receive copies directly or indirectly through you, then 229 | the only way you could satisfy both it and this License would be to 230 | refrain entirely from distribution of the Program. 231 | 232 | If any portion of this section is held invalid or unenforceable under 233 | any particular circumstance, the balance of the section is intended to 234 | apply and the section as a whole is intended to apply in other 235 | circumstances. 236 | 237 | It is not the purpose of this section to induce you to infringe any 238 | patents or other property right claims or to contest validity of any 239 | such claims; this section has the sole purpose of protecting the 240 | integrity of the free software distribution system, which is 241 | implemented by public license practices. Many people have made 242 | generous contributions to the wide range of software distributed 243 | through that system in reliance on consistent application of that 244 | system; it is up to the author/donor to decide if he or she is willing 245 | to distribute software through any other system and a licensee cannot 246 | impose that choice. 247 | 248 | This section is intended to make thoroughly clear what is believed to 249 | be a consequence of the rest of this License. 250 | 251 | 8. If the distribution and/or use of the Program is restricted in 252 | certain countries either by patents or by copyrighted interfaces, the 253 | original copyright holder who places the Program under this License 254 | may add an explicit geographical distribution limitation excluding 255 | those countries, so that distribution is permitted only in or among 256 | countries not thus excluded. In such case, this License incorporates 257 | the limitation as if written in the body of this License. 258 | 259 | 9. The Free Software Foundation may publish revised and/or new versions 260 | of the General Public License from time to time. Such new versions will 261 | be similar in spirit to the present version, but may differ in detail to 262 | address new problems or concerns. 263 | 264 | Each version is given a distinguishing version number. If the Program 265 | specifies a version number of this License which applies to it and "any 266 | later version", you have the option of following the terms and conditions 267 | either of that version or of any later version published by the Free 268 | Software Foundation. If the Program does not specify a version number of 269 | this License, you may choose any version ever published by the Free Software 270 | Foundation. 271 | 272 | 10. If you wish to incorporate parts of the Program into other free 273 | programs whose distribution conditions are different, write to the author 274 | to ask for permission. For software which is copyrighted by the Free 275 | Software Foundation, write to the Free Software Foundation; we sometimes 276 | make exceptions for this. Our decision will be guided by the two goals 277 | of preserving the free status of all derivatives of our free software and 278 | of promoting the sharing and reuse of software generally. 279 | 280 | NO WARRANTY 281 | 282 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 283 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 284 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 285 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 286 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 287 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 288 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 289 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 290 | REPAIR OR CORRECTION. 291 | 292 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 293 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 294 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 295 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 296 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 297 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 298 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 299 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 300 | POSSIBILITY OF SUCH DAMAGES. 301 | 302 | END OF TERMS AND CONDITIONS 303 | 304 | How to Apply These Terms to Your New Programs 305 | 306 | If you develop a new program, and you want it to be of the greatest 307 | possible use to the public, the best way to achieve this is to make it 308 | free software which everyone can redistribute and change under these terms. 309 | 310 | To do so, attach the following notices to the program. It is safest 311 | to attach them to the start of each source file to most effectively 312 | convey the exclusion of warranty; and each file should have at least 313 | the "copyright" line and a pointer to where the full notice is found. 314 | 315 | 316 | Copyright (C) 317 | 318 | This program is free software; you can redistribute it and/or modify 319 | it under the terms of the GNU General Public License as published by 320 | the Free Software Foundation; either version 2 of the License, or 321 | (at your option) any later version. 322 | 323 | This program is distributed in the hope that it will be useful, 324 | but WITHOUT ANY WARRANTY; without even the implied warranty of 325 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 326 | GNU General Public License for more details. 327 | 328 | You should have received a copy of the GNU General Public License along 329 | with this program; if not, write to the Free Software Foundation, Inc., 330 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 331 | 332 | Also add information on how to contact you by electronic and paper mail. 333 | 334 | If the program is interactive, make it output a short notice like this 335 | when it starts in an interactive mode: 336 | 337 | Gnomovision version 69, Copyright (C) year name of author 338 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 339 | This is free software, and you are welcome to redistribute it 340 | under certain conditions; type `show c' for details. 341 | 342 | The hypothetical commands `show w' and `show c' should show the appropriate 343 | parts of the General Public License. Of course, the commands you use may 344 | be called something other than `show w' and `show c'; they could even be 345 | mouse-clicks or menu items--whatever suits your program. 346 | 347 | You should also get your employer (if you work as a programmer) or your 348 | school, if any, to sign a "copyright disclaimer" for the program, if 349 | necessary. Here is a sample; alter the names: 350 | 351 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 352 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 353 | 354 | , 1 April 1989 355 | Ty Coon, President of Vice 356 | 357 | This General Public License does not permit incorporating your program into 358 | proprietary programs. If your program is a subroutine library, you may 359 | consider it more useful to permit linking proprietary applications with the 360 | library. If this is what you want to do, use the GNU Lesser General 361 | Public License instead of this License. -------------------------------------------------------------------------------- /modules/class-simplechart-insert-template.php: -------------------------------------------------------------------------------- 1 | 16 |
    17 |
    18 |
    19 |
    20 |

    {{ data.content }}

    21 |
    22 | 23 |

    24 | {{data.status}} 25 |

    26 |

    27 | {{ data.date }} 28 |

    29 |
    30 | 31 | 32 |
    33 |
    34 |
    35 |
    36 | 46 |
    47 |

    {{ data.content }}

    48 |
    49 | 60 |
    61 | 69 | 70 | 71 |
    72 |
    73 | 84 | 94 | set_template( new Simplechart_Insert_Template ); 20 | 21 | // Add actions to WP hooks 22 | add_action( 'print_media_templates', array( $this, 'action_print_media_templates' ) ); 23 | add_action( 'wp_ajax_simplechart_request', array( $this, 'ajax_request' ) ); 24 | add_action( 'wp_enqueue_media', array( $this, 'action_enqueue_media' ) ); 25 | } 26 | 27 | /** 28 | * Process an AJAX request and output the resulting JSON. 29 | * 30 | * @action wp_ajax_simplechart_request 31 | * @return null 32 | */ 33 | public function ajax_request() { 34 | 35 | if ( ! isset( $_POST['_nonce'] ) 36 | or ! wp_verify_nonce( sanitize_key( $_POST['_nonce'] ), 'simplechart_request' ) 37 | ) { 38 | die( '-1' ); 39 | } 40 | 41 | $request = wp_parse_args( 42 | stripslashes_deep( $_POST ), 43 | array( 44 | 'params' => array(), 45 | 'tab' => null, 46 | 'min_id' => null, 47 | 'max_id' => null, 48 | 'page' => 1, 49 | ) 50 | ); 51 | $request['page'] = absint( $request['page'] ); 52 | $request['user_id'] = absint( get_current_user_id() ); 53 | 54 | $response = $this->request( $request ); 55 | 56 | if ( is_wp_error( $response ) ) { 57 | wp_send_json_error( 58 | array( 59 | 'error_code' => $response->get_error_code(), 60 | 'error_message' => $response->get_error_message(), 61 | ) 62 | ); 63 | 64 | } else if ( is_array( $response ) ) { 65 | wp_send_json_success( $response ); 66 | } else { 67 | wp_send_json_success( false ); 68 | } 69 | } 70 | 71 | /** 72 | * Handles the AJAX request and returns an appropriate response. This should be used, for example, to perform an API request to the service provider and return the results. 73 | * 74 | * @param array $request The request parameters. 75 | * @return array|bool An array should be returned on success, boolean false should be returned if there are no results to show. 76 | */ 77 | public function request( array $request ) { 78 | 79 | // You'll want to handle connection errors to your service here. Look at the Twitter and YouTube implementations for how you could do this. 80 | 81 | // Create the response for the API 82 | $response = array(); 83 | $items = array(); 84 | 85 | $query_args = array( 86 | 'post_type' => 'simplechart', 87 | ); 88 | 89 | // pagination 90 | if ( isset( $request['page'] ) && absint( $request['page'] ) > 1 ) { 91 | $query_args['paged'] = absint( $request['page'] ); 92 | } 93 | 94 | // search query 95 | if ( isset( $request['params']['q'] ) ) { 96 | $query_args['s'] = sanitize_text_field( $request['params']['q'] ); 97 | } 98 | 99 | $simplechart_query = new WP_Query( $query_args ); 100 | 101 | if ( $simplechart_query->have_posts() ) { 102 | while ( $simplechart_query->have_posts() ) { 103 | $simplechart_query->the_post(); 104 | global $post; 105 | 106 | $thumb = wp_get_attachment_image_src( get_post_thumbnail_id( $post->ID ), array( 150, 150 ) ); 107 | $thumb_url = isset( $thumb[0] ) ? $thumb[0] : ''; 108 | $item = array(); 109 | $item['date'] = date( 'g:i A - j M y', intval( get_the_time( 'U' ) - $request['tz_off'] ) ); 110 | $item['id'] = absint( $post->ID ); 111 | $item['content'] = esc_html( get_the_title() ); 112 | $item['img'] = esc_url( $thumb_url ); 113 | 114 | // Add status like ' - Draft' if chart is not yet published 115 | $status = get_post_status( $post->ID ); 116 | if ( 'publish' === $status || ! $status ) { 117 | $status = esc_html__( 'Published', 'simplechart' ); 118 | } else { 119 | $status = esc_html( ucfirst( $status ) ); 120 | } 121 | $item['status'] = $status; 122 | 123 | $items[] = $item; 124 | } 125 | } else { 126 | return false; 127 | } 128 | 129 | $response['items'] = $items; 130 | $response['meta'] = array( 131 | 'min_id' => reset( $items )['id'], 132 | ); 133 | 134 | return $response; 135 | } 136 | 137 | /** 138 | * Load the Backbone templates for each of our registered services. 139 | * 140 | * @action print_media_templates 141 | * @return null 142 | */ 143 | public function action_print_media_templates() { 144 | $template = $this->get_template(); 145 | if ( ! $template ) { 146 | return; 147 | } 148 | 149 | foreach ( array( 'search', 'item' ) as $t ) { 150 | $id = sprintf( 151 | 'simplechart-insert-%s-all', 152 | esc_attr( $t ) 153 | ); 154 | 155 | $template->before_template( $id ); 156 | call_user_func( array( $template, $t ), $id, 'all' ); 157 | $template->after_template(); 158 | } 159 | 160 | $id = sprintf( 'simplechart-insert-thumbnail' ); 161 | 162 | $template->before_template( $id ); 163 | call_user_func( array( $template, 'thumbnail' ), $id ); 164 | $template->after_template(); 165 | } 166 | 167 | /** 168 | * Enqueue and localise the JS and CSS we need for the media manager. 169 | * 170 | * @action enqueue_media 171 | * @return null 172 | */ 173 | public function action_enqueue_media() { 174 | $simplechart = array( 175 | '_nonce' => wp_create_nonce( 'simplechart_request' ), 176 | 'base_url' => untrailingslashit( Simplechart::instance()->get_plugin_url() ), 177 | 'admin_url' => untrailingslashit( admin_url() ), 178 | ); 179 | 180 | wp_enqueue_script( 181 | 'simplechart-insert', 182 | Simplechart::instance()->get_plugin_url( 'js/plugin/build/simplechartInsert.js' ), 183 | array( 'jquery' ), 184 | Simplechart::instance()->get_config( 'version' ) 185 | ); 186 | 187 | wp_enqueue_style( 188 | 'simplechart-insert', 189 | Simplechart::instance()->get_plugin_url( 'css/simplechart-insert.css' ), 190 | array(), 191 | Simplechart::instance()->get_config( 'version' ) 192 | ); 193 | 194 | wp_localize_script( 195 | 'simplechart-insert', 196 | 'simplechart', 197 | $simplechart 198 | ); 199 | } 200 | 201 | /** 202 | * Sets the template object. 203 | * 204 | * @return null 205 | */ 206 | final public function set_template( Simplechart_Insert_Template $template ) { 207 | 208 | $this->template = $template; 209 | 210 | } 211 | 212 | /** 213 | * Returns the template object for this service. 214 | * 215 | * @return Template|null A Template object, 216 | * or null if a template isn't set. 217 | */ 218 | final public function get_template() { 219 | 220 | return $this->template; 221 | 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /modules/class-simplechart-plugin-versions.php: -------------------------------------------------------------------------------- 1 | get_config( 'version' ), 55 | $this->_latest_plugin_version, 56 | '<' 57 | ); 58 | } 59 | 60 | /** 61 | * Compares remote version transient to current version checking for update 62 | * Called on 'init' instead of 'admin_init' because the check needs to run before 'admin_menu' 63 | */ 64 | public function check_for_new_version() { 65 | if ( ! current_user_can( 'update_plugins' ) || ! is_admin() ) { 66 | return; 67 | } 68 | 69 | $this->_latest_plugin_version = get_transient( 'simplechart_plugin_version_remote' ); 70 | if ( false === $this->_latest_plugin_version ) { 71 | $this->update_simplechart_remote_metadata(); 72 | return; 73 | } 74 | 75 | $this->_latest_plugin_zip_url = get_transient( 'simplechart_plugin_zip_url_remote' ); 76 | if ( false === $this->_latest_plugin_zip_url ) { 77 | $this->_latest_plugin_zip_url = $this->_simplechart_repository_url; 78 | } 79 | 80 | $this->_latest_plugin_update_name = get_transient( 'simplechart_plugin_update_name' ); 81 | if ( false === $this->_latest_plugin_update_name ) { 82 | $this->_latest_plugin_update_name = __( 'Latest Update', 'simplechart' ); 83 | } 84 | 85 | $this->_latest_plugin_update_description = get_transient( 'simplechart_plugin_update_body' ); 86 | if ( false === $this->_latest_plugin_update_description ) { 87 | $this->_latest_plugin_update_description = __( 'See more on Github.', 'simplechart' ); 88 | } 89 | 90 | $this->_latest_plugin_last_updated = get_transient( 'simplechart_plugin_update_last_updated' ); 91 | 92 | if ( $this->update_available() ) { 93 | add_filter( 'site_transient_update_plugins', array( $this, 'extend_filter_update_plugins' ) ); 94 | } 95 | } 96 | 97 | /** 98 | * Add Simplechart to the plugin updates transient when appropraite 99 | * @param object $update_plugins Contains plugin update info 100 | * @return object $update_plugins Contains plugin update info, plus Simplechart 101 | */ 102 | public function extend_filter_update_plugins( $update_plugins ) { 103 | if ( ! is_object( $update_plugins ) ) { 104 | return $update_plugins; 105 | } 106 | 107 | if ( ! isset( $update_plugins->response ) || ! is_array( $update_plugins->response ) ) { 108 | $update_plugins->response = array(); 109 | } 110 | 111 | $update_plugins->response['wordpress-simplechart/simplechart.php'] = (object) array( 112 | 'slug' => 'wordpress-simplechart', 113 | 'new_version' => $this->_latest_plugin_version, 114 | 'url' => $this->_simplechart_repository_url, 115 | ); 116 | 117 | return $update_plugins; 118 | } 119 | 120 | /** 121 | * Intercept the plugins API result and return Simplechart remote information 122 | * @param object|WP_Error $res Response object or WP_Error. 123 | * @param string $action The type of information being requested from the Plugin Install API. 124 | * @param object $args Plugin API arguments. 125 | * @return object $args Mock response object for Simplechart 126 | */ 127 | public function mock_plugins_api( $res, $action, $args ) { 128 | if ( 'plugin-information' !== $action && ! empty( $args->slug ) && 'wordpress-simplechart' !== $args->slug ) { 129 | return $res; 130 | } 131 | $copy_generated = sprintf( 132 | __( 'Ask an administrator to replace your existing Simplechart plugin directory with the ZIP of %2$s via Github', 'simplechart' ), 133 | esc_url( $this->_latest_plugin_zip_url ), 134 | esc_html( $this->_latest_plugin_version ) 135 | ); 136 | $description = sprintf( 137 | __( '%1$s

    %2$s

    %3$s', 'simplechart' ), 138 | $copy_generated, 139 | esc_html( $this->_latest_plugin_update_name ), 140 | esc_html( $this->_latest_plugin_update_description ) 141 | ); 142 | 143 | return (object) array( 144 | 'name' => __( 'Simplechart', 'simplechart' ), 145 | 'banners' => array( 146 | 'high' => null, 147 | 'low' => null, 148 | ), 149 | 'external' => $this->_simplechart_repository_url, 150 | 'homepage' => $this->_simplechart_repository_url, 151 | 'slug' => 'wordpress-simplechart', 152 | 'version' => $this->_latest_plugin_version, 153 | 'last_updated' => $this->_latest_plugin_last_updated, 154 | 'sections' => array( 155 | 'changelog' => $description, 156 | ), 157 | ); 158 | } 159 | 160 | /** 161 | * Update the transients for Simplechart remote version 162 | */ 163 | public function update_simplechart_remote_metadata() { 164 | if ( function_exists( 'vip_safe_wp_remote_get' ) ) { 165 | $response = vip_safe_wp_remote_get( $this->_simplechart_releases_url ); 166 | } else { 167 | $response = wp_remote_get( $this->_simplechart_releases_url ); 168 | } 169 | 170 | if ( is_wp_error( $response ) ) { 171 | return; 172 | } 173 | 174 | if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { 175 | return; 176 | } 177 | 178 | $body = wp_remote_retrieve_body( $response ); 179 | 180 | if ( empty( $body ) ) { 181 | return; 182 | } 183 | 184 | $json = json_decode( $body, true ); 185 | 186 | if ( null === $json ) { 187 | return; 188 | } 189 | 190 | $transients = array( 191 | 'version_remote' => 'tag_name', 192 | 'zip_url_remote' => 'zipball_url', 193 | 'update_name' => 'name', 194 | 'update_last_updated' => 'published_at', 195 | 'update_body' => 'body', 196 | ); 197 | 198 | foreach ( $transients as $wp_key => $gh_key ) { 199 | if ( ! empty( $json[0][ $gh_key ] ) ) { 200 | set_transient( 'simplechart_plugin_' . $wp_key, $json[0][ $gh_key ], DAY_IN_SECONDS ); 201 | } 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /modules/class-simplechart-post-type.php: -------------------------------------------------------------------------------- 1 | '; 42 | } 43 | } 44 | 45 | public function action_add_meta_boxes() { 46 | remove_meta_box( 'submitdiv', 'simplechart', 'side' ); 47 | 48 | add_meta_box( 49 | 'simplechart-save', 50 | __( 'Save Chart', 'simplechart' ), 51 | array( $this, 'render_submit_button' ), 52 | 'simplechart', 53 | 'side', 54 | 'default' 55 | ); 56 | 57 | add_meta_box( 58 | 'simplechart-preview', 59 | __( 'Simplechart', 'simplechart' ), 60 | array( $this, 'render_meta_box' ), 61 | 'simplechart', 62 | 'normal', 63 | 'default' 64 | ); 65 | } 66 | 67 | public function render_submit_button() { 68 | global $post; 69 | if ( in_array( $post->post_status, array( 'pending', 'publish', 'draft' ) ) ) { 70 | $button_text = __( 'Update', 'simplechart' ); 71 | } else { 72 | $button_text = __( 'Publish', 'simplechart' ); 73 | } 74 | submit_button( $button_text, 'primary', 'publish', false ); 75 | wp_nonce_field( 'simplechart_save', 'simplechart-nonce' ); 76 | } 77 | 78 | public function register_post_type() { 79 | $args = array( 80 | 'labels' => array( 81 | 'name' => esc_html__( 'Charts', 'simplechart' ), 82 | 'singular_name' => esc_html__( 'Chart', 'simplechart' ), 83 | 'plural_name' => esc_html__( 'All Charts', 'simplechart' ), 84 | 'add_new' => esc_html__( 'Add New', 'simplechart' ), 85 | 'add_new_item' => esc_html__( 'Add New Chart', 'simplechart' ), 86 | 'edit_item' => esc_html__( 'Edit Chart', 'simplechart' ), 87 | 'new_item' => esc_html__( 'New Chart', 'simplechart' ), 88 | 'view_item' => esc_html__( 'View Chart', 'simplechart' ), 89 | 'search_items' => esc_html__( 'Search Charts', 'simplechart' ), 90 | 'not_found' => esc_html__( 'No charts found', 'simplechart' ), 91 | 'not_found_in_trash' => esc_html__( 'No charts found in Trash', 'simplechart' ), 92 | ), 93 | 94 | // external publicness 95 | 'public' => true, 96 | 'exclude_from_search' => true, 97 | 'publicly_queryable' => true, 98 | 99 | // wp-admin publicness 100 | 'show_in_nav_menus' => false, 101 | 'show_ui' => true, 102 | 103 | // just below Media 104 | //'menu_position' => 11, 105 | 106 | // enable single pages without permalink for checking template rendering 107 | 'rewrite' => false, 108 | 'has_archive' => false, 109 | 110 | 'menu_icon' => 'dashicons-chart-pie', 111 | 'supports' => array( 'title' ), 112 | ); 113 | 114 | register_post_type( 'simplechart', $args ); 115 | } 116 | 117 | public function render_meta_box( $post, $args ) { 118 | require_once( Simplechart::instance()->get_plugin_dir( 'templates/meta-box.php' ) ); 119 | } 120 | 121 | public function setup_iframe_page() { 122 | add_menu_page( 'Simplechart App', 'Simplechart App', $this->_app_cap, 'simplechart_app', array( $this, 'render_iframe_page' ) ); 123 | } 124 | 125 | public function render_iframe_page() { 126 | require_once( Simplechart::instance()->get_plugin_dir() . 'templates/iframe.php' ); 127 | } 128 | 129 | /** 130 | * remove menu page from wp-admin nav since we're only creating it for the iframe when creating/editing a chart 131 | */ 132 | public function remove_menu_link( $menu_list ) { 133 | 134 | // remove from flat array of slugs for menu order 135 | $menu_list = $this->_remove_slug_from_menu_order( $menu_list ); 136 | 137 | // remove from global 'default' menu order 138 | global $default_menu_order; 139 | $default_menu_order = $this->_remove_slug_from_menu_order( $default_menu_order ); 140 | 141 | // remove from global menu 142 | global $menu; 143 | if ( ! empty( $menu ) && is_array( $menu ) ) { 144 | foreach ( $menu as $index => $item ) { 145 | if ( ! empty( $item[2] ) && Simplechart::instance()->get_config( 'menu_page_slug' ) === $item[2] ) { 146 | unset( $menu[ $index ] ); 147 | $menu = array_values( $menu ); 148 | break; 149 | } 150 | } 151 | } 152 | 153 | return $menu_list; 154 | } 155 | 156 | private function _remove_slug_from_menu_order( $array ) { 157 | if ( ! empty( $array ) && is_array( $array ) ) { 158 | $index = array_search( Simplechart::instance()->get_config( 'menu_page_slug' ), $array, true ); 159 | if ( false !== $index ) { 160 | unset( $array[ $index ] ); 161 | $array = array_values( $array ); 162 | } 163 | } 164 | return $array; 165 | } 166 | 167 | /** 168 | * use capability for this feature 169 | */ 170 | public function current_user_can() { 171 | return current_user_can( $this->_app_cap ); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /modules/class-simplechart-request-handler.php: -------------------------------------------------------------------------------- 1 | query_vars['simplechart-api'] ) && empty( $query->query_vars['simplechart-iframe'] ) ) { 49 | return; 50 | } 51 | 52 | $id = empty( $query->query_vars['p'] ) ? 0 : absint( $query->query_vars['p'] ); 53 | if ( ! empty( $query->query_vars['simplechart-api'] ) ) { 54 | $this->_handle_api_request( $id ); 55 | } else { 56 | $this->_handle_iframe_request( $id ); 57 | } 58 | } 59 | 60 | /** 61 | * Render HTML page for use in iframe 62 | * 63 | * @var int $id WordPress ID for the simplechart post 64 | * @return none 65 | */ 66 | private function _handle_iframe_request( $id ) { 67 | $this->_iframe_id = $id; 68 | require_once( Simplechart::instance()->get_plugin_dir( 'templates/amp-iframe-source.php' ) ); 69 | exit(); 70 | } 71 | 72 | /** 73 | * Getter for current frame ID 74 | * 75 | * @return int ID 76 | */ 77 | public function frame_id() { 78 | return $this->_iframe_id; 79 | } 80 | 81 | /** 82 | * Send JSON success and data, or failure when chart data is requested 83 | * 84 | * @var int $id WordPress ID for the simplechart post 85 | * @return none 86 | */ 87 | private function _handle_api_request( $id ) { 88 | // https + iframe can cause 'null' request origin that we need to accept 89 | // Safe to accept all reqs because this method can *only* output JSON then die 90 | header( 'Access-Control-Allow-Origin: *' ); 91 | 92 | // Validate post type 93 | if ( empty( $id ) || 'simplechart' !== get_post_type( $id ) ) { 94 | wp_send_json_error( 95 | array( 96 | 'message' => sprintf( __( "Post ID %d is not in 'simplechart' post type", 'simplechart' ), $id ), 97 | ) 98 | ); 99 | } 100 | 101 | // Build array of save-chartData, etc. from post meta 102 | $response = array(); 103 | foreach ( array( 'Data', 'Options', 'Metadata', 'Annotations' ) as $key ) { 104 | $response[ strtolower( $key ) ] = get_post_meta( $id, 'save-chart' . $key, true ); 105 | } 106 | 107 | if ( apply_filters( 'simplechart_enable_subtitle_field', false ) ) { 108 | if ( is_array( $response['metadata'] ) ) { 109 | $response['metadata']['subtitle'] = get_post_meta( $id, 'save-chartSubtitle', true ); 110 | } else { 111 | $metadata = json_decode( $response['metadata'], true ); 112 | if ( ! is_null( $metadata ) ) { 113 | $metadata['subtitle'] = get_post_meta( $id, 'save-chartSubtitle', true ); 114 | $response['metadata'] = wp_json_encode( $metadata ); 115 | } else { 116 | $response['metadata'] = null; 117 | } 118 | } 119 | } 120 | 121 | wp_send_json_success( $response ); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /modules/class-simplechart-save.php: -------------------------------------------------------------------------------- 1 | _image_post_status, 34 | array( 35 | 'label' => __( 'Chart Image', 'simplechart' ), 36 | 'public' => false, 37 | 'exclude_from_search' => true, 38 | 'show_in_admin_all_list' => false, 39 | 'show_in_admin_status_list' => false, 40 | ) 41 | ); 42 | } 43 | 44 | // use remove-add to prevent infinite loop 45 | public function save_post_action( $post_id ) { 46 | 47 | // check user caps 48 | if ( ! current_user_can( 'edit_post', $post_id ) ) { 49 | return; 50 | } 51 | 52 | // only worry about the real post 53 | if ( wp_is_post_revision( $post_id ) ) { 54 | return; 55 | } 56 | 57 | remove_action( 'save_post_simplechart', array( $this, 'save_post_action' ), 10, 1 ); 58 | $post = get_post( $post_id ); 59 | $this->_do_save_post( $post ); 60 | add_action( 'save_post_simplechart', array( $this, 'save_post_action' ), 10, 1 ); 61 | } 62 | 63 | protected function _do_save_post( $post ) { 64 | // verify nonce 65 | if ( empty( $_POST['simplechart-nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['simplechart-nonce'] ) ), 'simplechart_save' ) ) { 66 | return; 67 | } 68 | 69 | // delete featured image if post is NOT published but has a featured image 70 | if ( 'publish' !== get_post_status( $post->ID ) && has_post_thumbnail( $post->ID ) ) { 71 | wp_delete_attachment( get_post_thumbnail_id( $post->ID ), true ); 72 | } 73 | 74 | // handle base64 image string if provided 75 | if ( ! empty( $_POST['save-previewImg'] ) ) { 76 | $this->_save_chart_image( $post, sanitize_text_field( wp_unslash( $_POST['save-previewImg'] ) ), $this->_default_img_type ); 77 | } 78 | 79 | // handle raw CSV multiline data 80 | if ( ! empty( $_POST['save-rawData'] ) ) { 81 | add_filter( 'sanitize_text_field', array( $this, 'sanitize_raw_data' ), 99, 2 ); 82 | $sanitized_raw_data = sanitize_text_field( wp_unslash( $_POST['save-rawData'] ) ); 83 | remove_filter( 'sanitize_text_field', array( $this, 'sanitize_raw_data' ), 99, 2 ); 84 | update_post_meta( $post->ID, 'save-rawData', $sanitized_raw_data ); 85 | } 86 | 87 | // handle chart type string 88 | if ( ! empty( $_POST['save-chartType'] ) ) { 89 | update_post_meta( $post->ID, 'save-chartType', sanitize_text_field( wp_unslash( $_POST['save-chartType'] ) ) ); 90 | } 91 | 92 | // save embed height 93 | if ( ! empty( $_POST['save-embedHeight'] ) ) { 94 | update_post_meta( $post->ID, 'embedHeight', absint( $_POST['save-embedHeight'] ) ); 95 | } 96 | 97 | // handle JSON fields 98 | foreach ( array( 'chartAnnotations', 'chartData', 'chartOptions', 'chartMetadata', 'googleSheetId' ) as $field ) { 99 | if ( ! empty( $_POST[ 'save-' . $field ] ) ) { 100 | // sanitize field name w/ esc_attr() instead of sanitize_key() because we want to preserve uppercase letters 101 | update_post_meta( $post->ID, 'save-' . esc_attr( $field ), sanitize_text_field( $_POST[ 'save-' . $field ] ) ); 102 | } 103 | } 104 | 105 | // save subtitle separately since we want to be able to save empty strings 106 | if ( isset( $_POST['save-chartSubtitle'] ) ) { 107 | if ( 'false' !== $_POST['save-chartSubtitle'] ) { 108 | update_post_meta( $post->ID, 'save-chartSubtitle', sanitize_text_field( wp_unslash( $_POST['save-chartSubtitle'] ) ) ); 109 | } 110 | } 111 | 112 | // save error messages 113 | if ( ! empty( $this->_errors ) ) { 114 | update_post_meta( $post->ID, 'simplechart-errors', $this->_errors ); 115 | } 116 | 117 | // save debug messages 118 | if ( ! empty( $this->_debug_messages ) ) { 119 | update_post_meta( $post->ID, 'simplechart-debug', $this->_debug_messages, true ); 120 | } 121 | } 122 | 123 | /** 124 | * Allow sanitize_text_field() for multiline CSV without stripping "\n" chars 125 | * 126 | * @param string $filtered Filtered input 127 | * @param string $initial Unfiltered input 128 | * @return string Sanitized multiline csv or empty string 129 | */ 130 | public function sanitize_raw_data( $filtered, $initial ) { 131 | // strip newlines from initial input 132 | $initial_stripped = preg_replace( '/[\r\n]+/', ' ', $initial ); 133 | if ( $filtered === $initial_stripped ) { 134 | // if that's the only difference, then the initial string is fine 135 | return $initial; 136 | } else { 137 | // if anything else was removed by sanitize_text_field(), return an empty string 138 | $this->_errors[] = __( 'CSV data may not include tabs or HTML tags.', 'simplechart' ); 139 | return ''; 140 | } 141 | } 142 | 143 | function admin_notices() { 144 | // skip if not editing single post in Chart post type 145 | $screen = get_current_screen(); 146 | if ( 'simplechart' !== $screen->post_type || 'post' !== $screen->base ) { 147 | return; 148 | } 149 | 150 | global $post; 151 | 152 | // print error messages 153 | $errors = maybe_unserialize( get_post_meta( $post->ID, 'simplechart-errors', true ) ); 154 | if ( is_array( $errors ) ) { 155 | foreach ( $errors as $error ) { 156 | echo '

    ' . esc_html( $error ) . '

    '; 157 | } 158 | } 159 | 160 | // print debug messages 161 | $this->_show_debug_messages = apply_filters( 'simplechart_show_debug_messages', $this->_show_debug_messages ); 162 | if ( $this->_show_debug_messages ) { 163 | $messages = maybe_unserialize( get_post_meta( $post->ID, 'simplechart-debug', true ) ); 164 | if ( is_array( $messages ) ) { 165 | foreach ( $messages as $message ) { 166 | echo '

    ' . esc_html( $message ) . '

    '; 167 | } 168 | } 169 | } 170 | 171 | // clear errors and debug messages 172 | delete_post_meta( $post->ID, 'simplechart-errors' ); 173 | delete_post_meta( $post->ID, 'simplechart-debug' ); 174 | } 175 | 176 | private function _save_chart_image( $post, $data_uri, $img_type ) { 177 | 178 | // make sure we have a post object 179 | if ( is_numeric( $post ) ) { 180 | $post = get_post( $post ); 181 | } 182 | 183 | $perm_file_name = 'simplechart_' . $post->ID . '.' . $img_type; 184 | $temp_file_name = 'temp_' . $perm_file_name; 185 | 186 | // make sure we have valid base64 data then proceed 187 | $img_data = $this->_process_data_uri( $data_uri, $img_type ); 188 | 189 | if ( is_wp_error( $img_data ) ) { 190 | $this->_errors = array_merge( $this->_errors, $img_data->get_error_messages() ); 191 | return false; 192 | } 193 | 194 | // delete existing chart image if present 195 | // so we can upload the new one to the same URL 196 | if ( has_post_thumbnail( $post->ID ) ) { 197 | $thumbnail_id = get_post_thumbnail_id( $post->ID ); 198 | $old_file_path = get_post_meta( $thumbnail_id, '_wp_attached_file', true ); 199 | $this->_debug_messages[] = sprintf( __( 'Found post thumbnail at %s', 'simplechart' ), $old_file_path ); 200 | wp_delete_attachment( $thumbnail_id, true ); 201 | } else { 202 | $this->_debug_messages[] = __( 'No existing post thumbnail found', 'simplechart' ); 203 | } 204 | 205 | // upload to temporary file location 206 | $temp_file = wp_upload_bits( $temp_file_name, null, base64_decode( $img_data ) ); 207 | if ( is_wp_error( $temp_file ) ) { 208 | $this->_errors = array_merge( $this->_errors, $temp_file->get_error_messages() ); // translation handled inside wp_upload_bits() 209 | return false; 210 | } elseif ( false !== $temp_file['error'] ) { 211 | $this->_errors[] = $temp_file['error']; // translation handled inside wp_upload_bits() 212 | return false; 213 | } 214 | $this->_debug_messages[] = sprintf( __( 'wp_upload_bits() stored in %s', 'simplechart' ), $temp_file['file'] ); 215 | 216 | // import to media library 217 | $desc = 'Chart: ' . sanitize_text_field( get_the_title( $post->ID ) ); 218 | $this->_attachment_id = media_handle_sideload( 219 | array( 220 | 'name' => $perm_file_name, 221 | 'tmp_name' => $temp_file['file'], 222 | ), 223 | $post->ID, 224 | $desc 225 | ); 226 | $new_file_path = get_post_meta( $this->_attachment_id, '_wp_attached_file', true ); 227 | $this->_debug_messages[] = sprintf( __( 'media_handle_sideload() to %s', 'simplechart' ), $new_file_path ); 228 | $this->_debug_messages[] = $new_file_path === $old_file_path ? __( 'New file path matches old file path', 'simplechart' ) : __( 'New file path does NOT match old file path', 'simplechart' ); 229 | 230 | if ( is_wp_error( $this->_attachment_id ) ) { 231 | $this->_errors = array_merge( $this->_errors, $this->_attachment_id->get_error_messages() ); 232 | return false; 233 | } 234 | 235 | if ( is_wp_error( $updated ) ) { 236 | $this->_errors = array_merge( $this->_errors, $updated->get_error_messages() ); 237 | return false; 238 | } 239 | 240 | // set as post featured image 241 | set_post_thumbnail( $post->ID, $this->_attachment_id ); 242 | 243 | // delete the temporary file! 244 | if ( file_exists( $temp_file['file'] ) ) { 245 | unlink( $temp_file['file'] ); 246 | $this->_debug_messages[] = sprintf( __( 'Deleted chart image %s', 'simplechart' ), $temp_file['file'] ); 247 | } else { 248 | $this->_debug_messages[] = sprintf( __( '%s was already deleted', 'simplechart' ), $temp_file['file'] ); 249 | } 250 | } 251 | 252 | public function set_chart_image_status( $data, $postarr ) { 253 | if ( 'simplechart' === get_post_type( $data['post_parent'] ) ) { 254 | $data['post_status'] = $this->_image_post_status; 255 | } 256 | return $data; 257 | } 258 | 259 | private function _process_data_uri( $data_uri, $img_type ) { 260 | $data_prefix = 'data:image/' . $img_type . ';base64,'; 261 | 262 | // validate input format for data URI 263 | if ( 0 !== strpos( $data_uri, $data_prefix ) ) { 264 | $this->_errors[] = __( 'Incorrect data URI formatting', 'simplechart' ); 265 | } 266 | 267 | // remove prefix to get base64 data 268 | $img_data = str_replace( $data_prefix, '', $data_uri ); 269 | 270 | return $img_data; 271 | } 272 | 273 | /** 274 | * HTML fragment to render the chart must be a single tag in one of the allowed tags, with no children 275 | * use this instead of wp_kses_* because there are a large number of potential attributes for these tags 276 | */ 277 | function validate_template_fragment( $fragment ) { 278 | libxml_use_internal_errors( true ); 279 | $el = simplexml_load_string( $fragment ); 280 | 281 | if ( $el && in_array( $el->getName(), $this->_allowed_template_tags, true ) && 0 === count( $el->children() ) ) { 282 | return $fragment; 283 | } else { 284 | foreach ( libxml_get_errors() as $error ) { 285 | $this->_errors[] = sprintf( __( 'SimpleXML error in template fragment: %s', 'simplechart' ), $error->message ); 286 | } 287 | libxml_clear_errors(); 288 | return false; 289 | } 290 | } 291 | 292 | private function _validate_json( $data ) { 293 | // Make sure we have data 294 | if ( 'undefined' === $data ) { 295 | $this->_errors[] = "JS app set value of input to 'undefined'"; 296 | return false; 297 | } 298 | 299 | $decoded = json_decode( $data ); 300 | if ( $decoded ) { 301 | // Attempt to validate JSON by decoding then re-encoding 302 | return json_encode( $decoded ); // returns a valid JSON string! 303 | } elseif ( function_exists( 'json_last_error_msg' ) ) { 304 | // Add error message 305 | $this->_errors[] = sprintf( __( 'JSON error: %s', 'simplechart' ), json_last_error_msg() ); 306 | return false; 307 | } elseif ( function_exists( 'json_last_error' ) ) { 308 | // Or just error code 309 | $this->_errors[] = sprintf( __( 'JSON error code: %s', 'simplechart' ), json_last_error() ); 310 | return false; 311 | } 312 | 313 | // Or catch-all error message if for some reason we get all the way to the end 314 | $this->_errors[] = __( 'Attempted to save invalid JSON', 'simplechart' ); 315 | return false; 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /modules/class-simplechart-template.php: -------------------------------------------------------------------------------- 1 | _amp_actions(); 27 | } 28 | 29 | private function _amp_actions() { 30 | // Hook into AMP plugin action 31 | add_action( 32 | 'pre_amp_render_post', 33 | function() { 34 | $this->_is_amp = true; 35 | } 36 | ); 37 | 38 | // Enable amp-iframe elements 39 | add_action( 40 | 'amp_post_template_data', 41 | function( $data ) { 42 | $data['amp_component_scripts']['amp-iframe'] = 'https://cdn.ampproject.org/v0/amp-iframe-0.1.js'; 43 | $data['amp_component_scripts']['amp-fit-text'] = 'https://cdn.ampproject.org/v0/amp-fit-text-0.1.js'; 44 | return $data; 45 | } 46 | ); 47 | 48 | do_action( 'simplechart_amp' ); 49 | } 50 | 51 | // do the shortcode 52 | public function render_shortcode( $attrs ) { 53 | if ( empty( $attrs['id'] ) || ! is_numeric( $attrs['id'] ) ) { 54 | return ''; 55 | } 56 | 57 | $chart = get_post( intval( $attrs['id'] ) ); 58 | 59 | if ( empty( $chart ) ) { 60 | return ''; 61 | } 62 | 63 | ob_start(); 64 | $this->render( $chart->ID ); 65 | return ob_get_clean(); 66 | } 67 | 68 | // render the chart from template 69 | public function render( $id ) { 70 | 71 | // Set object property so it can be retrieved later by template file 72 | $this->_id = $id; 73 | 74 | /** 75 | * Set this to true for AMP pages to render as `amp-iframe`. This is handled automatically if 76 | * you're using the offical WP AMP plugin (https://wordpress.org/plugins/amp/) 77 | * 78 | * @param bool $amp 79 | * @return bool 80 | */ 81 | $this->_is_amp = apply_filters( 'simplechart_is_amp_page', $this->_is_amp ); 82 | 83 | /** 84 | * Set this to true to disable Simplechart entirely on AMP pages 85 | * 86 | * @param bool $disable_amp Defaults to false 87 | * @return bool 88 | */ 89 | $disable_amp = apply_filters( 'simplechart_disable_amp', false ); 90 | 91 | $instance = Simplechart::instance(); 92 | 93 | if ( ! $this->_is_amp ) { 94 | wp_register_script( 95 | 'simplechart-vendor', 96 | $instance->get_config( 'vendor_js_url' ), 97 | false, 98 | false, 99 | true 100 | ); 101 | wp_enqueue_script( 102 | 'simplechart-widget', 103 | $instance->get_config( 'widget_loader_url' ), 104 | array( 'simplechart-vendor' ), 105 | false, 106 | true 107 | ); 108 | 109 | add_filter( 'script_loader_tag', array( $this, 'async_scripts' ), 10, 3 ); 110 | add_action( 'simplechart_iframe_footer', array( $this, 'iframe_scripts' ) ); 111 | require( $instance->get_plugin_dir( 'templates/embed.php' ) ); 112 | } else if ( ! $disable_amp ) { 113 | add_filter( 114 | 'simplechart_amp_iframe_placeholder', 115 | array( $this, 'default_placeholder' ), 116 | 10, 117 | 2 118 | ); 119 | require( $instance->get_plugin_dir( 'templates/amp-iframe.php' ) ); 120 | }//end if 121 | } 122 | 123 | /** 124 | * Get ID of chart currently being rendered 125 | * 126 | * @return int $id 127 | */ 128 | public function current_id() { 129 | return $this->_id; 130 | } 131 | 132 | /** 133 | * Get string for height style attribute based on post meta 134 | * 135 | * @param int $id Post ID for the chart 136 | * @return string 'height: XXXpx' or empty string if no height found 137 | */ 138 | public function height_style( $id ) { 139 | $height = get_post_meta( $id, 'height', true ); 140 | if ( empty( $height ) ) { 141 | return ''; 142 | } 143 | return 'height:' . absint( $height ) . 'px;'; 144 | } 145 | 146 | // automatically render chart if looking at the chart's own post 147 | public function add_filter_post_content() { 148 | if ( ! is_admin() && is_singular( 'simplechart' ) ) { 149 | add_filter( 'the_content', array( $this, 'filter_insert_chart' ) ); 150 | } 151 | } 152 | 153 | public function async_scripts( $tag, $handle, $src ) { 154 | $async_scripts = array( 'simplechart-widget' ); 155 | 156 | if ( in_array( $handle, $async_scripts ) ) { 157 | return '' . "\n"; 158 | } 159 | 160 | return $tag; 161 | } 162 | 163 | // prepend chart to post_content 164 | public function filter_insert_chart( $content ) { 165 | global $post; 166 | 167 | $template_html = $this->render( $post->ID ); 168 | 169 | return $template_html . $content; 170 | } 171 | 172 | public function iframe_scripts() { 173 | do_action( 'wp_print_footer_scripts' ); 174 | } 175 | 176 | public function default_placeholder( $id, $content ) { 177 | if ( ! $content ) { 178 | return 'Loading Chart...'; 179 | } 180 | 181 | return $content; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /modules/class-simplechart-wp-cli.php: -------------------------------------------------------------------------------- 1 | 41 | * : WPCOM site ID 42 | * 43 | * --posts= 44 | * : comma separated list of Simplechart post IDs on remote site 45 | * 46 | * 47 | * [--author=] 48 | * : Assigns imported posts to a specific author ID or username 49 | * 50 | * ## EXAMPLES 51 | * 52 | * wp simplechart import_wpcom --site=123456 --posts=12,13,14 --author=6 53 | * 54 | * @synopsis --site= --posts= [--author=] 55 | */ 56 | function import_wpcom( $args, $assoc_args ) { 57 | $post_ids = explode( ',', $assoc_args['posts'] ); 58 | $post_ids = array_map( 'absint', $post_ids ); 59 | $site_id = absint( $assoc_args['site'] ); 60 | $this->_set_author( $assoc_args ); 61 | WP_CLI::line( sprintf( "Starting import of %1$d posts from site %2$s\n", count( $post_ids ), $site_id ) ); 62 | foreach ( $post_ids as $post_id ) { 63 | WP_CLI::line( sprintf( 'Processing post %s', $post_id ) ); 64 | $post_object = $this->_extract_wpcom_post_object( $site_id, $post_id ); 65 | if ( $post_object ) { 66 | $transformed = $this->_transform_from_wpcom_api( $post_object ); 67 | $this->_load_post( $transformed ); 68 | } 69 | WP_CLI::line( '' ); 70 | } 71 | WP_CLI::line( sprintf( 'Processed %1$d posts from site %2$s', count( $post_ids ), $site_id ) ); 72 | } 73 | 74 | /** 75 | * Retrieve a single post from the WordPress.com REST API 76 | * see https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/posts/%24post_ID/#apidoc-response 77 | * @param int $site WPCOM site ID 78 | * @param int $post WPCOM post ID 79 | * @return array Associative array of JSON response 80 | */ 81 | private function _extract_wpcom_post_object( $site, $post ) { 82 | // build API url 83 | $url = str_replace( '{{site}}', $site, $this->_wpcom_api_url ); 84 | $url = str_replace( '{{post}}', $post, $url ); 85 | 86 | // API request 87 | $response = wp_remote_get( $url ); 88 | 89 | // validate response 90 | if ( is_wp_error( $response ) ) { 91 | $warning = sprintf( "Failed to GET post %1$s from site %2$s\n", $post, $site ); 92 | $warning .= implode( "\n", $response->get_error_messages() ); 93 | WP_CLI::warning( $warning ); 94 | return null; 95 | } 96 | 97 | $code = wp_remote_retrieve_response_code( $response ); 98 | if ( 200 !== $code ) { 99 | WP_CLI::warning( sprintf( 'Post %1$s from site %2$s returned error code %3$s', $post, $site, $code ) ); 100 | return null; 101 | } 102 | 103 | $post_object = json_decode( wp_remote_retrieve_body( $response ), true ); 104 | 105 | // make sure json_decode worked 106 | if ( empty( $post_object ) ) { 107 | WP_CLI::warning( sprintf( 'Invalid JSON response from %s', $url ) ); 108 | return null; 109 | } 110 | 111 | // make sure it's the right post type 112 | if ( $this->_post_type !== $post_object['type'] ) { 113 | WP_CLI::warning( sprintf( "Post %1$s from site %2$s does not have post_type '%3%s'", $post, $site, $this->_post_type ) ); 114 | return null; 115 | } 116 | 117 | WP_CLI::success( sprintf( 'Retrieved post %1$s from site %2$s', $post, $site ) ); 118 | return $post_object; 119 | } 120 | 121 | /** 122 | * Create a post in Simplechart post type using WPCOM REST API data 123 | * @param array $post_object API data for the post 124 | * @return array Post object transformed for our data loader 125 | */ 126 | private function _transform_from_wpcom_api( $post_object ) { 127 | // basic post info 128 | $post_data = array( 129 | 'post_name' => $post_object['slug'], 130 | 'post_title' => $post_object['title'], 131 | 'post_status' => $post_object['status'], 132 | 'post_type' => $this->_post_type, 133 | 'post_date_gmt' => date( 'Y-m-d H:i:s', strtotime( $post_object['date'] ) ), 134 | ); 135 | 136 | // author ID if we have one 137 | if ( $this->_author ) { 138 | $post_data['post_author'] = $this->_author; 139 | } 140 | 141 | // setup post metadata 142 | $post_meta = array( 143 | '_src_guid' => $post_data['guid'], 144 | ); 145 | if ( ! empty( $post_object['metadata'] ) && is_array( $post_object['metadata'] ) ) { 146 | foreach ( $post_object['metadata'] as $meta ) { 147 | if ( in_array( $meta['key'], $this->_meta_keys, true ) ) { 148 | $post_meta[ $meta['key'] ] = $meta['value']; 149 | } 150 | } 151 | } 152 | 153 | // featured image URL 154 | $featured_image = ! empty( $post_object['featured_image'] ) ? $post_object['featured_image'] : null; 155 | 156 | return array( 157 | 'post_data' => $post_data, 158 | 'post_meta' => $post_meta, 159 | 'featured_image' => $featured_image, 160 | ); 161 | } 162 | 163 | /** 164 | * create new post for the chart, using already transformed data 165 | * @var array $data Post data for new chart 166 | * array 'post_data' Arguments to pass to wp_insert_post() 167 | * array 'post_meta' Key-value pairs to set as post meta 168 | * array 'featured_image' Optional array of url, width, height of image to import 169 | * @return bool Success or failure 170 | */ 171 | private function _load_post( $data ) { 172 | if ( empty( $data['post_data'] ) ) { 173 | WP_CLI::warning( 'Cannot insert post with no post_data' ); 174 | return false; 175 | } 176 | 177 | // create the post 178 | $post_id = wp_insert_post( $data['post_data'] ); 179 | if ( 0 === $post_id ) { 180 | WP_CLI::warning( 'Failed to insert post' ); 181 | return false; 182 | } 183 | 184 | // add the post metadata 185 | foreach ( $data['post_meta'] as $key => $value ) { 186 | update_post_meta( $post_id, $key, $value ); 187 | } 188 | 189 | // attach the featured image 190 | if ( $data['featured_image'] ) { 191 | $this->_attach_featured_image_to_post( $data['featured_image'], $post_id, $data['post_data']['post_title'] ); 192 | } 193 | 194 | return true; 195 | } 196 | 197 | /** 198 | * sideload featured image from URL and set as post thumbnail 199 | * @var string $image Image URL 200 | * @var int $post_id ID of Simplechart post 201 | * @var string $desc Description for image 202 | */ 203 | private function _attach_featured_image_to_post( $image, $post_id, $desc ) { 204 | $tmp = download_url( $image ); 205 | if ( is_wp_error( $tmp ) ) { 206 | WP_CLI::warning( sprintf( 'Error downloading %s', esc_url( $image ) ) ); 207 | if ( file_exists( $tmp ) ) { 208 | @unlink( $tmp ); 209 | } 210 | return; 211 | } 212 | 213 | $file_array = array( 214 | 'tmp_name' => $tmp, 215 | 'name' => basename( $image ), 216 | ); 217 | $media_id = media_handle_sideload( $file_array, $post_id, $desc ); 218 | 219 | if ( ! is_wp_error( $media_id ) ) { 220 | set_post_thumbnail( $post_id, $media_id ); 221 | WP_CLI::success( sprintf( 'Set attachment %1$s as thumbnail for post %2$s', $media_id, $post_id ) ); 222 | } else { 223 | WP_CLI::warning( sprintf( 'Sideload failed: %s', $media_id->get_error_message() ) ); 224 | } 225 | if ( file_exists( $tmp ) ) { 226 | @unlink( $tmp ); 227 | } 228 | } 229 | 230 | /** 231 | * set author by ID or username to assign imported posts to 232 | * @var array Assoc args from command 233 | */ 234 | private function _set_author( $assoc_args ) { 235 | // was an author provided? 236 | if ( empty( $assoc_args['author'] ) ) { 237 | return; 238 | } 239 | 240 | // check for author ID 241 | if ( is_numeric( $assoc_args['author'] ) ) { 242 | $user = get_user_by( 'id', absint( $assoc_args['author'] ) ); 243 | } else { 244 | // check for author by slug 245 | $user = get_user_by( 'slug', $assoc_args['author'] ); 246 | } 247 | 248 | // set author 249 | if ( ! empty( $user ) ) { 250 | $this->_author = $user->ID; 251 | } 252 | return; 253 | } 254 | } 255 | WP_CLI::add_command( 'simplechart', 'Simplechart_WP_CLI' ); 256 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wordpress-simplechart", 3 | "version": "1.0.0", 4 | "description": "Wordpress plugin for Simplechart.", 5 | "private": true, 6 | "dependencies": {}, 7 | "devDependencies": { 8 | "babel-cli": "^6.24.1", 9 | "babel-loader": "^7.0.0", 10 | "babel-preset-es2015": "^6.24.0", 11 | "babel-plugin-transform-object-rest-spread": "^6.23.0", 12 | "eslint": "^3.19.0", 13 | "eslint-config-airbnb": "^14.1.0", 14 | "eslint-loader": "^1.7.0", 15 | "eslint-plugin-import": "^2.2.0", 16 | "eslint-plugin-jsx-a11y": "^4.0.0", 17 | "eslint-plugin-react": "^6.9.0", 18 | "webpack": "^2.5.0" 19 | }, 20 | "scripts": { 21 | "build": "webpack --config webpack.config.js --devtool=source-map" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | PHP_CodeSniffer standard for WP Starter Theme. 4 | 5 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | . 26 | 27 | 28 | */node_modules/* 29 | */vendor/ 30 | inc/cli.php 31 | tests/ 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 46 | warning 47 | 48 | 55 | 56 | 57 | 58 | 63 | error 64 | 65 | 66 | 67 | 68 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | themes(/vip)?/[^/]+/template-parts/ 93 | vendor/ 94 | 95 | 96 | 97 | 98 | 102 | 103 | 107 | 108 | 109 | 110 | 111 | 112 | 0 113 | 114 | 115 | 0 116 | 117 | 118 | 0 119 | 120 | 121 | 0 122 | 123 | 124 | 0 125 | 126 | 127 | 0 128 | 129 | 130 | 0 131 | 132 | 133 | 0 134 | 135 | 136 | 0 137 | 138 | 139 | 0 140 | 141 | 142 | 0 143 | 144 | 145 | 0 146 | 147 | 148 | 0 149 | 150 | 151 | 0 152 | 153 | 154 | 0 155 | 156 | 157 | 0 158 | 159 | 160 | 0 161 | 162 | 163 | 0 164 | 165 | 166 | 0 167 | 168 | 169 | 0 170 | 171 | 172 | 0 173 | 174 | 175 | 0 176 | 177 | 178 | 0 179 | 180 | 181 | 0 182 | 183 | 184 | 0 185 | 186 | 187 | 0 188 | 189 | 190 | 0 191 | 192 | 193 | 0 194 | 195 | 196 | 0 197 | 198 | 199 | 0 200 | 201 | 202 | 0 203 | 204 | 205 | 0 206 | 207 | 208 | 0 209 | 210 | 211 | 0 212 | 213 | 214 | 0 215 | 216 | 217 | 0 218 | 219 | 220 | 0 221 | 222 | 223 | 0 224 | 225 | 226 | 0 227 | 228 | 229 | 0 230 | 231 | 232 | 0 233 | 234 | 235 | 0 236 | 237 | 238 | 0 239 | 240 | 241 | 0 242 | 243 | 244 | 0 245 | 246 | 247 | 0 248 | 249 | 250 | 0 251 | 252 | 253 | 0 254 | 255 | 256 | 0 257 | 258 | 259 | 0 260 | 261 | 262 | 0 263 | 264 | 265 | 0 266 | 267 | 268 | 0 269 | 270 | 271 | 0 272 | 273 | 274 | 0 275 | 276 | 277 | 0 278 | 279 | 280 | 0 281 | 282 | 283 | 0 284 | 285 | 286 | 0 287 | 288 | 293 | 294 | 295 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | ./tests/ 12 | 13 | 14 | 15 | 16 | ./ 17 | 18 | ./tests/ 19 | ./bin/ 20 | ./inc/cli.php 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /simplechart.php: -------------------------------------------------------------------------------- 1 | array(), 21 | 'error' => array(), 22 | ); 23 | private $_plugin_id = 'wordpress-simplechart/simplechart.php'; 24 | 25 | // config vars that will eventually come from settings page 26 | private $_config = array( 27 | 'web_app_iframe_src' => null, 28 | 'web_app_js_url' => null, 29 | 'webpack_public_path' => null, 30 | 'widget_loader_url' => null, 31 | 'menu_page_slug' => 'simplechart_app', 32 | 'version' => '0.5.45', 33 | 'app_version' => '83926e3', 34 | ); 35 | 36 | // startup 37 | private function __construct() { 38 | // Both of these will have trailing slash 39 | $this->_plugin_dir_path = plugin_dir_path( __FILE__ ); 40 | $this->_plugin_dir_url = $this->_set_plugin_dir_url(); 41 | 42 | $this->_init_modules(); 43 | add_action( 'init', array( $this, 'action_init' ) ); 44 | } 45 | 46 | /** 47 | * Static accessor 48 | */ 49 | public static function instance() { 50 | if ( ! is_object( self::$instance ) ) { 51 | self::$instance = new Simplechart; 52 | } 53 | return self::$instance; 54 | } 55 | 56 | /** 57 | * deactivate plugin if it is active 58 | */ 59 | public function deactivate() { 60 | if ( is_plugin_active( $this->_plugin_id ) ) { 61 | deactivate_plugins( $this->_plugin_id ); 62 | } 63 | } 64 | 65 | /** 66 | * print admin notices, either 'updated' (green) or 'error' (red) 67 | */ 68 | public function admin_notices() { 69 | foreach ( $this->_admin_notices as $class => $notices ) { 70 | foreach ( $notices as $notice ) { 71 | printf( '

    %s

    ', esc_attr( $class ), esc_html( $notice ) ); 72 | } 73 | } 74 | } 75 | 76 | /** 77 | * get root url and path of plugin, whether loaded from plugins directory or in theme 78 | */ 79 | private function _set_plugin_dir_url() { 80 | 81 | // if running as regular plugin (i.e. inside wp-content/plugins/) 82 | if ( 0 === strpos( $this->_plugin_dir_path, WP_PLUGIN_DIR ) ) { 83 | $url = plugin_dir_url( __FILE__ ); 84 | } elseif ( function_exists( 'wpcom_vip_get_loaded_plugins' ) && in_array( 'alley-plugins/simplechart', wpcom_vip_get_loaded_plugins(), true ) ) { 85 | // if running as VIP Classic™ plugin 86 | $url = plugins_url( '', __FILE__ ); 87 | } else { 88 | // assume loaded directly by theme 89 | $path_relative_to_theme = str_replace( get_template_directory(), '', $this->_plugin_dir_path ); 90 | $url = get_template_directory_uri() . $path_relative_to_theme; 91 | } 92 | return trailingslashit( $url ); 93 | } 94 | 95 | /** 96 | * config getter 97 | */ 98 | public function get_config( $key ) { 99 | return isset( $this->_config[ $key ] ) ? $this->_config[ $key ] : null; 100 | } 101 | 102 | /** 103 | * LOAD MODULES 104 | */ 105 | private function _init_modules() { 106 | // setup simplechart post type 107 | require_once( $this->_plugin_dir_path . 'modules/class-simplechart-post-type.php' ); 108 | $this->post_type = new Simplechart_Post_Type; 109 | 110 | // setup save post stuff 111 | require_once( $this->_plugin_dir_path . 'modules/class-simplechart-save.php' ); 112 | $this->save = new Simplechart_Save; 113 | 114 | // load WP Media extension and initialize 115 | require_once( $this->_plugin_dir_path . 'modules/class-simplechart-insert.php' ); 116 | $this->insert = new Simplechart_Insert; 117 | 118 | // template rendering module 119 | require_once( $this->_plugin_dir_path . 'modules/class-simplechart-template.php' ); 120 | $this->template = new Simplechart_Template; 121 | 122 | require_once( $this->_plugin_dir_path . 'modules/class-simplechart-request-handler.php' ); 123 | $this->request_handler = new Simplechart_Request_Handler; 124 | 125 | // Handles checking for updates on Github 126 | require_once( $this->_plugin_dir_path . 'modules/class-simplechart-plugin-versions.php' ); 127 | $this->plugin_versions = new Simplechart_Plugin_Versions; 128 | 129 | // WP-CLI commands 130 | if ( defined( 'WP_CLI' ) && WP_CLI ) { 131 | require_once( $this->_plugin_dir_path . 'modules/class-simplechart-wp-cli.php' ); 132 | } 133 | } 134 | 135 | /** 136 | * on the 'init' action, do frontend or backend startup 137 | */ 138 | public function action_init() { 139 | // Allow constant or filter to force using localhost for JS files 140 | $use_localhost = ( defined( 'SIMPLECHART_USE_LOCALHOST' ) && SIMPLECHART_USE_LOCALHOST ); 141 | 142 | /** 143 | * Determine if we should load the Simplechart JS app from localhost or the plugin's copy 144 | * 145 | * @param bool $use_localhost Defaults to false unless set to true by a query var 146 | */ 147 | $use_localhost = apply_filters( 'simplechart_use_localhost', $use_localhost ); 148 | 149 | // Git commit that the app was bundled from 150 | $commit_version = $this->get_config( 'app_version' ); 151 | 152 | // Set URLs for JS app and widget 153 | if ( $use_localhost ) { 154 | $this->_config['webpack_public_path'] = 'http://localhost:8080/static/'; 155 | $this->_config['web_app_js_url'] = $this->_config['webpack_public_path'] . 'app.js'; 156 | $this->_config['widget_loader_url'] = $this->_config['webpack_public_path'] . 'widget.js'; 157 | } else { 158 | $this->_config['webpack_public_path'] = $this->get_plugin_url( 'js/app/' ); 159 | $this->_config['web_app_js_url'] = $this->get_plugin_url( sprintf( 'js/app/app.%s.js', $commit_version ) ); 160 | $this->_config['widget_loader_url'] = $this->get_plugin_url( sprintf( 'js/app/widget.%s.js', $commit_version ) ); 161 | } 162 | 163 | // URL for menu page set up by Simplechart_Post_Type module 164 | $this->_config['web_app_iframe_src'] = admin_url( '/admin.php?page=' . $this->get_config( 'menu_page_slug' ) . '&noheader' ); 165 | 166 | // Filters for app page and JS URLs 167 | $this->_config['webpack_public_path'] = apply_filters( 'simplechart_webpack_public_path', $this->_config['webpack_public_path'] ); 168 | $this->_config['web_app_iframe_src'] = apply_filters( 'simplechart_web_app_iframe_src', $this->_config['web_app_iframe_src'] ); 169 | $this->_config['web_app_js_url'] = apply_filters( 'simplechart_web_app_js_url', $this->_config['web_app_js_url'] ); 170 | $this->_config['widget_loader_url'] = apply_filters( 'simplechart_widget_loader_url', $this->_config['widget_loader_url'] ); 171 | 172 | if ( is_admin() ) { 173 | $this->_admin_setup(); 174 | } 175 | } 176 | 177 | /* 178 | * setup /wp-admin functionality 179 | */ 180 | private function _admin_setup() { 181 | add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) ); 182 | load_plugin_textdomain( 'simplechart', false, dirname( plugin_basename( __FILE__ ) ) . '/langs/' ); 183 | } 184 | 185 | public function enqueue_admin_scripts() { 186 | if ( ! is_admin() ) { 187 | return; 188 | } 189 | wp_register_script( 'simplechart-plugin', $this->get_plugin_url( 'js/plugin/build/plugin.js' ), array( 'jquery' ), $this->_config['version'] ); 190 | wp_register_script( 'simplechart-post-edit', $this->get_plugin_url( 'js/plugin/build/postEdit.js' ), array( 'jquery', 'underscore' ), $this->_config['version'] ); 191 | wp_register_style( 'simplechart-style', $this->_plugin_dir_url . 'css/style.css', array(), $this->_config['version'] ); 192 | wp_enqueue_script( 'simplechart-post-edit' ); 193 | wp_enqueue_style( 'simplechart-style' ); 194 | wp_enqueue_script( 'simplechart-plugin' ); 195 | } 196 | 197 | /** 198 | * Get URL of plugin directory, with optional path appended 199 | * 200 | * @param string $append Optional path to append to the plugin directory URL 201 | * @return string URL 202 | */ 203 | public function get_plugin_url( $append = '' ) { 204 | // should already have trailing slash but just to be safe... 205 | return trailingslashit( $this->_plugin_dir_url ) . ltrim( $append, '/' ); 206 | } 207 | 208 | /** 209 | * Get absolute path to plugin directory, with optional path appended 210 | * 211 | * @param string $append Optional path to append to the plugin directory pth 212 | * @return string Path 213 | */ 214 | public function get_plugin_dir( $append = '' ) { 215 | return trailingslashit( $this->_plugin_dir_path ) . ltrim( $append, '/' ); 216 | } 217 | 218 | /** 219 | * Clear rewrite rules so they get rebuilt the next time 'init' fires 220 | * 221 | * @return none 222 | */ 223 | public function clear_rules() { 224 | delete_option( 'rewrite_rules' ); 225 | } 226 | } 227 | Simplechart::instance(); 228 | 229 | /** 230 | * Rebuild rewrite rules on de/activation 231 | */ 232 | register_activation_hook( __FILE__, array( Simplechart::instance(), 'clear_rules' ) ); 233 | register_deactivation_hook( __FILE__, array( Simplechart::instance(), 'clear_rules' ) ); 234 | 235 | /** 236 | * Helper Functions 237 | */ 238 | function simplechart_render_chart( $id ) { 239 | return Simplechart::instance()->template->render( $id ); 240 | } 241 | 242 | /** 243 | * Confirm post type and that chart is either published or embedded in a preview 244 | * 245 | * @var int $id Post ID of chart 246 | * @return bool 247 | */ 248 | function simplechart_can_render( $id ) { 249 | return 'simplechart' === get_post_type( $id ) && ( 'publish' === get_post_status( $id ) || is_preview() ); 250 | } 251 | 252 | function simplechart_inline_style_height( $id ) { 253 | return Simplechart::instance()->template->height_style( $id ); 254 | } 255 | -------------------------------------------------------------------------------- /templates/amp-iframe-source.php: -------------------------------------------------------------------------------- 1 | request_handler->frame_id(); 7 | if ( $id ) : ?> 8 | 9 | 10 | 11 | 12 | 20 | 23 | 24 | 25 | template->render( $id ); 29 | remove_filter( 'simplechart_is_amp_page', '__return_false', 99 ); 30 | ?> 31 | ` tag 34 | * 35 | * @var int $id ID of chart being rendered 36 | */ 37 | do_action( 'simplechart_iframe_footer', $id ); 38 | ?> 39 | 40 | 41 | template->current_id(); 7 | $url = site_url( '/simplechart/iframe/' . $id . '/', 'https' ); 8 | $height = get_post_meta( $id, 'embedHeight', true ) ?: 400; 9 | 10 | if ( simplechart_can_render( $id ) ) : ?> 11 | 13 | sandbox="allow-scripts" 14 | layout="fixed-height" 15 | frameborder="0" 16 | src="" 17 | style="background-color: white" 18 | > 19 | 20 | 21 | template->current_id(); 7 | 8 | /** 9 | * Allow custom HTTP headers for the front-end API data request, e.g. basic auth on a staging site 10 | * 11 | * @param $headers array Defaults to empty array 12 | * @param int $id Post ID of current chart being rendered 13 | * @return array 14 | */ 15 | $http_headers = apply_filters( 'simplechart_api_http_headers', array(), $id ); 16 | 17 | /** 18 | * Text string to use while chart data is loading. 19 | * If none is provided, will use the JS app's default: "Loading" 20 | * 21 | * @param string|null $placeholder_text 22 | * @param int $id Post ID of chart being rendered 23 | * @return string|null 24 | */ 25 | $placeholder = apply_filters( 'simplechart_widget_placeholder_text', null, $id ); 26 | 27 | /** 28 | * Use a custom template for the Simplechart widget 29 | * 30 | * @param string|null $custom_template Null or string of HTML for template 31 | * @param int $id Post ID of chart being rendered 32 | * @return string|null 33 | */ 34 | $custom_template = apply_filters( 'simplechart_widget_template', null, $id ); 35 | 36 | // Chart must be published or embedded in a preview 37 | if ( 'simplechart' === get_post_type( $id ) && ( 'publish' === get_post_status( $id ) || is_preview() ) ) : ?> 38 |
    ' 42 | data-headers='' 43 | 44 | data-placeholder = '' 45 | 46 | > 47 | 48 | 49 | 50 |

    51 |

    52 |

    53 |
    57 |

    58 | 59 |
    60 | 66 | post_type->current_user_can() ) { 2 | die( esc_html__( 'Insufficient user capability', 'simplechart' ) ); 3 | } ?> 4 | 5 | 6 | 7 | 8 | 9 | Simplechart 10 | 22 | 34 | 35 | 36 |
    37 | 38 | 39 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /templates/meta-box.php: -------------------------------------------------------------------------------- 1 | id && 'add' === $screen->action ) { 25 | /** 26 | * Set custom default options for NVD3 27 | * 28 | * @param array $default_options Array of NVD3 options to pre-set 29 | */ 30 | $default_options = apply_filters( 'simplechart_chart_options_override', array() ); 31 | 32 | /** 33 | * Set custom default metadata. 34 | * 35 | * @param array $default_options Array of metadata. Possible keys are title, subtitle, caption, credit 36 | */ 37 | $default_metadata = apply_filters( 'simplechart_chart_default_metadata', array() ); 38 | 39 | /** 40 | * Change any set truthy default that isn't a string to an 41 | * empty string so that we don't get weird default subtitles 42 | * when creating charts. 43 | */ 44 | if ( ! empty( $default_metadata['subtitle'] ) && 'string' !== gettype( $default_metadata['subtitle'] ) ) { 45 | $default_metadata['subtitle'] = ''; 46 | } 47 | 48 | /** 49 | * Enables the subtitle field, which is disabled by default in the chart editor app. 50 | * Alternately, you can assign any truthy value to the 'subtitle' key in 'simplechart_chart_default_metadata' 51 | * 52 | * @param bool $enable_subtitle Whether to enable the subtitle field 53 | */ 54 | if ( ! isset( $default_metadata['subtitle'] ) && apply_filters( 'simplechart_enable_subtitle_field', false ) ) { 55 | $default_metadata['subtitle'] = ''; 56 | } 57 | 58 | $creating_chart = true; 59 | } else { 60 | $default_options = null; 61 | $default_metadata = null; 62 | $creating_chart = false; 63 | }//end if 64 | 65 | /** 66 | * If we're loading an existing chart and subtitles are enabled, 67 | * pull in the saved subtitle if it exists and add it to the metadata. 68 | * If the field is empty, just throw in a blank string. 69 | * If subtitles are disabled, do nothing and allow the existing metadata to go through. 70 | */ 71 | if ( ! $creating_chart && apply_filters( 'simplechart_enable_subtitle_field', false ) ) { 72 | $existing_subtitle = get_post_meta( get_the_ID(), 'save-chartSubtitle', true ); 73 | if ( empty( $existing_subtitle ) ) { 74 | $existing_subtitle = ''; 75 | } 76 | $loaded_metadata = json_decode( get_post_meta( get_the_ID(), 'save-chartMetadata', true ), true ); 77 | if ( ! isset( $loaded_metadata['subtitle'] ) ) { 78 | $loaded_metadata['subtitle'] = $existing_subtitle; 79 | } 80 | } else { 81 | $loaded_metadata = null; 82 | } 83 | 84 | ?> 85 | 86 | 111 | 112 | 113 |

    114 | 115 | 116 | 117 | 118 | save->meta_field_names as $field ) : ?> 119 | 124 | value="" 125 | 126 | value="" 127 | 128 | /> 129 | 130 | 131 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | post_id = $this->factory->post->create(); 7 | } 8 | 9 | /** 10 | * Handle multiline csv input 11 | */ 12 | function test_save_rawData() { 13 | $raw_data = file_get_contents( dirname( __FILE__ ) . '/data/testcsv.txt' ); 14 | 15 | // sanitize_text_field() with custom filter 16 | add_filter( 'sanitize_text_field', array( Simplechart::instance()->save, 'sanitize_raw_data' ), 99, 2 ); 17 | $sanitized_data = sanitize_text_field( wp_unslash( $raw_data ) ); 18 | remove_filter( 'sanitize_text_field', array( Simplechart::instance()->save, 'sanitize_raw_data' ), 99, 2 ); 19 | 20 | update_post_meta( $this->post_id, 'save-rawData', $sanitized_data ); 21 | $retrieved_data = get_post_meta( $this->post_id, 'save-rawData', true ); 22 | $this->assertSame( $raw_data, $retrieved_data ); 23 | } 24 | 25 | /** 26 | * Handle base64 ping image URI 27 | */ 28 | function test_save_previewImg() { 29 | $raw_data = file_get_contents( dirname( __FILE__ ) . '/data/testpngdata.txt' ); 30 | update_post_meta( $this->post_id, 'save-previewImg', sanitize_text_field( wp_unslash( $raw_data ) ) ); 31 | $retrieved_data = get_post_meta( $this->post_id, 'save-previewImg', true ); 32 | $this->assertSame( $raw_data, $retrieved_data ); 33 | } 34 | 35 | /** 36 | * Handle JSON data 37 | */ 38 | function _test_save_json( $path ) { 39 | $raw_data = file_get_contents( dirname( __FILE__ ) . $path ); 40 | 41 | // input must be valid JSON when unslashed 42 | $this->assertTrue( !!json_decode( wp_unslash( $raw_data ) ) ); 43 | 44 | // sanitize and store data 45 | $sanitized_data = sanitize_text_field( wp_unslash( $raw_data ) ); 46 | update_post_meta( $this->post_id, 'save-previewImg', $sanitized_data ); 47 | 48 | // retrieve data 49 | $retrieved_data = get_post_meta( $this->post_id, 'save-previewImg', true ); 50 | $this->assertJsonStringEqualsJsonString( wp_unslash( $raw_data ), $retrieved_data ); 51 | } 52 | 53 | /** 54 | * Test against different JSON inputs 55 | */ 56 | function test_save_json() { 57 | $this->_test_save_json( '/data/testjson.txt' ); 58 | $this->_test_save_json( '/data/testjson2.txt' ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | module.exports = { 4 | entry: { 5 | simplechartInsert: './js/plugin/src/simplechart-insert.js', 6 | postEdit: './js/plugin/src/post-edit.js', 7 | plugin: './js/plugin/src/plugin.js' 8 | }, 9 | output: { 10 | filename: '[name].js', 11 | path: path.join(__dirname, 'js/plugin/build') 12 | }, 13 | module: { 14 | rules: [ 15 | { 16 | enforce: 'pre', 17 | test: /\.js$/, 18 | exclude: /node_modules/, 19 | loader: 'eslint-loader', 20 | }, 21 | { 22 | test: /\.js$/, 23 | exclude: /node_modules/, 24 | loader: 'babel-loader', 25 | options: { 26 | presets: ['es2015'], 27 | plugins: ['transform-object-rest-spread'], 28 | }, 29 | }, 30 | ] 31 | } 32 | }; --------------------------------------------------------------------------------