├── includes ├── deprecated-functions.php ├── api │ ├── class-connection-exception.php │ └── class-resource-not-found-exception.php ├── default-actions.php ├── admin │ ├── migrations │ │ ├── 4.6.0-remove-lists-from-options.php │ │ ├── 4.1.3-reschedule-event.php │ │ ├── 4.1.2-flush-list-cache.php │ │ ├── 3.1.6-woocommerce-position-prefix.php │ │ ├── 3.0.0-general-options.php │ │ ├── 4.8.2-move-debug-log-to-subdirectory.php │ │ ├── 4.0.0-hidden-fields-value-delimiter.php │ │ ├── 4.0.7-rename-debug-log-file.php │ │ ├── 3.0.0-form-3-messages.php │ │ ├── 3.0.0-styles-builder.php │ │ ├── 3.0.0-widget-base-id.php │ │ ├── 3.0.0-integration-options.php │ │ ├── 3.0.0-form-2-options.php │ │ ├── 3.0.0-form-1-post-type.php │ │ └── 4.0.0-groupings-to-interests.php │ ├── class-admin-tools.php │ ├── class-admin-messages.php │ ├── class-admin-ajax.php │ ├── class-upgrade-routines.php │ ├── class-admin-texts.php │ └── class-review-notice.php ├── forms │ ├── views │ │ ├── js │ │ │ ├── dummy-api.js │ │ │ └── url-fields.js │ │ ├── parts │ │ │ ├── dynamic-content-tags.php │ │ │ └── add-fields-help.php │ │ ├── preview.php │ │ ├── tabs │ │ │ ├── form-appearance.php │ │ │ └── form-fields.php │ │ └── add-form.php │ ├── admin-functions.php │ ├── class-form-message.php │ ├── class-form-previewer.php │ ├── functions.php │ ├── class-form-amp.php │ └── class-form-tags.php ├── class-queue-job.php ├── default-filters.php ├── integrations │ ├── functions.php │ ├── class-user-integration.php │ ├── class-integration-tags.php │ └── class-integration-fixture.php ├── views │ ├── parts │ │ ├── admin-footer.php │ │ ├── admin-sidebar.php │ │ ├── lists-overview.php │ │ └── lists-overview-details.php │ ├── extensions.php │ └── general-settings.php ├── class-plugin.php ├── class-mailchimp-subscriber.php ├── class-personal-data-exporter.php ├── class-container.php └── class-debug-log-reader.php ├── config ├── default-settings.php ├── default-form-settings.php ├── default-form-content.php └── default-form-messages.php ├── integrations ├── prosopo-procaptcha │ ├── bootstrap.php │ ├── admin-before.php │ └── class-procaptcha-integration.php ├── wpforms │ ├── admin-before.php │ ├── bootstrap.php │ └── class-wpforms.php ├── ninja-forms │ ├── admin-before.php │ ├── bootstrap.php │ ├── class-ninja-forms.php │ └── class-field.php ├── contact-form-7 │ └── admin-before.php ├── gravity-forms │ ├── bootstrap.php │ └── admin-before.php ├── custom │ ├── admin-before.php │ └── class-custom.php ├── give │ └── class-give.php ├── memberpress │ └── class-memberpress.php ├── events-manager │ └── class-events-manager.php ├── affiliatewp │ └── class-affiliatewp.php ├── bootstrap.php ├── woocommerce │ └── admin-after.php ├── wp-registration-form │ └── class-registration-form.php ├── easy-digital-downloads │ └── class-easy-digital-downloads.php └── wp-comment-form │ └── class-comment-form.php ├── languages └── index.php ├── assets └── src │ ├── js │ ├── admin.js │ ├── forms-admin.js │ ├── admin │ │ ├── fields │ │ │ └── mailchimp-api-key.js │ │ ├── list-overview.js │ │ ├── settings.js │ │ ├── show-if.js │ │ ├── overlay.js │ │ ├── form-editor │ │ │ ├── field-forms.js │ │ │ ├── form-watcher.js │ │ │ ├── fields.js │ │ │ ├── field-helper.js │ │ │ └── form-editor.js │ │ ├── notices.js │ │ └── tabs.js │ ├── events.js │ ├── misc │ │ └── scroll-to-element.js │ ├── integrations-admin.js │ ├── forms │ │ ├── form.js │ │ ├── forms.js │ │ └── conditional-elements.js │ ├── forms-submitted.js │ ├── forms.js │ └── forms-block.js │ ├── css │ ├── checkbox-reset.css │ └── form-basic.css │ └── img │ ├── logo-red-on-white.svg │ ├── logo-white-on-red.svg │ └── icon.svg ├── SECURITY.md ├── uninstall.php ├── phpcs.xml ├── bin ├── check-php-syntax └── create-package ├── wpml-config.xml ├── webpack.config.js └── mailchimp-for-wp.php /includes/deprecated-functions.php: -------------------------------------------------------------------------------- 1 | '', 5 | 'debug_log_level' => 'warning', 6 | 'email_on_error' => '', 7 | ]; 8 | -------------------------------------------------------------------------------- /includes/admin/migrations/4.6.0-remove-lists-from-options.php: -------------------------------------------------------------------------------- 1 | query("DELETE FROM {$wpdb->options} WHERE option_name LIKE 'mc4wp_mailchimp_list_%'"); 5 | -------------------------------------------------------------------------------- /integrations/prosopo-procaptcha/bootstrap.php: -------------------------------------------------------------------------------- 1 | set_hooks(); 6 | -------------------------------------------------------------------------------- /includes/api/class-resource-not-found-exception.php: -------------------------------------------------------------------------------- 1 | 2 | your WPForms forms.', 'mailchimp-for-wp'), admin_url('admin.php?page=wpforms-overview')); ?> 3 |

4 | -------------------------------------------------------------------------------- /integrations/ninja-forms/admin-before.php: -------------------------------------------------------------------------------- 1 |

2 | one of your Ninja Forms forms.', 'mailchimp-for-wp'), admin_url('admin.php?page=ninja-forms')); ?> 3 |

4 | -------------------------------------------------------------------------------- /integrations/wpforms/bootstrap.php: -------------------------------------------------------------------------------- 1 | 2 | '); ?> 3 |

4 | -------------------------------------------------------------------------------- /includes/forms/views/js/dummy-api.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | window.mc4wp = window.mc4wp || { 3 | listeners: [], 4 | forms: { 5 | on: function(evt, cb) { 6 | window.mc4wp.listeners.push( 7 | { 8 | event : evt, 9 | callback: cb 10 | } 11 | ); 12 | } 13 | } 14 | } 15 | })(); 16 | -------------------------------------------------------------------------------- /includes/admin/migrations/4.1.3-reschedule-event.php: -------------------------------------------------------------------------------- 1 | 2 | one of your Gravity Forms forms.', 'mailchimp-for-wp'), admin_url('admin.php?page=gf_edit_forms')); 5 | ?> 6 |

7 | -------------------------------------------------------------------------------- /assets/src/js/admin.js: -------------------------------------------------------------------------------- 1 | // dependencies 2 | const tabs = require('./admin/tabs.js') 3 | const settings = require('./admin/settings.js') 4 | 5 | require('./admin/fields/mailchimp-api-key.js') 6 | require('./admin/list-overview.js') 7 | require('./admin/show-if.js') 8 | 9 | // expose some things 10 | window.mc4wp = window.mc4wp || {} 11 | window.mc4wp.settings = settings 12 | window.mc4wp.tabs = tabs 13 | -------------------------------------------------------------------------------- /assets/src/js/forms-admin.js: -------------------------------------------------------------------------------- 1 | const editor = require('./admin/form-editor/form-editor.js') 2 | 3 | require('./admin/form-editor/form-watcher.js') 4 | require('./admin/form-editor/field-helper.js') 5 | require('./admin/form-editor/field-manager.js') 6 | require('./admin/notices.js') 7 | 8 | // expose to global script 9 | window.mc4wp.forms = window.mc4wp.forms || {} 10 | window.mc4wp.forms.editor = editor 11 | -------------------------------------------------------------------------------- /includes/admin/migrations/4.1.2-flush-list-cache.php: -------------------------------------------------------------------------------- 1 | 0, 5 | 'double_optin' => 1, 6 | 'hide_after_success' => 0, 7 | 'lists' => [], 8 | 'redirect' => '', 9 | 'replace_interests' => 1, 10 | 'required_fields' => '', 11 | 'update_existing' => 0, 12 | 'subscriber_tags' => '', 13 | 'email_typo_check' => 0, 14 | ]; 15 | -------------------------------------------------------------------------------- /includes/forms/views/js/url-fields.js: -------------------------------------------------------------------------------- 1 | function maybePrefixUrlField () { 2 | const value = this.value.trim() 3 | if (value !== '' && value.indexOf('http') !== 0) { 4 | this.value = 'http://' + value 5 | } 6 | } 7 | 8 | const urlFields = document.querySelectorAll('.mc4wp-form input[type="url"]') 9 | for (let j = 0; j < urlFields.length; j++) { 10 | urlFields[j].addEventListener('blur', maybePrefixUrlField) 11 | } 12 | -------------------------------------------------------------------------------- /includes/admin/migrations/3.1.6-woocommerce-position-prefix.php: -------------------------------------------------------------------------------- 1 | ', 10 | '' 11 | ); 12 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | The Mailchimp for WordPress team and community take potential security issues in our software seriously. 4 | We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions. 5 | 6 | ## Reporting a Vulnerability 7 | 8 | To report a security issue, please email us at support@mc4wp.com or use the GitHub Security Advisory "[Report a Vulnerability](https://github.com/ibericode/mailchimp-for-wordpress/security/advisories/new)" tab. 9 | -------------------------------------------------------------------------------- /config/default-form-content.php: -------------------------------------------------------------------------------- 1 | \n\t\n

\n\n"; 9 | $content .= "

\n\t\n

"; 10 | 11 | return $content; 12 | -------------------------------------------------------------------------------- /uninstall.php: -------------------------------------------------------------------------------- 1 | query("DELETE FROM {$wpdb->options} WHERE option_name = 'mc4wp' OR option_name LIKE 'mc4wp_%' OR option_name LIKE '_transient_mc4wp_%' OR option_name LIKE '_transient_timeout_mc4wp_%';"); 12 | 13 | // Delete all MC4WP forms + settings 14 | $wpdb->query("DELETE p, pm FROM {$wpdb->posts} p LEFT JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID WHERE p.post_type = 'mc4wp-form';"); 15 | -------------------------------------------------------------------------------- /integrations/ninja-forms/bootstrap.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | 5 | 6 |

7 | 11 |

12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /assets/src/js/admin/fields/mailchimp-api-key.js: -------------------------------------------------------------------------------- 1 | function validate () { 2 | const node = document.createElement('p') 3 | node.className = 'mc4wp-red' 4 | node.innerText = window.mc4wp_vars.i18n.invalid_api_key 5 | 6 | if (field.nextElementSibling.innerText === node.innerText) { 7 | field.nextElementSibling.parentElement.removeChild(field.nextElementSibling) 8 | } 9 | 10 | if (!field.value.match(/^[0-9a-zA-Z*]{32}-[a-z]{2}[0-9]{1,2}$/)) { 11 | field.parentElement.insertBefore(node, field.nextElementSibling) 12 | } 13 | } 14 | 15 | const field = document.getElementById('mailchimp_api_key') 16 | if (field) { 17 | field.addEventListener('change', validate) 18 | } 19 | -------------------------------------------------------------------------------- /includes/class-queue-job.php: -------------------------------------------------------------------------------- 1 | id = (string) microtime(true) . rand(1, 10000); 38 | $this->data = $data; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /includes/forms/admin-functions.php: -------------------------------------------------------------------------------- 1 | text = $text; 28 | 29 | if (! empty($type)) { 30 | $this->type = $type; 31 | } 32 | } 33 | 34 | /** 35 | * @return string 36 | */ 37 | public function __toString() 38 | { 39 | return $this->text; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /assets/src/js/events.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Create a new EventEmitter that stores its own set of listeners 3 | * @constructor 4 | */ 5 | function EventEmitter () { 6 | this.listeners = {} 7 | } 8 | 9 | /** 10 | * Emit the event with the given name and arguments 11 | * @param {string} event 12 | * @param {array} args 13 | */ 14 | EventEmitter.prototype.emit = function (event, args) { 15 | this.listeners[event] = this.listeners[event] ?? [] 16 | this.listeners[event].forEach(f => f.apply(null, args)) 17 | } 18 | 19 | /** 20 | * Attach a new listener to the given event name. 21 | * @param {string} event 22 | * @param {function} func 23 | */ 24 | EventEmitter.prototype.on = function (event, func) { 25 | this.listeners[event] = this.listeners[event] ?? [] 26 | this.listeners[event].push(func) 27 | } 28 | 29 | module.exports = EventEmitter 30 | -------------------------------------------------------------------------------- /assets/src/js/misc/scroll-to-element.js: -------------------------------------------------------------------------------- 1 | function scrollTo (element) { 2 | const x = window.pageXOffset || document.documentElement.scrollLeft 3 | const y = calculateScrollOffset(element) 4 | window.scrollTo(x, y) 5 | } 6 | 7 | function calculateScrollOffset (elem) { 8 | const body = document.body 9 | const html = document.documentElement 10 | const elemRect = elem.getBoundingClientRect() 11 | const clientHeight = html.clientHeight 12 | const documentHeight = Math.max(body.scrollHeight, body.offsetHeight, 13 | html.clientHeight, html.scrollHeight, html.offsetHeight) 14 | 15 | const scrollPosition = elemRect.bottom - clientHeight / 2 - elemRect.height / 2 16 | const maxScrollPosition = documentHeight - clientHeight 17 | return Math.min(scrollPosition + window.pageYOffset, maxScrollPosition) 18 | } 19 | 20 | module.exports = scrollTo 21 | -------------------------------------------------------------------------------- /includes/admin/migrations/4.0.0-hidden-fields-value-delimiter.php: -------------------------------------------------------------------------------- 1 | 'mc4wp-form', 17 | 'numberposts' => -1, 18 | ] 19 | ); 20 | 21 | foreach ($posts as $post) { 22 | // find hidden field values in form and pass through replace function 23 | $old = $post->post_content; 24 | $new = preg_replace_callback('/type="hidden" .* value="(.*)"/i', '_mc4wp_400_replace_comma_with_pipe', $old); 25 | 26 | // update post if we replaced something 27 | if ($new != $old) { 28 | $post->post_content = $new; 29 | wp_update_post($post); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Code standard rules for Mailchimp for WordPress 4 | 5 | mailchimp-for-wp.php 6 | uninstall.php 7 | autoload.php 8 | includes/ 9 | integrations/ 10 | config/ 11 | tests/ 12 | sample-code-snippets/ 13 | *\.(html|css|js|md|json|xml|sh) 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /includes/default-filters.php: -------------------------------------------------------------------------------- 1 | i.checked).map(i => i.value).join(',') 7 | const allowedFields = ['EMAIL', 'FNAME', 'NAME', 'LNAME'] 8 | let showNotice = false 9 | 10 | window.fetch(`${ajaxurl}?action=mc4wp_get_list_details&ids=${ids}`) 11 | .then(r => r.json()) 12 | .then(lists => { 13 | lists.forEach(list => { 14 | list.merge_fields.forEach(f => { 15 | if (f.required && allowedFields.indexOf(f.tag) < 0) { 16 | showNotice = true 17 | } 18 | }) 19 | }) 20 | }).finally(() => { 21 | notice.style.display = showNotice ? '' : 'none' 22 | }) 23 | } 24 | 25 | if (notice) { 26 | checkRequiredListFields() 27 | 28 | settings.on('selectedLists.change', checkRequiredListFields) 29 | } 30 | -------------------------------------------------------------------------------- /bin/check-php-syntax: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | getPathname(), './vendor/') === 0 17 | || strpos($file->getPathname(), './node_modules/') === 0 18 | || false == $file->isFile() 19 | || $file->getExtension() !== 'php' 20 | ) { 21 | continue; 22 | } 23 | 24 | $exit_code = 0; 25 | $output = []; 26 | exec("php --define error_reporting=-1 -l {$file->getPathname()}", $output, $exit_code); 27 | $output = join("\n", $output); 28 | echo $output . "\n"; 29 | 30 | if ($exit_code || strpos($output, 'Deprecated') !== false) { 31 | $global_exit_code = 1; 32 | } 33 | } 34 | 35 | exit($global_exit_code); 36 | -------------------------------------------------------------------------------- /assets/src/img/logo-red-on-white.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/src/img/logo-white-on-red.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /includes/forms/class-form-previewer.php: -------------------------------------------------------------------------------- 1 | 0) { 30 | ob_end_clean(); 31 | } 32 | 33 | $form_id = (int) $_GET['mc4wp_preview_form']; 34 | status_header(200); 35 | 36 | require __DIR__ . '/views/preview.php'; 37 | exit; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /assets/src/js/admin/list-overview.js: -------------------------------------------------------------------------------- 1 | const ajaxurl = window.mc4wp_vars.ajaxurl 2 | 3 | function showDetails (evt) { 4 | evt.preventDefault() 5 | 6 | const link = evt.target 7 | const next = link.parentElement.parentElement.nextElementSibling 8 | const listID = link.getAttribute('data-list-id') 9 | const mount = next.querySelector('div') 10 | 11 | if (next.style.display === 'none') { 12 | const xhr = new XMLHttpRequest() 13 | xhr.open('GET', ajaxurl + '?action=mc4wp_get_list_details&format=html&ids=' + listID, true) 14 | xhr.onload = function () { 15 | if (this.status >= 400) { 16 | return 17 | } 18 | 19 | mount.innerHTML = this.responseText 20 | } 21 | xhr.send(null) 22 | 23 | next.style.display = '' 24 | } else { 25 | next.style.display = 'none' 26 | } 27 | } 28 | 29 | const table = document.getElementById('mc4wp-mailchimp-lists-overview') 30 | if (table) { 31 | table.addEventListener('click', (evt) => { 32 | if (!evt.target.matches('.mc4wp-mailchimp-list')) { 33 | return 34 | } 35 | 36 | showDetails(evt) 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /includes/admin/migrations/4.0.7-rename-debug-log-file.php: -------------------------------------------------------------------------------- 1 | '; 26 | if (strpos($line, $php_exit_string) !== 0) { 27 | rewind($handle); 28 | fwrite($handle, $php_exit_string . PHP_EOL . $line); 29 | } 30 | 31 | fclose($handle); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /includes/admin/migrations/3.0.0-form-3-messages.php: -------------------------------------------------------------------------------- 1 | 'mc4wp-form', 9 | 'post_status' => 'publish', 10 | 'numberposts' => -1, 11 | ] 12 | ); 13 | 14 | // set form message texts 15 | $message_keys = [ 16 | 'text_subscribed', 17 | 'text_error', 18 | 'text_invalid_email', 19 | 'text_already_subscribed', 20 | 'text_required_field_missing', 21 | 'text_unsubscribed', 22 | 'text_not_subscribed', 23 | ]; 24 | 25 | foreach ($posts as $post) { 26 | $settings = get_post_meta($post->ID, '_mc4wp_settings', true); 27 | 28 | foreach ($message_keys as $key) { 29 | if (empty($settings[ $key ])) { 30 | continue; 31 | } 32 | 33 | $message = $settings[ $key ]; 34 | 35 | // move message setting over to post meta 36 | update_post_meta($post->ID, $key, $message); 37 | unset($settings[ $key ]); 38 | } 39 | 40 | // update post meta with unset message keys 41 | update_post_meta($post->ID, '_mc4wp_settings', $settings); 42 | } 43 | -------------------------------------------------------------------------------- /includes/admin/migrations/3.0.0-styles-builder.php: -------------------------------------------------------------------------------- 1 | get_tags(); 5 | ?> 6 |

7 |

8 | add some dynamic content to your form or success and error messages.', 'mailchimp-for-wp'), [ 'a' => [ 'href' => [] ] ]), 'https://www.mc4wp.com/kb/using-variables-in-your-form-or-messages/') . ' ' . __('This allows you to personalise your form or response messages.', 'mailchimp-for-wp'); ?> 9 |

10 | 11 | $config) { 13 | $tag = ! empty($config['example']) ? $config['example'] : $tag; 14 | ?> 15 | 16 | 20 | 21 | 24 |
17 | 18 |

'); ?>

19 |
25 | -------------------------------------------------------------------------------- /includes/admin/migrations/3.0.0-widget-base-id.php: -------------------------------------------------------------------------------- 1 | $widgets) { 9 | // WP has an "array_version" key that is not an array... 10 | if (! is_array($widgets)) { 11 | continue; 12 | } 13 | 14 | // loop through widget ID's 15 | foreach ($widgets as $key => $widget_id) { 16 | // does this widget ID start with "mc4wp_widget"? 17 | if (strpos($widget_id, 'mc4wp_widget') === 0) { 18 | // replace "mc4wp_widget" with "mc4wp_form_widget" 19 | $new_widget_id = str_replace('mc4wp_widget', 'mc4wp_form_widget', $widget_id); 20 | $section_widgets[ $section ][ $key ] = $new_widget_id; 21 | $replaced = true; 22 | } 23 | } 24 | } 25 | 26 | 27 | // update option if we made changes 28 | if ($replaced) { 29 | update_option('sidebars_widgets', $section_widgets); 30 | } 31 | 32 | // update widget options 33 | $options = get_option('widget_mc4wp_widget', false); 34 | if ($options) { 35 | update_option('widget_mc4wp_form_widget', $options); 36 | 37 | // delete old option 38 | delete_option('widget_mc4wp_widget'); 39 | } 40 | -------------------------------------------------------------------------------- /includes/integrations/functions.php: -------------------------------------------------------------------------------- 1 | get_all(); 14 | } 15 | 16 | /** 17 | * Get an instance of a registered integration class 18 | * 19 | * @since 3.0 20 | * @access public 21 | * 22 | * @param string $slug 23 | * 24 | * @return MC4WP_Integration 25 | */ 26 | function mc4wp_get_integration($slug) 27 | { 28 | return mc4wp('integrations')->get($slug); 29 | } 30 | 31 | /** 32 | * Register a new integration with Mailchimp for WordPress 33 | * 34 | * @since 3.0 35 | * @access public 36 | * 37 | * @param string $slug 38 | * @param string $class 39 | * 40 | * @param bool $always_enabled 41 | */ 42 | function mc4wp_register_integration($slug, $class, $always_enabled = false) 43 | { 44 | return mc4wp('integrations')->register_integration($slug, $class, $always_enabled); 45 | } 46 | 47 | /** 48 | * Deregister a previously registered integration with Mailchimp for WordPress 49 | * 50 | * @since 3.0 51 | * @access public 52 | * @param string $slug 53 | */ 54 | function mc4wp_deregister_integration($slug) 55 | { 56 | mc4wp('integrations')->deregister_integration($slug); 57 | } 58 | -------------------------------------------------------------------------------- /integrations/give/class-give.php: -------------------------------------------------------------------------------- 1 | options['implicit']) { 17 | add_action('give_purchase_form_register_login_fields', [ $this, 'output_checkbox' ], 50); 18 | } 19 | 20 | add_action('give_checkout_before_gateway', [ $this, 'subscribe_from_give' ], 90, 2); 21 | } 22 | 23 | public function subscribe_from_give($posted, $user) 24 | { 25 | // was sign-up checkbox checked? 26 | if (true !== $this->triggered()) { 27 | return; 28 | } 29 | 30 | $merge_fields = [ 31 | 'EMAIL' => $user['email'], 32 | ]; 33 | 34 | if (! empty($user['first_name'])) { 35 | $merge_fields['FNAME'] = $user['first_name']; 36 | } 37 | 38 | if (! empty($user['last_name'])) { 39 | $merge_fields['LNAME'] = $user['last_name']; 40 | } 41 | 42 | return $this->subscribe($merge_fields); 43 | } 44 | 45 | public function is_installed() 46 | { 47 | return defined('GIVE_VERSION'); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /integrations/prosopo-procaptcha/class-procaptcha-integration.php: -------------------------------------------------------------------------------- 1 | '0', 55 | 'css' => '0', 56 | 'site_key' => '', 57 | 'secret_key' => '', 58 | 'theme' => 'light', 59 | 'type' => 'frictionless', 60 | 'display_for_authorized' => '0', 61 | ]; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /includes/forms/views/preview.php: -------------------------------------------------------------------------------- 1 | 'raw' ]); 6 | $GLOBALS['wp_query'] = new \WP_Query(); 7 | 8 | // render simple page with form in it. 9 | ?> 10 | 11 | 12 | Mailchimp for WordPress Form Preview 13 | 14 | 15 | 16 | 19 | 44 | 45 | 46 |
47 | 48 |
49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /assets/src/js/admin/settings.js: -------------------------------------------------------------------------------- 1 | const context = document.getElementById('mc4wp-admin') 2 | const listInputs = context.querySelectorAll('.mc4wp-list-input') 3 | const lists = window.mc4wp_vars.mailchimp.lists 4 | let selectedLists = [] 5 | const EventEmitter = require('../events.js') 6 | const events = new EventEmitter() 7 | 8 | function getSelectedLists () { 9 | return selectedLists 10 | } 11 | 12 | function updateSelectedLists () { 13 | selectedLists = [] 14 | for (let i = 0; i < listInputs.length; i++) { 15 | const input = listInputs[i] 16 | 17 | // skip unchecked checkboxes 18 | if (typeof (input.checked) === 'boolean' && !input.checked) { 19 | continue 20 | } 21 | 22 | if (typeof (lists[input.value]) === 'object') { 23 | selectedLists.push(lists[input.value]) 24 | } 25 | } 26 | 27 | toggleVisibleLists() 28 | events.emit('selectedLists.change', [selectedLists]) 29 | return selectedLists 30 | } 31 | 32 | function toggleVisibleLists () { 33 | const rows = document.querySelectorAll('.lists--only-selected > *') 34 | for (let i = 0; i < rows.length; i++) { 35 | const listId = rows[i].getAttribute('data-list-id') 36 | const isSelected = selectedLists.filter(list => list.id === listId).length > 0 37 | rows[i].style.display = isSelected ? '' : 'none' 38 | } 39 | } 40 | 41 | const listsWrapperEl = document.getElementById('mc4wp-lists') 42 | if (listsWrapperEl) listsWrapperEl.addEventListener('change', updateSelectedLists) 43 | 44 | updateSelectedLists() 45 | 46 | module.exports = { 47 | getSelectedLists, 48 | on: events.on.bind(events) 49 | } 50 | -------------------------------------------------------------------------------- /wpml-config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | mc4wp-form 4 | 5 | 6 | _mc4wp_settings 7 | text_subscribed 8 | text_error 9 | text_invalid_email 10 | text_already_subscribed 11 | text_required_field_missing 12 | text_unsubscribed 13 | text_not_subscribed 14 | text_subscribed 15 | text_no_lists_selected 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /includes/integrations/class-user-integration.php: -------------------------------------------------------------------------------- 1 | $user->user_email, 24 | 'NAME' => $user->user_login, 25 | ]; 26 | 27 | if ('' !== $user->first_name) { 28 | $data['NAME'] = $user->first_name; 29 | $data['FNAME'] = $user->first_name; 30 | } 31 | 32 | if ('' !== $user->last_name) { 33 | $data['LNAME'] = $user->last_name; 34 | } 35 | 36 | if ('' !== $user->first_name && '' !== $user->last_name) { 37 | $data['NAME'] = sprintf('%s %s', $user->first_name, $user->last_name); 38 | } 39 | 40 | /** 41 | * @use mc4wp_integration_user_data 42 | * @since 3.0 43 | * @deprecated 4.0 44 | * @ignore 45 | */ 46 | $data = (array) apply_filters('mc4wp_user_merge_vars', $data, $user); 47 | 48 | /** 49 | * Filters the data for user-related integrations 50 | * @since 4.2 51 | * @param array $data 52 | * @param WP_User $user 53 | */ 54 | $data = (array) apply_filters('mc4wp_integration_user_data', $data, $user); 55 | 56 | return $data; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /assets/src/js/admin/show-if.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {HTMLElement} el 3 | * @param {boolean} hide 4 | * @param {HTMLInputElement} input 5 | * @param {boolean|string} condition 6 | */ 7 | function toggleElement (el, hide, input, condition) { 8 | // do nothing with unchecked radio inputs 9 | if (input.type === 'radio' && !input.checked) { 10 | return 11 | } 12 | 13 | const value = (input.type === 'checkbox') ? input.checked : input.value 14 | const conditionMet = (String(value) === String(condition)) 15 | 16 | if (hide) { 17 | el.style.display = conditionMet ? '' : 'none' 18 | el.style.visibility = conditionMet ? '' : 'hidden' 19 | } else { 20 | el.style.opacity = conditionMet ? '' : '0.4' 21 | el.style.pointerEvents = conditionMet ? '' : 'none' 22 | } 23 | 24 | // disable input fields inside this element to stop sending their values to server 25 | [].forEach.call(el.querySelectorAll('input,select,textarea:not([readonly])'), function (inputElement) { 26 | inputElement.readOnly = !conditionMet 27 | }) 28 | } 29 | 30 | function init (el) { 31 | const config = JSON.parse(el.getAttribute('data-showif')) 32 | const parentElements = document.querySelectorAll('[name="' + config.element + '"]') 33 | const hide = config.hide === undefined || config.hide 34 | 35 | // find checked element and call toggleElement function 36 | for (let i = 0; i < parentElements.length; i++) { 37 | parentElements[i].addEventListener('change', toggleElement.bind(null, el, hide, parentElements[i], config.value)) 38 | toggleElement(el, hide, parentElements[i], config.value) 39 | } 40 | } 41 | 42 | [].forEach.call(document.querySelectorAll('[data-showif]'), init) 43 | -------------------------------------------------------------------------------- /assets/src/js/forms/form.js: -------------------------------------------------------------------------------- 1 | const serialize = require('form-serialize') 2 | const populate = require('populate.js') 3 | 4 | /** 5 | * Creates a new Form object from the given ID and HTML element 6 | * @param {string} id 7 | * @param {Element} element 8 | * @constructor 9 | */ 10 | const Form = function (id, element) { 11 | this.id = id 12 | this.element = element || document.createElement('form') 13 | this.name = this.element.getAttribute('data-name') || 'Form #' + this.id 14 | this.errors = [] 15 | this.started = false 16 | } 17 | 18 | /** 19 | * Sets the value for each field inside the form to the given data object 20 | * @param {object} data 21 | */ 22 | Form.prototype.setData = function (data) { 23 | try { 24 | populate(this.element, data) 25 | } catch (e) { 26 | console.error(e) 27 | } 28 | } 29 | 30 | /** 31 | * Returns the form values as a JSON object 32 | * @returns {object} 33 | */ 34 | Form.prototype.getData = function () { 35 | return serialize(this.element, { hash: true, empty: true }) 36 | } 37 | 38 | /** 39 | * Returns the form values as a query param string 40 | * @returns {string} 41 | */ 42 | Form.prototype.getSerializedData = function () { 43 | return serialize(this.element, { hash: false, empty: true }) 44 | } 45 | 46 | /** 47 | * Sets the HTML of the .mc4wp-response element inside this form to the provided msg param 48 | * @param {string} msg 49 | */ 50 | Form.prototype.setResponse = function (msg) { 51 | this.element.querySelector('.mc4wp-response').innerHTML = msg 52 | } 53 | 54 | /** 55 | * Reverts the form back to its initial state 56 | */ 57 | Form.prototype.reset = function () { 58 | this.setResponse('') 59 | this.element.querySelector('.mc4wp-form-fields').style.display = '' 60 | this.element.reset() 61 | } 62 | 63 | module.exports = Form 64 | -------------------------------------------------------------------------------- /includes/integrations/class-integration-tags.php: -------------------------------------------------------------------------------- 1 | tags['subscriber_count'] = [ 32 | 'description' => __('Replaced with the number of subscribers on the selected list(s)', 'mailchimp-for-wp'), 33 | 'callback' => [ $this, 'get_subscriber_count' ], 34 | ]; 35 | } 36 | 37 | /** 38 | * @hooked `mc4wp_integration_checkbox_label` 39 | * @param string $string 40 | * @param MC4WP_Integration $integration 41 | * @return string 42 | */ 43 | public function replace_in_checkbox_label($string, MC4WP_Integration $integration) 44 | { 45 | $this->integration = $integration; 46 | return $this->replace_in_html($string); 47 | } 48 | 49 | /** 50 | * Returns the number of subscribers on the selected lists (for the form context) 51 | * 52 | * @return int 53 | */ 54 | public function get_subscriber_count() 55 | { 56 | $mailchimp = new MC4WP_MailChimp(); 57 | $list_ids = $this->integration->get_lists(); 58 | $count = $mailchimp->get_subscriber_count($list_ids); 59 | return number_format($count); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const CopyPlugin = require('copy-webpack-plugin') 3 | const css = require('lightningcss') 4 | 5 | module.exports = { 6 | mode: process.env.NODE_ENV === 'development' ? 'development' : 'production', 7 | entry: { 8 | admin: './assets/src/js/admin.js', 9 | 'integrations-admin': './assets/src/js/integrations-admin.js', 10 | forms: './assets/src/js/forms.js', 11 | 'forms-submitted': './assets/src/js/forms-submitted.js', 12 | 'forms-admin': './assets/src/js/forms-admin.js', 13 | 'forms-block': './assets/src/js/forms-block.js', 14 | 'email-typo-checker': './assets/src/js/forms/email-typo-checker.js' 15 | }, 16 | output: { 17 | path: path.resolve(__dirname, 'assets/js'), 18 | filename: '[name].js' 19 | }, 20 | module: { 21 | rules: [ 22 | { 23 | test: /\.m?js$/, 24 | exclude: /(node_modules|bower_components)/, 25 | use: { 26 | loader: 'babel-loader', 27 | options: { 28 | presets: ['@babel/preset-env'], 29 | plugins: ['@babel/plugin-transform-react-jsx'] 30 | } 31 | } 32 | } 33 | ] 34 | }, 35 | plugins: [ 36 | new CopyPlugin({ 37 | patterns: [ 38 | { from: './assets/src/img', to: path.resolve(__dirname, './assets/img') }, 39 | { 40 | from: './assets/src/css', 41 | to: path.resolve(__dirname, './assets/css'), 42 | transform: (content, path) => { 43 | const { code } = css.transform({ 44 | filename: path.split('/').pop(), 45 | code: Buffer.from(content), 46 | minify: true, 47 | sourceMap: false 48 | }) 49 | return code 50 | } 51 | } 52 | ] 53 | }) 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /includes/views/parts/admin-footer.php: -------------------------------------------------------------------------------- 1 | ' . sprintf(wp_kses(__('Mailchimp for WordPress is in need of translations. Is the plugin not translated in your language or do you spot errors with the current translations? Helping out is easy! Please help translate the plugin using your WordPress.org account.', 'mailchimp-for-wp'), ['a' => ['href' => []]]), 'https://translate.wordpress.org/projects/wp-plugins/mailchimp-for-wp/stable/') . '

'; 12 | } 13 | 14 | function _mc4wp_admin_github_notice() 15 | { 16 | if (strpos($_SERVER['HTTP_HOST'], 'localhost') === false && ! WP_DEBUG) { 17 | return; 18 | } 19 | 20 | echo '

Developer? Follow Mailchimp for WordPress on GitHub or have a look at our repository of sample code snippets.

'; 21 | } 22 | 23 | function _mc4wp_admin_disclaimer_notice() 24 | { 25 | echo '

', esc_html__('This plugin is not developed by or affiliated with Mailchimp in any way.', 'mailchimp-for-wp'), '

'; 26 | } 27 | 28 | add_action('mc4wp_admin_footer', '_mc4wp_admin_translation_notice', 20); 29 | add_action('mc4wp_admin_footer', '_mc4wp_admin_github_notice', 50); 30 | add_action('mc4wp_admin_footer', '_mc4wp_admin_disclaimer_notice', 80); 31 | 32 | ?> 33 |
34 | 35 |
36 | -------------------------------------------------------------------------------- /includes/class-plugin.php: -------------------------------------------------------------------------------- 1 | file = $file; 31 | $this->version = $version; 32 | } 33 | 34 | /** 35 | * Get the main plugin file. 36 | * 37 | * @return string 38 | */ 39 | public function file() 40 | { 41 | return $this->file; 42 | } 43 | 44 | /** 45 | * Get the plugin version. 46 | * 47 | * @return string 48 | */ 49 | public function version() 50 | { 51 | return $this->version; 52 | } 53 | 54 | /** 55 | * Gets the directory the plugin lives in. 56 | * 57 | * @param string $path 58 | * 59 | * @return string 60 | */ 61 | public function dir($path = '') 62 | { 63 | 64 | // ensure path has leading slash 65 | if ('' !== $path) { 66 | $path = '/' . ltrim($path, '/'); 67 | } 68 | 69 | return dirname($this->file) . $path; 70 | } 71 | 72 | /** 73 | * Gets the URL to the plugin files. 74 | * 75 | * @param string $path 76 | * 77 | * @return string 78 | */ 79 | public function url($path = '') 80 | { 81 | return plugins_url($path, $this->file); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /config/default-form-messages.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'type' => 'success', 6 | 'text' => esc_html__('Thank you, your sign-up request was successful! Please check your email inbox to confirm.', 'mailchimp-for-wp'), 7 | ], 8 | 'updated' => [ 9 | 'type' => 'success', 10 | 'text' => esc_html__('Thank you, your records have been updated!', 'mailchimp-for-wp'), 11 | ], 12 | 'unsubscribed' => [ 13 | 'type' => 'success', 14 | 'text' => esc_html__('You were successfully unsubscribed.', 'mailchimp-for-wp'), 15 | ], 16 | 'not_subscribed' => [ 17 | 'type' => 'notice', 18 | 'text' => esc_html__('Given email address is not subscribed.', 'mailchimp-for-wp'), 19 | ], 20 | 'error' => [ 21 | 'type' => 'error', 22 | 'text' => esc_html__('Oops. Something went wrong. Please try again later.', 'mailchimp-for-wp'), 23 | ], 24 | 'invalid_email' => [ 25 | 'type' => 'error', 26 | 'text' => esc_html__('Please provide a valid email address.', 'mailchimp-for-wp'), 27 | ], 28 | 'already_subscribed' => [ 29 | 'type' => 'notice', 30 | 'text' => esc_html__('Given email address is already subscribed, thank you!', 'mailchimp-for-wp'), 31 | ], 32 | 'required_field_missing' => [ 33 | 'type' => 'error', 34 | 'text' => esc_html__('Please fill in the required fields.', 'mailchimp-for-wp'), 35 | ], 36 | 'no_lists_selected' => [ 37 | 'type' => 'error', 38 | 'text' => esc_html__('Please select at least one list.', 'mailchimp-for-wp'), 39 | ], 40 | 'spam' => [ 41 | 'type' => 'error', 42 | 'text' => esc_html__('Your submission was marked as spam.', 'mailchimp-for-wp'), 43 | ], 44 | ]; 45 | -------------------------------------------------------------------------------- /includes/admin/migrations/3.0.0-integration-options.php: -------------------------------------------------------------------------------- 1 | 'wp-comment-form', 20 | 'registration_form' => 'wp-registration-form', 21 | 'buddypress_form' => 'buddypress', 22 | 'bbpres_forms' => 'bbpress', 23 | 'woocommerce_checkout' => 'woocommerce', 24 | 'edd_checkout' => 'easy-digital-downloads', 25 | ]; 26 | 27 | $option_keys = [ 28 | 'label', 29 | 'precheck', 30 | 'css', 31 | 'lists', 32 | 'double_optin', 33 | 'update_existing', 34 | 'replace_interests', 35 | 'send_welcome', 36 | ]; 37 | 38 | foreach ($map as $old_integration_slug => $new_integration_slug) { 39 | // check if integration is enabled using its old slug 40 | $show_key = sprintf('show_at_%s', $old_integration_slug); 41 | if (empty($old_options[ $show_key ])) { 42 | continue; 43 | } 44 | 45 | $options = [ 46 | 'enabled' => 1, 47 | ]; 48 | 49 | foreach ($option_keys as $option_key) { 50 | if (isset($old_options[ $option_key ])) { 51 | $options[ $option_key ] = $old_options[ $option_key ]; 52 | } 53 | } 54 | 55 | // add to new options 56 | $new_options[ $new_integration_slug ] = $options; 57 | } 58 | 59 | // save new settings 60 | update_option('mc4wp_integrations', $new_options); 61 | 62 | // delete old options 63 | delete_option('mc4wp_lite_checkbox'); 64 | delete_option('mc4wp_checkbox'); 65 | -------------------------------------------------------------------------------- /includes/admin/class-admin-tools.php: -------------------------------------------------------------------------------- 1 | get_plugin_page() === $page; 33 | } 34 | 35 | /** 36 | * Does the logged-in user have the required capability? 37 | * 38 | * @return bool 39 | */ 40 | public function is_user_authorized() 41 | { 42 | return current_user_can($this->get_required_capability()); 43 | } 44 | 45 | /** 46 | * Get required capability to access settings page and view dashboard widgets. 47 | * 48 | * @return string 49 | */ 50 | public function get_required_capability() 51 | { 52 | $capability = 'manage_options'; 53 | 54 | /** 55 | * Filters the required user capability to access the Mailchimp for WordPress' settings pages, view the dashboard widgets. 56 | * 57 | * Defaults to `manage_options` 58 | * 59 | * @since 3.0 60 | * @param string $capability 61 | * @see https://codex.wordpress.org/Roles_and_Capabilities 62 | */ 63 | $capability = (string) apply_filters('mc4wp_admin_required_capability', $capability); 64 | 65 | return $capability; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /assets/src/js/forms-submitted.js: -------------------------------------------------------------------------------- 1 | import scrollToElement from './misc/scroll-to-element.js' 2 | const submittedForm = window.mc4wp_submitted_form 3 | const forms = window.mc4wp.forms 4 | 5 | function handleFormRequest (form, eventName, errors, data) { 6 | const timeStart = Date.now() 7 | const pageHeight = document.body.clientHeight 8 | 9 | // re-populate form if an error occurred 10 | if (errors) { 11 | form.setData(data) 12 | } 13 | 14 | // scroll to form 15 | if (window.scrollY <= 10 && submittedForm.auto_scroll) { 16 | scrollToElement(form.element) 17 | } 18 | 19 | // trigger events on window.load so all other scripts have loaded 20 | window.addEventListener('load', function () { 21 | forms.trigger('submitted', [form]) 22 | 23 | if (errors) { 24 | forms.trigger('error', [form, errors]) 25 | } else { 26 | // form was successfully submitted 27 | forms.trigger('success', [form, data]) 28 | 29 | // subscribed / unsubscribed 30 | forms.trigger(eventName, [form, data]) 31 | 32 | // for BC: always trigger "subscribed" event when firing "updated_subscriber" event 33 | if (eventName === 'updated_subscriber') { 34 | forms.trigger('subscribed', [form, data, true]) 35 | } 36 | } 37 | 38 | // scroll to form again if page height changed since last scroll, eg because of slow loading images 39 | // (only if load didn't take too long to prevent overtaking user scroll) 40 | const timeElapsed = Date.now() - timeStart 41 | if (submittedForm.auto_scroll && timeElapsed > 1000 && timeElapsed < 2000 && document.body.clientHeight !== pageHeight) { 42 | scrollToElement(form.element) 43 | } 44 | }) 45 | } 46 | 47 | if (submittedForm) { 48 | const element = document.getElementById(submittedForm.element_id) 49 | const form = forms.getByElement(element) 50 | handleFormRequest(form, submittedForm.event, submittedForm.errors, submittedForm.data) 51 | } 52 | -------------------------------------------------------------------------------- /includes/admin/class-admin-messages.php: -------------------------------------------------------------------------------- 1 | bag)) { 33 | $this->bag = get_option('mc4wp_flash_messages', []); 34 | } 35 | } 36 | 37 | // empty flash bag 38 | private function reset() 39 | { 40 | $this->bag = []; 41 | $this->dirty = true; 42 | } 43 | 44 | /** 45 | * Flash a message (shows on next pageload) 46 | * 47 | * @param $message 48 | * @param string $type 49 | */ 50 | public function flash($message, $type = 'success') 51 | { 52 | $this->load(); 53 | $this->bag[] = [ 54 | 'text' => $message, 55 | 'type' => $type, 56 | ]; 57 | $this->dirty = true; 58 | } 59 | 60 | 61 | 62 | /** 63 | * Show queued flash messages 64 | */ 65 | public function show() 66 | { 67 | $this->load(); 68 | 69 | foreach ($this->bag as $message) { 70 | echo sprintf('

%s

', $message['type'], $message['text']); 71 | } 72 | 73 | $this->reset(); 74 | } 75 | 76 | /** 77 | * Save queued messages 78 | * 79 | * @hooked `shutdown` 80 | */ 81 | public function save() 82 | { 83 | if ($this->dirty) { 84 | update_option('mc4wp_flash_messages', $this->bag, false); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /includes/forms/functions.php: -------------------------------------------------------------------------------- 1 | 'publish', 31 | 'posts_per_page' => -1, 32 | 'ignore_sticky_posts' => true, 33 | 'no_found_rows' => true, 34 | ]; 35 | $args = array_merge($default_args, $args); 36 | 37 | // set post_type here so it can't be overwritten using function arguments 38 | $args['post_type'] = 'mc4wp-form'; 39 | 40 | $q = new WP_Query(); 41 | $posts = $q->query($args); 42 | $forms = []; 43 | foreach ($posts as $post) { 44 | try { 45 | $form = mc4wp_get_form($post); 46 | } catch (Exception $e) { 47 | continue; 48 | } 49 | 50 | $forms[] = $form; 51 | } 52 | return $forms; 53 | } 54 | 55 | /** 56 | * Echoes the given form 57 | * 58 | * @access public 59 | * 60 | * @param int $form_id 61 | * @param array $config 62 | * @param bool $echo 63 | * 64 | * @return string 65 | */ 66 | function mc4wp_show_form($form_id = 0, $config = [], $echo = true) 67 | { 68 | /** @var MC4WP_Form_Manager $forms */ 69 | $forms = mc4wp('forms'); 70 | return $forms->output_form($form_id, $config, $echo); 71 | } 72 | 73 | 74 | /** 75 | * Gets an instance of the submitted form, if any. 76 | * 77 | * @access public 78 | * 79 | * @return MC4WP_Form|null 80 | */ 81 | function mc4wp_get_submitted_form() 82 | { 83 | return mc4wp('forms')->get_submitted_form(); 84 | } 85 | -------------------------------------------------------------------------------- /includes/admin/class-admin-ajax.php: -------------------------------------------------------------------------------- 1 | tools = $tools; 18 | } 19 | 20 | /** 21 | * Hook AJAX actions 22 | */ 23 | public function add_hooks() 24 | { 25 | add_action('wp_ajax_mc4wp_get_list_details', [ $this, 'get_list_details' ]); 26 | } 27 | 28 | 29 | /** 30 | * Retrieve details (merge fields and interest categories) for one or multiple lists in Mailchimp 31 | * @throws MC4WP_API_Exception 32 | */ 33 | public function get_list_details() 34 | { 35 | if (! $this->tools->is_user_authorized()) { 36 | wp_send_json_error(); 37 | return; 38 | } 39 | 40 | $list_ids = (array) explode(',', $_GET['ids']); 41 | $data = []; 42 | $mailchimp = new MC4WP_MailChimp(); 43 | foreach ($list_ids as $list_id) { 44 | $data[] = (object) [ 45 | 'id' => $list_id, 46 | 'merge_fields' => $mailchimp->get_list_merge_fields($list_id), 47 | 'interest_categories' => $mailchimp->get_list_interest_categories($list_id), 48 | 'marketing_permissions' => $mailchimp->get_list_marketing_permissions($list_id), 49 | ]; 50 | } 51 | 52 | if (isset($_GET['format']) && $_GET['format'] === 'html') { 53 | $merge_fields = $data[0]->merge_fields; 54 | $interest_categories = $data[0]->interest_categories; 55 | $marketing_permissions = $data[0]->marketing_permissions; 56 | require MC4WP_PLUGIN_DIR . '/includes/views/parts/lists-overview-details.php'; 57 | } else { 58 | wp_send_json($data); 59 | } 60 | exit; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /includes/admin/migrations/3.0.0-form-2-options.php: -------------------------------------------------------------------------------- 1 | 'mc4wp-form', 11 | 'post_status' => 'publish', 12 | 'numberposts' => -1, 13 | ] 14 | ); 15 | 16 | $css_map = [ 17 | 'default' => 'basic', 18 | 'custom' => 'styles-builder', 19 | 'light' => 'theme-light', 20 | 'dark' => 'theme-dark', 21 | 'red' => 'theme-red', 22 | 'green' => 'theme-green', 23 | 'blue' => 'theme-blue', 24 | 'custom-color' => 'theme-custom-color', 25 | ]; 26 | 27 | $stylesheets = []; 28 | 29 | foreach ($posts as $post) { 30 | // get form options from post meta directly 31 | $options = (array) get_post_meta($post->ID, '_mc4wp_settings', true); 32 | 33 | // store all global options in scoped form settings 34 | // do this BEFORE changing css key, so we take that as well. 35 | foreach ($global_options as $key => $value) { 36 | if (strlen($value) > 0 && ( ! isset($options[ $key ]) || strlen($options[ $key ]) == 0 )) { 37 | $options[ $key ] = $value; 38 | } 39 | } 40 | 41 | // update "css" option value 42 | if (isset($options['css']) && isset($css_map[ $options['css'] ])) { 43 | $options['css'] = $css_map[ $options['css'] ]; 44 | } 45 | 46 | // create stylesheets option 47 | if (! empty($options['css'])) { 48 | $stylesheet = $options['css']; 49 | if (strpos($stylesheet, 'theme-') === 0) { 50 | $stylesheet = 'themes'; 51 | } 52 | 53 | if (! in_array($stylesheet, $stylesheets)) { 54 | $stylesheets[] = $stylesheet; 55 | } 56 | } 57 | 58 | update_post_meta($post->ID, '_mc4wp_settings', $options); 59 | } 60 | 61 | // update stylesheets option 62 | update_option('mc4wp_form_stylesheets', $stylesheets); 63 | 64 | // delete old options 65 | delete_option('mc4wp_form'); 66 | -------------------------------------------------------------------------------- /integrations/memberpress/class-memberpress.php: -------------------------------------------------------------------------------- 1 | options['implicit']) { 29 | if (has_action('mepr_checkout_before_submit')) { 30 | add_action('mepr_checkout_before_submit', [ $this, 'output_checkbox' ]); 31 | } else { 32 | add_action('mepr-checkout-before-submit', [ $this, 'output_checkbox' ]); 33 | } 34 | } 35 | if (has_action('mepr_signup')) { 36 | add_action('mepr_signup', [ $this, 'subscribe_from_memberpress' ], 5); 37 | } else { 38 | add_action('mepr-signup', [ $this, 'subscribe_from_memberpress' ], 5); 39 | } 40 | } 41 | 42 | 43 | 44 | /** 45 | * Subscribe from MemberPress sign-up forms. 46 | * 47 | * @param MeprTransaction $txn 48 | * @return bool 49 | */ 50 | public function subscribe_from_memberpress($txn) 51 | { 52 | 53 | // Is this integration triggered? (checkbox checked or implicit) 54 | if (! $this->triggered()) { 55 | return false; 56 | } 57 | 58 | $user = get_userdata($txn->user_id); 59 | 60 | $data = [ 61 | 'EMAIL' => $user->user_email, 62 | 'FNAME' => $user->first_name, 63 | 'LNAME' => $user->last_name, 64 | ]; 65 | 66 | // subscribe using email and name 67 | return $this->subscribe($data, $txn->id); 68 | } 69 | 70 | /** 71 | * @return bool 72 | */ 73 | public function is_installed() 74 | { 75 | return defined('MEPR_VERSION'); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /includes/class-mailchimp-subscriber.php: -------------------------------------------------------------------------------- 1 | $value) { 66 | // skip null values 67 | if ($value === null) { 68 | continue; 69 | } 70 | 71 | // skip empty marketing_permissions property 72 | if ($key === 'marketing_permissions' && empty($value)) { 73 | continue; 74 | } 75 | 76 | // otherwise, add to final array 77 | $array[ $key ] = $value; 78 | } 79 | 80 | return $array; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /integrations/ninja-forms/class-ninja-forms.php: -------------------------------------------------------------------------------- 1 | options; 35 | $this->options['double_optin'] = $double_optin; 36 | $this->options['update_existing'] = $update_existing; 37 | $this->options['replace_interests'] = $replace_interests; 38 | $this->options['lists'] = [ $list_id ]; 39 | 40 | $data = $merge_fields; 41 | $data['EMAIL'] = $email_address; 42 | 43 | $this->subscribe($data, $form_id); 44 | 45 | // revert to original options 46 | $this->options = $orig_options; 47 | } 48 | 49 | /** 50 | * @return bool 51 | */ 52 | public function is_installed() 53 | { 54 | return class_exists('Ninja_Forms'); 55 | } 56 | 57 | /** 58 | * @since 3.0 59 | * @return array 60 | */ 61 | public function get_ui_elements() 62 | { 63 | return []; 64 | } 65 | 66 | /** 67 | * @param int $form_id 68 | * @return string 69 | */ 70 | public function get_object_link($form_id) 71 | { 72 | return 'Ninja Forms'; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /assets/src/img/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 11 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /integrations/events-manager/class-events-manager.php: -------------------------------------------------------------------------------- 1 | options['implicit']) { 29 | add_action('em_booking_form_footer', [ $this, 'output_checkbox' ]); 30 | } 31 | 32 | add_action('em_bookings_added', [ $this, 'subscribe_from_events_manager' ], 5); 33 | } 34 | 35 | 36 | 37 | /** 38 | * Subscribe from Events Manager booking forms. 39 | * 40 | * @param EM_Booking $args 41 | * @return bool 42 | */ 43 | public function subscribe_from_events_manager($args) 44 | { 45 | 46 | // Is this integration triggered? (checkbox checked or implicit) 47 | if (! $this->triggered()) { 48 | return false; 49 | } 50 | 51 | $em_data = $this->get_data(); 52 | 53 | // logged-in users do not have these form fields, so grab from user object instead 54 | if (empty($em_data['user_email']) && is_user_logged_in()) { 55 | $user = wp_get_current_user(); 56 | $em_data['user_email'] = $user->user_email; 57 | $em_data['user_name'] = sprintf('%s %s', $user->first_name, $user->last_name); 58 | } 59 | 60 | if (empty($em_data['user_email'])) { 61 | return false; 62 | } 63 | 64 | $data = [ 65 | 'EMAIL' => $em_data['user_email'], 66 | 'NAME' => $em_data['user_name'], 67 | ]; 68 | 69 | // subscribe using email and name 70 | return $this->subscribe($data, $args->booking_id); 71 | } 72 | 73 | /** 74 | * @return bool 75 | */ 76 | public function is_installed() 77 | { 78 | return defined('EM_VERSION'); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /assets/src/js/admin/overlay.js: -------------------------------------------------------------------------------- 1 | const m = require('mithril') 2 | const i18n = window.mc4wp_forms_i18n 3 | 4 | function Overlay () {} 5 | 6 | Overlay.prototype.oncreate = function (vnode) { 7 | // bind event handlers so we can remove 'em later 8 | this.onDocumentKeydown = onKeyDown.bind(null, vnode.attrs.onClose) 9 | this.onWindowResize = onWindowResize.bind(null, vnode.dom) 10 | document.addEventListener('keydown', this.onDocumentKeydown) 11 | window.addEventListener('resize', this.onWindowResize) 12 | 13 | // call onWindowResize to properly center overlay 14 | this.onWindowResize() 15 | } 16 | 17 | Overlay.prototype.onremove = function () { 18 | document.removeEventListener('keydown', this.onDocumentKeydown) 19 | window.removeEventListener('resize', this.onWindowResize) 20 | } 21 | 22 | Overlay.prototype.view = function (vnode) { 23 | return [ 24 | m('div.mc4wp-overlay-wrap', 25 | m('div.mc4wp-overlay', [ 26 | // close icon 27 | m('span', { 28 | class: 'close dashicons dashicons-no', 29 | title: i18n.close, 30 | onclick: vnode.attrs.onClose 31 | }), 32 | vnode.children 33 | ]) 34 | ), 35 | m('div.mc4wp-overlay-background', { 36 | title: i18n.close, 37 | onclick: vnode.attrs.onClose 38 | }) 39 | ] 40 | } 41 | 42 | function onWindowResize (wrapperEl) { 43 | const el = wrapperEl.children[0] 44 | // fix for window width in IE8 45 | const windowWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth 46 | const windowHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight 47 | 48 | const marginLeft = (windowWidth - el.clientWidth - 40) / 2 49 | const marginTop = (windowHeight - el.clientHeight - 40) / 2 50 | 51 | el.style.left = (marginLeft > 0 ? marginLeft : 0) + 'px' 52 | el.style.top = (marginTop > 0 ? marginTop : 0) + 'px' 53 | } 54 | 55 | function onKeyDown (closeFn, evt) { 56 | switch (evt.keyCode) { 57 | // close overlay when pressing ESC 58 | case 27: closeFn(); break 59 | 60 | // prevent ENTER while overlay is open 61 | case 13: evt.preventDefault(); break 62 | } 63 | } 64 | 65 | module.exports = Overlay 66 | -------------------------------------------------------------------------------- /integrations/wpforms/class-wpforms.php: -------------------------------------------------------------------------------- 1 | $field) { 51 | if ($field['type'] === 'mailchimp' && (int) $field['value_raw'] === 1) { 52 | return $this->subscribe_from_wpforms($field_id, $fields, $form_data); 53 | } 54 | } 55 | } 56 | 57 | public function subscribe_from_wpforms($checkbox_field_id, $fields, $form_data) 58 | { 59 | foreach ($fields as $field) { 60 | if ($field['type'] === 'email') { 61 | $email_address = $field['value']; 62 | } 63 | } 64 | 65 | $mailchimp_list_id = $form_data['fields'][ $checkbox_field_id ]['mailchimp_list']; 66 | $this->options['lists'] = [ $mailchimp_list_id ]; 67 | 68 | if (! empty($email_address)) { 69 | return $this->subscribe([ 'EMAIL' => $email_address ], $form_data['id']); 70 | } 71 | } 72 | 73 | /** 74 | * @param int $form_id 75 | * @return string 76 | */ 77 | public function get_object_link($form_id) 78 | { 79 | return 'WPForms'; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /integrations/affiliatewp/class-affiliatewp.php: -------------------------------------------------------------------------------- 1 | options['implicit']) { 33 | add_action('affwp_register_fields_before_tos', [ $this, 'maybe_output_checkbox' ], 20); 34 | } 35 | 36 | add_action('affwp_register_user', [ $this, 'subscribe_from_registration' ], 90, 1); 37 | } 38 | 39 | /** 40 | * Output checkbox, once. 41 | */ 42 | public function maybe_output_checkbox() 43 | { 44 | if (! $this->shown) { 45 | $this->output_checkbox(); 46 | $this->shown = true; 47 | } 48 | } 49 | 50 | /** 51 | * Subscribes from WP Registration Form 52 | * 53 | * @param int $affiliate_id 54 | * 55 | * @return bool|string 56 | */ 57 | public function subscribe_from_registration($affiliate_id) 58 | { 59 | 60 | // was sign-up checkbox checked? 61 | if (! $this->triggered()) { 62 | return false; 63 | } 64 | 65 | // gather emailadress from user who WordPress registered 66 | $user_id = affwp_get_affiliate_user_id($affiliate_id); 67 | $user = get_userdata($user_id); 68 | 69 | // was a user found with the given ID? 70 | if (! $user instanceof WP_User) { 71 | return false; 72 | } 73 | 74 | $data = $this->user_merge_vars($user); 75 | 76 | return $this->subscribe($data, $user_id); 77 | } 78 | /* End registration form functions */ 79 | 80 | 81 | /** 82 | * @return bool 83 | */ 84 | public function is_installed() 85 | { 86 | return class_exists('Affiliate_WP'); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /includes/admin/class-upgrade-routines.php: -------------------------------------------------------------------------------- 1 | version_from = $from; 35 | $this->version_to = $to; 36 | $this->migrations_dir = $migrations_dir; 37 | } 38 | 39 | /** 40 | * Run the various upgrade routines, all the way up to the latest version 41 | */ 42 | public function run() 43 | { 44 | $migrations = $this->find_migrations(); 45 | 46 | // run in sub-function for scope 47 | array_map([ $this, 'run_migration' ], $migrations); 48 | } 49 | 50 | /** 51 | * @return array 52 | */ 53 | public function find_migrations() 54 | { 55 | $files = glob(rtrim($this->migrations_dir, '/') . '/*.php'); 56 | $migrations = []; 57 | 58 | // return empty array when glob returns non-array value. 59 | if (! is_array($files)) { 60 | return $migrations; 61 | } 62 | 63 | foreach ($files as $file) { 64 | $migration = basename($file); 65 | $parts = explode('-', $migration); 66 | $version = $parts[0]; 67 | 68 | if (version_compare($this->version_from, $version, '<')) { 69 | $migrations[] = $file; 70 | } 71 | } 72 | 73 | return $migrations; 74 | } 75 | 76 | /** 77 | * Include a migration file and runs it. 78 | * 79 | * @param string $file 80 | */ 81 | protected function run_migration($file) 82 | { 83 | include $file; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /integrations/bootstrap.php: -------------------------------------------------------------------------------- 1 | slug . '/admin-before.php'; 13 | if (file_exists($file)) { 14 | include $file; 15 | } 16 | } 17 | 18 | /** 19 | * Try to include a file before each integration's settings page 20 | * 21 | * @param MC4WP_Integration $integration 22 | * @param array $opts 23 | * @ignore 24 | */ 25 | function mc4wp_admin_after_integration_settings(MC4WP_Integration $integration, $opts) 26 | { 27 | $file = __DIR__ . '/' . $integration->slug . '/admin-after.php'; 28 | if (file_exists($file)) { 29 | include $file; 30 | } 31 | } 32 | 33 | add_action('mc4wp_admin_before_integration_settings', 'mc4wp_admin_before_integration_settings', 30, 2); 34 | add_action('mc4wp_admin_after_integration_settings', 'mc4wp_admin_after_integration_settings', 30, 2); 35 | 36 | // Register core integrations 37 | mc4wp_register_integration('wp-comment-form', 'MC4WP_Comment_Form_Integration'); 38 | mc4wp_register_integration('wp-registration-form', 'MC4WP_Registration_Form_Integration'); 39 | mc4wp_register_integration('buddypress', 'MC4WP_BuddyPress_Integration'); 40 | mc4wp_register_integration('easy-digital-downloads', 'MC4WP_Easy_Digital_Downloads_Integration'); 41 | mc4wp_register_integration('contact-form-7', 'MC4WP_Contact_Form_7_Integration', true); 42 | mc4wp_register_integration('events-manager', 'MC4WP_Events_Manager_Integration'); 43 | mc4wp_register_integration('memberpress', 'MC4WP_MemberPress_Integration'); 44 | mc4wp_register_integration('affiliatewp', 'MC4WP_AffiliateWP_Integration'); 45 | mc4wp_register_integration('give', 'MC4WP_Give_Integration'); 46 | mc4wp_register_integration('custom', 'MC4WP_Custom_Integration', true); 47 | mc4wp_register_integration('woocommerce', 'MC4WP_WooCommerce_Integration'); 48 | 49 | require __DIR__ . '/prosopo-procaptcha/bootstrap.php'; 50 | require __DIR__ . '/wpforms/bootstrap.php'; 51 | require __DIR__ . '/gravity-forms/bootstrap.php'; 52 | require __DIR__ . '/ninja-forms/bootstrap.php'; 53 | -------------------------------------------------------------------------------- /includes/admin/migrations/3.0.0-form-1-post-type.php: -------------------------------------------------------------------------------- 1 | 'mc4wp-form', 17 | 'post_status' => 'publish', 18 | 'numberposts' => 1, 19 | ] 20 | ); 21 | 22 | // There are forms already, don't continue. 23 | if (! empty($has_forms)) { 24 | // delete option as it apparently exists. 25 | delete_option('mc4wp_lite_form'); 26 | return; 27 | } 28 | 29 | // create post type for form 30 | $id = wp_insert_post( 31 | [ 32 | 'post_type' => 'mc4wp-form', 33 | 'post_status' => 'publish', 34 | 'post_title' => __('Default sign-up form', 'mailchimp-for-wp'), 35 | 'post_content' => ( empty($form_options['markup']) ) ? '' : $form_options['markup'], 36 | ] 37 | ); 38 | 39 | // set default_form_id 40 | update_option('mc4wp_default_form_id', $id); 41 | 42 | // set form settings 43 | $setting_keys = [ 44 | 'css', 45 | 'custom_theme_color', 46 | 'double_optin', 47 | 'update_existing', 48 | 'replace_interests', 49 | 'send_welcome', 50 | 'redirect', 51 | 'hide_after_success', 52 | ]; 53 | 54 | $settings = []; 55 | 56 | foreach ($setting_keys as $setting_key) { 57 | // use isset to account for "0" settings 58 | if (isset($form_options[ $setting_key ])) { 59 | $settings[ $setting_key ] = $form_options[ $setting_key ]; 60 | } 61 | } 62 | 63 | // get only keys of lists setting 64 | if (isset($form_options['lists'])) { 65 | $settings['lists'] = array_keys($form_options['lists']); 66 | } 67 | 68 | update_post_meta($id, '_mc4wp_settings', $settings); 69 | 70 | // set form message texts 71 | $message_keys = [ 72 | 'text_subscribed', 73 | 'text_error', 74 | 'text_invalid_email', 75 | 'text_already_subscribed', 76 | 'text_required_field_missing', 77 | 'text_unsubscribed', 78 | 'text_not_subscribed', 79 | ]; 80 | 81 | foreach ($message_keys as $message_key) { 82 | if (! empty($form_options[ $message_key ])) { 83 | update_post_meta($id, $message_key, $form_options[ $message_key ]); 84 | } 85 | } 86 | 87 | // delete old option 88 | delete_option('mc4wp_lite_form'); 89 | -------------------------------------------------------------------------------- /assets/src/js/forms.js: -------------------------------------------------------------------------------- 1 | const mc4wp = window.mc4wp || {} 2 | const forms = require('./forms/forms.js') 3 | require('./forms/conditional-elements.js') 4 | 5 | /** 6 | * Binds event to document but only fires if event was triggered inside a .mc4wp-form element 7 | * @param {string} evtName 8 | * @param {function} cb 9 | * @private 10 | */ 11 | function bind (evtName, cb) { 12 | document.addEventListener(evtName, evt => { 13 | if (!evt.target) { 14 | return 15 | } 16 | 17 | const el = evt.target 18 | const fireEvent = (typeof el.className === 'string' && el.className.indexOf('mc4wp-form') > -1) || (typeof el.matches === 'function' && el.matches('.mc4wp-form *')) 19 | if (fireEvent) { 20 | cb.call(evt, evt) 21 | } 22 | }, true) 23 | } 24 | 25 | /** 26 | * Handles 'submit' events for any .mc4wp-form element 27 | * @param {Event} evt 28 | * @private 29 | */ 30 | function onSubmit (evt) { 31 | if (evt.defaultPrevented) { 32 | return 33 | } 34 | 35 | const form = forms.getByElement(evt.target) 36 | forms.trigger('submit', [form, evt]) 37 | } 38 | 39 | /** 40 | * Handles 'focus' events for any relevant element inside a .mc4wp-form 41 | * @param {Event} evt 42 | * @private 43 | */ 44 | function onFocus (evt) { 45 | const form = forms.getByElement(evt.target) 46 | if (!form.started) { 47 | forms.trigger('started', [form, evt]) 48 | form.started = true 49 | } 50 | } 51 | 52 | /** 53 | * Handles 'change' events for any relevant element inside a .mc4wp-form 54 | * @param {Event} evt 55 | * @private 56 | */ 57 | function onChange (evt) { 58 | const form = forms.getByElement(evt.target) 59 | forms.trigger('change', [form, evt]) 60 | } 61 | 62 | /** 63 | * Copies over listeners which were added before this script was loaded 64 | * These are stored in a temporary variable called `mc4wp.listeners` which we print very early on in wp_head(). 65 | * @param {object} lstnr 66 | * @private 67 | */ 68 | function registerListener (lstnr) { 69 | forms.on(lstnr.event, lstnr.callback) 70 | } 71 | 72 | // bind event listeners 73 | bind('submit', onSubmit) 74 | bind('focus', onFocus) 75 | bind('change', onChange) 76 | 77 | // register early listeners 78 | if (mc4wp.listeners) { 79 | [].forEach.call(mc4wp.listeners, registerListener) 80 | delete mc4wp.listeners 81 | } 82 | 83 | // expose forms object 84 | mc4wp.forms = forms 85 | 86 | // expose mc4wp object globally 87 | window.mc4wp = mc4wp 88 | -------------------------------------------------------------------------------- /bin/create-package: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | # Check if VERSION argument was supplied 6 | if [ "$#" -lt 1 ]; then 7 | echo "1 parameters expected, $# found" 8 | echo "Usage: package.sh " 9 | exit 1 10 | fi 11 | 12 | PLUGIN_SLUG=$(basename "$PWD") 13 | PLUGIN_FILE="$PLUGIN_SLUG.php" 14 | VERSION=$1 15 | PACKAGE_FILE="$PWD/../$PLUGIN_SLUG-$VERSION.zip" 16 | 17 | # Check if we're inside plugin directory 18 | if [ ! -e "$PLUGIN_FILE" ]; then 19 | echo "Plugin entry file not found. Please run this command from inside the $PLUGIN_SLUG directory." 20 | exit 1 21 | fi 22 | 23 | # Check if there are uncommitted changes 24 | if [ -n "$(git status --porcelain)" ]; then 25 | echo "There are uncommitted changes. Please commit those changes before initiating a release." 26 | exit 1 27 | fi 28 | 29 | # Check if there is an existing file for this release already 30 | rm -f "$PACKAGE_FILE" 31 | 32 | # Build (optimized) client-side assets 33 | npm run build 34 | 35 | # Update version numbers in code 36 | sed -i "s/^Version: .*$/Version: $VERSION/g" "$PLUGIN_FILE" 37 | sed -i "s/define('\(.*_VERSION\)', '.*');/define('\1', '$VERSION');/g" "$PLUGIN_FILE" 38 | sed -i "s/^Stable tag: .*$/Stable tag: $VERSION/g" "readme.txt" 39 | 40 | # Copy over changelog from CHANGELOG.md to readme.txt 41 | # Ref: https://git.sr.ht/~dvko/dotfiles/tree/master/item/bin/wp-update-changelog 42 | wp-update-changelog 43 | 44 | # Move up one directory level because we need plugin directory in ZIP file 45 | cd .. 46 | 47 | # Create archive (excl. development files) 48 | zip -r "$PACKAGE_FILE" "$PLUGIN_SLUG" \ 49 | -x "$PLUGIN_SLUG/.*" \ 50 | -x "$PLUGIN_SLUG/vendor/*" \ 51 | -x "$PLUGIN_SLUG/node_modules/*" \ 52 | -x "$PLUGIN_SLUG/tests/*" \ 53 | -x "$PLUGIN_SLUG/webpack.config*.js" \ 54 | -x "$PLUGIN_SLUG/*.json" \ 55 | -x "$PLUGIN_SLUG/*.lock" \ 56 | -x "$PLUGIN_SLUG/phpcs.xml" \ 57 | -x "$PLUGIN_SLUG/phpunit.xml.dist" \ 58 | -x "$PLUGIN_SLUG/*.sh" \ 59 | -x "$PLUGIN_SLUG/bin/*" \ 60 | -x "$PLUGIN_SLUG/.github/*" \ 61 | -x "$PLUGIN_SLUG/.git/*" \ 62 | -x "$PLUGIN_SLUG/assets/src/*" \ 63 | -x "$PLUGIN_SLUG/sample-code-snippets/*" 64 | 65 | cd "$PLUGIN_SLUG" 66 | 67 | SIZE=$(ls -lh "$PACKAGE_FILE" | cut -d' ' -f5) 68 | echo "$(basename "$PACKAGE_FILE") created ($SIZE)" 69 | 70 | # Create tag in Git and push to remote 71 | git add . -A 72 | git commit -m "v$VERSION" 73 | git tag "$VERSION" 74 | git push origin main 75 | git push origin "tags/$VERSION" 76 | -------------------------------------------------------------------------------- /includes/integrations/class-integration-fixture.php: -------------------------------------------------------------------------------- 1 | slug = $slug; 50 | $this->class = $class; 51 | $this->enabled_by_default = $enabled_by_default; 52 | $this->enabled = $enabled_by_default; 53 | $this->options = $options; 54 | 55 | if (! empty($options['enabled'])) { 56 | $this->enabled = true; 57 | } 58 | } 59 | 60 | /** 61 | * Returns the actual instance 62 | * 63 | * @return MC4WP_Integration 64 | */ 65 | public function load() 66 | { 67 | if (! $this->instance instanceof MC4WP_Integration) { 68 | $this->instance = new $this->class($this->slug, $this->options); 69 | } 70 | 71 | return $this->instance; 72 | } 73 | 74 | /** 75 | * Tunnel everything to MC4WP_Integration class 76 | * 77 | * @param string $name 78 | * @param array $arguments 79 | * 80 | * @return MC4WP_Integration 81 | */ 82 | public function __call($name, $arguments) 83 | { 84 | return call_user_func_array([ $this->load(), $name ], $arguments); 85 | } 86 | 87 | /** 88 | * @param string $name 89 | * 90 | * @return string 91 | */ 92 | public function __get($name) 93 | { 94 | return $this->load()->$name; 95 | } 96 | 97 | /** 98 | * @return string 99 | */ 100 | public function __toString() 101 | { 102 | return $this->slug; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /assets/src/css/form-basic.css: -------------------------------------------------------------------------------- 1 | .mc4wp-form input[name^="_mc4wp_honey"] { display: none !important; } 2 | 3 | /* Mailchimp for WP - Default Form Styles */ 4 | .mc4wp-form-basic { 5 | margin: 1em 0; 6 | } 7 | .mc4wp-form-basic label, 8 | .mc4wp-form-basic input { 9 | width: auto; 10 | display: block; 11 | box-sizing: border-box; 12 | cursor: auto; 13 | height: auto; 14 | vertical-align: baseline; 15 | line-height: normal; 16 | } 17 | .mc4wp-form-basic label:after, 18 | .mc4wp-form-basic input:after { 19 | content:""; 20 | display:table; 21 | clear:both; 22 | } 23 | .mc4wp-form-basic label { 24 | font-weight: bold; 25 | margin-bottom: 6px; 26 | display: block; 27 | } 28 | 29 | /* Form Elements */ 30 | .mc4wp-form-basic input[type="text"], 31 | .mc4wp-form-basic input[type="email"], 32 | .mc4wp-form-basic input[type="tel"], 33 | .mc4wp-form-basic input[type="url"], 34 | .mc4wp-form-basic input[type="date"], 35 | .mc4wp-form-basic textarea, 36 | .mc4wp-form-basic select { 37 | min-height: 32px; 38 | width: 100%; 39 | max-width: 480px; 40 | } 41 | .mc4wp-form-basic input[type="number"] { 42 | min-width: 40px; 43 | } 44 | .mc4wp-form-basic input[type="checkbox"], 45 | .mc4wp-form-basic input[type="radio"] { 46 | position: relative; 47 | margin: 0 6px 0 0; 48 | padding: 0; 49 | height: 13px; 50 | width: 13px; 51 | display: inline-block; 52 | border: 0; 53 | } 54 | .mc4wp-form-basic input[type="checkbox"] { 55 | -webkit-appearance: checkbox; 56 | -moz-appearance: checkbox; 57 | appearance: checkbox; 58 | } 59 | .mc4wp-form-basic input[type="radio"] { 60 | -webkit-appearance: radio; 61 | -moz-appearance: radio; 62 | appearance: radio; 63 | } 64 | .mc4wp-form-basic input[type="submit"], 65 | .mc4wp-form-basic button, 66 | .mc4wp-form-basic input[type="button"] { 67 | cursor: pointer; 68 | display: inline-block; 69 | -webkit-appearance: none; 70 | -moz-appearance: none; 71 | appearance: none; 72 | } 73 | 74 | /* Remove font-weight bold from nested elements */ 75 | .mc4wp-form-basic label > span, 76 | .mc4wp-form-basic li > label { 77 | font-weight: normal; 78 | } 79 | 80 | /* Alert */ 81 | .mc4wp-alert{ 82 | color: #c09853; 83 | clear: both; 84 | } 85 | .mc4wp-success { 86 | color: #468847; 87 | } 88 | .mc4wp-notice { 89 | color: #3a87ad; 90 | } 91 | .mc4wp-error { 92 | color: #CD5C5C; 93 | } 94 | 95 | /* Right-To-Left specific styles */ 96 | .rtl .mc4wp-form-basic input[type="checkbox"], 97 | .rtl .mc4wp-form-basic input[type="radio"] { 98 | margin: 0 0 0 6px; 99 | } 100 | -------------------------------------------------------------------------------- /integrations/woocommerce/admin-after.php: -------------------------------------------------------------------------------- 1 | __('After email field', 'mailchimp-for-wp'), 5 | 'checkout_billing' => __('After billing details', 'mailchimp-for-wp'), 6 | 'checkout_shipping' => __('After shipping details', 'mailchimp-for-wp'), 7 | 'checkout_after_customer_details' => __('After customer details', 'mailchimp-for-wp'), 8 | 'review_order_before_submit' => __('Before submit button', 'mailchimp-for-wp'), 9 | 'after_order_notes' => __('After order notes', 'mailchimp-for-wp'), 10 | ]; 11 | 12 | if (defined('CFW_NAME')) { 13 | $position_options['cfw_checkout_before_payment_method_tab_nav'] = __('Checkout for WooCommerce: Before complete order button', 'mailchimp-for-wp'); 14 | $position_options['cfw_after_customer_info_account_details'] = __('Checkout for WooCommerce: After account info', 'mailchimp-for-wp'); 15 | $position_options['cfw_checkout_after_customer_info_address'] = __('Checkout for WooCommerce: After customer info', 'mailchimp-for-wp'); 16 | } 17 | 18 | /** @var MC4WP_Integration $integration */ 19 | 20 | $body_config = [ 21 | 'element' => 'mc4wp_integrations[' . $integration->slug . '][enabled]', 22 | 'value' => '1', 23 | 'hide' => false, 24 | ]; 25 | 26 | $config = [ 27 | 'element' => 'mc4wp_integrations[' . $integration->slug . '][implicit]', 28 | 'value' => '0', 29 | ]; 30 | 31 | ?> 32 | 33 | 34 | 35 | 38 | 50 | 51 | 52 |
36 | 37 | 39 | 48 |

49 |
53 | -------------------------------------------------------------------------------- /assets/src/js/admin/form-editor/field-forms.js: -------------------------------------------------------------------------------- 1 | const forms = {} 2 | const rows = require('./field-forms-rows.js') 3 | const m = require('mithril') 4 | 5 | // wrap row in div element with margin class 6 | function wrap (rows) { 7 | for (let i = 0; i < rows.length; i++) { 8 | rows[i] = m('div.mc4wp-margin-s', rows[i]) 9 | } 10 | return rows 11 | } 12 | 13 | // route to one of the other form configs, default to "text" 14 | forms.render = function (config) { 15 | const type = config.type 16 | 17 | if (typeof (forms[type]) === 'function') { 18 | return wrap(forms[type](config)) 19 | } 20 | 21 | if (['select', 'radio', 'checkbox'].indexOf(type) > -1) { 22 | return wrap(forms.choice(config)) 23 | } 24 | 25 | // fallback to good old text field 26 | return wrap(forms.text(config)) 27 | } 28 | 29 | forms.text = function (config) { 30 | return [ 31 | rows.label(config), 32 | rows.placeholder(config), 33 | rows.value(config), 34 | rows.isRequired(config), 35 | rows.useParagraphs(config) 36 | ] 37 | } 38 | 39 | forms.choice = function (config) { 40 | const visibleRows = [ 41 | rows.label(config), 42 | rows.choiceType(config), 43 | rows.choices(config) 44 | ] 45 | 46 | if (config.type === 'select') { 47 | visibleRows.push(rows.placeholder(config)) 48 | } 49 | 50 | visibleRows.push(rows.useParagraphs(config)) 51 | 52 | if (config.type === 'select' || config.type === 'radio') { 53 | visibleRows.push(rows.isRequired(config)) 54 | } 55 | 56 | return visibleRows 57 | } 58 | 59 | forms.hidden = function (config) { 60 | config.placeholder = '' 61 | config.label = '' 62 | config.wrap = false 63 | 64 | return [ 65 | rows.showType(config), 66 | rows.value(config) 67 | ] 68 | } 69 | 70 | forms.submit = function (config) { 71 | config.label = '' 72 | config.placeholder = '' 73 | 74 | return [ 75 | rows.value(config), 76 | rows.useParagraphs(config) 77 | ] 78 | } 79 | 80 | forms['terms-checkbox'] = function (config) { 81 | return [ 82 | rows.label(config), 83 | rows.linkToTerms(config), 84 | rows.isRequired(config), 85 | rows.useParagraphs(config) 86 | ] 87 | } 88 | 89 | forms.number = function (config) { 90 | return [ 91 | forms.text(config), 92 | rows.numberMinMax(config) 93 | ] 94 | } 95 | 96 | // renders the UI for the procaptcha field 97 | forms.procaptcha = function (config) { 98 | return [ 99 | rows.description(config) 100 | ] 101 | } 102 | 103 | module.exports = forms 104 | -------------------------------------------------------------------------------- /includes/forms/views/tabs/form-appearance.php: -------------------------------------------------------------------------------- 1 | sprintf(esc_html__('Inherit from %s theme', 'mailchimp-for-wp'), $theme->Name), 6 | 'basic' => esc_html__('Basic', 'mailchimp-for-wp'), 7 | esc_html__('Form Themes', 'mailchimp-for-wp') => [ 8 | 'theme-light' => esc_html__('Light Theme', 'mailchimp-for-wp'), 9 | 'theme-dark' => esc_html__('Dark Theme', 'mailchimp-for-wp'), 10 | 'theme-red' => esc_html__('Red Theme', 'mailchimp-for-wp'), 11 | 'theme-green' => esc_html__('Green Theme', 'mailchimp-for-wp'), 12 | 'theme-blue' => esc_html__('Blue Theme', 'mailchimp-for-wp'), 13 | ], 14 | ]; 15 | 16 | /** 17 | * Filters the ', $label); 37 | foreach ($options as $key => $option) { 38 | printf('', $key, selected($opts['css'], $key, false), $option); 39 | } 40 | print( '' ); 41 | } else { 42 | printf('', $key, selected($opts['css'], $key, false), $option); 43 | } 44 | } 45 | ?> 46 | 47 |

48 | 49 |

50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /integrations/wp-registration-form/class-registration-form.php: -------------------------------------------------------------------------------- 1 | options['implicit']) { 33 | add_action('login_head', [ $this, 'print_css_reset' ]); 34 | add_action('um_after_register_fields', [ $this, 'maybe_output_checkbox' ], 20); 35 | add_action('register_form', [ $this, 'maybe_output_checkbox' ], 20); 36 | add_action('woocommerce_register_form', [ $this, 'maybe_output_checkbox' ], 20); 37 | } 38 | 39 | add_action('um_user_register', [ $this, 'subscribe_from_registration' ], 90, 1); 40 | add_action('user_register', [ $this, 'subscribe_from_registration' ], 90, 1); 41 | 42 | if (defined('um_plugin') && class_exists('UM')) { 43 | $this->name = 'UltimateMember'; 44 | $this->description = 'Subscribes people from your UltimateMember registration form.'; 45 | } 46 | } 47 | 48 | /** 49 | * Output checkbox, once. 50 | */ 51 | public function maybe_output_checkbox() 52 | { 53 | if (! $this->shown) { 54 | $this->output_checkbox(); 55 | $this->shown = true; 56 | } 57 | } 58 | 59 | /** 60 | * Subscribes from WP Registration Form 61 | * 62 | * @param int $user_id 63 | * 64 | * @return bool|string 65 | */ 66 | public function subscribe_from_registration($user_id) 67 | { 68 | 69 | // was sign-up checkbox checked? 70 | if (! $this->triggered()) { 71 | return false; 72 | } 73 | 74 | // gather emailadress from user who WordPress registered 75 | $user = get_userdata($user_id); 76 | 77 | // was a user found with the given ID? 78 | if (! $user instanceof WP_User) { 79 | return false; 80 | } 81 | 82 | $data = $this->user_merge_vars($user); 83 | 84 | return $this->subscribe($data, $user_id); 85 | } 86 | /* End registration form functions */ 87 | 88 | 89 | /** 90 | * @return bool 91 | */ 92 | public function is_installed() 93 | { 94 | return true; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /assets/src/js/admin/notices.js: -------------------------------------------------------------------------------- 1 | const editor = require('./form-editor/form-editor.js') 2 | const fields = require('./form-editor/fields.js') 3 | const settings = require('./settings') 4 | const notices = {} 5 | 6 | function show (id, text) { 7 | notices[id] = text 8 | render() 9 | } 10 | 11 | function hide (id) { 12 | delete notices[id] 13 | render() 14 | } 15 | 16 | function render () { 17 | const html = Object.values(notices).map(text => '

' + text + '

').join() 18 | let container = document.querySelector('.mc4wp-notices') 19 | if (!container) { 20 | container = document.createElement('div') 21 | container.className = 'mc4wp-notices' 22 | const heading = document.querySelector('h1, h2') 23 | heading.parentNode.insertBefore(container, heading.nextSibling) 24 | } 25 | 26 | container.innerHTML = html 27 | } 28 | 29 | const groupingsNotice = function () { 30 | const text = 'Your form contains deprecated GROUPINGS fields.

Please remove these fields from your form and then re-add them through the available field buttons to make sure your data is getting through to Mailchimp correctly.' 31 | const hasGroupingsField = editor.getValue().toLowerCase().indexOf('name="groupings') > -1 32 | hasGroupingsField ? show('deprecated_groupings', text) : hide('deprecated_groupings') 33 | } 34 | 35 | const requiredFieldsNotice = function () { 36 | const missingFields = fields.getAll().filter(f => f.forceRequired === true && !editor.containsField(f.name.toUpperCase())) 37 | 38 | let text = 'Heads up! Your form is missing fields that are required in Mailchimp. Either add these fields to your form or mark them as optional in Mailchimp.' 39 | text += '
'; 40 | 41 | (missingFields.length > 0) ? show('required_fields_missing', text) : hide('required_fields_missing') 42 | } 43 | 44 | const mailchimpListsNotice = function () { 45 | const text = 'Heads up! You have not yet selected a Mailchimp audience to subscribe people to. Please select at least one audience from the settings tab.' 46 | 47 | if (settings.getSelectedLists().length > 0) { 48 | hide('no_lists_selected') 49 | } else { 50 | show('no_lists_selected', text) 51 | } 52 | } 53 | 54 | // old groupings 55 | groupingsNotice() 56 | editor.on('focus', groupingsNotice) 57 | editor.on('blur', groupingsNotice) 58 | 59 | // missing required fields 60 | requiredFieldsNotice() 61 | editor.on('blur', requiredFieldsNotice) 62 | editor.on('focus', requiredFieldsNotice) 63 | 64 | document.body.addEventListener('change', mailchimpListsNotice) 65 | -------------------------------------------------------------------------------- /includes/forms/class-form-amp.php: -------------------------------------------------------------------------------- 1 | 33 |
34 | 37 |
38 |
39 | 51 |
52 |
53 | 56 |
57 | element 46 | function createFromElement (formElement, id) { 47 | id = id || parseInt(formElement.getAttribute('data-id')) || 0 48 | const form = new Form(id, formElement) 49 | forms.push(form) 50 | return form 51 | } 52 | 53 | /** 54 | * Triggers two events. One namespaced to the specific form ID and one global (ie fires for all forms) 55 | * 56 | * @param {string} eventName The name of the event to trigger 57 | * @param {array} eventArgs Arguments to pass to registered event listeners. The first argument should be a Form object. 58 | * @public 59 | */ 60 | function trigger (eventName, eventArgs) { 61 | if (eventName === 'submit' || eventName.indexOf('.submit') > 0) { 62 | // don't spin up new thread for submit event as we want to preventDefault()... 63 | events.emit(eventArgs[0].id + '.' + eventName, eventArgs) 64 | events.emit(eventName, eventArgs) 65 | } else { 66 | // process in separate thread to prevent errors from breaking core functionality 67 | window.setTimeout(function () { 68 | events.emit(eventArgs[0].id + '.' + eventName, eventArgs) 69 | events.emit(eventName, eventArgs) 70 | }, 10) 71 | } 72 | } 73 | 74 | /** 75 | * Add event listener. 76 | * For a list of valid event names, please see https://www.mc4wp.com/kb/javascript-form-events/. 77 | * @param {string} eventName 78 | * @param {function} callback 79 | */ 80 | function on (eventName, callback) { 81 | events.on(eventName, callback) 82 | } 83 | 84 | module.exports = { get, getByElement, on, trigger } 85 | -------------------------------------------------------------------------------- /includes/views/parts/admin-sidebar.php: -------------------------------------------------------------------------------- 1 | 11 |
12 |

13 |

14 | 18 |

support forums on WordPress.org.', 'mailchimp-for-wp'), [ 'a' => [ 'href' => [] ] ]), 'https://wordpress.org/support/plugin/mailchimp-for-wp'); ?>

19 |

open an issue on GitHub.', 'mailchimp-for-wp'), [ 'a' => [ 'href' => [] ] ]), 'https://github.com/ibericode/mailchimp-for-wordpress/issues'); ?>

20 |
21 | '; 30 | echo '

', esc_html__('Other plugins by ibericode', 'mailchimp-for-wp'), '

'; 31 | echo ''; 46 | echo ''; 47 | } 48 | 49 | add_action('mc4wp_admin_sidebar', '_mc4wp_admin_sidebar_other_plugins', 40); 50 | add_action('mc4wp_admin_sidebar', '_mc4wp_admin_sidebar_support_notice', 50); 51 | 52 | /** 53 | * Runs when the sidebar is outputted on Mailchimp for WordPress settings pages. 54 | * 55 | * Please note that not all pages have a sidebar. 56 | * 57 | * @since 3.0 58 | */ 59 | do_action('mc4wp_admin_sidebar'); 60 | -------------------------------------------------------------------------------- /includes/forms/views/parts/add-fields-help.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

5 | 6 |
7 | 8 |

9 | 10 |

11 | 12 |

13 | 14 |
    15 |
  1. 16 |

    17 | 18 |

    19 |
  2. 20 |
  3. 21 |

    22 | 23 | 24 |

    25 | 39 |
  4. 40 |
  5. 41 |

    42 | 43 |

    44 | 45 |

    46 | 58 | 59 | 60 |

    61 |
  6. 62 |
63 | 64 | 65 |
66 |
67 | -------------------------------------------------------------------------------- /includes/forms/views/tabs/form-fields.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

14 | 15 | 16 |
17 | 18 |
19 |
20 |

21 | 22 | 23 |
24 |
25 |

26 | 27 | 28 |

29 | 30 |
31 |
32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |

ID)) . '" size="' . ( strlen($form->ID) + 15 ) . '">'); ?>

40 | 41 | 42 | 43 | 46 | 47 | 50 | -------------------------------------------------------------------------------- /includes/views/parts/lists-overview.php: -------------------------------------------------------------------------------- 1 |

2 |

3 | 4 |
5 |
6 | 7 | 8 |

9 | 10 |

11 |
12 |
13 | 14 |
15 | 18 |

.

19 | ', sprintf(esc_html__('A total of %d audiences were found in your Mailchimp account.', 'mailchimp-for-wp'), count($lists)), '

'; 22 | echo ''; 23 | 24 | $headings = [ 25 | esc_html__('Audience name', 'mailchimp-for-wp'), 26 | esc_html__('Audience ID', 'mailchimp-for-wp'), 27 | esc_html__('# of contacts', 'mailchimp-for-wp'), 28 | ]; 29 | 30 | echo ''; 31 | echo ''; 32 | foreach ($headings as $heading) { 33 | echo ''; 34 | } 35 | echo ''; 36 | echo ''; 37 | echo ''; 38 | 39 | foreach ($lists as $list) { 40 | $attr_data_list_id = esc_attr($list->id); 41 | $list_name = esc_html($list->name); 42 | echo ''; 43 | echo ''; 44 | echo ''; 45 | echo ''; 46 | echo ''; 47 | 48 | echo ''; 49 | echo ''; 53 | echo ''; 54 | ?> 55 | '; 58 | echo '
', $heading, '
', $list_name, '', esc_html($list->id), '', esc_html($list->stats->member_count), '
'; 59 | } // end if empty 60 | ?> 61 |
62 | -------------------------------------------------------------------------------- /assets/src/js/admin/form-editor/form-watcher.js: -------------------------------------------------------------------------------- 1 | const m = require('mithril') 2 | const editor = require('./form-editor.js') 3 | const fields = require('./fields.js') 4 | 5 | const REGEX_ARRAY_BRACKETS_WITH_KEY = /\[(\w+)\]/g 6 | const REGEX_ARRAY_BRACKETS_EMPTY = /\[\]$/ 7 | const requiredFieldsInput = document.getElementById('required-fields') 8 | 9 | function updateFields () { 10 | fields.getAll().forEach(function (field) { 11 | // don't run for empty field names 12 | if (field.name.length <= 0) return 13 | 14 | let fieldName = field.name 15 | if (field.type === 'checkbox') { 16 | fieldName += '[]' 17 | } 18 | 19 | field.inFormContent = editor.containsField(fieldName) 20 | 21 | // if form contains 1 address field of group, mark all fields in this group as "required" 22 | if (field.mailchimpType === 'address') { 23 | if (field.originalRequiredValue === undefined) { 24 | field.originalRequiredValue = field.forceRequired 25 | } 26 | 27 | // query other fields for this address group 28 | const nameGroup = field.name.replace(REGEX_ARRAY_BRACKETS_WITH_KEY, '') 29 | if (editor.query('[name^="' + nameGroup + '"]').length > 0) { 30 | field.forceRequired = true 31 | } else { 32 | field.forceRequired = field.originalRequiredValue 33 | } 34 | } 35 | }) 36 | 37 | findRequiredFields() 38 | m.redraw() 39 | } 40 | 41 | function findRequiredFields () { 42 | // query fields required by Mailchimp 43 | const requiredFields = fields.getAll().filter(f => f.forceRequired === true) 44 | .map(f => f.name.toUpperCase().replace(REGEX_ARRAY_BRACKETS_WITH_KEY, '.$1')) 45 | 46 | // query fields in form with [required] attribute 47 | const requiredFieldElements = editor.query('[required]'); 48 | [].forEach.call(requiredFieldElements, function (el) { 49 | let name = el.name 50 | 51 | // bail if name attr empty or starts with underscore 52 | if (!name || name.length < 0 || name[0] === '_') { 53 | return 54 | } 55 | 56 | // replace array brackets with dot style notation 57 | name = name.replace(REGEX_ARRAY_BRACKETS_WITH_KEY, '.$1') 58 | 59 | // replace array-style fields 60 | name = name.replace(REGEX_ARRAY_BRACKETS_EMPTY, '') 61 | 62 | // uppercase everything before the . 63 | let pos = name.indexOf('.') 64 | pos = pos > 0 ? pos : name.length 65 | name = name.substr(0, pos).toUpperCase() + name.substr(pos) 66 | 67 | // only add field if it's not already in it 68 | if (requiredFields.indexOf(name) === -1) { 69 | requiredFields.push(name) 70 | } 71 | }) 72 | 73 | // update meta 74 | requiredFieldsInput.value = requiredFields.join(',') 75 | } 76 | /** 77 | * @param {function} callback 78 | * @param {int} delay in ms 79 | */ 80 | function debounce (callback, delay) { 81 | let timeout 82 | return () => { 83 | if (timeout) clearTimeout(timeout) 84 | timeout = window.setTimeout(callback, delay) 85 | } 86 | } 87 | 88 | // events 89 | editor.on('change', debounce(updateFields, 500)) 90 | fields.on('change', debounce(updateFields, 100)) 91 | -------------------------------------------------------------------------------- /assets/src/js/admin/form-editor/fields.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('../../events.js') 2 | const events = new EventEmitter() 3 | const fields = {} 4 | 5 | function Field (data) { 6 | return { 7 | name: data.name, 8 | title: data.title || data.name, 9 | type: data.type, 10 | mailchimpType: data.mailchimpType || null, 11 | label: data.label || data.title || '', 12 | showLabel: typeof (data.showLabel) === 'boolean' ? data.showLabel : true, 13 | value: data.value || '', 14 | placeholder: data.placeholder || '', 15 | required: typeof (data.required) === 'boolean' ? data.required : false, 16 | forceRequired: typeof (data.forceRequired) === 'boolean' ? data.forceRequired : false, 17 | wrap: typeof (data.wrap) === 'boolean' ? data.wrap : true, 18 | min: data.min, 19 | max: data.max, 20 | help: data.help || '', 21 | choices: data.choices || [], 22 | inFormContent: null, 23 | acceptsMultipleValues: data.acceptsMultipleValues, 24 | link: data.link || '', 25 | description: data.description || '' 26 | } 27 | } 28 | 29 | function FieldChoice (data) { 30 | return { 31 | title: data.title || data.label, 32 | selected: data.selected || false, 33 | value: data.value || data.label, 34 | label: data.label 35 | } 36 | } 37 | 38 | function createChoices (data) { 39 | // Here we create FieldChoice object for each choice item 40 | // If we got an associate array / dictionary / object then we use keys as values 41 | // Otherwise, we leave it to the rendering phase to set the value attribute 42 | return Object.keys(data).map((key) => new FieldChoice({ label: data[key], value: Array.isArray(data) ? null : key })) 43 | } 44 | 45 | function register (category, data) { 46 | // if a field with the exact same name already exists, 47 | // update its forceRequired property 48 | const existingField = fields[data.name] 49 | if (existingField) { 50 | if (!existingField.forceRequired && data.forceRequired) { 51 | existingField.forceRequired = true 52 | } 53 | 54 | return existingField 55 | } 56 | 57 | // array of choices given? convert to FieldChoice objects 58 | if (data.choices) { 59 | data.choices = createChoices(data.choices) 60 | 61 | if (data.value) { 62 | data.choices = data.choices.map(function (choice) { 63 | if (choice.value === data.value) { 64 | choice.selected = true 65 | } 66 | return choice 67 | }) 68 | } 69 | } 70 | 71 | // create Field object 72 | const field = new Field(data) 73 | field.category = category 74 | 75 | // add to array 76 | fields[data.name] = field 77 | 78 | // trigger event 79 | events.emit('change', []) 80 | return field 81 | } 82 | 83 | function deregister (field) { 84 | delete fields[field.name] 85 | } 86 | 87 | function get (name) { 88 | return fields[name] 89 | } 90 | 91 | function getAll () { 92 | return Object.values(fields) 93 | } 94 | 95 | module.exports = { 96 | get, 97 | getAll, 98 | deregister, 99 | register, 100 | on: events.on.bind(events) 101 | } 102 | -------------------------------------------------------------------------------- /includes/admin/migrations/4.0.0-groupings-to-interests.php: -------------------------------------------------------------------------------- 1 | name === $interest_category->title) { 16 | return $grouping; 17 | } 18 | } 19 | 20 | return null; 21 | } 22 | 23 | /** 24 | * @ignore 25 | * @return object 26 | */ 27 | function _mc4wp_400_find_group_for_interest($groups, $interest) 28 | { 29 | foreach ($groups as $group_id => $group_name) { 30 | if ($group_name === $interest->name) { 31 | return (object) [ 32 | 'name' => $group_name, 33 | 'id' => $group_id, 34 | ]; 35 | } 36 | } 37 | 38 | return null; 39 | } 40 | 41 | // in case the migration is _very_ late to the party 42 | if (! class_exists('MC4WP_API_V3')) { 43 | return; 44 | } 45 | 46 | $options = get_option('mc4wp', []); 47 | if (empty($options['api_key'])) { 48 | return; 49 | } 50 | 51 | // get current state from transient 52 | $lists = get_transient('mc4wp_mailchimp_lists_fallback'); 53 | if (empty($lists)) { 54 | return; 55 | } 56 | 57 | @set_time_limit(600); 58 | $api_v3 = new MC4WP_API_V3($options['api_key']); 59 | $map = []; 60 | 61 | foreach ($lists as $list) { 62 | // cast to stdClass because of missing classes 63 | $list = (object) (array) $list; 64 | 65 | // no groupings? easy! 66 | if (empty($list->groupings)) { 67 | continue; 68 | } 69 | 70 | // fetch (new) interest categories for this list 71 | try { 72 | $interest_categories = $api_v3->get_list_interest_categories($list->id); 73 | } catch (MC4WP_API_Exception $e) { 74 | continue; 75 | } 76 | 77 | 78 | foreach ($interest_categories as $interest_category) { 79 | // compare interest title with grouping name, if it matches, get new id. 80 | $grouping = _mc4wp_400_find_grouping_for_interest_category($list->groupings, $interest_category); 81 | if (! $grouping) { 82 | continue; 83 | } 84 | 85 | $groups = []; 86 | 87 | try { 88 | $interests = $api_v3->get_list_interest_category_interests($list->id, $interest_category->id); 89 | } catch (MC4WP_API_Exception $e) { 90 | continue; 91 | } 92 | 93 | foreach ($interests as $interest) { 94 | $group = _mc4wp_400_find_group_for_interest($grouping->groups, $interest); 95 | 96 | if ($group) { 97 | $groups[ $group->id ] = $interest->id; 98 | $groups[ $group->name ] = $interest->id; 99 | } 100 | } 101 | 102 | $map[ (string) $grouping->id ] = [ 103 | 'id' => $interest_category->id, 104 | 'groups' => $groups, 105 | ]; 106 | } 107 | } 108 | 109 | 110 | if (! empty($map)) { 111 | update_option('mc4wp_groupings_map', $map); 112 | } 113 | -------------------------------------------------------------------------------- /includes/admin/class-admin-texts.php: -------------------------------------------------------------------------------- 1 | plugin_file = $plugin_file; 22 | } 23 | 24 | /** 25 | * Add hooks 26 | */ 27 | public function add_hooks() 28 | { 29 | global $pagenow; 30 | 31 | add_filter('admin_footer_text', [ $this, 'footer_text' ]); 32 | 33 | // Hooks for Plugins overview page 34 | if ($pagenow === 'plugins.php') { 35 | add_filter('plugin_action_links_' . $this->plugin_file, [ $this, 'add_plugin_settings_link' ], 10, 2); 36 | add_filter('plugin_row_meta', [ $this, 'add_plugin_meta_links' ], 10, 2); 37 | } 38 | } 39 | 40 | /** 41 | * Ask for a plugin review in the WP Admin footer, if this is one of the plugin pages. 42 | * 43 | * @param string $text 44 | * 45 | * @return string 46 | */ 47 | public function footer_text($text) 48 | { 49 | if (! empty($_GET['page']) && strpos($_GET['page'], 'mailchimp-for-wp') === 0) { 50 | $text = sprintf('If you enjoy using Mailchimp for WordPress, please leave us a ★★★★★ plugin review on WordPress.org.', 'https://wordpress.org/support/plugin/mailchimp-for-wp/reviews/#new-post'); 51 | } 52 | 53 | return $text; 54 | } 55 | 56 | /** 57 | * Add the settings link to the Plugins overview 58 | * 59 | * @param array $links 60 | * @param $file 61 | * 62 | * @return array 63 | */ 64 | public function add_plugin_settings_link($links, $file) 65 | { 66 | if ($file !== $this->plugin_file) { 67 | return $links; 68 | } 69 | 70 | $settings_link = sprintf('%s', admin_url('admin.php?page=mailchimp-for-wp'), esc_html__('Settings', 'mailchimp-for-wp')); 71 | array_unshift($links, $settings_link); 72 | return $links; 73 | } 74 | 75 | /** 76 | * Adds meta links to the plugin in the WP Admin > Plugins screen 77 | * 78 | * @param array $links 79 | * @param string $file 80 | * 81 | * @return array 82 | */ 83 | public function add_plugin_meta_links($links, $file) 84 | { 85 | if ($file !== $this->plugin_file) { 86 | return $links; 87 | } 88 | 89 | $links[] = '' . esc_html__('Documentation', 'mailchimp-for-wp') . ''; 90 | 91 | /** 92 | * Filters meta links shown on the Plugins overview page 93 | * 94 | * This takes an array of strings 95 | * 96 | * @since 3.0 97 | * @param array $links 98 | * @ignore 99 | */ 100 | $links = apply_filters('mc4wp_admin_plugin_meta_links', $links); 101 | 102 | return $links; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /assets/src/js/forms-block.js: -------------------------------------------------------------------------------- 1 | const __ = window.wp.i18n.__ 2 | const { registerBlockType } = window.wp.blocks 3 | const { SelectControl } = window.wp.components // eslint-disable-line no-unused-vars 4 | const forms = window.mc4wp_forms 5 | 6 | registerBlockType('mailchimp-for-wp/form', { 7 | apiVersion: 3, 8 | title: __('Mailchimp for WordPress Form'), 9 | description: __('Block showing a Mailchimp for WordPress sign-up form'), 10 | category: 'widgets', 11 | attributes: { 12 | id: { 13 | type: 'int' 14 | } 15 | }, 16 | icon: (), 17 | supports: { 18 | html: false 19 | }, 20 | 21 | edit: function (props) { 22 | const options = forms.map(f => { 23 | return { 24 | label: f.name, 25 | value: f.id 26 | } 27 | }) 28 | 29 | if (props.attributes.id === undefined && forms.length > 0) { 30 | props.setAttributes({ id: forms[0].id }) 31 | } 32 | 33 | return ( 34 |
35 | { 40 | props.setAttributes({ id: value }) 41 | }} 42 | /> 43 |
44 | ) 45 | }, 46 | 47 | // Render nothing in the saved content, because we render in PHP 48 | save: function (props) { 49 | return null 50 | // return `[mc4wp_form id="${props.attributes.id}"]`; 51 | } 52 | }) 53 | -------------------------------------------------------------------------------- /assets/src/js/forms/conditional-elements.js: -------------------------------------------------------------------------------- 1 | function getFieldValues (form, fieldName) { 2 | const values = [] 3 | const inputs = form.querySelectorAll('input[name="' + fieldName + '"],select[name="' + fieldName + '"],textarea[name="' + fieldName + '"]') 4 | 5 | for (let i = 0; i < inputs.length; i++) { 6 | if ((inputs[i].type === 'radio' || inputs[i].type === 'checkbox') && !inputs[i].checked) { 7 | continue 8 | } 9 | 10 | values.push(inputs[i].value) 11 | } 12 | 13 | return values 14 | } 15 | 16 | function findForm (element) { 17 | let bubbleElement = element 18 | 19 | while (bubbleElement.parentElement) { 20 | bubbleElement = bubbleElement.parentElement 21 | 22 | if (bubbleElement.tagName === 'FORM') { 23 | return bubbleElement 24 | } 25 | } 26 | 27 | return null 28 | } 29 | 30 | function toggleElement (el) { 31 | const show = !!el.getAttribute('data-show-if') 32 | const conditions = show ? el.getAttribute('data-show-if').split(':') : el.getAttribute('data-hide-if').split(':') 33 | const fieldName = conditions[0] 34 | const expectedValues = ((conditions.length > 1 ? conditions[1] : '*').split('|')) 35 | const form = findForm(el) 36 | const values = getFieldValues(form, fieldName) 37 | 38 | // determine whether condition is met 39 | let conditionMet = false 40 | for (let i = 0; i < values.length && !conditionMet; i++) { 41 | // condition is met when value is in array of expected values OR expected values contains a wildcard and value is not empty 42 | conditionMet = expectedValues.indexOf(values[i]) > -1 || (expectedValues.indexOf('*') > -1 && values[i].length > 0) 43 | } 44 | 45 | // toggle element display 46 | if (show) { 47 | el.style.display = conditionMet ? '' : 'none' 48 | } else { 49 | el.style.display = conditionMet ? 'none' : '' 50 | } 51 | 52 | // find all inputs inside this element and toggle [required] attr (to prevent HTML5 validation on hidden elements) 53 | const inputs = el.querySelectorAll('input,select,textarea') 54 | for (let i = 0; i < inputs.length; i++) { 55 | if ((conditionMet || show) && inputs[i].getAttribute('data-was-required')) { 56 | inputs[i].required = true 57 | inputs[i].removeAttribute('data-was-required') 58 | } 59 | 60 | if ((!conditionMet || !show) && inputs[i].required) { 61 | inputs[i].setAttribute('data-was-required', 'true') 62 | inputs[i].required = false 63 | } 64 | } 65 | } 66 | 67 | // evaluate conditional elements globally 68 | function evaluate () { 69 | const elements = document.querySelectorAll('.mc4wp-form [data-show-if],.mc4wp-form [data-hide-if]') 70 | for (let i = 0; i < elements.length; i++) { 71 | toggleElement(elements[i]) 72 | } 73 | } 74 | 75 | // re-evaluate conditional elements for change events on forms 76 | function handleInputEvent (evt) { 77 | if (!evt.target || !evt.target.form || evt.target.form.className.indexOf('mc4wp-form') < 0) { 78 | return 79 | } 80 | 81 | const elements = evt.target.form.querySelectorAll('[data-show-if],[data-hide-if]') 82 | for (let i = 0; i < elements.length; i++) { 83 | toggleElement(elements[i]) 84 | } 85 | } 86 | 87 | document.addEventListener('keyup', handleInputEvent, true) 88 | document.addEventListener('change', handleInputEvent, true) 89 | document.addEventListener('mc4wp-refresh', evaluate, true) 90 | window.addEventListener('load', evaluate) 91 | evaluate() 92 | -------------------------------------------------------------------------------- /includes/views/parts/lists-overview-details.php: -------------------------------------------------------------------------------- 1 | 8 |

Merge fields

9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 25 | 26 | 39 | 40 | 41 | 42 |
NameTagType
name); ?> required) { 22 | ?> 23 | * 24 | tag); ?> 27 | type); ?> 28 | options->date_format)) { 30 | echo esc_html('(' . $f->options->date_format . ')'); 31 | } 32 | ?> 33 | options->choices)) { 35 | echo esc_html('(' . join(', ', $f->options->choices) . ')'); 36 | } 37 | ?> 38 |
43 | 44 | 45 |

Interest Categories

46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 80 | 81 | 82 | 83 |
NameTypeInterests
58 | title); ?> 59 |
60 |
61 | ID: id); ?> 62 |
type); ?> 65 | 66 | 67 | 68 | 69 | 70 | interests as $id => $name) { ?> 71 | 72 | 73 | 74 | 75 | 76 | 77 |
NameID
78 | 79 |
84 | 85 | 86 | 87 |

Marketing Permissions

88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 |
IDName
marketing_permission_id); ?>text); ?>
104 | 105 | -------------------------------------------------------------------------------- /integrations/easy-digital-downloads/class-easy-digital-downloads.php: -------------------------------------------------------------------------------- 1 | options['implicit']) { 28 | // TODO: Allow more positions 29 | add_action('edd_purchase_form_user_info_fields', [ $this, 'output_checkbox' ], 1); 30 | add_action('edd_payment_meta', [ $this, 'save_checkbox_value' ]); 31 | } 32 | 33 | add_action('edd_complete_purchase', [ $this, 'subscribe_from_edd' ], 50); 34 | } 35 | 36 | /** 37 | * @param array $meta 38 | * 39 | * @return array 40 | */ 41 | public function save_checkbox_value($meta) 42 | { 43 | 44 | // don't save anything if the checkbox was not checked 45 | if (! $this->checkbox_was_checked()) { 46 | return $meta; 47 | } 48 | 49 | $meta['_mc4wp_optin'] = 1; 50 | return $meta; 51 | } 52 | 53 | /** 54 | * {@inheritdoc} 55 | * 56 | * @param $object_id 57 | * 58 | * @return bool 59 | */ 60 | public function triggered($object_id = null) 61 | { 62 | if ($this->options['implicit']) { 63 | return true; 64 | } 65 | 66 | if (! $object_id) { 67 | return false; 68 | } 69 | 70 | $meta = edd_get_payment_meta($object_id); 71 | if (is_array($meta) && isset($meta['_mc4wp_optin']) && $meta['_mc4wp_optin']) { 72 | return true; 73 | } 74 | 75 | return false; 76 | } 77 | 78 | /** 79 | * @param int $payment_id The ID of the payment 80 | * 81 | * @return bool|string 82 | */ 83 | public function subscribe_from_edd($payment_id) 84 | { 85 | if (! $this->triggered($payment_id)) { 86 | return false; 87 | } 88 | 89 | $email = (string) edd_get_payment_user_email($payment_id); 90 | $data = [ 91 | 'EMAIL' => $email, 92 | ]; 93 | 94 | // add first and last name to merge vars, if given 95 | $user_info = (array) edd_get_payment_meta_user_info($payment_id); 96 | 97 | if (! empty($user_info['first_name']) && ! empty($user_info['last_name'])) { 98 | $data['NAME'] = $user_info['first_name'] . ' ' . $user_info['last_name']; 99 | } 100 | 101 | if (! empty($user_info['first_name'])) { 102 | $data['FNAME'] = $user_info['first_name']; 103 | } 104 | 105 | if (! empty($user_info['last_name'])) { 106 | $data['LNAME'] = $user_info['last_name']; 107 | } 108 | 109 | return $this->subscribe($data, $payment_id); 110 | } 111 | 112 | /** 113 | * @return bool 114 | */ 115 | public function is_installed() 116 | { 117 | return class_exists('Easy_Digital_Downloads'); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /includes/views/extensions.php: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 |
8 | 12 | 13 |

Mailchimp for WordPress: Add-on plugins

14 | 15 |
16 |

Mailchimp for WordPress Premium, take your email marketing to the next level!

17 |

You're currently on the free version of the MC4WP: Mailchimp for WordPress plugin.

18 |

Did you know that there is a premium version too? It comes with the following additional features:

19 |
    20 |
  • Multiple and improved forms — allowing an unlimited amount of sign-up forms that submit without requiring a full page reload.
  • 21 |
  • E-Commerce integration — tightly integrate your WooCommerce store with Mailchimp. 22 |
  • User Sync — keep your WordPress user database in sync with a Mailchimp list.
  • 23 |
  • Logging - every form submission is stored locally, allowing charted data and exporting to CSV or JSON
  • 24 |
  • Form designer — make your forms look pretty without having to know or write a single line of CSS.
  • 25 |
  • Append form to posts — an easy setting to automatically append a form to all posts (in a certain category).
  • 26 |
  • Priority support — gain access to our 24/7 support team.
  • 27 |
28 |

29 | Buy Mailchimp for WordPress Premium   30 | More information 31 |

32 | 33 |

Comes with our 30-day no questions asked money back guarantee. 34 |

35 | 36 |
37 | 38 |
39 |

The following (free) add-on plugins are available for Mailchimp for WordPress.

40 | 41 |
42 |

Mailchimp Top Bar

43 |

Adds a sign-up bar to the top or bottom of your site. A sure fire way to grow your lists.

44 |
45 | 46 |
47 |

WPML Integration

48 |

Improved Mailchimp integration for multilingual sites using WPML.

49 |
50 | 51 |
52 |

Boxzilla Pop-ups

53 |

Pop-ups for your sign-up forms.

54 |
55 |
56 | 57 | 58 | 59 |
60 | -------------------------------------------------------------------------------- /includes/admin/class-review-notice.php: -------------------------------------------------------------------------------- 1 | tools = $tools; 28 | } 29 | 30 | /** 31 | * Add action & filter hooks. 32 | */ 33 | public function add_hooks() 34 | { 35 | add_action('admin_notices', [ $this, 'show' ]); 36 | add_action('mc4wp_admin_dismiss_review_notice', [ $this, 'dismiss' ]); 37 | } 38 | 39 | /** 40 | * Set flag in user meta so notice won't be shown. 41 | */ 42 | public function dismiss() 43 | { 44 | $user = wp_get_current_user(); 45 | update_user_meta($user->ID, $this->meta_key_dismissed, 1); 46 | } 47 | 48 | /** 49 | * @return bool 50 | */ 51 | public function show() 52 | { 53 | // only show on Mailchimp for WordPress' pages. 54 | if (! $this->tools->on_plugin_page()) { 55 | return false; 56 | } 57 | 58 | // only show if 2 weeks have passed since first use. 59 | $two_weeks_in_seconds = ( 60 * 60 * 24 * 14 ); 60 | if ($this->time_since_first_use() <= $two_weeks_in_seconds) { 61 | return false; 62 | } 63 | 64 | // only show if user did not dismiss before 65 | $user = wp_get_current_user(); 66 | if (get_user_meta($user->ID, $this->meta_key_dismissed, true)) { 67 | return false; 68 | } 69 | 70 | echo '
'; 71 | echo '

'; 72 | echo esc_html__('You\'ve been using Mailchimp for WordPress for some time now; we hope you love it!', 'mailchimp-for-wp'), '
'; 73 | echo sprintf(wp_kses(__('If you do, please leave us a 5★ rating on WordPress.org. It would be of great help to us.', 'mailchimp-for-wp'), [ 'a' => [ 'href' => [] ] ]), 'https://wordpress.org/support/view/plugin-reviews/mailchimp-for-wp?rate=5#new-post'); 74 | echo '

'; 75 | echo '
', wp_nonce_field('_mc4wp_action', '_wpnonce', true, false), '
'; 76 | echo '
'; 77 | return true; 78 | } 79 | 80 | /** 81 | * @return int 82 | */ 83 | private function time_since_first_use() 84 | { 85 | $options = get_option('mc4wp', []); 86 | if (! is_array($options)) { 87 | $options = []; 88 | } 89 | 90 | // option was never added before, do it now. 91 | if (empty($options['first_activated_on'])) { 92 | $options['first_activated_on'] = time(); 93 | update_option('mc4wp', $options); 94 | } 95 | 96 | return time() - $options['first_activated_on']; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /includes/forms/views/add-form.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 |

6 | 7 |

8 |

9 |
10 |
11 | 12 | 13 |
14 |

15 | 18 |

19 | 20 |
21 |
22 |

23 | 26 |

27 | 28 | 31 |
    32 | 35 |
  • 36 | 40 |
  • 41 | 44 |
45 | 48 |

49 | connect with Mailchimp?', 'mailchimp-for-wp'), [ 'a' => [ 'href' => [] ] ]), admin_url('admin.php?page=mailchimp-for-wp')); ?> 50 |

51 | 54 |
55 | 56 |
57 |
58 | 59 |
60 |
61 | 62 |
63 |
64 |
65 | -------------------------------------------------------------------------------- /integrations/ninja-forms/class-field.php: -------------------------------------------------------------------------------- 1 | _settings['label_pos']['value'] = 'right'; 31 | 32 | add_filter('ninja_forms_custom_columns', [ $this, 'custom_columns' ], 10, 2); 33 | add_action('init', [$this, 'translate_nicename']); 34 | } 35 | 36 | public function translate_nicename() 37 | { 38 | $this->_nicename = __('Mailchimp opt-in', 'mailchimp-for-wp'); 39 | } 40 | 41 | /** 42 | * Admin Form Element 43 | * Display the checkbox on the edit submissions area. 44 | * @since 3.0 45 | * 46 | * @param $id Field ID. 47 | * @param $value Field value. 48 | * @return string HTML used for display of checkbox. 49 | */ 50 | public function admin_form_element($id, $value) 51 | { 52 | // If the checkboxes value is one... 53 | if (1 === (int) $value) { 54 | // ...this variable to checked. 55 | $checked = 'checked'; 56 | } else { 57 | // ...else leave the variable empty. 58 | $checked = ''; 59 | } 60 | 61 | // Return HTML to be output to the submission edit page. 62 | return ""; 63 | } 64 | 65 | /** 66 | * Custom Columns 67 | * Creates what is displayed in the columns on the submissions page. 68 | * @since 3.0 69 | * 70 | * @param string $value checkbox value 71 | * @param MC4WP_Ninja_Forms_Field $field field model. 72 | * @return $value string|void 73 | */ 74 | public function custom_columns($value, $field) 75 | { 76 | // If the field type is equal to checkbox... 77 | if ('mc4wp_optin' === $field->get_setting('type')) { 78 | // Backwards compatibility check for the new checked value setting. 79 | if (null === $field->get_setting('checked_value') && 1 === (int) $value) { 80 | return __('Checked', 'ninja-forms'); 81 | } elseif (null === $field->get_setting('unchecked_value') && 0 === (int) $value) { 82 | return __('Unchecked', 'ninja-forms'); 83 | } 84 | 85 | // If the field value is set to 1.... 86 | if (1 === (int) $value) { 87 | // Set the value to the checked value setting. 88 | $value = $field->get_setting('checked_value'); 89 | } else { 90 | // Else set the value to the unchecked value setting. 91 | $value = $field->get_setting('unchecked_value'); 92 | } 93 | } 94 | return $value; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /integrations/wp-comment-form/class-comment-form.php: -------------------------------------------------------------------------------- 1 | options['implicit']) { 33 | // hooks for outputting the checkbox 34 | add_filter('comment_form_submit_field', [ $this, 'add_checkbox_before_submit_button' ], 90); 35 | 36 | add_action('thesis_hook_after_comment_box', [ $this, 'maybe_output_checkbox' ], 90); 37 | add_action('comment_form', [ $this, 'maybe_output_checkbox' ], 90); 38 | } 39 | 40 | // hooks for checking if we should subscribe the commenter 41 | add_action('comment_post', [ $this, 'subscribe_from_comment' ], 40, 2); 42 | } 43 | 44 | /** 45 | * This adds the checkbox just before the submit button and sets a flag to prevent it from outputting twice 46 | * 47 | * @param $submit_button_html 48 | * 49 | * @return string 50 | */ 51 | public function add_checkbox_before_submit_button($submit_button_html) 52 | { 53 | $this->added_through_filter = true; 54 | return $this->get_checkbox_html() . $submit_button_html; 55 | } 56 | 57 | /** 58 | * Output fallback 59 | * Will output the checkbox if comment_form() function does not use `comment_form_submit_field` filter yet. 60 | */ 61 | public function maybe_output_checkbox() 62 | { 63 | if (! $this->added_through_filter) { 64 | $this->output_checkbox(); 65 | } 66 | } 67 | 68 | /** 69 | * Grabs data from WP Comment Form 70 | * 71 | * @param int $comment_id 72 | * @param string $comment_approved 73 | * 74 | * @return bool|string 75 | */ 76 | public function subscribe_from_comment($comment_id, $comment_approved = '') 77 | { 78 | 79 | // was sign-up checkbox checked? 80 | if (! $this->triggered()) { 81 | return false; 82 | } 83 | 84 | // is this a spam comment? 85 | if ($comment_approved === 'spam') { 86 | return false; 87 | } 88 | 89 | $comment = get_comment($comment_id); 90 | 91 | $data = [ 92 | 'EMAIL' => $comment->comment_author_email, 93 | 'NAME' => $comment->comment_author, 94 | 'OPTIN_IP' => $comment->comment_author_IP, 95 | ]; 96 | 97 | return $this->subscribe($data, $comment_id); 98 | } 99 | 100 | /** 101 | * @return bool 102 | */ 103 | public function is_installed() 104 | { 105 | return true; 106 | } 107 | 108 | /** 109 | * {@inheritdoc } 110 | */ 111 | public function get_object_link($object_id) 112 | { 113 | $comment = get_comment($object_id); 114 | 115 | if (! $comment) { 116 | return ''; 117 | } 118 | 119 | return sprintf('Comment #%d', get_edit_comment_link($object_id), $object_id); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /includes/class-personal-data-exporter.php: -------------------------------------------------------------------------------- 1 | __('Mailchimp Subscriptions'), 18 | 'callback' => [self::class, 'get_mailchimp_subscription_data'] 19 | ]; 20 | 21 | return $exporters; 22 | } 23 | 24 | /** 25 | * Retrieves the Mailchimp subscription data for a given email address. 26 | * 27 | * This method uses the Mailchimp for WordPress (MC4WP) API to search for members based on the provided 28 | * email address and returns a list of Mailchimp lists the user is subscribed to, if any. 29 | * 30 | * @param string $email_address The email address of the user to search for. 31 | * 32 | * @return array An array containing the user's Mailchimp subscription data: 33 | * - 'data' (array): The subscription information, including: 34 | * - 'group_id' (string): The group identifier for Mailchimp. 35 | * - 'group_label' (string): The label for the group ('Mailchimp Subscriptions'). 36 | * - 'item_id' (string): The item identifier ('mailchimp-subscriptions'). 37 | * - 'data' (array): The subscription details, with: 38 | * - 'name' (string): The label ('Mailchimp List'). 39 | * - 'value' (string): A comma-separated list of Mailchimp lists the user is subscribed to. 40 | * - 'done' (bool): Indicates the completion of the process (always true). 41 | */ 42 | public static function get_mailchimp_subscription_data($email_address) 43 | { 44 | $api = mc4wp_get_api_v3(); 45 | $client = $api->get_client(); 46 | $data = $client->get('search-members?query=' . urlencode($email_address)); 47 | 48 | // Parse the API response to get the lists the user is subscribed to. 49 | $subscribed_lists = []; 50 | $data_to_export = []; 51 | 52 | if (!empty($data->exact_matches->members)) { 53 | $lists = $api->get_lists(); 54 | foreach ($data->exact_matches->members as $member) { 55 | // Fetch the user's subscribed lists. 56 | if (isset($member->list_id)) { 57 | foreach ($lists as $list) { 58 | if ($list->id == $member->list_id) { 59 | $subscribed_lists[] = $list->name; 60 | continue; 61 | } 62 | } 63 | } 64 | } 65 | } 66 | 67 | if ($subscribed_lists) { 68 | $data_to_export[] = [ 69 | 'group_id' => 'mailchimp', 70 | 'group_label' => __('Mailchimp Subscriptions', 'mailchimp-for-wp'), 71 | 'item_id' => 'mailchimp-subscriptions', 72 | 'data' => [ 73 | [ 74 | 'name' => __('Mailchimp Lists', 'mailchimp-for-wp'), 75 | 'value' => implode(', ', $subscribed_lists), 76 | ] 77 | ] 78 | ]; 79 | } 80 | 81 | return [ 82 | 'data' => $data_to_export, 83 | 'done' => true, 84 | ]; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /includes/class-container.php: -------------------------------------------------------------------------------- 1 | services[ $name ]); 28 | } 29 | 30 | /** 31 | * @param string $name 32 | * 33 | * @return mixed 34 | * @throws Exception 35 | */ 36 | public function get($name) 37 | { 38 | if (! $this->has($name)) { 39 | throw new Exception(sprintf('No service named %s was registered.', $name)); 40 | } 41 | 42 | $service = $this->services[ $name ]; 43 | 44 | // is this a resolvable service? 45 | if (is_callable($service)) { 46 | // resolve service if it's not resolved yet 47 | if (! isset($this->resolved_services[ $name ])) { 48 | $this->resolved_services[ $name ] = call_user_func($service); 49 | } 50 | 51 | return $this->resolved_services[ $name ]; 52 | } 53 | 54 | return $this->services[ $name ]; 55 | } 56 | 57 | /** 58 | * (PHP 5 >= 5.0.0)
59 | * Whether a offset exists 60 | * @link http://php.net/manual/en/arrayaccess.offsetexists.php 61 | * 62 | * @param mixed $offset

63 | * An offset to check for. 64 | *

65 | * 66 | * @return boolean true on success or false on failure. 67 | *

68 | *

69 | * The return value will be casted to boolean if non-boolean was returned. 70 | */ 71 | #[\ReturnTypeWillChange] 72 | public function offsetExists($offset) 73 | { 74 | return $this->has($offset); 75 | } 76 | 77 | /** 78 | * (PHP 5 >= 5.0.0)
79 | * Offset to retrieve 80 | * @link http://php.net/manual/en/arrayaccess.offsetget.php 81 | * 82 | * @param mixed $offset

83 | * The offset to retrieve. 84 | *

85 | * 86 | * @return mixed Can return all value types. 87 | */ 88 | #[\ReturnTypeWillChange] 89 | public function offsetGet($offset) 90 | { 91 | return $this->get($offset); 92 | } 93 | 94 | /** 95 | * (PHP 5 >= 5.0.0)
96 | * Offset to set 97 | * @link http://php.net/manual/en/arrayaccess.offsetset.php 98 | * 99 | * @param mixed $offset

100 | * The offset to assign the value to. 101 | *

102 | * @param mixed $value

103 | * The value to set. 104 | *

105 | * 106 | * @return void 107 | */ 108 | #[\ReturnTypeWillChange] 109 | public function offsetSet($offset, $value) 110 | { 111 | $this->services[ $offset ] = $value; 112 | } 113 | 114 | /** 115 | * (PHP 5 >= 5.0.0)
116 | * Offset to unset 117 | * @link http://php.net/manual/en/arrayaccess.offsetunset.php 118 | * 119 | * @param mixed $offset

120 | * The offset to unset. 121 | *

122 | * 123 | * @return void 124 | */ 125 | #[\ReturnTypeWillChange] 126 | public function offsetUnset($offset) 127 | { 128 | unset($this->services[ $offset ]); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /includes/views/general-settings.php: -------------------------------------------------------------------------------- 1 | 4 |
5 | 6 |

7 | 8 | Mailchimp for WordPress 9 |

10 | 11 |
12 |
13 | 14 |

15 | Mailchimp for WordPress: 16 |

17 | 18 |

19 | messages->show(); 22 | ?> 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 33 | 46 | 47 | 48 | 49 | 50 | 51 | 64 | 65 |
31 | 32 | 34 | 37 | 38 | 41 | 42 | 45 |
52 | /> 53 |

54 | 55 | 56 |

57 | 58 | ', wp_kses(__('You defined your Mailchimp API key using the MC4WP_API_KEY constant.', 'mailchimp-for-wp'), [ 'code' => [] ]), '

'; 61 | } 62 | ?> 63 |
66 | 67 | 68 |
69 | 70 | '; 75 | include __DIR__ . '/parts/lists-overview.php'; 76 | } 77 | 78 | require __DIR__ . '/parts/admin-footer.php'; 79 | 80 | ?> 81 |
82 | 83 |
84 | 85 |
86 |
87 |
88 | 89 | -------------------------------------------------------------------------------- /integrations/custom/class-custom.php: -------------------------------------------------------------------------------- 1 | get_data(); 42 | $value = isset($data[ $this->checkbox_name ]) ? $data[ $this->checkbox_name ] : ''; 43 | $truthy_values = [ 1, '1', 'yes', true, 'true', 'y' ]; 44 | return in_array($value, $truthy_values, true); 45 | } 46 | 47 | /** 48 | * Maybe fire a general subscription request 49 | * 50 | * @return bool|string 51 | */ 52 | public function listen() 53 | { 54 | if (! $this->checkbox_was_checked()) { 55 | return false; 56 | } 57 | 58 | // ignore requests from bots, crawlers and link previews 59 | if (empty($_SERVER['HTTP_USER_AGENT']) || preg_match('/bot|crawl|spider|seo|lighthouse|facebookexternalhit|preview/i', $_SERVER['HTTP_USER_AGENT'])) { 60 | return false; 61 | } 62 | 63 | // ignore requests without an HTTP referrer 64 | if (empty($_SERVER['HTTP_REFERER'])) { 65 | return false; 66 | } 67 | 68 | // ignore requests where HTTP Referer does not contain hostname from home_url 69 | $site_hostname = parse_url(get_home_url(), PHP_URL_HOST); 70 | if (strpos($_SERVER['HTTP_REFERER'], $site_hostname) === false) { 71 | return false; 72 | } 73 | 74 | $data = $this->get_data(); 75 | 76 | // don't run for CF7 or Events Manager requests 77 | // (since they use the same "mc4wp-subscribe" trigger) 78 | $disable_triggers = [ 79 | '_wpcf7' => '', 80 | 'action' => 'booking_add', 81 | ]; 82 | 83 | foreach ($disable_triggers as $trigger => $trigger_value) { 84 | if (isset($data[ $trigger ])) { 85 | $value = $data[ $trigger ]; 86 | 87 | // do nothing if trigger value is optional 88 | // or if trigger value matches 89 | if (empty($trigger_value) || $value === $trigger_value) { 90 | return false; 91 | } 92 | } 93 | } 94 | 95 | // run! 96 | return $this->process(); 97 | } 98 | 99 | /** 100 | * Process custom form 101 | * 102 | * @return bool|string 103 | */ 104 | public function process() 105 | { 106 | $parser = new MC4WP_Field_Guesser($this->get_data()); 107 | $data = $parser->combine([ 'guessed', 'namespaced' ]); 108 | 109 | // do nothing if no email was found 110 | if (empty($data['EMAIL'])) { 111 | $this->get_log()->warning(sprintf('%s > Unable to find EMAIL field.', $this->name)); 112 | return false; 113 | } 114 | 115 | return $this->subscribe($data); 116 | } 117 | 118 | /** 119 | * @return bool 120 | */ 121 | public function is_installed() 122 | { 123 | return true; 124 | } 125 | 126 | /** 127 | * @return array 128 | */ 129 | public function get_ui_elements() 130 | { 131 | return [ 'lists', 'double_optin', 'update_existing', 'replace_interests' ]; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /assets/src/js/admin/form-editor/field-helper.js: -------------------------------------------------------------------------------- 1 | const m = require('mithril') 2 | const editor = require('./form-editor.js') 3 | const fields = require('./fields.js') 4 | const i18n = window.mc4wp_forms_i18n 5 | const generate = require('./field-generator.js') 6 | const Overlay = require('../overlay.js') 7 | const forms = require('./field-forms.js') 8 | let fieldConfig 9 | 10 | editor.on('blur', () => m.redraw()) 11 | 12 | /** 13 | * Choose a field to open the helper form for 14 | */ 15 | function setActiveField (name) { 16 | fieldConfig = name !== null ? fields.get(name) : null 17 | 18 | // if this hidden field has choices (hidden groups), glue them together by their label. 19 | if (fieldConfig && fieldConfig.type === 'hidden' && fieldConfig.choices.length > 0) { 20 | fieldConfig.value = fieldConfig.choices.map(function (c) { 21 | return c.label 22 | }).join('|') 23 | } 24 | 25 | m.redraw() 26 | } 27 | 28 | /** 29 | * Create HTML based on current config object 30 | */ 31 | function createFieldHTMLAndAddToForm () { 32 | // generate html 33 | const html = generate(fieldConfig) 34 | 35 | // add to editor 36 | editor.insert(html) 37 | 38 | // reset field form 39 | setActiveField(null) 40 | } 41 | 42 | /** 43 | * View 44 | * @returns {*} 45 | */ 46 | function view () { 47 | // build DOM for fields choice 48 | const availableFields = fields.getAll() 49 | 50 | const fieldsChoice = m('div#mc4wp-available-fields.mc4wp-margin-s', [ 51 | m('h4', { style: { marginTop: 0 } }, i18n.chooseField), 52 | 53 | [i18n.listFields, i18n.interestCategories, i18n.formFields].map(function (category) { 54 | const categoryFields = availableFields.filter(function (f) { 55 | return f.category === category 56 | }) 57 | 58 | if (categoryFields.length === 0) { 59 | return '' 60 | } 61 | 62 | return m('div.mc4wp-margin-s', [ 63 | m('h4', category), 64 | 65 | // render fields 66 | categoryFields.map(function (field) { 67 | let className = 'button' 68 | if (field.forceRequired) { 69 | className += ' is-required' 70 | } 71 | 72 | const inForm = field.inFormContent 73 | if (inForm !== null) { 74 | className += ' ' + (inForm ? 'in-form' : 'not-in-form') 75 | } 76 | 77 | return m('button', { 78 | className, 79 | type: 'button', 80 | onclick: (evt) => setActiveField(evt.target.value), 81 | value: field.name 82 | }, field.title) 83 | }) 84 | ]) 85 | }) 86 | ]) 87 | 88 | // build DOM for overlay 89 | let form = null 90 | if (fieldConfig) { 91 | form = m(Overlay, { onClose: () => setActiveField(null) }, // field wizard 92 | m('div#mc4wp-add-form-field', [ 93 | // heading 94 | m('h3', [ 95 | fieldConfig.title, 96 | fieldConfig.forceRequired ? m('span.mc4wp-red', '*') : '', 97 | fieldConfig.name.length ? m('code', fieldConfig.name) : '' 98 | ]), 99 | 100 | // help text 101 | (fieldConfig.help.length) ? m('p', m.trust(fieldConfig.help)) : '', 102 | 103 | // actual form 104 | forms.render(fieldConfig), 105 | 106 | // add to form button 107 | m('p', [ 108 | m('button', { 109 | class: 'button-primary', 110 | type: 'button', 111 | onkeydown: function (evt) { 112 | if (evt.keyCode === 13) { 113 | createFieldHTMLAndAddToForm() 114 | } 115 | }, 116 | onclick: createFieldHTMLAndAddToForm 117 | }, i18n.addToForm) 118 | ]) 119 | ])) 120 | } 121 | 122 | return [ 123 | fieldsChoice, 124 | form 125 | ] 126 | } 127 | 128 | const fieldHelperRootElement = document.getElementById('mc4wp-field-wizard') 129 | if (fieldHelperRootElement) { 130 | m.mount(fieldHelperRootElement, { view }) 131 | } 132 | -------------------------------------------------------------------------------- /includes/forms/class-form-tags.php: -------------------------------------------------------------------------------- 1 | tags['response'] = [ 36 | 'description' => __('Replaced with the form response (error or success messages).', 'mailchimp-for-wp'), 37 | 'callback' => [ $this, 'get_form_response' ], 38 | 'raw_html' => true, 39 | ]; 40 | 41 | $this->tags['data'] = [ 42 | 'description' => sprintf(__('Data from the URL or a submitted form.', 'mailchimp-for-wp')), 43 | 'callback' => [ $this, 'get_data' ], 44 | 'example' => "data key='UTM_SOURCE' default='Default Source'", 45 | ]; 46 | 47 | $this->tags['subscriber_count'] = [ 48 | 'description' => __('Replaced with the number of subscribers on the selected list(s)', 'mailchimp-for-wp'), 49 | 'callback' => [ $this, 'get_subscriber_count' ], 50 | ]; 51 | } 52 | 53 | 54 | public function replace_in_form_content($string, MC4WP_Form $form, MC4WP_Form_Element $element) 55 | { 56 | $this->form = $form; 57 | $this->form_element = $element; 58 | 59 | $string = $this->replace_in_html($string); 60 | return $string; 61 | } 62 | 63 | public function replace_in_form_response($string, MC4WP_Form $form) 64 | { 65 | $this->form = $form; 66 | 67 | $string = $this->replace_in_html($string); 68 | return $string; 69 | } 70 | 71 | public function replace_in_form_redirect_url($string, MC4WP_Form $form) 72 | { 73 | $this->form = $form; 74 | $string = $this->replace_in_url($string); 75 | return $string; 76 | } 77 | 78 | /** 79 | * Returns the number of subscribers on the selected lists (for the form context) 80 | * 81 | * @return int 82 | */ 83 | public function get_subscriber_count() 84 | { 85 | $mailchimp = new MC4WP_MailChimp(); 86 | $count = $mailchimp->get_subscriber_count($this->form->get_lists()); 87 | return number_format($count); 88 | } 89 | 90 | /** 91 | * Returns the form response 92 | * 93 | * @return string 94 | */ 95 | public function get_form_response() 96 | { 97 | if ($this->form_element instanceof MC4WP_Form_Element) { 98 | return $this->form_element->get_response_html(); 99 | } 100 | 101 | return ''; 102 | } 103 | 104 | /** 105 | * Gets data value from GET or POST variables. 106 | * 107 | * @param array $args 108 | * @return string 109 | */ 110 | public function get_data(array $args = []) 111 | { 112 | if (empty($args['key'])) { 113 | return ''; 114 | } 115 | 116 | $default = isset($args['default']) ? $args['default'] : ''; 117 | $key = $args['key']; 118 | 119 | $data = array_merge($_GET, $_POST); 120 | $value = isset($data[ $key ]) ? $data[ $key ] : $default; 121 | 122 | // turn array into readable value 123 | if (is_array($value)) { 124 | $value = array_filter($value); 125 | $value = join(', ', $value); 126 | } 127 | 128 | return $value; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /assets/src/js/admin/form-editor/form-editor.js: -------------------------------------------------------------------------------- 1 | // load CodeMirror & plugins 2 | /** 3 | * @type {CodeMirror} 4 | */ 5 | const CodeMirror = require('codemirror') 6 | require('codemirror/mode/htmlmixed/htmlmixed') 7 | require('codemirror/addon/edit/matchtags.js') 8 | 9 | /* variables */ 10 | const FormEditor = {} 11 | const _dom = document.createElement('form') 12 | let domDirty = false 13 | let editor 14 | const element = document.getElementById('mc4wp-form-content') 15 | const previewFrame = document.getElementById('mc4wp-form-preview') 16 | let previewDom 17 | const templateRegex = /\{[^{}]+\}/g 18 | 19 | /* functions */ 20 | function setPreviewDom () { 21 | const frameContent = previewFrame.contentDocument || previewFrame.contentWindow.document 22 | previewDom = frameContent.querySelector('.mc4wp-form-fields') 23 | 24 | if (previewDom) { 25 | updatePreview() 26 | } 27 | } 28 | 29 | function updatePreview () { 30 | if (!previewDom) { 31 | return setPreviewDom() 32 | } 33 | 34 | let markup = FormEditor.getValue() 35 | 36 | // replace template tags (twice, to allow for nested tags) 37 | markup = markup.replace(templateRegex, '').replace(templateRegex, '') 38 | 39 | // update dom 40 | previewDom.innerHTML = markup 41 | previewDom.dispatchEvent(new Event('mc4wp-refresh')) 42 | } 43 | 44 | /** 45 | * @returns {HTMLFormElement} 46 | */ 47 | function dom () { 48 | if (domDirty) { 49 | _dom.innerHTML = FormEditor.getValue().toLowerCase() 50 | domDirty = false 51 | } 52 | 53 | return _dom 54 | } 55 | 56 | /** 57 | * @returns {string} 58 | */ 59 | FormEditor.getValue = function () { 60 | return editor ? editor.getValue() : element.value 61 | } 62 | 63 | /** 64 | * @param {string} query 65 | * @returns {NodeListOf} 66 | */ 67 | FormEditor.query = function (query) { 68 | return dom().querySelectorAll(query.toLowerCase()) 69 | } 70 | 71 | /** 72 | * @param {string} fieldName 73 | * @returns {boolean} 74 | */ 75 | FormEditor.containsField = function (fieldName) { 76 | return dom().elements.namedItem(fieldName.toLowerCase()) !== null 77 | } 78 | 79 | /** 80 | * @param {string} html 81 | */ 82 | FormEditor.insert = function (html) { 83 | if (editor) { 84 | editor.replaceSelection(html) 85 | editor.focus() 86 | } else { 87 | element.value += html 88 | } 89 | } 90 | 91 | /** 92 | * 93 | * @param {string} event 94 | * @param {function} callback 95 | * @returns {*} 96 | */ 97 | FormEditor.on = function (event, callback) { 98 | if (editor) { 99 | // translate "input" event for CodeMirror 100 | event = (event === 'input') ? 'changes' : event 101 | return editor.on(event, callback) 102 | } 103 | 104 | return element.addEventListener(event, callback) 105 | } 106 | 107 | FormEditor.refresh = function () { 108 | if (editor) editor.refresh() 109 | } 110 | 111 | /* bootstrap */ 112 | if (element) { 113 | _dom.innerHTML = element.value.toLowerCase() 114 | 115 | // turn