Compare commits

...

122 Commits
0.0.1 ... 0.0.5

Author SHA1 Message Date
ebca4257a6 Version bump: 0.0.5 2015-11-21 00:39:11 +01:00
c3944095bc Install npm deps in build command. 2015-11-21 00:36:12 +01:00
a1445d1b6a Vagrant Container and new build process. 2015-11-21 00:28:33 +01:00
e62c24879b Merge pull request #239 from mailpoet/revert-238-queue
Revert "Queue"
2015-11-20 23:51:19 +01:00
00f06ea202 Revert "Queue" 2015-11-20 23:51:02 +01:00
32ca24ce38 Merge pull request #238 from mailpoet/queue
Queue
2015-11-20 23:02:30 +01:00
44e3adb422 Merge pull request #235 from mailpoet/alc
Editor: ALC fixes
2015-11-20 23:00:47 +01:00
a1346ebecc Merge pull request #237 from mailpoet/changelog
welcome/update pages display logic
2015-11-20 23:00:30 +01:00
25b51d0446 - Adds queue management and supervisor. Issue #227 2015-11-20 16:20:54 -05:00
556a170903 - Bootstraps queue 2015-11-20 16:20:35 -05:00
7ac386a8e2 fixed welcome/update pages display logic 2015-11-20 13:37:52 +01:00
d91b55ec52 Include static sticky-kit library, patch it, fix sticky editor sidebar 2015-11-20 14:16:51 +02:00
786fbc36a2 Change post types to plural labels, move Posts/ALC UI elements 2015-11-19 19:08:48 +02:00
160e8b7a12 Merge pull request #232 from mailpoet/export-rename
Rename BootstrapMenu.php to BootStrapMenu.php
2015-11-19 15:01:24 +01:00
0b1f85da09 Rename BootstrapMenu.php to BootStrapMenu.php
- Renames BootStrapMenu class to use proper camelCase
2015-11-19 08:57:37 -05:00
fbc6f54ddc Merge pull request #230 from mailpoet/export
Export
2015-11-19 10:35:27 +01:00
a603e97b8c Merge pull request #229 from mailpoet/editor_posts
Editor: Posts widget
2015-11-19 10:31:31 +01:00
0875b627b6 Merge pull request #231 from mailpoet/newsletters_select_all_fix
fixed missing messages in newsletters listing
2015-11-19 10:27:45 +01:00
035784ece0 fixed missing messages in newsletters listing 2015-11-19 10:24:27 +01:00
aa93c7349f - Rebases master
- Adds tests for all Export class methods
Closes #221
2015-11-18 22:14:48 -05:00
82cf4a28fd - Updates export tests 2015-11-18 14:42:28 -05:00
832e5ef342 - Fixed minor import issue wrt to status not being set 2015-11-18 14:42:27 -05:00
e3de3a123a - Corrects exported subscriber count
- Properly exports subscribers not in any list
- Adds test for export class constructor method
Resolves #221
2015-11-18 14:42:27 -05:00
db91590159 Merge pull request #228 from mailpoet/welcome_redirect
Welcome page redirection
2015-11-18 14:33:48 +01:00
28c61fca0b Newsletters listing
- fixed listing actions on newsletters
2015-11-18 14:04:34 +01:00
e62a8c2ec5 Implement Becs' changes to Posts widget and post rendering 2015-11-18 14:44:57 +02:00
fdbd1245e3 Redirect to welcome or update page 2015-11-17 20:11:03 +01:00
0eef46db57 Fix post transformation to take titleIsLink option into account 2015-11-17 16:55:55 +02:00
080ae88a04 Fix Posts block tests 2015-11-17 16:31:57 +02:00
225be9f3cd Starting welcome page redirection 2015-11-17 13:43:25 +01:00
c9a42ebb76 Add auto-refresh of Posts block contents on option change 2015-11-17 14:40:39 +02:00
ae9b3df92d Merge pull request #223 from mailpoet/build
Fix JS lib inclusion in build step
2015-11-16 13:38:33 +01:00
63a08ebb55 Fix inclusion of js/lib/tinymce by disabling removal of node_modules/ 2015-11-16 14:27:29 +02:00
2ef5096fa9 Optimize symlinks when building. 2015-11-13 23:55:24 +01:00
95cfe2d8ec Version bump: 0.0.4. 2015-11-13 19:57:22 +01:00
49676b3fc5 Merge pull request #219 from mailpoet/export
Export
2015-11-13 18:49:32 +01:00
c96ac06423 - Moves ImportExport under Subscribers namespace
- Updates tests
2015-11-13 12:46:54 -05:00
6a3166c311 - Implements export
Closes #210
2015-11-13 12:25:33 -05:00
0017df1c2d - Work-in-progress on the UI 2015-11-13 12:25:32 -05:00
f2a0d4ce96 fixed wrong count value onGetItems in Listing 2015-11-13 12:25:32 -05:00
cb50517cbc toggle Export button depending on subscribers count 2015-11-13 12:25:31 -05:00
0fedd1779f - Moves Import and Export under ImportExport namespace
- Cretes a single BootStrapMenu class for Import and Export
- Updates tests
- Adds 2 new methods to Segments model
2015-11-13 12:25:31 -05:00
1625e1771b - Bootstraps export 2015-11-13 12:25:30 -05:00
bde78b607b Merge pull request #218 from mailpoet/listings_bugfix
Listings bugfixes & Welcome page
2015-11-13 16:01:23 +01:00
f3c58c27be set welcome page as default page 2015-11-13 15:58:36 +01:00
9fec460295 Merge pull request #220 from mailpoet/helpscout_beacon
Adds a HelpScout beacon to admin pages
2015-11-13 15:23:03 +01:00
162859529e reinstated removed tests 2015-11-13 13:56:59 +01:00
3bdaaf8368 Add a HelpScout beacon to admin pages 2015-11-13 14:45:25 +02:00
f6ab0050b2 added welcome page 2015-11-13 12:59:49 +01:00
10a20935c3 cleanup tests 2015-11-12 14:11:27 +01:00
8135b677f7 Merge pull request #216 from mailpoet/editor_layouts
Add layout settings to newsletter editor
2015-11-12 14:04:04 +01:00
90382bc86d Add layout block bg color, remove bg colors of individual columns 2015-11-11 16:55:44 +02:00
47af8939cc Merge pull request #214 from mailpoet/editor_images
Editor small image handling
2015-11-11 09:32:32 +01:00
4a0deb2182 Preserve image width for smaller than column width images 2015-11-10 18:09:36 +02:00
3a206b2c88 Adapt newsletter template selection to API change 2015-11-10 18:08:16 +02:00
70cfcbe7cc Merge pull request #209 from mailpoet/mixpanel
Add MixPanel analytics tracking
2015-11-10 10:41:24 +01:00
6342cb17bd Merge pull request #208 from mailpoet/listing_tests
Listings
2015-11-10 10:39:53 +01:00
64c35b12c8 Merge pull request #191 from mailpoet/import
Import
2015-11-09 23:23:08 +01:00
6a14f97419 Fix select2 results z-index issue for newsletter editor 2015-11-09 18:17:10 +02:00
dfec34eda9 Add Analytics integration with MixPanel 2015-11-09 18:11:06 +02:00
893231e8e5 List selection fix 2015-11-09 14:19:59 +01:00
e9110680ee Listings
- fixed selection field JSX
- fixed bulk actions (added filter function)
- added getPublished/getTrashed static methods on Model
- fixed step 3 of newsletter process
- updated save/get methods of all listing-able models to conform with the new norm
2015-11-09 13:26:33 +01:00
7b54285ca6 - Adds tests for the main Import class
- Updates tests for Env (proper host detection with port)
- Improves import
2015-11-09 00:25:24 -05:00
33ea16eb0f - Cleans up import
- Adds tests for modified models
- Adds tests for import BootStrapMenu and MailChimp classes
2015-11-08 15:57:43 -05:00
b1ae07d38e - Rebased master
- Cleaned up import & moved it under Subscribers menu
2015-11-07 21:16:38 -05:00
3f168d052f - Finishes import migration
- Updates models
- Improves Notice.js
2015-11-07 11:40:42 -05:00
158d26ef86 - Adds import's step 1 method selection logic 2015-11-07 11:31:40 -05:00
ae03ee2c46 - Bootstrapping import menu 2015-11-07 11:25:05 -05:00
ad0adb48e1 - Updates Migrator with new column for Segments
- Updates Segmnets tests
- Updates MailPoet's Notice.js with additional options
- Updates Import's router, WP menu bootstrap logic, client- and
  server-side logic
2015-11-07 11:24:02 -05:00
ff5353c894 - Completes MailChimp import 2015-11-07 11:15:49 -05:00
abb2389378 - Enables MailChimp key verification (import step 1) 2015-11-07 11:15:04 -05:00
3cf50810f9 - Fixed conflic with backbone router in settings and import
- WIP on step 1
2015-11-07 11:09:13 -05:00
20a6e6d6de - Adds import's step 1 method selection logic 2015-11-07 11:09:13 -05:00
0b1fc8f6c3 - Updates webpack config file
- Adds an option to watch/compile just the JS files with webpack
2015-11-07 11:05:48 -05:00
0a771acb02 - Bootstrapping import menu 2015-11-06 21:38:25 -05:00
0199e2c7e1 Version bump: 0.0.3 2015-11-06 21:49:28 +01:00
d1f407bf19 Merge branch 'master' of github.com:mailpoet/mailpoet 2015-11-06 21:47:49 +01:00
f18d2842b9 Version bump: 0.0.3. 2015-11-06 21:47:17 +01:00
f640cbb307 Merge pull request #203 from mailpoet/form_editor
Form editor
2015-11-06 21:39:03 +01:00
b20d92c9b1 fixed unit tests and added form model unit test 2015-11-06 18:43:56 +01:00
795485d42a fixed sortable segments in form editor 2015-11-06 16:09:56 +01:00
dfadda2d12 converted form renderer validation to Parsley 2015-11-06 16:09:56 +01:00
31305a04c0 form validation with Parsley 2015-11-06 16:09:56 +01:00
cfdb886e88 Date widget
- fixed date widget
- fixed default styles for radio inputs in form rendering
2015-11-06 16:09:09 +01:00
1ce4b16327 custom fields (create and update) 2015-11-06 16:09:09 +01:00
b12f7f29de Custom fields
- added listing of custom fields in form editor
- ability to delete a custom field
- mades changes to the form editor in order to accomodate for the new custom fields system
- fix form editor bugs
- cleanup
2015-11-06 16:09:09 +01:00
5473f94e24 List selection & subscribe
- fixed list selection widget (form editor & rendered form)
- ajax subscription works (minus sending the confirmation email)
- bug fixes / polishing / refactoring / cleanup
2015-11-06 16:09:09 +01:00
a31dce6226 fixed list selection widget + started form submission 2015-11-06 16:09:09 +01:00
d996b78561 fixed form_editor.js being ignored by git 2015-11-06 16:09:09 +01:00
c2e7513381 Form editor
- new form with default data
- load/save in form editor
- widgets -> settings form
- widgets -> shortcode for subscribers count
- widgets -> form rendering
- added useful filters to Subscribers (for status related search)
- refactor & cleanup
2015-11-06 16:09:09 +01:00
541696863e Form editor
- new/edit in forms listing goes to editor
- fixed editor dependencies (via Webpack)
- updated forms table schema
- saving/loading a form works
2015-11-06 16:09:09 +01:00
6c8d2be18c fix for selection field jsx 2015-11-06 16:08:16 +01:00
907fe585de add Form renderer and fixed Newsletter saving issue 2015-11-06 16:08:16 +01:00
e24f8c9653 forms listing complete 2015-11-06 16:08:16 +01:00
5df0475b1a Merge pull request #204 from mailpoet/newsletter_templates
Newsletter template import/export
2015-11-06 12:23:27 +01:00
cf154455e3 Add error reporting for newsletter template export fields 2015-11-05 19:11:32 +02:00
dcfe6357cf Switch to FileSaver lib for downloading Blob files, add Blob polyfill 2015-11-05 17:17:54 +02:00
983df216f3 Add basic template export 2015-11-04 18:02:55 +02:00
f750d2359f Base for template import 2015-11-03 16:33:13 +02:00
d85f51e9fc Update composer lockfile. 2015-10-30 22:26:40 +01:00
40a62687cf Update Composer lockfile. 2015-10-30 22:13:00 +01:00
136e09e9fb Merge pull request #202 from mailpoet/newsletter_width
Increase newsletter width to 660px from 600px
2015-10-30 12:37:16 +01:00
f509dc0d7e Increase newsletter width to 660px from 600px 2015-10-30 13:25:45 +02:00
c100130f39 Merge pull request #201 from mailpoet/forms
Listing/Model/Router refactoring + Forms
2015-10-30 11:45:34 +01:00
9922ecd93c Merge pull request #200 from mailpoet/notification_type
Add notification email type
2015-10-30 11:40:50 +01:00
a4cf2f9c76 Major refactoring of listing/router/model relation
- updated Subscribers listing
- udpated Segments listing
- added Forms router
2015-10-29 15:30:24 +01:00
576fbf2085 Add notification email type 2015-10-29 15:59:09 +02:00
5c63971314 Merge pull request #198 from mailpoet/editor_select2
Update select2 version to 4.0 for newsletter editor
2015-10-28 15:30:08 +01:00
7418923bbc Remove glow and margins from select2 input 2015-10-28 16:01:23 +02:00
a8f8134f67 Adapt select2 integration code to select2 4.0 2015-10-28 16:01:23 +02:00
103da61d45 basic listing files 2015-10-28 13:19:48 +01:00
01e6a5e6b2 forms table 2015-10-28 13:19:48 +01:00
f5ccf3b38a Merge pull request #195 from mailpoet/subscribers_round_1
Subscriber & Segment listings
2015-10-28 12:24:19 +01:00
c8929351ba Merge pull request #196 from mailpoet/openssl
use of Crypt\RSA library in order to generate dkim keys
2015-10-28 12:23:33 +01:00
6ca536e9ca use of Crypt\RSA library in order to generate dkim keys 2015-10-27 16:41:28 +01:00
89b04e8691 Subscriber & Segment listings
- fixed filters
- added load/save state from url
- added goBack on forms in order to get back listing states
wx# Please enter the commit message for your changes. Lines starting
2015-10-27 16:24:00 +01:00
588b441fb1 Merge pull request #194 from mailpoet/lists_round_1
Lists round 1
2015-10-27 10:30:45 +01:00
13dc3577f1 lotta fixes for filtering + listing 2015-10-26 18:23:32 +01:00
505b979ac5 Segment actions
- added duplicate
- added view subscribers
2015-10-23 17:34:35 +02:00
3b4c5c83e1 added segment stats in listing 2015-10-22 20:40:46 +02:00
056e79eeac bugfix 2015-10-22 19:24:58 +02:00
4bde705f04 listing modifications + added description to segments 2015-10-22 19:19:40 +02:00
174 changed files with 16590 additions and 3285 deletions

3
.gitignore vendored
View File

@ -15,4 +15,5 @@ temp
wysija-newsletters.zip
tests/javascript/testBundles
assets/css/*.css
assets/js/*.js
assets/js/*.js
.vagrant

View File

@ -47,6 +47,10 @@ class RoboFile extends \Robo\Tasks {
->run();
}
function watchJs() {
$this->_exec('./node_modules/webpack/bin/webpack.js --watch');
}
function compileAll() {
$this->compileJs();
$this->compileCss();
@ -61,7 +65,8 @@ class RoboFile extends \Robo\Tasks {
'assets/css/src/admin.styl',
'assets/css/src/newsletter_editor/newsletter_editor.styl',
'assets/css/src/public.styl',
'assets/css/src/rtl.styl'
'assets/css/src/rtl.styl',
'assets/css/src/importExport.styl'
);
$this->_exec(join(' ', array(
@ -113,7 +118,6 @@ class RoboFile extends \Robo\Tasks {
function testFailed() {
$this->loadEnv();
$this->_exec('vendor/bin/codecept build');
$this->startPhantomJS();
$this->_exec('vendor/bin/codecept run -g failed');
}
@ -121,4 +125,4 @@ class RoboFile extends \Robo\Tasks {
$dotenv = new Dotenv\Dotenv(__DIR__);
$dotenv->load();
}
}
}

67
Vagrantfile vendored Normal file
View File

@ -0,0 +1,67 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure(2) do |config|
config.vm.provider "virtualbox" do |v|
v.name = "phoenix"
v.memory = "2048"
end
config.vm.define :web do |web|
web.vm.box = "ubuntu/trusty64"
web.vm.hostname = "phoenix"
web.vm.network "forwarded_port", guest: 80, host: 8080
web.vm.synced_folder(
".",
"/var/www/html/wp-content/plugins/wysija-newsletters",
create: true,
owner: "vagrant",
group: "www-data"
)
web.vm.provision "shell", inline: <<-SHELL
sudo apt-get update
sudo apt-get install -y apache2 curl zip sendmail git build-essential
sudo debconf-set-selections <<< 'mysql-server mysql-server/root_password password root'
sudo debconf-set-selections <<< 'mysql-server mysql-server/root_password_again password root'
sudo apt-get install -y mysql-server-5.5
sudo apt-get install -y php5 libapache2-mod-php5 php5-curl php5-gd php5-mcrypt php5-readline mysql-server-5.5 php5-mysql php-apc
sudo sed -i "s/error_reporting = .*/error_reporting = E_ALL/" /etc/php5/apache2/php.ini
sudo sed -i "s/display_errors = .*/display_errors = On/" /etc/php5/apache2/php.ini
cd /var/www/html
sudo wget https://github.com/calvinlough/sqlbuddy/raw/gh-pages/sqlbuddy.zip -O /var/www/html/sqlbuddy.zip
sudo rm index.html
unzip sqlbuddy.zip
sudo curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
sudo chmod +x wp-cli.phar
sudo mv wp-cli.phar /usr/local/bin/wp
sudo wp core download --allow-root
mysql -uroot -proot -e "create database wordpress"
sudo wp core config --allow-root --dbname=wordpress --dbuser=root --dbpass=root
sudo wp core install --allow-root --url="http://localhost:8080" --title=WordPress --admin_user=admin --admin_password=password --admin_email=test@mailpoet-container.com
sudo sed -i "s/upload_max_filesize = .*/upload_max_filesize = 32M/" /etc/php5/apache2/php.ini
sudo sed -i "s/post_max_size = .*/post_max_size = 32M/" /etc/php5/apache2/php.ini
sudo chown -hR vagrant:www-data /var/www/html/
sudo a2enmod rewrite > /dev/null 2>&1
cd /var/www/html/wp-content/plugins/wysija-newsletters
curl -sS https://getcomposer.org/installer | php
sudo add-apt-repository -y ppa:chris-lea/node.js
sudo apt-get update
sudo apt-get install -y nodejs
sudo sed -i "s/export APACHE_RUN_USER.*/export APACHE_RUN_USER=vagrant/" /etc/apache2/envvars
sudo chown -R vagrant:www-data /var/lock/apache2
sudo service apache2 restart
SHELL
end
end

View File

@ -5,7 +5,7 @@
@require 'common'
@require 'modal'
@require 'notice'
@require 'validation_engine'
@require 'parsley'
@require 'form_editor'
@require 'listing'

View File

@ -19,8 +19,10 @@ a:focus
// select 2
.select2-container
// textareas
textarea.regular-text
width: 25em !important
@media screen and (max-width: 782px)
.select2-container
width: 100% !important
width: 100% !important

View File

@ -1,3 +1,6 @@
@require 'codemirror/lib/codemirror.css'
@require 'codemirror/theme/neo.css'
icons = '../img/form_editor_icons.png'
handle_icon = '../img/handle.png'

View File

@ -0,0 +1,78 @@
.mailpoet_hidden, .mailpoet_validation_error
display none
.form-table
th
width 300px
#paste_input
width 100%
input[type="radio"]
margin-right 0.5em !important
& + span
margin-right 2.5em
span
&.mailpoet_mailchimp-key-status
&.mailpoet_mailchimp-ok
&:before
content "\2713"
color #0e90d2
margin-left 15px
&.mailpoet_mailchimp-error
&:before
content "\2717"
color #900
margin-left 15px
#subscribers_data
overflow auto
table
width auto
td
padding 0.5em
& > table
& > tbody
& > td
padding 0.5em
& > tr
&:nth-child(odd)
background #f9f9f9
.mailpoet_header
text-transform uppercase
font-weight 600
text-decoration underline
#subscribers_data th:first-child, #subscribers_data td:first-child
width 10em !important
text-align center !important
padding 0 1em 0 1em !important
vertical-align inherit !important
#subscribers_data
& > table
& > thead
& > tr
& > th
& > span
width 15em !important
.mailpoet_data_match
color #0e90d2
margin-left 0.25em
.mailpoet_import_error, .mailpoet_validation_error
color #900
tr
&.mailpoet_segments
& > td
& > a
margin-left 15px
span
&.select2-search
&.select2-search--dropdown
display none !important

View File

@ -1,4 +1,4 @@
.mailpoet_listing_loading tbody tr,
.mailpoet_listing_loading tbody tr
.mailpoet_form_loading tbody tr
opacity: 0.2
@ -8,6 +8,20 @@
.mailpoet_select_all td
text-align: center
table.widefat thead .check-column,
table.widefat tfoot .check-column
padding: 10px 0 0 3px
.mailpoet_listing_table
th span
white-space: nowrap
thead .check-column
tfoot .check-column
padding: 10px 0 0 3px
thead th.column-primary
tfoot th.column-primary
width: 25em
// responsive
@media screen and (max-width: 782px)
thead th.column-primary
tfoot th.column-primary
width: 100%

View File

@ -159,23 +159,6 @@ body.mailpoet_modal_opened
margin: 0
text-align: right
.mailpoet_button
padding: 3px 15px
border: 1px solid #444
font-weight: normal
cursor: pointer
background-color: #222
color: #cfcfcf
font-size: 1em
.mailpoet_button:hover
background-color: #00aacc
color: #fff
.mailpoet_button:active
background-color: #00ccff
color: #fff
@media screen and (max-width: 782px)
#mailpoet_modal_overlay.mailpoet_panel_overlay
top: 46px

View File

@ -45,7 +45,8 @@
&::before
content: '\f140'
.mailpoet_save_as_template_container
.mailpoet_save_as_template_container,
.mailpoet_export_template_container
border-radius(3px)
float: left
clear: both
@ -55,7 +56,8 @@
background-color: $white-color
border: 1px solid $structure-border-color
.mailpoet_save_as_template_title
.mailpoet_save_as_template_title,
.mailpoet_export_template_title
font-size: 1.1em
.mailpoet_editor_last_saved

View File

@ -1,3 +1,8 @@
$column-margin = 20px
$one-column-width = $newsletter-width - (2 * $column-margin)
$two-column-width = ($newsletter-width / 2) - (2 * $column-margin)
$three-column-width = ($newsletter-width / 3) - (2 * $column-margin)
.mailpoet_container
width: 100%
min-height: 15px
@ -44,7 +49,7 @@
.mailpoet_container_horizontal > .mailpoet_container_block
margin-bottom: 0
width: 20px + 560px + 20px
width: $column-margin + $one-column-width + $column-margin
// More than one column
& > .mailpoet_container_block > .mailpoet_container > .mailpoet_container_block > .mailpoet_container_horizontal
@ -57,14 +62,14 @@
& > .mailpoet_block:first-child:nth-last-child(2) ~ .mailpoet_block
//padding-left: 20px
//padding-right: 20px
width: 260px + 20px + 20px
width: $column-margin + $two-column-width + $column-margin
// Three columns
& > .mailpoet_block:first-child:nth-last-child(3)
& > .mailpoet_block:first-child:nth-last-child(3) ~ .mailpoet_block
//padding-left: 20px
//padding-right: 20px
width: 160px + 20px + 20px
width: $column-margin + $three-column-width + $column-margin
.mailpoet_container_empty
text-align: center

View File

@ -19,6 +19,10 @@ $divider-hover-border-color = $primary-active-color
width: 100%
border: 1px solid transparent
.mailpoet_active_divider_style
border: 1px solid $active-divider-border-color
background: $active-divider-background-color
.mailpoet_field_divider_style:hover
border: 1px solid $divider-hover-border-color

View File

@ -1,15 +1,19 @@
.mailpoet_image_block
img
vertical-align: bottom
max-width: 100%
width: auto
height: auto
&.mailpoet_full_image
padding-left: 0
padding-right: 0
margin-bottom: 0
img
width: 100%
.mailpoet_content a:hover
cursor: all-scroll
img
vertical-align: bottom
max-width: $newsletter-width
width: 100%
height: auto

View File

@ -1,15 +1,12 @@
.mailpoet_posts_block
box-shadow(none)
padding-left: 0
padding-right: 0
& > .mailpoet_content
font-size: 1em
text-align: center
background-color: $primary-active-color
margin: 20px 0
padding: 15px
box-shadow(inset 1px 2px 1px $primary-inset-shadow-color)
color: $white-color
border-radius(3px)
.mailpoet_posts_block_posts
overflow: auto
& > .mailpoet_block
width: 100%
.mailpoet_post_selection_filter_row
margin-top: 5px

View File

@ -1,12 +1,25 @@
/* Fix select2 z-index to work with MailPoet.Modal */
.select2-drop
z-index: 101000
.select2-dropdown
z-index: 101000 !important
/* Remove input field styles from select2 type input */
.select2-container
border: none
padding: 0
/* Fix select2 input glow and margins that wordpress may insert */
.select2 input,
.select2 input:focus
border-color: none
box-shadow: none
margin: 0
padding: 0
/* Fix width overrides for select2 */
.mailpoet_editor_settings .select2-container
width: 100% !important
/* Fix inline TinyMCE toolbar to have minimal width instead of being close to 100% of the screen */
div.mce-toolbar-grp.mce-container
position: absolute

View File

@ -23,4 +23,4 @@ $warning-alternate-text-color = #f4c6c8
$error-text-color = #d54e21
// Dimensions
$newsletter-width = 600px
$newsletter-width = 660px

View File

@ -0,0 +1,27 @@
input.parsley-success,
select.parsley-success,
textarea.parsley-success
color #468847
background-color #DFF0D8
border 1px solid #D6E9C6
input.parsley-error,
select.parsley-error,
textarea.parsley-error
color #B94A48
background-color #F2DEDE
border 1px solid #EED3D7
.parsley-errors-list
margin 2px 0 3px
padding 0
list-style-type none
font-size 0.9em
line-height 0.9em
opacity 0
transition all .3s ease-in
-o-transition all .3s ease-in
-moz-transition all .3s ease-in
-webkit-transition all .3s ease-in
&.filled
opacity 1

View File

@ -0,0 +1,3 @@
@import 'nib'
@require 'parsley'

View File

@ -1,141 +0,0 @@
lesscss-percentage(n)
(n * 100)%
popupBg = rgb(0, 87, 154, 1)
popupTextColor = rgb(255, 255, 255, 1)
borderColor = rgb(255, 255, 255, 1)
borderWidth = 1px
popupFontSize = 12px
popupRadius = 0
popupShadowWidth = 2px
popupShadowColor = rgb(51, 51, 51, 1)
/* Z-INDEX */
.formError
z-index 990
.formError .formErrorContent
z-index 991
.formError .formErrorArrow
z-index 996
.ui-dialog .formError
z-index 5000
.ui-dialog .formError .formErrorContent
z-index 5001
.ui-dialog .formError .formErrorArrow
z-index 5006
.inputContainer
position relative
float left
.formError
position absolute
top 300px
left 300px
display block
cursor pointer
text-align left
.formError.inline
position relative
top 0
left 0
display inline-block
.ajaxSubmit
padding 20px
background #55ea55
border 1px solid #999
display none
.formError .formErrorContent
width 100%
background popupBg
position relative
color popupTextColor
min-width 120px
font-size popupFontSize
border borderWidth solid borderColor
box-shadow 0 0 popupShadowWidth popupShadowColor
-moz-box-shadow 0 0 popupShadowWidth popupShadowColor
-webkit-box-shadow 0 0 popupShadowWidth popupShadowColor
-o-box-shadow 0 0 popupShadowWidth popupShadowColor
padding 4px 10px 4px 10px
border-radius popupRadius
-moz-border-radius popupRadius
-webkit-border-radius popupRadius
-o-border-radius popupRadius
.formError.inline .formErrorContent
box-shadow none
-moz-box-shadow none
-webkit-box-shadow none
-o-box-shadow none
border none
border-radius 0
-moz-border-radius 0
-webkit-border-radius 0
-o-border-radius 0
.greenPopup .formErrorContent
background #33be40
.blackPopup .formErrorContent
background #393939
color #FFF
.formError .formErrorArrow
width 15px
margin -2px 0 0 13px
position relative
body[dir='rtl'] .formError .formErrorArrow, body.rtl .formError .formErrorArrow
margin -2px 13px 0 0
.formError .formErrorArrowBottom
box-shadow none
-moz-box-shadow none
-webkit-box-shadow none
-o-box-shadow none
margin 0px 0 0 12px
top 2px
.formError .formErrorArrow div
border-left borderWidth solid borderColor
border-right borderWidth solid borderColor
box-shadow 0 ceil((popupShadowWidth / 3)) ceil((popupShadowWidth / 2)) lighten(popupShadowColor, 10%)
-moz-box-shadow 0 ceil((popupShadowWidth / 3)) ceil((popupShadowWidth / 2)) lighten(popupShadowColor, 10%)
-webkit-box-shadow 0 ceil((popupShadowWidth / 3)) ceil((popupShadowWidth / 2)) lighten(popupShadowColor, 10%)
-o-box-shadow 0 ceil((popupShadowWidth / 3)) ceil((popupShadowWidth / 2)) lighten(popupShadowColor, 10%)
font-size 0px
height 1px
background popupBg
margin 0 auto
line-height 0
font-size 0
display block
.formError .formErrorArrowBottom div
box-shadow none
-moz-box-shadow none
-webkit-box-shadow none
-o-box-shadow none
.greenPopup .formErrorArrow div
background #33be40
.blackPopup .formErrorArrow div
background #393939
color #FFF
.formError .formErrorArrow .line10
width 13px
border none
.formError .formErrorArrow .line9
width 11px
border none
.formError .formErrorArrow .line8
width 11px
.formError .formErrorArrow .line7
width 9px
.formError .formErrorArrow .line6
width 7px
.formError .formErrorArrow .line5
width 5px
.formError .formErrorArrow .line4
width 3px
.formError .formErrorArrow .line3
width ceil((borderWidth / 2))
border-left borderWidth solid borderColor
border-right borderWidth solid borderColor
border-bottom 0 solid borderColor
.formError .formErrorArrow .line2
width 3px
border none
background borderColor
.formError .formErrorArrow .line1
width 1px
border none
background borderColor

View File

@ -0,0 +1,8 @@
(function(e,b){if(!b.__SV){var a,f,i,g;window.mixpanel=b;b._i=[];b.init=function(a,e,d){function f(b,h){var a=h.split(".");2==a.length&&(b=b[a[0]],h=a[1]);b[h]=function(){b.push([h].concat(Array.prototype.slice.call(arguments,0)))}}var c=b;"undefined"!==typeof d?c=b[d]=[]:d="mixpanel";c.people=c.people||[];c.toString=function(b){var a="mixpanel";"mixpanel"!==d&&(a+="."+d);b||(a+=" (stub)");return a};c.people.toString=function(){return c.toString(1)+".people (stub)"};i="disable time_event track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config people.set people.set_once people.increment people.append people.union people.track_charge people.clear_charges people.delete_user".split(" ");
for(g=0;g<i.length;g++)f(c,i[g]);b._i.push([a,e,d])};b.__SV=1.2;a=e.createElement("script");a.type="text/javascript";a.async=!0;a.src="undefined"!==typeof MIXPANEL_CUSTOM_LIB_URL?MIXPANEL_CUSTOM_LIB_URL:"file:"===e.location.protocol&&"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js".match(/^\/\//)?"https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js":"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js";f=e.getElementsByTagName("script")[0];f.parentNode.insertBefore(a,f)}})(document,window.mixpanel||[]);
mixpanel.init("f683d388fb25fcf331f1b2b5c4449798");
if (typeof mailpoet_analytics_data === 'object') {
mixpanel.track('Wysija Usage', mailpoet_analytics_data || {});
}

4
assets/js/lib/prototype.min.js vendored Normal file

File diff suppressed because one or more lines are too long

2
assets/js/lib/scriptaculous.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,170 @@
define(
[
'underscore',
'jquery',
'mailpoet',
'handlebars',
'select2'
],
function (
_,
jQuery,
MailPoet,
Handlebars
) {
if (!jQuery("#mailpoet_subscribers_export").length) {
return;
}
jQuery(document).ready(function () {
if (!exportData.segments) {
return;
}
var subscribers_export_template =
Handlebars.compile(jQuery('#mailpoet_subscribers_export_template').html());
//render template
jQuery('#mailpoet_subscribers_export > div.inside').html(subscribers_export_template(exportData));
// define reusable variables
var segmentsContainerElement = jQuery("#export_lists"),
subscriberFieldsContainerElement = jQuery("#export_columns"),
exportConfirmedOptionElement = jQuery(':radio[name="option_confirmed"]'),
groupBySegmentOptionElement = jQuery(':checkbox[name="option_group_by_list"]'),
nextStepButton = jQuery("a.mailpoet_export_process"),
renderSegmentsAndFields = function (container, data) {
if (container.data('select2')) {
container
.html('')
.select2('destroy');
}
container
.select2({
data: data,
width: '20em',
templateResult: function (item) {
return (item.subscriberCount > 0)
? item.name + ' (' + item.subscriberCount + ')'
: item.name;
},
templateSelection: function (item) {
return (item.subscriberCount > 0)
? item.name + ' (' + item.subscriberCount + ')'
: item.name;
}
})
.on('select2:selecting', function (selectEvent) {
var selectElement = this,
selectedOptionId = selectEvent.params.args.data.id,
fieldsToExclude = [
'select',
'deselect'
];
if (_.contains(fieldsToExclude, selectedOptionId)) {
selectEvent.preventDefault();
if (selectedOptionId === 'deselect') {
jQuery(selectElement).select2('val', '');
} else {
var allOptions = [];
_.each(container.find('option'), function (field) {
if (!_.contains(fieldsToExclude, field.value)) {
allOptions.push(field.value);
}
});
jQuery(selectElement).select2('val', allOptions);
}
jQuery(selectElement).select2('close');
}
})
.on('change', function () {
if ((exportData.segments && segmentsContainerElement.select2('data').length && subscriberFieldsContainerElement.select2('data').length)
||
(!exportData.segments && subscriberFieldsContainerElement.select2('data').length)
) {
toggleNextStepButton('on');
}
else {
toggleNextStepButton('off');
}
if (segmentsContainerElement.select2('data').length > 1 && exportData.groupBySegmentOption) {
jQuery('.mailpoet_group_by_list').show();
}
else if (exportData.groupBySegmentOption) {
jQuery('.mailpoet_group_by_list').hide();
}
});
};
renderSegmentsAndFields(subscriberFieldsContainerElement, subscriberFieldsSelect2);
renderSegmentsAndFields(segmentsContainerElement, segments);
subscriberFieldsContainerElement.select2('val', [
'status',
'email',
'first_name',
'last_name'
]);
exportConfirmedOptionElement.change(function () {
var selectedSegments = segmentsContainerElement.val();
if (this.value == 1) {
exportData.exportConfirmedOption = true;
renderSegmentsAndFields(segmentsContainerElement, segmentsWithConfirmedSubscribers);
}
else {
exportData.exportConfirmedOption = false;
renderSegmentsAndFields(segmentsContainerElement, segments);
}
segmentsContainerElement.select2('val', selectedSegments);
});
function toggleNextStepButton(condition) {
var disabled = 'button-disabled';
if (condition === 'on') {
nextStepButton.removeClass(disabled);
}
else {
nextStepButton.addClass(disabled);
}
}
nextStepButton.click(function () {
if (jQuery(this).hasClass('button-disabled')) {
return;
}
MailPoet.Modal.loading(true);
MailPoet.Ajax
.post({
endpoint: 'ImportExport',
action: 'processExport',
data: JSON.stringify({
'exportConfirmedOption': exportData.exportConfirmedOption,
'exportFormatOption': jQuery(':radio[name="option_format"]:checked').val(),
'groupBySegmentOption': (groupBySegmentOptionElement.is(":visible")) ? groupBySegmentOptionElement.prop('checked') : false,
'segments': (exportData.segments) ? segmentsContainerElement.val() : false,
'subscriberFields': subscriberFieldsContainerElement.val()
})
})
.done(function (response) {
MailPoet.Modal.loading(false);
if (response.result === false) {
MailPoet.Notice.error(response.error);
} else {
resultMessage = MailPoetI18n.exportMessage
.replace('%1$s', '<strong>' + response.data.totalExported + '</strong>')
.replace('[link]', '<a href="' + response.data.exportFileURL + '" target="_blank" >')
.replace('[/link]', '</a>');
jQuery('#export_result_notice > ul > li').html(resultMessage);
jQuery('#export_result_notice').show();
window.location.href = response.data.exportFileURL;
}
})
.error(function (error) {
MailPoet.Modal.loading(false);
MailPoet.Notice.error(
MailPoetI18n.serverError + error.statusText.toLowerCase() + '.'
);
});
});
});
});

View File

@ -1,10 +1,12 @@
define([
'react',
'react-dom',
'jquery',
'select2'
],
function(
React,
ReactDOM,
jQuery
) {
var Selection = React.createClass({
@ -16,50 +18,72 @@ function(
},
componentDidMount: function() {
this.loadCachedItems();
},
componentDidUpdate: function() {
this.setupSelect2();
},
setupSelect2: function() {
if(this.state.initialized === true) {
return;
}
if(this.props.field.select2 && Object.keys(this.props.item).length > 0) {
var select2 = jQuery('#'+this.props.field.id).select2({
width: (this.props.width || ''),
templateResult: function(item) {
if (item.element && item.element.selected) {
return null;
} else {
return item.text;
}
}
});
select2.on('change', this.handleChange)
select2.select2(
componentDidUpdate: function(prevProps, prevState) {
if(
(this.props.item !== undefined && prevProps.item !== undefined)
&& (this.props.item.id !== prevProps.item.id)
) {
jQuery('#'+this.refs.select.id).select2(
'val',
this.props.item[this.props.field.name]
);
this.setState({ initialized: true });
}
this.setupSelect2();
},
setupSelect2: function() {
if(
!this.props.field.multiple
|| this.state.initialized === true
|| this.refs.select === undefined
) {
return;
}
var select2 = jQuery('#'+this.refs.select.id).select2({
width: (this.props.width || ''),
templateResult: function(item) {
if(item.element && item.element.selected) {
return null;
} else {
return item.text;
}
}
});
select2.on('change', this.handleChange);
select2.select2(
'val',
this.props.item[this.props.field.name]
);
this.setState({ initialized: true });
},
loadCachedItems: function() {
if(typeof(window['mailpoet_'+this.props.field.endpoint]) !== 'undefined') {
var items = window['mailpoet_'+this.props.field.endpoint];
if(this.props.field['filter'] !== undefined) {
items = items.filter(this.props.field.filter);
}
this.setState({
items: items
});
}
},
handleChange: function() {
handleChange: function(e) {
if(this.props.onValueChange !== undefined) {
if(this.props.field.multiple) {
value = jQuery('#'+this.refs.select.id).select2('val');
} else {
value = e.target.value;
}
this.props.onValueChange({
target: {
value: jQuery('#'+this.props.field.id).select2('val'),
value: value,
name: this.props.field.name
}
});
@ -67,36 +91,33 @@ function(
return true;
},
render: function() {
if(this.state.items.length === 0) {
return false;
} else {
var options = this.state.items.map(function(item, index) {
return (
<option
key={ item.id }
value={ item.id }
>
{ item.name }
</option>
);
});
var default_value = (
(this.props.item !== undefined && this.props.field.name !== undefined)
? this.props.item[this.props.field.name]
: null
);
var options = this.state.items.map(function(item, index) {
return (
<select
id={ this.props.field.id }
placeholder={ this.props.field.placeholder }
multiple={ this.props.field.multiple }
onChange={ this.handleChange }
defaultValue={ default_value }
>{ options }</select>
<option
key={ item.id }
value={ item.id }
>
{ item.name }
</option>
);
}
});
var default_value = (
(this.props.item !== undefined && this.props.field.name !== undefined)
? this.props.item[this.props.field.name]
: null
);
return (
<select
id={ this.props.field.id || this.props.field.name }
ref="select"
placeholder={ this.props.field.placeholder }
multiple={ this.props.field.multiple }
onChange={ this.handleChange }
defaultValue={ default_value }
>{ options }</select>
);
}
});

View File

@ -70,25 +70,41 @@ define(
this.setState({ loading: true });
// only get values from displayed fields
item = {};
this.props.fields.map(function(field) {
item[field.name] = this.state.item[field.name];
}.bind(this));
// set id if specified
if(this.props.params.id !== undefined) {
item.id = this.props.params.id;
}
MailPoet.Ajax.post({
endpoint: this.props.endpoint,
action: 'save',
data: this.state.item
data: item
}).done(function(response) {
this.setState({ loading: false });
if(response === true) {
this.history.pushState(null, '/');
if(this.props.params.id !== undefined) {
this.props.messages['updated']();
if(response.result === true) {
if(this.props.onSuccess !== undefined) {
this.props.onSuccess();
} else {
this.props.messages['created']();
this.history.pushState(null, '/')
}
if(this.props.params.id !== undefined) {
this.props.messages.onUpdate();
} else {
this.props.messages.onCreate();
}
} else {
if(response === false) {
// unknown error occurred
} else {
this.setState({ errors: response });
if(response.result === false) {
if(response.errors.length > 0) {
this.setState({ errors: response.errors });
}
}
}
}.bind(this));
@ -105,13 +121,15 @@ define(
return true;
},
render: function() {
var errors = this.state.errors.map(function(error, index) {
return (
<p key={ 'error-'+index } className="mailpoet_error">
{ error }
</p>
);
});
if(this.state.errors !== undefined) {
var errors = this.state.errors.map(function(error, index) {
return (
<p key={ 'error-'+index } className="mailpoet_error">
{ error }
</p>
);
});
}
var formClasses = classNames(
'mailpoet_form',

View File

@ -1,993 +0,0 @@
/*
* name: MailPoet Form Editor
* author: Jonathan Labreuille
* company: Wysija
* framework: prototype 1.7.2
*/
'use strict';
Event.cacheDelegated = {};
Object.extend(document, (function () {
var cache = Event.cacheDelegated;
function getCacheForSelector(selector) {
return cache[selector] = cache[selector] || {};
}
function getWrappersForSelector(selector, eventName) {
var c = getCacheForSelector(selector);
return c[eventName] = c[eventName] || [];
}
function findWrapper(selector, eventName, handler) {
var c = getWrappersForSelector(selector, eventName);
return c.find(function (wrapper) {
return wrapper.handler === handler
});
}
function destroyWrapper(selector, eventName, handler) {
var c = getCacheForSelector(selector);
if (!c[eventName]) return false;
var wrapper = findWrapper(selector, eventName, handler)
c[eventName] = c[eventName].without(wrapper);
return wrapper;
}
function createWrapper(selector, eventName, handler, context) {
var wrapper, c = getWrappersForSelector(selector, eventName);
if (c.pluck('handler').include(handler)) return false;
wrapper = function (event) {
var element = event.findElement(selector);
if (element) handler.call(context || element, event, element);
};
wrapper.handler = handler;
c.push(wrapper);
return wrapper;
}
return {
delegate: function (selector, eventName, handler, context) {
var wrapper = createWrapper.apply(null, arguments);
if (wrapper) document.observe(eventName, wrapper);
return document;
},
stopDelegating: function (selector, eventName, handler) {
var length = arguments.length;
switch (length) {
case 2:
getWrappersForSelector(selector, eventName).each(function (wrapper) {
document.stopDelegating(selector, eventName, wrapper.handler);
});
break;
case 1:
Object.keys(getCacheForSelector(selector)).each(function (eventName) {
document.stopDelegating(selector, eventName);
});
break;
case 0:
Object.keys(cache).each(function (selector) {
document.stopDelegating(selector);
});
break;
default:
var wrapper = destroyWrapper.apply(null, arguments);
if (wrapper) document.stopObserving(eventName, wrapper);
}
return document;
}
}
})());
var Observable = (function () {
function getEventName(name, namespace) {
name = name.substring(2);
if (namespace) name = namespace + ':' + name;
return name.underscore().split('_').join(':');
}
function getHandlers(klass) {
var proto = klass.prototype,
namespace = proto.namespace;
return Object.keys(proto).grep(/^on/).inject($H(), function (handlers, name) {
if (name === 'onDomLoaded') return handlers;
handlers.set(getEventName(name, namespace), getWrapper(proto[name], klass));
return handlers;
});
}
function getWrapper(handler, klass) {
return function (event) {
return handler.call(new klass(this), event, event.memo);
}
}
function onDomLoad(selector, klass) {
$$(selector).each(function (element) {
new klass(element).onDomLoaded();
});
}
return {
observe: function (selector) {
if (!this.handlers) this.handlers = {};
if (this.handlers[selector]) return;
var klass = this;
if (this.prototype.onDomLoaded) document.loaded ? onDomLoad(selector, klass) : document.observe('dom:loaded', onDomLoad.curry(selector, klass));
this.handlers[selector] = getHandlers(klass).each(function (handler) {
document.delegate(selector, handler.key, handler.value);
});
},
stopObserving: function (selector) {
if (!this.handlers || !this.handlers[selector]) return;
this.handlers[selector].each(function (handler) {
document.stopDelegating(selector, handler.key, handler.value);
});
delete this.handlers[selector];
}
}
})();
// override droppables
Object.extend(Droppables, {
deactivate: Droppables.deactivate.wrap(function (proceed, drop, draggable) {
if (drop.onLeave) drop.onLeave(draggable, drop.element);
return proceed(drop);
}),
activate: Droppables.activate.wrap(function (proceed, drop, draggable) {
if (drop.onEnter) drop.onEnter(draggable, drop.element);
return proceed(drop);
}),
show: function (point, element) {
if (!this.drops.length) return;
var drop, affected = [];
this.drops.each(function (drop) {
if (Droppables.isAffected(point, element, drop)) affected.push(drop);
});
if (affected.length > 0) drop = Droppables.findDeepestChild(affected);
if (this.last_active && this.last_active !== drop) this.deactivate(this.last_active, element);
if (drop) {
Position.within(drop.element, point[0], point[1]);
if (drop.onHover) drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
if (drop !== this.last_active) Droppables.activate(drop, element);
}
},
displayArea: function(draggable) {
if(!this.drops.length) return;
// hide controls when displaying drop areas.
WysijaForm.hideBlockControls();
this.drops.each(function (drop, iterator) {
if(drop.element.hasClassName('block_placeholder')) {
drop.element.addClassName('active');
}
});
},
hideArea: function() {
if (!this.drops.length) return;
this.drops.each(function (drop, iterator) {
if(drop.element.hasClassName('block_placeholder')) {
drop.element.removeClassName('active');
} else if(drop.element.hasClassName('image_placeholder')) {
drop.element.removeClassName('active');
drop.element.up().removeClassName('active');
} else if(drop.element.hasClassName('text_placeholder')) {
drop.element.removeClassName('active');
}
});
},
reset: function (draggable) {
if (this.last_active) this.deactivate(this.last_active, draggable);
}
});
/*
Wysija History handling
POTENTIAL FEATURES:
- set a maximum number of items to be stored
*/
var WysijaHistory = {
container: 'mailpoet_form_history',
size: 30,
enqueue: function(element) {
// create deep clone (includes child elements) of passed element
var clone = element.clone(true);
// check if the field is unique
if(parseInt(clone.readAttribute('wysija_unique'), 10) === 1) {
// check if the field is already in the queue
$(WysijaHistory.container).select('[wysija_field="'+clone.readAttribute('wysija_field')+'"]').invoke('remove');
}
// check history size
if($(WysijaHistory.container).select('> div').length >= WysijaHistory.size) {
// remove oldest element (last in the list)
$(WysijaHistory.container).select('> div').last().remove();
}
// store block in history
$(WysijaHistory.container).insert({ top: clone });
},
dequeue: function() {
// pop last block off the history
var block = $(WysijaHistory.container).select('div').first();
if(block !== undefined) {
// insert block back into the editor
$(WysijaForm.options.body).insert({top: block});
}
},
clear: function() {
$(WysijaHistory.container).innerHTML = '';
},
remove: function(field) {
$(WysijaHistory.container).select('[wysija_field="'+field+'"]').invoke('remove');
}
};
/* MailPoet Form */
var WysijaForm = {
version: '0.6',
options: {
container: 'mailpoet_form_container',
editor: 'mailpoet_form_editor',
body: 'mailpoet_form_body',
toolbar: 'mailpoet_form_toolbar',
templates: 'wysija_widget_templates',
debug: false
},
toolbar: {
effect: null,
x: null,
y: null,
top: null,
left: null
},
scroll: {
top: 0,
left: 0
},
flags: {
doSave: false
},
locks: {
dragging: false,
selectingColor: false,
showingTools: false
},
encodeHtmlValue: function(str) {
return str.replace(/&/g, '&amp;').replace(/>/g, '&gt;').replace(/</g, '&lt;').replace(/"/g, '&quot;');
// ": fix for FileMerge because the previous line fucks up its syntax coloring
},
decodeHtmlValue: function(str) {
return str.replace(/&amp;/g, '&').replace(/&gt;/g, '>').replace(/&lt;/g, '<').replace(/&quot;/g, '"');
// ": fix for FileMerge because the previous line fucks up its syntax coloring
},
loading: function(is_loading) {
if(is_loading) {
$(WysijaForm.options.editor).addClassName('loading');
$(WysijaForm.options.toolbar).addClassName('loading');
} else {
$(WysijaForm.options.editor).removeClassName('loading');
$(WysijaForm.options.toolbar).removeClassName('loading');
}
},
loadStatic: function(blocks) {
$A(blocks).each(function(block) {
// create block
WysijaForm.Block.create(block, $('block_placeholder'));
});
},
load: function(form) {
if(form.data === undefined) return;
// load body
if(form.data.body !== undefined) {
$A(form.data.body).each(function(block) {
// create block
WysijaForm.Block.create(block, $('block_placeholder'));
});
// load settings
var settings_elements = $('mailpoet_form_settings').getElements();
settings_elements.each(function(setting) {
// skip lists
if(setting.name === 'lists') {
return true;
} else if(setting.name === 'on_success') {
// if the input value is equal to the one stored in the settings
if(setting.value === form.data.settings[setting.name]) {
// check selected value
$(setting).checked = true;
}
} else if(form.data.settings[setting.name] !== undefined) {
if(typeof form.data.settings[setting.name] === 'string') {
setting.setValue(WysijaForm.decodeHtmlValue(form.data.settings[setting.name]));
} else {
setting.setValue(form.data.settings[setting.name]);
}
}
});
}
},
save: function() {
var position = 1,
data = {
'version': WysijaForm.version,
'settings': $('mailpoet_form_settings').serialize(true),
'body': [],
'styles': (MailPoet.CodeEditor !== undefined) ? MailPoet.CodeEditor.getValue() : null
};
// body
WysijaForm.getBlocks().each(function(b) {
var block_data = (typeof(b.block['save']) === 'function') ? b.block.save() : null;
if(block_data !== null) {
// set block position
block_data['position'] = position;
// increment position
position++;
// add block data to body
data['body'].push(block_data);
}
});
return data;
},
init: function() {
// set document scroll
info('init -> set scroll offsets');
WysijaForm.setScrollOffsets();
// position toolbar
info('init -> set toolbar position');
WysijaForm.setToolbarPosition();
// enable droppable targets
info('init -> make droppable');
WysijaForm.makeDroppable();
// enable sortable
info('init -> make sortable');
WysijaForm.makeSortable();
// hide controls
info('init -> hide controls');
WysijaForm.hideControls();
// hide settings
info('init -> hide settings');
WysijaForm.hideSettings();
// set settings buttons position
info('init -> init settings');
WysijaForm.setSettingsPosition();
// toggle widgets
info('init -> toggle widgets');
WysijaForm.toggleWidgets();
},
getFieldData: function(element) {
// get basic field data
var data = {
type: element.readAttribute('wysija_type'),
field: element.readAttribute('wysija_field'),
name: element.readAttribute('wysija_name'),
unique: parseInt(element.readAttribute('wysija_unique') || 0, 10),
static: parseInt(element.readAttribute('wysija_static') || 0, 10),
element: element,
params: ''
};
// get params (may be empty)
if(element.readAttribute('wysija_params') !== null && element.readAttribute('wysija_params').length > 0) {
data.params = JSON.parse(element.readAttribute('wysija_params'));
}
return data;
},
toggleWidgets: function() {
$$('a[wysija_unique="1"]').invoke('removeClassName', 'disabled');
// loop through each unique field already inserted in the editor and disable its toolbar equivalent
$$('#'+WysijaForm.options.editor+' [wysija_unique="1"]').each(function(element) {
var field = $$('#'+WysijaForm.options.toolbar+' [wysija_field="'+element.readAttribute('wysija_field')+'"]').first();
if(field !== undefined) {
field.addClassName('disabled');
}
});
// hide list selection if a list widget has been dragged into the editor
$('mailpoet_settings_list_selection')[(($$('#'+WysijaForm.options.editor+' [wysija_field="list"]').length > 0) === true) ? 'hide': 'show']();
},
setBlockPositions: function(event, target) {
// release dragging lock
WysijaForm.locks.dragging = false;
var index = 1;
WysijaForm.getBlocks().each(function (container) {
container.setPosition(index++);
// remove z-index value to avoid issues when resizing images
if(container['block'] !== undefined) {
container.block.element.setStyle({zIndex: ''});
}
});
if(target !== undefined) {
// get placeholders (previous placeholder matches the placeholder linked to the next block)
var block_placeholder = $(target.element.readAttribute('wysija_placeholder')),
previous_placeholder = target.element.previous('.block_placeholder');
if(block_placeholder !== null) {
// put block placeholder before the current block
target.element.insert({before: block_placeholder});
// if the next block is a wysija_block, insert previous placeholder
if(target.element.next() !== undefined && target.element.next().hasClassName('mailpoet_form_block') && previous_placeholder !== undefined) {
target.element.insert({after: previous_placeholder});
}
}
}
},
setScrollOffsets: function() {
WysijaForm.scroll = document.viewport.getScrollOffsets();
},
hideSettings: function() {
$(WysijaForm.options.container).select('.wysija_settings').invoke('hide');
},
setSettingsPosition: function() {
// get viewport offsets and dimensions
var viewportHeight = document.viewport.getHeight(),
blockPadding = 5;
$(WysijaForm.options.container).select('.wysija_settings').each(function(element) {
// get parent dimensions and position
var parentDim = element.up('.mailpoet_form_block').getDimensions(),
parentPos = element.up('.mailpoet_form_block').cumulativeOffset(),
is_visible = (parentPos.top <= (WysijaForm.scroll.top + viewportHeight)) ? true : false,
buttonMargin = 5,
relativeTop = buttonMargin;
if(is_visible) {
// desired position is set to center of viewport
var absoluteTop = parseInt(WysijaForm.scroll.top + ((viewportHeight / 2) - (element.getHeight() / 2)), 10),
parentTop = parseInt(parentPos.top - blockPadding, 10),
parentBottom = parseInt(parentPos.top + parentDim.height - blockPadding, 10);
// always center
relativeTop = parseInt((parentDim.height / 2) - (element.getHeight() / 2), 10);
}
// set position for button
$(element).setStyle({
left: parseInt((parentDim.width / 2) - (element.getWidth() / 2)) + 'px',
top: relativeTop + 'px'
});
});
},
initToolbarPosition: function() {
if(WysijaForm.toolbar.top === null) WysijaForm.toolbar.top = parseInt($(WysijaForm.options.container).positionedOffset().top);
if(WysijaForm.toolbar.y === null) WysijaForm.toolbar.y = parseInt(WysijaForm.toolbar.top);
if(isRtl) {
if(WysijaForm.toolbar.left === null) WysijaForm.toolbar.left = 0;
} else {
if(WysijaForm.toolbar.left === null) WysijaForm.toolbar.left = parseInt($(WysijaForm.options.container).positionedOffset().left);
}
if(WysijaForm.toolbar.x === null) WysijaForm.toolbar.x = parseInt(WysijaForm.toolbar.left + $(WysijaForm.options.container).getDimensions().width + 15);
},
setToolbarPosition: function() {
WysijaForm.initToolbarPosition();
var position = { top: WysijaForm.toolbar.y + 'px', visibility: 'visible' };
if(isRtl) {
position.right = WysijaForm.toolbar.x + 'px';
} else {
position.left = WysijaForm.toolbar.x + 'px';
}
$(WysijaForm.options.toolbar).setStyle(position);
},
updateToolbarPosition: function() {
// init toolbar position (updates scroll and toolbar y)
WysijaForm.initToolbarPosition();
// cancel previous effect
if(WysijaForm.toolbar.effect !== null) WysijaForm.toolbar.effect.cancel();
if(WysijaForm.scroll.top >= (WysijaForm.toolbar.top - 20)) {
WysijaForm.toolbar.y = parseInt(20 + WysijaForm.scroll.top);
// start effect
WysijaForm.toolbar.effect = new Effect.Move(WysijaForm.options.toolbar, {
x: WysijaForm.toolbar.x,
y: WysijaForm.toolbar.y,
mode: 'absolute',
duration: 0.2
});
} else {
$(WysijaForm.options.toolbar).setStyle({
left: WysijaForm.toolbar.x + 'px',
top: WysijaForm.toolbar.top + 'px'
});
}
},
blockDropOptions: {
accept: $w('mailpoet_form_field'), // acceptable items (classes array)
onEnter: function (draggable, droppable) {
$(droppable).addClassName('hover');
},
onLeave: function (draggable, droppable) {
$(droppable).removeClassName('hover');
},
onDrop: function (draggable, droppable) {
// custom data for images
droppable.fire('wjfe:item:drop', WysijaForm.getFieldData(draggable));
$(droppable).removeClassName('hover');
}
},
hideControls: function() {
try {
return WysijaForm.getBlocks().invoke('hideControls');
} catch(e) { return; }
},
hideTools: function() {
$$('.wysija_tools').invoke('hide');
WysijaForm.locks.showingTools = false;
},
instances: {},
get: function (element, type) {
if(type === undefined) type = 'block';
// identify element
var id = element.identify();
var instance = WysijaForm.instances[id] || new WysijaForm[type.capitalize().camelize()](id);
WysijaForm.instances[id] = instance;
return instance;
},
makeDroppable: function() {
Droppables.add('block_placeholder', WysijaForm.blockDropOptions);
},
makeSortable: function () {
var body = $(WysijaForm.options.body);
Sortable.create(body, {
tag: 'div',
only: 'mailpoet_form_block',
scroll: window,
handle: 'handle',
constraint: 'vertical'
});
Draggables.removeObserver(body);
Draggables.addObserver({
element: body,
onStart: WysijaForm.startBlockPositions,
onEnd: WysijaForm.setBlockPositions
});
},
hideBlockControls: function() {
$$('.wysija_controls').invoke('hide');
this.getBlockElements().invoke('removeClassName', 'hover');
},
getBlocks: function () {
return WysijaForm.getBlockElements().map(function (element) {
return WysijaForm.get(element);
});
},
getBlockElements: function () {
return $(WysijaForm.options.container).select('.mailpoet_form_block');
},
startBlockPositions: function(event, target) {
if(target.element.hasClassName('mailpoet_form_block')) {
// store block placeholder id for the block that is being repositionned
if(target.element.previous('.block_placeholder') !== undefined) {
target.element.writeAttribute('wysija_placeholder', target.element.previous('.block_placeholder').identify());
}
}
WysijaForm.locks.dragging = true;
},
encodeURIComponent: function(str) {
// check if it's a url and if so, prevent encoding of protocol
var regexp = new RegExp(/^http[s]?:\/\//),
protocol = regexp.exec(str);
if(protocol === null) {
// this is not a url so encode the whole thing
return encodeURIComponent(str).replace(/[!'()*]/g, escape);
} else if(protocol.length === 1) {
// this is a url, so do not encode the protocol
return encodeURI(str).replace(/[!'()*]/g, escape);
}
}
};
WysijaForm.DraggableItem = Class.create({
initialize: function (element) {
this.elementType = $(element).readAttribute('wysija_type');
this.element = $(element).down() || $(element);
this.clone = this.cloneElement();
this.insert();
},
STYLES: new Template('position: absolute; top: #{top}px; left: #{left}px;'),
cloneElement: function () {
var clone = this.element.clone(),
offset = this.element.cumulativeOffset(),
list = this.getList(),
styles = this.STYLES.evaluate({
top: offset.top - list.scrollTop,
left: offset.left - list.scrollLeft
});
clone.setStyle(styles);
clone.addClassName('mailpoet_form_widget');
clone.addClassName(this.elementType);
clone.innerHTML = this.element.innerHTML;
return clone;
},
getOffset: function () {
return this.element.offsetTop - this.getList().scrollTop;
},
getList: function () {
return this.element.up('ul');
},
insert: function () {
$$("body")[0].insert(this.clone);
},
onMousedown: function (event) {
var draggable = new Draggable(this.clone, {
scroll: window,
onStart: function () {
Droppables.displayArea(draggable);
},
onEnd: function (drag) {
drag.destroy();
drag.element.remove();
Droppables.hideArea();
},
starteffect: function (element) {
new Effect.Opacity(element, {
duration: 0.2,
from: element.getOpacity(),
to: 0.7
});
},
endeffect: Prototype.emptyFunction
});
draggable.initDrag(event);
draggable.startDrag(event);
return draggable;
}
});
Object.extend(WysijaForm.DraggableItem, Observable).observe('a[class="mailpoet_form_field"]');
WysijaForm.Block = Class.create({
/* Invoked on load */
initialize: function(element) {
info('block -> init');
this.element = $(element);
this.block = new WysijaForm.Widget(this.element);
// enable block placeholder
this.block.makeBlockDroppable();
// setup events
if(this.block['setup'] !== undefined) {
this.block.setup();
}
return this;
},
setPosition: function(position) {
this.element.writeAttribute('wysija_position', position);
},
hideControls: function() {
if(this['getControls']) {
this.element.removeClassName('hover');
this.getControls().hide();
}
},
showControls: function() {
if(this['getControls']) {
this.element.addClassName('hover');
try {
this.getControls().show();
} catch(e) {
;
}
}
},
makeBlockDroppable: function() {
if(this.isBlockDroppableEnabled() === false) {
var block_placeholder = this.getBlockDroppable();
Droppables.add(block_placeholder.identify(), WysijaForm.blockDropOptions);
block_placeholder.addClassName('enabled');
}
},
removeBlockDroppable: function() {
if(this.isBlockDroppableEnabled()) {
var block_placeholder = this.getBlockDroppable();
Droppables.remove(block_placeholder.identify());
block_placeholder.removeClassName('enabled');
}
},
isBlockDroppableEnabled: function() {
// if the block_placeholder does not exist, create it
var block_placeholder = this.getBlockDroppable();
if(block_placeholder === null) {
return this.createBlockDroppable().hasClassName('enabled');
} else {
return block_placeholder.hasClassName('enabled');
}
},
createBlockDroppable: function() {
info('block -> createBlockDroppable');
this.element.insert({before: '<div class=\"block_placeholder\">'+$('block_placeholder').innerHTML+'</div>'});
return this.element.previous('.block_placeholder');
},
getBlockDroppable: function() {
if(this.element.previous() === undefined || this.element.previous().hasClassName('block_placeholder') === false) {
return null;
} else {
return this.element.previous();
}
},
getControls: function() {
return this.element.down('.wysija_controls');
},
setupControls: function() {
// enable controls
this.controls = this.getControls();
if(this.controls) {
// setup events for block controls
this.element.observe('mouseover', function() {
// special cases where controls shouldn't be displayed
if(WysijaForm.locks.dragging === true || WysijaForm.locks.selectingColor === true || WysijaForm.locks.showingTools === true) return;
// set block flag
this.element.addClassName('hover');
// show controls
this.showControls();
// show settings if present
if(this.element.down('.wysija_settings') !== undefined) {
this.element.down('.wysija_settings').show();
}
}.bind(this));
this.element.observe('mouseout', function() {
// special cases where controls shouldn't hide
if(WysijaForm.locks.dragging === true || WysijaForm.locks.selectingColor === true) return;
// hide controls
this.hideControls();
// hide settings if present
if(this.element.down('.wysija_settings') !== undefined) {
this.element.down('.wysija_settings').hide();
}
}.bind(this));
// setup click event for remove button
this.removeButton = this.controls.down('.remove') || null;
if(this.removeButton !== null) {
this.removeButton.observe('click', function() {
this.removeBlock();
this.removeButton.stopObserving('click');
}.bind(this));
}
// setup click event for settings button
this.settingsButton = this.element.down('.settings') || null;
if(this.settingsButton !== null) {
this.settingsButton.observe('click', function(event) {
// TODO: refactor
var block = $(event.target).up('.mailpoet_form_block') || null;
if(block !== null) {
var field = WysijaForm.getFieldData(block);
this.editSettings();
}
}.bind(this));
}
}
return this;
},
removeBlock: function(callback) {
info('block -> removeBlock');
// save block in history
WysijaHistory.enqueue(this.element);
Effect.Fade(this.element.identify(), {
duration: 0.2,
afterFinish: function(effect) {
if(effect.element.next('.mailpoet_form_block') !== undefined && callback !== false) {
// show controls of next block to allow mass delete
WysijaForm.get(effect.element.next('.mailpoet_form_block')).block.showControls();
}
// remove placeholder
if(effect.element.previous('.block_placeholder') !== undefined) {
effect.element.previous('.block_placeholder').remove();
}
// remove element from the DOM
this.element.remove();
// reset block positions
WysijaForm.setBlockPositions();
// toggle widgets
WysijaForm.toggleWidgets();
// optional callback execution after completely removing block
if(callback !== undefined && typeof(callback) === 'function') {
callback();
}
// remove block instance
delete WysijaForm.instances[this.element.identify()];
}.bind(this)
});
}
});
/* Invoked on item dropped */
WysijaForm.Block.create = function(block, target) {
if($('form_template_'+block.type) === null) {
return false;
}
var body = $(WysijaForm.options.body),
block_template = Handlebars.compile($('form_template_block').innerHTML),
template = Handlebars.compile($('form_template_'+block.type).innerHTML),
output = '';
// set block template (depending on the block type)
block.template = template(block);
output = block_template(block);
// check if the new block is unique and if there's already an instance
// of it in the history. If so, remove its former instance from the history
if(block.unique === 1) {
WysijaHistory.remove(block.field);
}
// if the drop target was the bottom placeholder
if(target.identify() === 'block_placeholder') {
// insert block at the bottom
body.insert(output);
//block = body.childElements().last();
} else {
// insert block before the drop target
target.insert({before: output });
//block = target.previous('.mailpoet_form_block');
}
// refresh sortable items
WysijaForm.makeSortable();
// refresh block positions
WysijaForm.setBlockPositions();
// position settings
WysijaForm.setSettingsPosition();
};
document.observe('wjfe:item:drop', function(event) {
info('create block');
WysijaForm.Block.create(event.memo, event.target);
// hide block controls
info('hide controls');
WysijaForm.hideBlockControls();
// toggle widgets
setTimeout(function() {
WysijaForm.toggleWidgets();
}, 1);
});
/* Form Widget */
WysijaForm.Widget = Class.create(WysijaForm.Block, {
initialize: function(element) {
info('widget -> init');
this.element = $(element);
return this;
},
setup: function() {
info('widget -> setup');
this.setupControls();
},
save: function() {
info('widget -> save');
var data = this.getData();
if(data.element !== undefined) {
delete data.element;
}
return data;
},
setData: function(data) {
var current_data = this.getData(),
params = $H(current_data.params).merge(data.params).toObject();
// update type if it changed
if(data.type !== undefined && data.type !== current_data.type) {
this.element.writeAttribute('wysija_type', data.type);
}
// update params
this.element.writeAttribute('wysija_params', JSON.stringify(params));
},
getData: function() {
var data = WysijaForm.getFieldData(this.element);
// decode params
if(data.params.length > 0) {
data.params = JSON.parse(data.params);
}
return data;
},
getControls: function() {
return this.element.down('.wysija_controls');
},
remove: function() {
this.removeBlock();
},
redraw: function(data) {
// set parameters
this.setData(data);
var options = this.getData();
// redraw block
var block_template = Handlebars.compile($('form_template_block').innerHTML),
template = Handlebars.compile($('form_template_'+options.type).innerHTML),
data = $H(options).merge({ template: template(options) }).toObject();
this.element.replace(block_template(data));
WysijaForm.init();
},
editSettings: function() {
MailPoet.Modal.popup({
title: 'Edit field settings', // TODO: translate!
template: jQuery('#form_template_field_settings').html(),
data: this.getData(),
onSuccess: function() {
var data = jQuery('#form_field_settings').serializeObject();
this.redraw(data);
}.bind(this)
});
},
getSettings: function() {
return this.element.down('.wysija_settings');
}
});
/* When dom is loaded, initialize WysijaForm */
document.observe('dom:loaded', WysijaForm.init);
/* LOGGING */
function info(value) {
if(WysijaForm.options.debug === false) return;
if(!(window.console && console.log)) {
(function() {
var noop = function() {};
var methods = ['assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error', 'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log', 'markTimeline', 'profile', 'profileEnd', 'markTimeline', 'table', 'time', 'timeEnd', 'timeStamp', 'trace', 'warn'];
var length = methods.length;
var console = window.console = {};
while(length--) {
console[methods[length]] = noop;
}
}());
}
try {
console.log('[DEBUG] '+value);
} catch(e) {}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,26 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { Router, Route, IndexRoute } from 'react-router'
import FormList from 'forms/list.jsx'
import createHashHistory from 'history/lib/createHashHistory'
let history = createHashHistory({ queryKey: false })
const App = React.createClass({
render() {
return this.props.children
}
});
let container = document.getElementById('forms_container');
if(container) {
ReactDOM.render((
<Router history={ history }>
<Route path="/" component={ App }>
<IndexRoute component={ FormList } />
<Route path="*" component={ FormList } />
</Route>
</Router>
), container);
}

View File

@ -0,0 +1,188 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { Router, Link } from 'react-router'
import Listing from 'listing/listing.jsx'
import classNames from 'classnames'
import MailPoet from 'mailpoet'
const columns = [
{
name: 'name',
label: 'Name',
sortable: true
},
{
name: 'segments',
label: 'Lists',
sortable: false
},
{
name: 'created_at',
label: 'Created on',
sortable: true
}
];
const messages = {
onTrash: function(response) {
if(response) {
let message = null;
if(~~response === 1) {
message = (
'1 form was moved to the trash.'
);
} else if(~~response > 1) {
message = (
'%$1d forms were moved to the trash.'
).replace('%$1d', ~~response);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
},
onDelete: function(response) {
if(response) {
let message = null;
if(~~response === 1) {
message = (
'1 form was permanently deleted.'
);
} else if(~~response > 1) {
message = (
'%$1d forms were permanently deleted.'
).replace('%$1d', ~~response);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
},
onRestore: function(response) {
if(response) {
let message = null;
if(~~response === 1) {
message = (
'1 form has been restored from the trash.'
);
} else if(~~response > 1) {
message = (
'%$1d forms have been restored from the trash.'
).replace('%$1d', ~~response);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
}
};
const item_actions = [
{
name: 'edit',
label: 'Edit',
link: function(item) {
return (
<a href={ `admin.php?page=mailpoet-form-editor&id=${item.id}` }>Edit</a>
);
}
},
{
name: 'duplicate_form',
label: 'Duplicate',
onClick: function(item, refresh) {
return MailPoet.Ajax.post({
endpoint: 'forms',
action: 'duplicate',
data: item.id
}).done(function(response) {
MailPoet.Notice.success(
('Form "%$1s" has been duplicated.').replace('%$1s', response.name)
);
refresh();
});
}
}
];
const bulk_actions = [
{
name: 'trash',
label: 'Trash',
onSuccess: messages.onTrash
}
];
const FormList = React.createClass({
createForm() {
MailPoet.Ajax.post({
endpoint: 'forms',
action: 'create'
}).done(function(response) {
if(response !== false) {
window.location = response;
}
});
},
renderItem(form, actions) {
let row_classes = classNames(
'manage-column',
'column-primary',
'has-row-actions'
);
let segments = mailpoet_segments.filter(function(segment) {
return (jQuery.inArray(segment.id, form.segments) !== -1);
}).map(function(segment) {
return segment.name;
}).join(', ');
return (
<div>
<td className={ row_classes }>
<strong>
<a>{ form.name }</a>
</strong>
{ actions }
</td>
<td className="column-format" data-colname="Lists">
{ segments }
</td>
<td className="column-date" data-colname="Created on">
<abbr>{ form.created_at }</abbr>
</td>
</div>
);
},
render() {
return (
<div>
<h2 className="title">
Forms <a
className="add-new-h2"
href="javascript:;"
onClick={ this.createForm }
>New</a>
</h2>
<Listing
location={ this.props.location }
params={ this.props.params }
messages={ messages }
search={ false }
limit={ 1000 }
endpoint="forms"
onRenderItem={ this.renderItem }
columns={ columns }
bulk_actions={ bulk_actions }
item_actions={ item_actions }
/>
</div>
);
}
});
module.exports = FormList;

View File

@ -0,0 +1,3 @@
define([], function() {
!function(e,o,n){window.HSCW=o,window.HS=n,n.beacon=n.beacon||{};var t=n.beacon;t.userConfig={},t.readyQueue=[],t.config=function(e){this.userConfig=e},t.ready=function(e){this.readyQueue.push(e)},o.config={docs:{enabled:!1,baseUrl:""},contact:{enabled:!0,formId:"e5c408c7-895e-11e5-9e75-0a7d6919297d"}};var r=e.getElementsByTagName("script")[0],c=e.createElement("script");c.type="text/javascript",c.async=!0,c.src="https://djtflbt20bdde.cloudfront.net/",r.parentNode.insertBefore(c,r)}(document,window.HSCW||{},window.HS||{});
});

File diff suppressed because it is too large Load Diff

View File

@ -15,16 +15,16 @@ function(
this.setState({
action: e.target.value,
extra: false
});
}, function() {
var action = this.getSelectedAction();
var action = this.getSelectedAction();
// action on select callback
if(action !== null && action['onSelect'] !== undefined) {
this.setState({
extra: action.onSelect(e)
});
}
// action on select callback
if(action !== null && action['onSelect'] !== undefined) {
this.setState({
extra: action.onSelect(e)
});
}
}.bind(this));
},
handleApplyAction: function(e) {
e.preventDefault();
@ -45,12 +45,13 @@ function(
data.action = this.state.action;
var callback = function() {};
if(action['onSuccess'] !== undefined) {
data.onSuccess = action.onSuccess;
callback = action.onSuccess;
}
if(data.action) {
this.props.onBulkAction(selected_ids, data);
this.props.onBulkAction(selected_ids, data, callback);
}
this.setState({
@ -84,7 +85,12 @@ function(
Select bulk action
</label>
<select ref="action" value={ this.state.action } onChange={this.handleChangeAction}>
<select
name="bulk_actions"
ref="action"
value={ this.state.action }
onChange={this.handleChangeAction}
>
<option value="">Bulk Actions</option>
{ this.props.bulk_actions.map(function(action, index) {
return (

View File

@ -1,57 +1,66 @@
define([
'react'
'react',
'jquery'
],
function(
React
React,
jQuery
) {
var ListingFilters = React.createClass({
handleFilterAction: function() {
var filters = this.props.filters.map(function(filter, index) {
var value = this.refs['filter-'+index].value;
if(value) {
return {
'name': filter.name,
'value': value
};
}
}.bind(this));
let filters = {}
this.getAvailableFilters().map((filter, i) => {
filters[this.refs['filter-'+i].name] = this.refs['filter-'+i].value
})
return this.props.onSelectFilter(filters);
},
handleChangeAction: function() {
return true;
getAvailableFilters: function() {
let filters = this.props.filters;
return Object.keys(filters).filter(function(filter) {
return !(
filters[filter].length === 0
|| (
filters[filter].length === 1
&& !filters[filter][0].value
)
);
})
},
render: function() {
var filters = this.props.filters
.filter(function(filter) {
return !(
filter.options.length === 0
|| (
filter.options.length === 1
&& !filter.options[0].value
)
);
})
const filters = this.props.filters;
const selected_filters = this.props.filter;
const available_filters = this.getAvailableFilters()
.map(function(filter, i) {
let default_value = false;
if(selected_filters[filter] !== undefined && selected_filters[filter]) {
default_value = selected_filters[filter]
} else {
jQuery(`select[name="${filter}"]`).val('');
}
return (
<select
ref={ 'filter-'+i }
key={ 'filter-'+i }
onChange={ this.handleChangeAction }>
{ filter.options.map(function(option, j) {
return (
<option
value={ option.value }
key={ 'filter-option-' + j }
>{ option.label }</option>
);
}.bind(this)) }
ref={ `filter-${i}` }
key={ `filter-${i}` }
name={ filter }
defaultValue={ default_value }
>
{ filters[filter].map(function(option, j) {
return (
<option
value={ option.value }
key={ 'filter-option-' + j }
>{ option.label }</option>
);
}.bind(this)) }
</select>
);
}.bind(this));
var button = false;
let button = false;
if(filters.length > 0) {
if(available_filters.length > 0) {
button = (
<input
onClick={ this.handleFilterAction }
@ -63,7 +72,7 @@ function(
return (
<div className="alignleft actions actions">
{ filters }
{ available_filters }
{ button }
</div>
);

View File

@ -9,6 +9,9 @@ define(['react', 'classnames'], function(React, classNames) {
render: function() {
var columns = this.props.columns.map(function(column, index) {
column.is_primary = (index === 0);
column.sorted = (this.props.sort_by === column.name)
? this.props.sort_order
: 'desc';
return (
<ListingColumn
onSort={this.props.onSort}
@ -29,6 +32,7 @@ define(['react', 'classnames'], function(React, classNames) {
</label>
<input
type="checkbox"
name="select_all"
ref="toggle"
checked={ this.props.selection }
onChange={ this.handleSelectItems } />

View File

@ -34,11 +34,9 @@ define(
};
},
handleSelectItem: function(e) {
var is_checked = jQuery(e.target).is(':checked');
this.props.onSelectItem(
parseInt(e.target.value, 10),
is_checked
e.target.checked
);
return !e.target.checked;
@ -46,8 +44,11 @@ define(
handleRestoreItem: function(id) {
this.props.onRestoreItem(id);
},
handleDeleteItem: function(id, confirm = false) {
this.props.onDeleteItem(id, confirm);
handleTrashItem: function(id) {
this.props.onTrashItem(id);
},
handleDeleteItem: function(id) {
this.props.onDeleteItem(id);
},
handleToggleItem: function(id) {
this.setState({ toggled: !this.state.toggled });
@ -58,11 +59,12 @@ define(
if(this.props.is_selectable === true) {
checkbox = (
<th className="check-column" scope="row">
<label className="screen-reader-text">
{ 'Select ' + this.props.item.email }</label>
<label className="screen-reader-text">{
'Select ' + this.props.item[this.props.columns[0].name]
}</label>
<input
type="checkbox"
defaultValue={ this.props.item.id }
value={ this.props.item.id }
checked={
this.props.item.selected || this.props.selection === 'all'
}
@ -77,12 +79,39 @@ define(
if(custom_actions.length > 0) {
item_actions = custom_actions.map(function(action, index) {
return (
<span key={ 'action-'+index } className={ action.name }>
{ action.link(this.props.item.id) }
{(index < (custom_actions.length - 1)) ? ' | ' : ''}
</span>
);
if(action.refresh) {
return (
<span
onClick={ this.props.onRefreshItems }
key={ 'action-'+index } className={ action.name }>
{ action.link(this.props.item) }
{(index < (custom_actions.length - 1)) ? ' | ' : ''}
</span>
);
} else if(action.link) {
return (
<span
key={ 'action-'+index } className={ action.name }>
{ action.link(this.props.item) }
{(index < (custom_actions.length - 1)) ? ' | ' : ''}
</span>
);
} else {
return (
<span
key={ 'action-'+index } className={ action.name }>
<a href="javascript:;" onClick={
(action.onClick !== undefined)
? action.onClick.bind(null,
this.props.item,
this.props.onRefreshItems
)
: false
}>{ action.label }</a>
{(index < (custom_actions.length - 1)) ? ' | ' : ''}
</span>
);
}
}.bind(this));
} else {
item_actions = (
@ -112,8 +141,7 @@ define(
href="javascript:;"
onClick={ this.handleDeleteItem.bind(
null,
this.props.item.id,
true
this.props.item.id
)}
>Delete permanently</a>
</span>
@ -134,10 +162,9 @@ define(
<span className="trash">
<a
href="javascript:;"
onClick={ this.handleDeleteItem.bind(
onClick={ this.handleTrashItem.bind(
null,
this.props.item.id,
false
this.props.item.id
) }>
Trash
</a>
@ -222,7 +249,7 @@ define(
</td>
</tr>
{this.props.items.map(function(item) {
{this.props.items.map(function(item, index) {
item.id = parseInt(item.id, 10);
item.selected = (this.props.selected_ids.indexOf(item.id) !== -1);
@ -233,11 +260,13 @@ define(
onRenderItem={ this.props.onRenderItem }
onDeleteItem={ this.props.onDeleteItem }
onRestoreItem={ this.props.onRestoreItem }
onTrashItem={ this.props.onTrashItem }
onRefreshItems={ this.props.onRefreshItems }
selection={ this.props.selection }
is_selectable={ this.props.is_selectable }
item_actions={ this.props.item_actions }
group={ this.props.group }
key={ 'item-' + item.id }
key={ `item-${item.id}-${index}` }
item={ item } />
);
}.bind(this))}
@ -248,6 +277,9 @@ define(
});
var Listing = React.createClass({
mixins: [
Router.History
],
getInitialState: function() {
return {
loading: false,
@ -260,43 +292,142 @@ define(
items: [],
groups: [],
group: 'all',
filters: [],
filter: [],
filters: {},
filter: {},
selected_ids: [],
selection: false
};
},
componentDidUpdate: function(prevProps, prevState) {
// reset group to "all" if trash gets emptied
if(
// we were viewing the trash
(prevState.group === 'trash' && prevState.count > 0)
&&
// we are still viewing the trash but there are no items left
(this.state.group === 'trash' && this.state.count === 0)
&&
// only do this when no filter is set
(Object.keys(this.state.filter).length === 0)
) {
this.handleGroup('all');
}
},
getParam: function(param) {
var regex = /(.*)\[(.*)\]/
var matches = regex.exec(param)
return [matches[1], matches[2]]
},
initWithParams: function(params) {
let state = this.state || {}
let original_state = state
// check for url params
if(params.splat !== undefined) {
params.splat.split('/').map(param => {
let [key, value] = this.getParam(param);
switch(key) {
case 'filter':
let filters = {}
value.split('&').map(function(pair) {
let [k, v] = pair.split('=')
filters[k] = v
}
)
state.filter = filters
break;
default:
state[key] = value
}
})
}
if(this.props.limit !== undefined) {
state.limit = Math.abs(~~this.props.limit);
}
this.setState(state, function() {
this.getItems();
}.bind(this));
},
setParams: function() {
var params = Object.keys(this.state)
.filter(key => {
return (
[
'group',
'filter',
'search',
'page',
'sort_by',
'sort_order'
].indexOf(key) !== -1
)
})
.map(key => {
let value = this.state[key]
if(value === Object(value)) {
value = jQuery.param(value)
} else if(value === Boolean(value)) {
value = value.toString()
}
if(value !== '') {
return `${key}[${value}]`
}
})
.filter(key => { return (key !== undefined) })
.join('/');
params = '/'+params
if(this.props.location) {
if(this.props.location.pathname !== params) {
this.history.pushState(null, `${params}`)
}
}
},
componentDidMount: function() {
this.getItems();
if(this.isMounted()) {
const params = this.props.params || {}
this.initWithParams(params)
}
},
componentWillReceiveProps: function(nextProps) {
const params = nextProps.params || {}
this.initWithParams(params)
},
getItems: function() {
this.setState({ loading: true });
if(this.isMounted()) {
this.setState({ loading: true });
this.clearSelection();
this.clearSelection();
MailPoet.Ajax.post({
endpoint: this.props.endpoint,
action: 'listing',
data: {
offset: (this.state.page - 1) * this.state.limit,
limit: this.state.limit,
group: this.state.group,
filter: this.state.filter,
search: this.state.search,
sort_by: this.state.sort_by,
sort_order: this.state.sort_order
}
}).done(function(response) {
if(this.isMounted()) {
MailPoet.Ajax.post({
endpoint: this.props.endpoint,
action: 'listing',
data: {
offset: (this.state.page - 1) * this.state.limit,
limit: this.state.limit,
group: this.state.group,
filter: this.state.filter,
search: this.state.search,
sort_by: this.state.sort_by,
sort_order: this.state.sort_order
}
}).done(function(response) {
this.setState({
items: response.items || [],
filters: response.filters || [],
filters: response.filters || {},
groups: response.groups || [],
count: response.count || 0,
loading: false
});
}
}.bind(this));
}, function() {
if(this.props['onGetItems'] !== undefined) {
this.props.onGetItems(
~~(this.state.groups[0]['count'])
);
}
}.bind(this));
}.bind(this));
}
},
handleRestoreItem: function(id) {
this.setState({
@ -318,7 +449,27 @@ define(
this.getItems();
}.bind(this));
},
handleDeleteItem: function(id, confirm = false) {
handleTrashItem: function(id) {
this.setState({
loading: true,
page: 1
});
MailPoet.Ajax.post({
endpoint: this.props.endpoint,
action: 'trash',
data: id
}).done(function(response) {
if(
this.props.messages !== undefined
&& this.props.messages['onTrash'] !== undefined
) {
this.props.messages.onTrash(response);
}
this.getItems();
}.bind(this));
},
handleDeleteItem: function(id) {
this.setState({
loading: true,
page: 1
@ -327,31 +478,18 @@ define(
MailPoet.Ajax.post({
endpoint: this.props.endpoint,
action: 'delete',
data: {
id: id,
confirm: confirm
}
data: id
}).done(function(response) {
if(confirm === true) {
if(
this.props.messages !== undefined
&& this.props.messages['onConfirmDelete'] !== undefined
) {
this.props.messages.onConfirmDelete(response);
}
} else {
if(
this.props.messages !== undefined
&& this.props.messages['onDelete'] !== undefined
) {
this.props.messages.onDelete(response);
}
if(
this.props.messages !== undefined
&& this.props.messages['onDelete'] !== undefined
) {
this.props.messages.onDelete(response);
}
this.getItems();
}.bind(this));
},
handleBulkAction: function(selected_ids, params) {
handleBulkAction: function(selected_ids, params, callback) {
if(
this.state.selection === false
&& this.state.selected_ids.length === 0
@ -362,12 +500,6 @@ define(
this.setState({ loading: true });
var data = params || {};
var callback = ((data['onSuccess'] !== undefined)
? data['onSuccess']
: function() {}
);
delete data.onSuccess;
data.listing = {
offset: 0,
limit: 0,
@ -379,7 +511,7 @@ define(
MailPoet.Ajax.post({
endpoint: this.props.endpoint,
action: 'bulk_action',
action: 'bulkAction',
data: data
}).done(function(response) {
this.getItems();
@ -393,6 +525,7 @@ define(
selection: false,
selected_ids: []
}, function() {
this.setParams();
this.getItems();
}.bind(this));
},
@ -401,6 +534,7 @@ define(
sort_by: sort_by,
sort_order: sort_order,
}, function() {
this.setParams();
this.getItems();
}.bind(this));
},
@ -460,6 +594,7 @@ define(
filter: filters,
page: 1
}, function() {
this.setParams();
this.getItems();
}.bind(this));
},
@ -469,10 +604,11 @@ define(
this.setState({
group: group,
filter: [],
filter: {},
search: '',
page: 1
}, function() {
this.setParams();
this.getItems();
}.bind(this));
},
@ -482,6 +618,7 @@ define(
selection: false,
selected_ids: []
}, function() {
this.setParams();
this.getItems();
}.bind(this));
},
@ -489,17 +626,14 @@ define(
var render = this.props.onRenderItem(item, actions);
return render.props.children;
},
handleRefreshItems: function() {
this.getItems();
},
render: function() {
var items = this.state.items,
sort_by = this.state.sort_by,
sort_order = this.state.sort_order;
// set sortable columns
columns = this.props.columns.map(function(column) {
column.sorted = (column.name === sort_by) ? sort_order : false;
return column;
});
// bulk actions
var bulk_actions = this.props.bulk_actions || [];
@ -511,12 +645,9 @@ define(
onSuccess: this.props.messages.onRestore
},
{
name: 'trash',
name: 'delete',
label: 'Delete permanently',
onSuccess: this.props.messages.onConfirmDelete,
getData: function() {
return { confirm: true };
}
onSuccess: this.props.messages.onDelete
}
];
}
@ -524,22 +655,42 @@ define(
// item actions
var item_actions = this.props.item_actions || [];
var tableClasses = classNames(
var table_classes = classNames(
'mailpoet_listing_table',
'wp-list-table',
'widefat',
'fixed',
'striped',
{ 'mailpoet_listing_loading': this.state.loading }
);
// search
var search = (
<ListingSearch
onSearch={ this.handleSearch }
search={ this.state.search }
/>
);
if(this.props.search === false) {
search = false;
}
// groups
var groups = (
<ListingGroups
groups={ this.state.groups }
group={ this.state.group }
onSelectGroup={ this.handleGroup }
/>
);
if(this.props.groups === false) {
groups = false;
}
return (
<div>
<ListingGroups
groups={ this.state.groups }
group={ this.state.group }
onSelectGroup={ this.handleGroup } />
<ListingSearch
onSearch={ this.handleSearch }
search={ this.state.search } />
{ groups }
{ search }
<div className="tablenav top clearfix">
<ListingBulkActions
bulk_actions={ bulk_actions }
@ -548,7 +699,7 @@ define(
onBulkAction={ this.handleBulkAction } />
<ListingFilters
filters={ this.state.filters }
filter={ this.state.filter }
filter={ this.state.filter }
onSelectFilter={ this.handleFilter } />
<ListingPages
count={ this.state.count }
@ -556,7 +707,7 @@ define(
limit={ this.state.limit }
onSetPage={ this.handleSetPage } />
</div>
<table className={ tableClasses }>
<table className={ table_classes }>
<thead>
<ListingHeader
onSort={ this.handleSort }
@ -572,6 +723,8 @@ define(
onRenderItem={ this.handleRenderItem }
onDeleteItem={ this.handleDeleteItem }
onRestoreItem={ this.handleRestoreItem }
onTrashItem={ this.handleTrashItem }
onRefreshItems={ this.handleRefreshItems }
columns={ this.props.columns }
is_selectable={ bulk_actions.length > 0 }
onSelectItem={ this.handleSelectItem }

View File

@ -40,23 +40,23 @@ define(['react', 'classnames'], function(React, classNames) {
},
render: function() {
if(this.props.count === 0) {
return (<div></div>);
return false;
} else {
var pagination,
firstPage = (
var pagination = false;
var firstPage = (
<span aria-hidden="true" className="tablenav-pages-navspan">«</span>
),
previousPage = (
);
var previousPage = (
<span aria-hidden="true" className="tablenav-pages-navspan"></span>
),
nextPage = (
);
var nextPage = (
<span aria-hidden="true" className="tablenav-pages-navspan"></span>
),
lastPage = (
);
var lastPage = (
<span aria-hidden="true" className="tablenav-pages-navspan">»</span>
);
if(this.props.count > this.props.limit) {
if(this.props.limit > 0 && this.props.count > this.props.limit) {
if(this.props.page > 1) {
previousPage = (
<a href="javascript:;"
@ -104,6 +104,7 @@ define(['react', 'classnames'], function(React, classNames) {
pagination = (
<span className="pagination-links">
{firstPage}
&nbsp;
{previousPage}
&nbsp;
<span className="paging-input">
@ -128,6 +129,7 @@ define(['react', 'classnames'], function(React, classNames) {
</span>
&nbsp;
{nextPage}
&nbsp;
{lastPage}
</span>
);
@ -140,7 +142,7 @@ define(['react', 'classnames'], function(React, classNames) {
return (
<div className={ classes }>
<span className="displaying-num">{ this.props.count } item(s)</span>
<span className="displaying-num">{ this.props.count } items</span>
{ pagination }
</div>
);

View File

@ -7,26 +7,33 @@ define(['react'], function(React) {
this.refs.search.value
);
},
componentWillReceiveProps: function(nextProps) {
this.refs.search.value = nextProps.search
},
render: function() {
return (
<form name="search" onSubmit={this.handleSearch}>
<p className="search-box">
<label htmlFor="search_input" className="screen-reader-text">
Search
</label>
<input
type="search"
id="search_input"
ref="search"
name="s"
defaultValue={this.props.search} />
<input
type="submit"
defaultValue={MailPoetI18n.searchLabel}
className="button" />
</p>
</form>
);
if(this.props.search === false) {
return false;
} else {
return (
<form name="search" onSubmit={this.handleSearch}>
<p className="search-box">
<label htmlFor="search_input" className="screen-reader-text">
Search
</label>
<input
type="search"
id="search_input"
ref="search"
name="s"
defaultValue={this.props.search} />
<input
type="submit"
defaultValue={MailPoetI18n.searchLabel}
className="button" />
</p>
</form>
);
}
}
});

View File

@ -166,81 +166,62 @@ define([
this.$('.mailpoet_automated_latest_content_categories_and_tags').select2({
multiple: true,
allowClear: true,
query: function(options) {
var taxonomies = [];
// Delegate data loading to our own endpoints
WordpressComponent.getTaxonomies(that.model.get('contentType')).then(function(tax) {
taxonomies = tax;
// Fetch available terms based on the list of taxonomies already fetched
var promise = WordpressComponent.getTerms({
search: options.term,
taxonomies: _.keys(taxonomies)
}).then(function(terms) {
return {
taxonomies: taxonomies,
terms: terms,
};
ajax: {
data: function (params) {
return {
term: params.term
};
},
transport: function(options, success, failure) {
var taxonomies,
promise = WordpressComponent.getTaxonomies(that.model.get('contentType')).then(function(tax) {
taxonomies = tax;
// Fetch available terms based on the list of taxonomies already fetched
var promise = WordpressComponent.getTerms({
search: options.data.term,
taxonomies: _.keys(taxonomies)
}).then(function(terms) {
return {
taxonomies: taxonomies,
terms: terms,
};
});
return promise;
});
promise.then(success);
promise.fail(failure);
return promise;
}).done(function(args) {
},
processResults: function(data) {
// Transform taxonomies and terms into select2 compatible format
options.callback({
return {
results: _.map(
args.terms,
data.terms,
function(item) {
return _.defaults({
text: args.taxonomies[item.taxonomy].labels.singular_name + ': ' + item.name,
text: data.taxonomies[item.taxonomy].labels.singular_name + ': ' + item.name,
id: item.term_id
}, item);
}
)
});
});
};
},
},
initSelection: function(element, callback) {
// On external data load tell select2 which terms to preselect
callback(_.map(
that.model.get('terms').toJSON(),
function(item) {
return {
id: item.id,
text: item.text,
};
}
));
}).on({
'select2:select': function(event) {
var terms = that.model.get('terms');
terms.add(event.params.data);
// Reset whole model in order for change events to propagate properly
that.model.set('terms', terms.toJSON());
},
}).trigger( 'change' ).on({
'change': function(e){
var data = jQuery(this).data('selected');
if (typeof data === 'string') {
if (data === '') {
data = [];
} else {
data = JSON.parse(data);
}
}
if ( e.added ){
data.push(e.added);
} else {
data = _.filter(data, function(item) {
return item.id !== e.removed.id;
});
}
// Update ALC model
that.model.set('terms', data);
jQuery(this).data('selected', JSON.stringify(data));
}
});
},
onBeforeDestroy: function() {
base.BlockSettingsView.prototype.onBeforeDestroy.apply(this, arguments);
// Force close select2 if it hasn't closed yet
this.$('.mailpoet_automated_latest_content_categories_and_tags').select2('close');
'select2:unselect': function(event) {
var terms = that.model.get('terms');
terms.remove(event.params.data);
// Reset whole model in order for change events to propagate properly
that.model.set('terms', terms.toJSON());
},
}).trigger( 'change' );
},
toggleDisplayOptions: function(event) {
var el = this.$('.mailpoet_automated_latest_content_display_options'),
@ -325,7 +306,7 @@ define([
_.each(postTypes, function(type) {
select.append(jQuery('<option>', {
value: type.name,
text: type.labels.singular_name,
text: type.label,
}));
});
select.val(selectedValue);

View File

@ -203,6 +203,9 @@ define([
changeBoolField: function(field, event) {
this.model.set(field, (jQuery(event.target).val() === 'true') ? true : false);
},
changeBoolCheckboxField: function(field, event) {
this.model.set(field, (!!jQuery(event.target).prop('checked')));
},
changeColorField: function(field, event) {
var value = jQuery(event.target).val();
if (value === '') {

View File

@ -162,10 +162,10 @@ define([
this.toolsView = new Module.ContainerBlockToolsView({
model: this.model,
tools: {
settings: this.renderOptions.depth > 1,
settings: this.renderOptions.depth === 1,
delete: this.renderOptions.depth === 1,
move: this.renderOptions.depth === 1,
layerSelector: this.renderOptions.depth === 1,
layerSelector: false,
},
});
this.toolsRegion.show(this.toolsView);
@ -265,6 +265,41 @@ define([
behaviors: {
ColorPickerBehavior: {},
},
regions: {
columnsSettingsRegion: '.mailpoet_container_columns_settings',
},
initialize: function() {
base.BlockSettingsView.prototype.initialize.apply(this, arguments);
this._columnsSettingsView = new (Module.ContainerBlockColumnsSettingsView)({
collection: this.model.get('blocks'),
});
},
onRender: function() {
this.columnsSettingsRegion.show(this._columnsSettingsView);
},
});
Module.ContainerBlockColumnsSettingsView = Marionette.CollectionView.extend({
getChildView: function() { return Module.ContainerBlockColumnSettingsView; },
childViewOptions: function(model, index) {
return {
columnIndex: index,
};
},
});
Module.ContainerBlockColumnSettingsView = Marionette.ItemView.extend({
getTemplate: function() { return templates.containerBlockColumnSettings; },
initialize: function(options) {
this.columnNumber = (options.columnIndex || 0) + 1;
},
templateHelpers: function() {
return {
model: this.model.toJSON(),
columnNumber: this.columnNumber,
};
},
});
Module.OneColumnContainerWidgetView = base.WidgetView.extend({

View File

@ -66,7 +66,7 @@ define([
"keyup .mailpoet_field_image_link": _.partial(this.changeField, "link"),
"keyup .mailpoet_field_image_address": _.partial(this.changeField, "src"),
"keyup .mailpoet_field_image_alt_text": _.partial(this.changeField, "alt"),
"change .mailpoet_field_image_padded": _.partial(this.changeBoolField, "padded"),
"change .mailpoet_field_image_padded": _.partial(this.changeBoolCheckboxField, "padded"),
"change .mailpoet_field_image_alignment": _.partial(this.changeField, "styles.block.textAlign"),
"click .mailpoet_field_image_select_another_image": "showMediaManager",
"click .mailpoet_done_editing": "close",
@ -297,9 +297,9 @@ define([
// Following advice from Becs, the target width should
// be a double of one column width to render well on
// retina screen devices
targetImageWidth = 1200,
targetImageWidth = 1320,
// For main image use the size, that's closest to being 600px in width
// For main image use the size, that's closest to being 660px in width
sizeKeys = _.keys(sizes),
// Pick the width that is closest to target width

View File

@ -21,7 +21,8 @@ define([
'newsletter_editor/components/wordpress',
'newsletter_editor/blocks/base',
'newsletter_editor/blocks/button',
'newsletter_editor/blocks/divider'
'newsletter_editor/blocks/divider',
'select2'
], function(Backbone, Marionette, Radio, _, jQuery, MailPoet, App, WordpressComponent, BaseBlock, ButtonBlock, DividerBlock) {
"use strict";
@ -30,7 +31,7 @@ define([
base = BaseBlock;
Module.PostsBlockModel = base.BlockModel.extend({
stale: ['_selectedPosts', '_availablePosts'],
stale: ['_selectedPosts', '_availablePosts', '_transformedPosts'],
defaults: function() {
return this._getDefaults({
type: 'posts',
@ -62,6 +63,7 @@ define([
divider: {},
_selectedPosts: [],
_availablePosts: [],
_transformedPosts: new (App.getBlockTypeModel('container'))(),
}, App.getConfig().get('blockDefaults.posts'));
},
relations: function() {
@ -70,15 +72,26 @@ define([
divider: App.getBlockTypeModel('divider'),
_selectedPosts: Backbone.Collection,
_availablePosts: Backbone.Collection,
_transformedPosts: App.getBlockTypeModel('container'),
};
},
initialize: function() {
var that = this;
var that = this,
POST_REFRESH_DELAY_MS = 500,
refreshAvailablePosts = _.debounce(this.fetchAvailablePosts.bind(this), POST_REFRESH_DELAY_MS),
refreshTransformedPosts = _.debounce(this._refreshTransformedPosts.bind(this), POST_REFRESH_DELAY_MS);
// Attach Radio.Requests API primarily for highlighting
_.extend(this, Radio.Requests);
this.fetchAvailablePosts();
this.on('change:amount change:contentType change:terms change:inclusionType change:postStatus change:search change:sortBy', this._scheduleFetchAvailablePosts, this);
this.on('change:amount change:contentType change:terms change:inclusionType change:postStatus change:search change:sortBy', refreshAvailablePosts);
this.listenTo(this.get('_selectedPosts'), 'add remove reset', refreshTransformedPosts);
this.on('change:displayType change:titleFormat change:titlePosition change:titleAlignment change:titleIsLink change:imagePadded change:showAuthor change:authorPrecededBy change:showCategories change:categoriesPrecededBy change:readMoreType change:readMoreText change:showDivider', refreshTransformedPosts);
this.listenTo(this.get('readMoreButton'), 'change', refreshTransformedPosts);
this.listenTo(this.get('divider'), 'change', refreshTransformedPosts);
this.on('insertSelectedPosts', this._insertSelectedPosts, this);
},
fetchAvailablePosts: function() {
@ -92,20 +105,23 @@ define([
console.log('Posts fetchPosts error', arguments);
});
},
/**
* Batch more changes during a specific time, instead of fetching
* ALC posts on each model change
*/
_scheduleFetchAvailablePosts: function() {
var timeout = 500,
that = this;
if (this._fetchPostsTimer !== undefined) {
clearTimeout(this._fetchPostsTimer);
_refreshTransformedPosts: function() {
var that = this,
data = this.toJSON();
data.posts = this.get('_selectedPosts').pluck('ID');
if (data.posts.length === 0) {
this.get('_transformedPosts.blocks').reset();
return;
}
this._fetchPostsTimer = setTimeout(function() {
that.fetchAvailablePosts();
that._fetchPostsTimer = undefined;
}, timeout);
WordpressComponent.getTransformedPosts(data).done(function(posts) {
console.log('Transformed posts fetched', arguments);
that.get('_transformedPosts').get('blocks').reset(posts, {parse: true});
}).fail(function() {
console.log('Posts _refreshTransformedPosts error', arguments);
});
},
_insertSelectedPosts: function() {
var that = this,
@ -130,6 +146,9 @@ define([
className: "mailpoet_block mailpoet_posts_block mailpoet_droppable_block",
getTemplate: function() { return templates.postsBlock; },
modelEvents: {},
regions: _.extend({
postsRegion: '.mailpoet_posts_block_posts',
}, base.BlockView.prototype.regions),
onDragSubstituteBy: function() { return Module.PostsWidgetView; },
initialize: function() {
base.BlockView.prototype.initialize.apply(this, arguments);
@ -141,6 +160,13 @@ define([
this.toolsRegion.show(this.toolsView);
}
this.trigger('showSettings');
var ContainerView = App.getBlockTypeView('container'),
renderOptions = {
disableTextEditor: true,
disableDragAndDrop: true,
};
this.postsRegion.show(new ContainerView({ model: this.model.get('_transformedPosts'), renderOptions: renderOptions }));
},
notifyAboutSelf: function() {
return this;
@ -215,6 +241,7 @@ define([
insertPosts: function() {
this.model.trigger('insertSelectedPosts');
this.model.destroy();
this.close();
},
});
@ -249,64 +276,62 @@ define([
this.$('.mailpoet_posts_categories_and_tags').select2({
multiple: true,
allowClear: true,
query: function(options) {
var taxonomies = [];
// Delegate data loading to our own endpoints
WordpressComponent.getTaxonomies(that.model.get('contentType')).then(function(tax) {
taxonomies = tax;
// Fetch available terms based on the list of taxonomies already fetched
var promise = WordpressComponent.getTerms({
search: options.term,
taxonomies: _.keys(taxonomies)
}).then(function(terms) {
return {
taxonomies: taxonomies,
terms: terms,
};
ajax: {
data: function (params) {
return {
term: params.term
};
},
transport: function(options, success, failure) {
var taxonomies,
promise = WordpressComponent.getTaxonomies(that.model.get('contentType')).then(function(tax) {
taxonomies = tax;
// Fetch available terms based on the list of taxonomies already fetched
var promise = WordpressComponent.getTerms({
search: options.data.term,
taxonomies: _.keys(taxonomies)
}).then(function(terms) {
return {
taxonomies: taxonomies,
terms: terms,
};
});
return promise;
});
promise.then(success);
promise.fail(failure);
return promise;
}).done(function(args) {
},
processResults: function(data) {
// Transform taxonomies and terms into select2 compatible format
options.callback({
return {
results: _.map(
args.terms,
data.terms,
function(item) {
return _.defaults({
text: args.taxonomies[item.taxonomy].labels.singular_name + ': ' + item.name,
text: data.taxonomies[item.taxonomy].labels.singular_name + ': ' + item.name,
id: item.term_id
}, item);
}
)
});
});
};
},
},
}).trigger( 'change' ).on({
'change': function(e){
var data = [];
if (typeof data === 'string') {
if (data === '') {
data = [];
} else {
data = JSON.parse(data);
}
}
if ( e.added ){
data.push(e.added);
}
// Update ALC model
that.model.set('terms', data);
jQuery(this).data('selected', JSON.stringify(data));
}
});
},
onBeforeDestroy: function() {
base.BlockSettingsView.prototype.onBeforeDestroy.apply(this, arguments);
// Force close select2 if it hasn't closed yet
this.$('.mailpoet_posts_categories_and_tags').select2('close');
}).on({
'select2:select': function(event) {
var terms = that.model.get('terms');
terms.add(event.params.data);
// Reset whole model in order for change events to propagate properly
that.model.set('terms', terms.toJSON());
},
'select2:unselect': function(event) {
var terms = that.model.get('terms');
terms.remove(event.params.data);
// Reset whole model in order for change events to propagate properly
that.model.set('terms', terms.toJSON());
},
}).trigger( 'change' );
},
changeField: function(field, event) {
this.model.set(field, jQuery(event.target).val());
@ -319,7 +344,7 @@ define([
_.each(postTypes, function(type) {
select.append(jQuery('<option>', {
value: type.name,
text: type.labels.singular_name,
text: type.label,
}));
});
select.val(selectedValue);

View File

@ -1,9 +1,13 @@
define([
'newsletter_editor/App',
'mailpoet',
'notice',
'backbone',
'backbone.marionette'
], function(App, MailPoet, Backbone, Marionette) {
'backbone.marionette',
'jquery',
'blob',
'filesaver'
], function(App, MailPoet, Notice, Backbone, Marionette, jQuery, Blob, FileSaver) {
"use strict";
@ -52,6 +56,18 @@ define([
});
};
Module.exportTemplate = function(options) {
var data = _.extend(options || {}, {
body: App.getBody(),
});
var blob = new Blob(
[JSON.stringify(data)],
{ type: 'application/json;charset=utf-8' }
);
FileSaver.saveAs(blob, 'template.json');
};
Module.SaveView = Marionette.LayoutView.extend({
getTemplate: function() { return templates.save; },
events: {
@ -62,7 +78,8 @@ define([
'click .mailpoet_save_template': 'toggleSaveAsTemplate',
'click .mailpoet_save_as_template': 'saveAsTemplate',
/* Export template */
'click .mailpoet_save_export': 'exportTemplate',
'click .mailpoet_save_export': 'toggleExportTemplate',
'click .mailpoet_export_template': 'exportTemplate',
},
initialize: function(options) {
App.getChannel().on('beforeEditorSave', this.beforeSave, this);
@ -117,12 +134,33 @@ define([
this.hideOptionContents();
},
toggleExportTemplate: function() {
this.$('.mailpoet_export_template_container').toggleClass('mailpoet_hidden');
this.toggleSaveOptions();
},
hideExportTemplate: function() {
this.$('.mailpoet_export_template_container').addClass('mailpoet_hidden');
},
exportTemplate: function() {
console.log('Exporting template');
this.hideOptionContents();
var templateName = this.$('.mailpoet_export_template_name').val(),
templateDescription = this.$('.mailpoet_export_template_description').val();
if (templateName === '') {
MailPoet.Notice.error(App.getConfig().get('translations.templateNameMissing'));
} else if (templateDescription === '') {
MailPoet.Notice.error(App.getConfig().get('translations.templateDescriptionMissing'));
} else {
console.log('Exporting template with ', templateName, templateDescription);
Module.exportTemplate({
name: templateName,
description: templateDescription,
});
this.hideExportTemplate();
}
},
hideOptionContents: function() {
this.hideSaveAsTemplate();
this.hideExportTemplate();
this.$('.mailpoet_save_options').addClass('mailpoet_hidden');
},
next: function() {

View File

@ -56,7 +56,7 @@ define([
};
Module.getTransformedPosts = function(options) {
return Module._cachedQuery({
return Module._query({
action: 'getTransformedPosts',
options: options,
});

View File

@ -37,19 +37,77 @@ define(
}
];
var messages = {
onTrash: function(response) {
var count = ~~response.newsletters;
var message = null;
if(count === 1 || response === true) {
message = (
'1 newsletter was moved to the trash.'
);
} else if(count > 1) {
message = (
'%$1d newsletters were moved to the trash.'
).replace('%$1d', count);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
},
onDelete: function(response) {
var count = ~~response.newsletters;
var message = null;
if(count === 1 || response === true) {
message = (
'1 newsletter was permanently deleted.'
);
} else if(count > 1) {
message = (
'%$1d newsletters were permanently deleted.'
).replace('%$1d', count);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
},
onRestore: function(response) {
var count = ~~response.newsletters;
var message = null;
if(count === 1 || response === true) {
message = (
'1 newsletter has been restored from the trash.'
);
} else if(count > 1) {
message = (
'%$1d newsletters have been restored from the trash.'
).replace('%$1d', count);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
};
var bulk_actions = [
{
name: 'trash',
label: 'Trash'
label: 'Trash',
onSuccess: messages.onTrash
}
];
var item_actions = [
{
name: 'edit',
link: function(id) {
link: function(item) {
return (
<a href={ '?page=mailpoet-newsletter-editor&id=' + id }>
<a href={ `?page=mailpoet-newsletter-editor&id=${ item.id }` }>
Edit
</a>
);
@ -100,11 +158,13 @@ define(
</h2>
<Listing
params={ this.props.params }
endpoint="newsletters"
onRenderItem={this.renderItem}
columns={columns}
bulk_actions={ bulk_actions }
item_actions={ item_actions } />
item_actions={ item_actions }
messages={ messages } />
</div>
);
}

View File

@ -7,6 +7,7 @@ import NewsletterTemplates from 'newsletters/templates.jsx'
import NewsletterSend from 'newsletters/send.jsx'
import NewsletterStandard from 'newsletters/types/standard.jsx'
import NewsletterWelcome from 'newsletters/types/welcome.jsx'
import NewsletterNotification from 'newsletters/types/notification.jsx'
import createHashHistory from 'history/lib/createHashHistory'
let history = createHashHistory({ queryKey: false })
@ -27,8 +28,10 @@ if(container) {
<Route path="new" component={ NewsletterTypes } />
<Route name="standard" path="new/standard" component={ NewsletterStandard } />
<Route name="welcome" path="new/welcome" component={ NewsletterWelcome } />
<Route name="notification" path="new/notification" component={ NewsletterNotification } />
<Route name="template" path="template/:id" component={ NewsletterTemplates } />
<Route path="send/:id" component={ NewsletterSend } />
<Route path="filter[:filter]" component={ NewsletterList } />
<Route path="*" component={ NewsletterList } />
</Route>
</Router>

View File

@ -35,7 +35,9 @@ define(
id: "mailpoet_segments",
endpoint: "segments",
multiple: true,
select2: true
filter: function(segment) {
return !!(!segment.deleted_at);
}
},
{
name: 'sender',

View File

@ -1,6 +1,7 @@
define(
[
'react',
'underscore',
'mailpoet',
'react-router',
'classnames',
@ -8,11 +9,67 @@ define(
],
function(
React,
_,
MailPoet,
Router,
classNames,
Breadcrumb
) {
var ImportTemplate = React.createClass({
saveTemplate: function(template) {
MailPoet.Ajax.post({
endpoint: 'newsletterTemplates',
action: 'save',
data: template
}).done(function(response) {
if(response === true) {
this.props.onImport(template);
} else {
response.map(function(error) {
MailPoet.Notice.error(error);
});
}
}.bind(this));
},
handleSubmit: function(e) {
e.preventDefault();
if (_.size(this.refs.templateFile.files) <= 0) return false;
var file = _.first(this.refs.templateFile.files),
reader = new FileReader(),
saveTemplate = this.saveTemplate;
reader.onload = function(e) {
try {
saveTemplate(JSON.parse(e.target.result));
} catch (err) {
MailPoet.Notice.error('This template file appears to be malformed. Please try another one.');
}
}.bind(this);
reader.readAsText(file);
},
render: function() {
return (
<div>
<h2>Import a template</h2>
<form onSubmit={this.handleSubmit}>
<input type="file" placeholder="Select a .json file to upload" ref="templateFile" />
<p className="submit">
<input
className="button button-primary"
type="submit"
value="Upload" />
</p>
</form>
</div>
);
},
});
var NewsletterTemplates = React.createClass({
mixins: [
Router.History
@ -62,11 +119,11 @@ define(
body: template.body
}
}).done(function(response) {
if(response === true) {
if(response.result === true) {
// TODO: Move this URL elsewhere
window.location = 'admin.php?page=mailpoet-newsletter-editor&id=' + this.props.params.id;
} else {
response.map(function(error) {
response.errors.map(function(error) {
MailPoet.Notice.error(error);
});
}
@ -93,6 +150,9 @@ define(
this.setState({ loading: false });
}
},
handleTemplateImport: function() {
this.getTemplates();
},
render: function() {
var templates = this.state.templates.map(function(template, index) {
var deleteLink = (
@ -152,6 +212,8 @@ define(
<ul className={ boxClasses }>
{ templates }
</ul>
<ImportTemplate onImport={this.handleTemplateImport} />
</div>
);
}

View File

@ -85,6 +85,26 @@ define(
</a>
</div>
</li>
<li data-type="notification">
<div className="mailpoet_thumbnail"></div>
<div className="mailpoet_description">
<h3>Post notifications</h3>
<p>
Automatically send posts immediately, daily, weekly or monthly. Filter by categories, if you like.
</p>
</div>
<div className="mailpoet_actions">
<a
className="button button-primary"
onClick={ this.setupNewsletter.bind(null, 'notification') }
>
Set up
</a>
</div>
</li>
</ul>
</div>
);

View File

@ -0,0 +1,225 @@
define(
[
'underscore',
'react',
'react-router',
'mailpoet',
'form/form.jsx',
'form/fields/select.jsx',
'form/fields/selection.jsx',
'form/fields/text.jsx',
'newsletters/breadcrumb.jsx'
],
function(
_,
React,
Router,
MailPoet,
Form,
Select,
Selection,
Text,
Breadcrumb
) {
var intervalField = {
name: 'interval',
values: {
'daily': 'Once a day at...',
'weekly': 'Weekly on...',
'monthly': 'Monthly on the...',
'nthWeekDay': 'Monthly every...',
'immediately': 'Immediately.',
},
};
var SECONDS_IN_DAY = 86400;
var TIME_STEP_SECONDS = 3600; // Default: 3600
var numberOfTimeSteps = SECONDS_IN_DAY / TIME_STEP_SECONDS;
var timeOfDayValues = _.object(_.map(
_.times(numberOfTimeSteps, function(step) { return step * TIME_STEP_SECONDS; }),
function(seconds) {
var date = new Date(null);
date.setSeconds(seconds);
var timeLabel = date.toISOString().substr(11, 5);
return [seconds, timeLabel];
}
));
var timeOfDayField = {
name: 'timeOfDay',
values: timeOfDayValues,
};
var weekDayField = {
name: 'weekDay',
values: {
0: 'Monday',
1: 'Tuesday',
2: 'Wednesday',
3: 'Thursday',
4: 'Friday',
5: 'Saturday',
6: 'Sunday',
},
};
var NUMBER_OF_DAYS_IN_MONTH = 28; // 28 for compatibility with MP2
var monthDayField = {
name: 'monthDay',
values: _.object(_.map(
_.times(NUMBER_OF_DAYS_IN_MONTH, function(day) { return day; }),
function(day) {
var suffixes = {
0: 'st',
1: 'nd',
2: 'rd'
};
var suffix = suffixes[day] || 'th';
return [day, (day + 1).toString() + suffix];
},
)),
};
var nthWeekDayField = {
name: 'nthWeekDay',
values: {
'0': '1st',
'1': '2nd',
'2': '3rd',
'3': 'last',
},
};
var NewsletterWelcome = React.createClass({
mixins: [
Router.History
],
getInitialState: function() {
return {
intervalType: 'immediate', // 'immediate'|'daily'|'weekly'|'monthly'
timeOfDay: 0,
weekDay: 0,
monthDay: 0,
nthWeekDay: 0,
};
},
handleIntervalChange: function(event) {
this.setState({
intervalType: event.target.value,
});
},
handleTimeOfDayChange: function(event) {
this.setState({
timeOfDay: event.target.value,
});
},
handleWeekDayChange: function(event) {
this.setState({
weekDay: event.target.value,
});
},
handleMonthDayChange: function(event) {
this.setState({
monthDay: event.target.value,
});
},
handleNthWeekDayChange: function(event) {
this.setState({
nthWeekDay: event.target.value,
});
},
handleNext: function() {
MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'create',
data: {
type: 'notification',
options: this.state,
},
}).done(function(response) {
if(response.id !== undefined) {
this.showTemplateSelection(response.id);
} else {
response.map(function(error) {
MailPoet.Notice.error(error);
});
}
}.bind(this));
},
showTemplateSelection: function(newsletterId) {
this.history.pushState(null, `/template/${newsletterId}`);
},
render: function() {
var timeOfDaySelection,
weekDaySelection,
monthDaySelection,
nthWeekDaySelection;
if (this.state.intervalType !== 'immediately') {
timeOfDaySelection = (
<Select
field={timeOfDayField}
item={this.state}
onValueChange={this.handleTimeOfDayChange} />
);
}
if (this.state.intervalType === 'weekly'
|| this.state.intervalType === 'nthWeekDay') {
weekDaySelection = (
<Select
field={weekDayField}
item={this.state}
onValueChange={this.handleWeekDayChange} />
);
}
if (this.state.intervalType === 'monthly') {
monthDaySelection = (
<Select
field={monthDayField}
item={this.state}
onValueChange={this.handleMonthDayChange} />
);
}
if (this.state.intervalType === 'nthWeekDay') {
nthWeekDaySelection = (
<Select
field={nthWeekDayField}
item={this.state}
onValueChange={this.handleNthWeekDayChange} />
);
}
return (
<div>
<h1>Post notifications</h1>
<Breadcrumb step="type" />
<Select
field={intervalField}
item={this.state}
onValueChange={this.handleIntervalChange} />
{nthWeekDaySelection}
{monthDaySelection}
{weekDaySelection}
{timeOfDaySelection}
<p className="submit">
<input
className="button button-primary"
type="button"
onClick={ this.handleNext }
value="Next" />
</p>
</div>
);
},
});
return NewsletterWelcome;
}
);

View File

@ -47,6 +47,8 @@ define('notice', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
type: 'success',
message: '',
static: false,
hideClose: false,
id: null,
scroll: false,
timeout: 2000,
onOpen: null,
@ -60,6 +62,9 @@ define('notice', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
// clone element
this.element = jQuery('#mailpoet_notice_'+this.options.type).clone();
// add data-id to the element
if (this.options.id) this.element.attr('data-id', 'notice_' + this.options.id);
// remove id from clone
this.element.removeAttr('id');
@ -73,7 +78,6 @@ define('notice', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
}
// listen to remove event
var element = this.element;
jQuery(this.element).on('close', function() {
jQuery(this).fadeOut(200, function() {
// on close callback
@ -148,7 +152,7 @@ define('notice', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
// if the notice is not static, it has to disappear after a timeout
if(this.options.static === false) {
this.element.delay(this.options.timeout).trigger('close');
} else {
} else if (this.options.hideClose === false) {
this.element.append('<a href="javascript:;" class="mailpoet_notice_close"><span class="dashicons dashicons-dismiss"></span></a>');
this.element.find('.mailpoet_notice_close').on('click', function() {
jQuery(this).trigger('close');
@ -163,6 +167,14 @@ define('notice', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
hide: function(all) {
if(all !== undefined && all === true) {
jQuery('.mailpoet_notice:not([id])').trigger('close');
} else if (all !== undefined && jQuery.isArray(all)) {
for (var id in all) {
jQuery('[data-id="notice_' + all[id] + '"]')
.trigger('close');
}
} if (all !== undefined) {
jQuery('[data-id="notice_' + all + '"]')
.trigger('close');
} else {
jQuery('.mailpoet_notice.updated:not([id]), .mailpoet_notice.error:not([id])')
.trigger('close');
@ -188,4 +200,4 @@ define('notice', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
}, options));
}
};
});
});

View File

@ -1,88 +1,78 @@
define('public', ['mailpoet', 'jquery', 'jquery-validation'],
function(MailPoet, $) {
'use strict';
define([
'mailpoet',
'jquery',
'parsleyjs'
],
function(
MailPoet,
jQuery,
Parsley
) {
jQuery(function($) {
function isSameDomain(url) {
var link = document.createElement('a');
link.href = url;
return (window.location.hostname === link.hostname);
}
function formatData(raw) {
var data = {};
$.each(raw, function(index, value) {
if(value.name.endsWith('[]')) {
var value_name = value.name.substr(0, value.name.length - 2);
// it's an array
if(data[value_name] === undefined) {
data[value_name] = [];
}
data[value_name].push(value.value);
} else {
data[value.name] = value.value;
}
});
return data;
}
$(function() {
// setup form validation
$('form.mailpoet_form').each(function() {
$(this).validate({
submitHandler: function(form) {
var data = $(form).serializeArray() || {};
var form = $(this);
// clear messages
$(form).find('.mailpoet_message').html('');
form.parsley().on('form:submit', function(parsley) {
// check if we're on the same domain
if(isSameDomain(MailPoetForm.ajax_url) === false) {
// non ajax post request
return true;
} else {
// ajax request
MailPoet.Ajax.post({
url: MailPoetForm.ajax_url,
token: MailPoetForm.token,
endpoint: 'subscribers',
action: 'save',
data: formatData(data),
onSuccess: function(response) {
if(response !== true) {
// errors
$.each(response, function(index, error) {
$(form)
.find('.mailpoet_message')
.append('<p class="mailpoet_validate_error">'+
error+
'</p>');
});
} else {
// successfully subscribed
if(response.page !== undefined) {
// go to page
window.location.href = response.page;
} else if(response.message !== undefined) {
// display success message
$(form)
.find('.mailpoet_message')
.html('<p class="mailpoet_validate_success">'+
response.message+
'</p>');
}
var data = form.serializeObject() || {};
// reset form
$(form).trigger('reset');
}
// clear messages
form.find('.mailpoet_message').html('');
// check if we're on the same domain
if(isSameDomain(MailPoetForm.ajax_url) === false) {
// non ajax post request
return true;
} else {
// ajax request
MailPoet.Ajax.post({
url: MailPoetForm.ajax_url,
token: MailPoetForm.token,
endpoint: 'subscribers',
action: 'subscribe',
data: data
}).done(function(response) {
if(response.result !== true) {
// errors
$.each(response.errors, function(index, error) {
form
.find('.mailpoet_message')
.append('<p class="mailpoet_validate_error">'+
error+
'</p>');
});
} else {
// successfully subscribed
if(response.page !== undefined) {
// go to page
window.location.href = response.page;
} else if(response.message !== undefined) {
// display success message
form
.find('.mailpoet_message')
.html('<p class="mailpoet_validate_success">'+
response.message+
'</p>');
}
});
}
return false;
// reset form
form.trigger('reset');
// reset validation
parsley.reset();
}
});
}
return false;
});
});
});
}
);
});
});

View File

@ -17,33 +17,44 @@ define(
name: 'name',
label: 'Name',
type: 'text'
},
{
name: 'description',
label: 'Description',
type: 'textarea'
}
];
var messages = {
updated: function() {
onUpdate: function() {
MailPoet.Notice.success('Segment successfully updated!');
},
created: function() {
onCreate: function() {
MailPoet.Notice.success('Segment successfully added!');
}
};
var Link = Router.Link;
var SegmentForm = React.createClass({
mixins: [
Router.History
],
render: function() {
return (
<div>
<h2 className="title">
Segment <Link className="add-new-h2" to="/">Back to list</Link>
Segment <a
href="javascript:;"
className="add-new-h2"
onClick={ this.history.goBack }
>Back to list</a>
</h2>
<Form
endpoint="segments"
fields={ fields }
params={ this.props.params }
messages={ messages } />
messages={ messages }
/>
</div>
);
}

View File

@ -1,85 +1,202 @@
define(
[
'react',
'react-router',
'listing/listing.jsx',
'classnames'
],
function(
React,
Router,
Listing,
classNames
) {
var columns = [
{
name: 'name',
label: 'Name',
sortable: true
},
{
name: 'created_at',
label: 'Created on',
sortable: true
},
{
name: 'updated_at',
label: 'Last modified on',
sortable: true
}
];
import React from 'react'
import { Router, Link } from 'react-router'
var bulk_actions = [
{
name: 'trash',
label: 'Trash'
}
];
import jQuery from 'jquery'
import MailPoet from 'mailpoet'
import classNames from 'classnames'
var Link = Router.Link;
import Listing from 'listing/listing.jsx'
var SegmentList = React.createClass({
renderItem: function(segment, actions) {
var rowClasses = classNames(
'manage-column',
'column-primary',
'has-row-actions'
);
return (
<div>
<td className={ rowClasses }>
<strong>
<a>{ segment.name }</a>
</strong>
{ actions }
</td>
<td className="column-date" data-colname="Subscribed on">
<abbr>{ segment.created_at }</abbr>
</td>
<td className="column-date" data-colname="Last modified on">
<abbr>{ segment.updated_at }</abbr>
</td>
</div>
);
},
render: function() {
return (
<div>
<h2 className="title">
Segments <Link className="add-new-h2" to="/new">New</Link>
</h2>
<Listing
endpoint="segments"
onRenderItem={this.renderItem}
columns={columns}
bulk_actions={ bulk_actions } />
</div>
);
}
});
return SegmentList;
var columns = [
{
name: 'name',
label: 'Name',
sortable: true
},
{
name: 'description',
label: 'Description',
sortable: false
},
{
name: 'subscribed',
label: 'Subscribed',
sortable: false
},
{
name: 'unconfirmed',
label: 'Unconfirmed',
sortable: false
},
{
name: 'unsubscribed',
label: 'Unsubscribed',
sortable: false
},
{
name: 'created_at',
label: 'Created on',
sortable: true
}
);
];
const messages = {
onTrash: function(response) {
if(response) {
let message = null;
if(~~response === 1) {
message = (
'1 segment was moved to the trash.'
);
} else if(~~response > 1) {
message = (
'%$1d segments were moved to the trash.'
).replace('%$1d', ~~response);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
},
onDelete: function(response) {
if(response) {
let message = null;
if(~~response === 1) {
message = (
'1 segment was permanently deleted.'
);
} else if(~~response > 1) {
message = (
'%$1d segments were permanently deleted.'
).replace('%$1d', ~~response);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
},
onRestore: function(response) {
if(response) {
let message = null;
if(~~response === 1) {
message = (
'1 segment has been restored from the trash.'
);
} else if(~~response > 1) {
message = (
'%$1d segments have been restored from the trash.'
).replace('%$1d', ~~response);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
}
};
const item_actions = [
{
name: 'edit',
label: 'Edit',
link: function(item) {
return (
<Link to={ `/edit/${item.id}` }>Edit</Link>
);
}
},
{
name: 'duplicate_segment',
label: 'Duplicate',
onClick: function(item, refresh) {
return MailPoet.Ajax.post({
endpoint: 'segments',
action: 'duplicate',
data: item.id
}).done(function(response) {
MailPoet.Notice.success(
('List "%$1s" has been duplicated.').replace('%$1s', response.name)
);
refresh();
});
}
},
{
name: 'view_subscribers',
link: function(item) {
return (
<a href={ item.subscribers_url }>View subscribers</a>
);
}
}
];
const bulk_actions = [
{
name: 'trash',
label: 'Trash',
onSuccess: messages.onTrash
}
];
const SegmentList = React.createClass({
renderItem: function(segment, actions) {
var rowClasses = classNames(
'manage-column',
'column-primary',
'has-row-actions'
);
return (
<div>
<td className={ rowClasses }>
<strong>
<a>{ segment.name }</a>
</strong>
{ actions }
</td>
<td className="column-date" data-colname="Description">
<abbr>{ segment.description }</abbr>
</td>
<td className="column-date" data-colname="Subscribed">
<abbr>{ segment.subscribed || 0 }</abbr>
</td>
<td className="column-date" data-colname="Unconfirmed">
<abbr>{ segment.unconfirmed || 0 }</abbr>
</td>
<td className="column-date" data-colname="Unsubscribed">
<abbr>{ segment.unsubscribed || 0 }</abbr>
</td>
<td className="column-date" data-colname="Created on">
<abbr>{ segment.created_at }</abbr>
</td>
</div>
);
},
render: function() {
return (
<div>
<h2 className="title">
Segments <Link className="add-new-h2" to="/new">New</Link>
</h2>
<Listing
location={ this.props.location }
params={ this.props.params }
messages={ messages }
search={ false }
limit={ 1000 }
endpoint="segments"
onRenderItem={ this.renderItem }
columns={ columns }
bulk_actions={ bulk_actions }
item_actions={ item_actions }
/>
</div>
);
}
});
module.exports = SegmentList;

View File

@ -73,7 +73,7 @@ define(
}));
jQuery(document).ready(function() {
Backbone.history.start();
if (!Backbone.History.started) Backbone.history.start();
});
}
);

View File

@ -37,14 +37,25 @@ define(
'subscribed': 'Subscribed',
'unsubscribed': 'Unsubscribed'
}
},
{
name: 'segments',
label: 'Lists',
type: 'selection',
placeholder: "Select a list",
endpoint: "segments",
multiple: true,
filter: function(segment) {
return !!(!segment.deleted_at);
}
}
];
var messages = {
updated: function() {
onUpdate: function() {
MailPoet.Notice.success('Subscriber successfully updated!');
},
created: function() {
onCreate: function() {
MailPoet.Notice.success('Subscriber successfully added!');
}
};
@ -52,18 +63,26 @@ define(
var Link = Router.Link;
var SubscriberForm = React.createClass({
mixins: [
Router.History
],
render: function() {
return (
<div>
<h2 className="title">
Subscriber <Link className="add-new-h2" to="/">Back to list</Link>
Subscriber <a
href="javascript:;"
className="add-new-h2"
onClick={ this.history.goBack }
>Back to list</a>
</h2>
<Form
endpoint="subscribers"
fields={ fields }
params={ this.props.params }
messages={ messages } />
messages={ messages }
/>
</div>
);
}

View File

@ -1,313 +1,306 @@
define(
[
'react',
'react-router',
'listing/listing.jsx',
'form/fields/selection.jsx',
'classnames',
'mailpoet',
'jquery',
'select2'
],
function(
React,
Router,
Listing,
Selection,
classNames,
MailPoet,
jQuery
) {
var Link = Router.Link;
import React from 'react'
import { Router, Route, Link } from 'react-router'
var columns = [
{
name: 'email',
label: 'Email',
sortable: true
},
{
name: 'first_name',
label: 'Firstname',
sortable: true
},
{
name: 'last_name',
label: 'Lastname',
sortable: true
},
{
name: 'status',
label: 'Status',
sortable: true
},
{
name: 'segments',
label: 'Lists',
sortable: false
},
import jQuery from 'jquery'
import MailPoet from 'mailpoet'
import classNames from 'classnames'
{
name: 'created_at',
label: 'Subscribed on',
sortable: true
},
{
name: 'updated_at',
label: 'Last modified on',
sortable: true
},
];
import Listing from 'listing/listing.jsx'
import Selection from 'form/fields/selection.jsx'
var messages = {
onDelete: function(response) {
var count = ~~response.subscribers;
var message = null;
const columns = [
{
name: 'email',
label: 'Subscriber',
sortable: true
},
{
name: 'status',
label: 'Status',
sortable: true
},
{
name: 'segments',
label: 'Lists',
sortable: false
},
if(count === 1) {
message = (
'1 subscriber was moved to the trash.'
).replace('%$1d', count);
} else if(count > 1) {
message = (
'%$1d subscribers were moved to the trash.'
).replace('%$1d', count);
}
{
name: 'created_at',
label: 'Subscribed on',
sortable: true
},
{
name: 'updated_at',
label: 'Last modified on',
sortable: true
},
];
if(message !== null) {
MailPoet.Notice.success(message);
}
},
onConfirmDelete: function(response) {
var count = ~~response.subscribers;
var message = null;
if(count === 1) {
message = (
'1 subscriber was permanently deleted.'
).replace('%$1d', count);
} else if(count > 1) {
message = (
'%$1d subscribers were permanently deleted.'
).replace('%$1d', count);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
},
onRestore: function(response) {
var count = ~~response.subscribers;
var message = null;
if(count === 1) {
message = (
'1 subscriber has been restored from the trash.'
).replace('%$1d', count);
} else if(count > 1) {
message = (
'%$1d subscribers have been restored from the trash.'
).replace('%$1d', count);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
};
var bulk_actions = [
{
name: 'moveToList',
label: 'Move to list...',
onSelect: function() {
var field = {
id: 'move_to_segment',
endpoint: 'segments'
};
return (
<Selection field={ field }/>
);
},
getData: function() {
return {
segment_id: ~~(jQuery('#move_to_segment').val())
}
},
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers were moved to list <strong>%$2s</strong>.'
.replace('%$1d', ~~response.subscribers)
.replace('%$2s', response.segment)
);
}
},
{
name: 'addToList',
label: 'Add to list...',
onSelect: function() {
var field = {
id: 'add_to_segment',
endpoint: 'segments'
};
return (
<Selection field={ field }/>
);
},
getData: function() {
return {
segment_id: ~~(jQuery('#add_to_segment').val())
}
},
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers were added to list <strong>%$2s</strong>.'
.replace('%$1d', ~~response.subscribers)
.replace('%$2s', response.segment)
);
}
},
{
name: 'removeFromList',
label: 'Remove from list...',
onSelect: function() {
var field = {
id: 'remove_from_segment',
endpoint: 'segments'
};
return (
<Selection field={ field }/>
);
},
getData: function() {
return {
segment_id: ~~(jQuery('#remove_from_segment').val())
}
},
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers were removed from list <strong>%$2s</strong>.'
.replace('%$1d', ~~response.subscribers)
.replace('%$2s', response.segment)
);
}
},
{
name: 'removeFromAllLists',
label: 'Remove from all lists',
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers were removed from all lists.'
.replace('%$1d', ~~response.subscribers)
.replace('%$2s', response.segment)
);
}
},
{
name: 'confirmUnconfirmed',
label: 'Confirm unconfirmed',
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers have been confirmed.'
.replace('%$1d', ~~response.subscribers)
);
}
},
{
name: 'trash',
label: 'Trash',
getData: function() {
return {
confirm: false
}
},
onSuccess: messages.onDelete
}
];
var SubscriberList = React.createClass({
renderItem: function(subscriber, actions) {
var row_classes = classNames(
'manage-column',
'column-primary',
'has-row-actions'
);
var status = '';
switch(subscriber.status) {
case 'subscribed':
status = 'Subscribed';
break;
case 'unconfirmed':
status = 'Unconfirmed';
break;
case 'unsubscribed':
status = 'Unsubscribed';
break;
}
var segments = mailpoet_segments.filter(function(segment) {
return (jQuery.inArray(segment.id, subscriber.segments) !== -1);
}).map(function(segment) {
return segment.name;
}).join(', ');
var row_actions = false;
return (
<div>
<td className={ row_classes }>
<strong><Link to={ `/edit/${ subscriber.id }` }>
{ subscriber.email }
</Link></strong>
{ actions }
</td>
<td className="column" data-colname="First name">
{ subscriber.first_name }
</td>
<td className="column" data-colname="Last name">
{ subscriber.last_name }
</td>
<td className="column" data-colname="Status">
{ status }
</td>
<td className="column" data-colname="Lists">
{ segments }
</td>
<td className="column-date" data-colname="Subscribed on">
<abbr>{ subscriber.created_at }</abbr>
</td>
<td className="column-date" data-colname="Last modified on">
<abbr>{ subscriber.updated_at }</abbr>
</td>
</div>
);
},
render: function() {
return (
<div>
<h2 className="title">
Subscribers <Link className="add-new-h2" to="/new">New</Link>
</h2>
<Listing
endpoint="subscribers"
onRenderItem={ this.renderItem }
columns={ columns }
bulk_actions={ bulk_actions }
messages={ messages }
/>
</div>
const messages = {
onTrash: function(response) {
if(response) {
var message = null;
if(~~response === 1) {
message = (
'1 subscriber was moved to the trash.'
);
} else if(~~response > 1) {
message = (
'%$1d subscribers were moved to the trash.'
).replace('%$1d', ~~response);
}
});
return SubscriberList;
if(message !== null) {
MailPoet.Notice.success(message);
}
}
},
onDelete: function(response) {
if(response) {
var message = null;
if(~~response === 1) {
message = (
'1 subscriber was permanently deleted.'
);
} else if(~~response > 1) {
message = (
'%$1d subscribers were permanently deleted.'
).replace('%$1d', ~~response);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
},
onRestore: function(response) {
if(response) {
var message = null;
if(~~response === 1) {
message = (
'1 subscriber has been restored from the trash.'
);
} else if(~~response > 1) {
message = (
'%$1d subscribers have been restored from the trash.'
).replace('%$1d', ~~response);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
}
);
};
const bulk_actions = [
{
name: 'moveToList',
label: 'Move to list...',
onSelect: function() {
let field = {
id: 'move_to_segment',
endpoint: 'segments',
filter: function(segment) {
return !!(!segment.deleted_at);
}
};
return (
<Selection field={ field }/>
);
},
getData: function() {
return {
segment_id: ~~(jQuery('#move_to_segment').val())
}
},
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers were moved to list <strong>%$2s</strong>.'
.replace('%$1d', ~~response.subscribers)
.replace('%$2s', response.segment)
);
}
},
{
name: 'addToList',
label: 'Add to list...',
onSelect: function() {
let field = {
id: 'add_to_segment',
endpoint: 'segments',
filter: function(segment) {
return !!(!segment.deleted_at);
}
};
return (
<Selection field={ field }/>
);
},
getData: function() {
return {
segment_id: ~~(jQuery('#add_to_segment').val())
}
},
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers were added to list <strong>%$2s</strong>.'
.replace('%$1d', ~~response.subscribers)
.replace('%$2s', response.segment)
);
}
},
{
name: 'removeFromList',
label: 'Remove from list...',
onSelect: function() {
let field = {
id: 'remove_from_segment',
endpoint: 'segments'
};
return (
<Selection field={ field }/>
);
},
getData: function() {
return {
segment_id: ~~(jQuery('#remove_from_segment').val())
}
},
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers were removed from list <strong>%$2s</strong>.'
.replace('%$1d', ~~response.subscribers)
.replace('%$2s', response.segment)
);
}
},
{
name: 'removeFromAllLists',
label: 'Remove from all lists',
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers were removed from all lists.'
.replace('%$1d', ~~response)
);
}
},
{
name: 'confirmUnconfirmed',
label: 'Confirm unconfirmed',
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers have been confirmed.'
.replace('%$1d', ~~response)
);
}
},
{
name: 'trash',
label: 'Trash',
onSuccess: messages.onTrash
}
];
const SubscriberList = React.createClass({
renderItem: function(subscriber, actions) {
let row_classes = classNames(
'manage-column',
'column-primary',
'has-row-actions',
'column-username'
);
let status = '';
switch(subscriber.status) {
case 'subscribed':
status = 'Subscribed';
break;
case 'unconfirmed':
status = 'Unconfirmed';
break;
case 'unsubscribed':
status = 'Unsubscribed';
break;
}
let segments = mailpoet_segments.filter(function(segment) {
return (jQuery.inArray(segment.id, subscriber.segments) !== -1);
}).map(function(segment) {
return segment.name;
}).join(', ');
let avatar = false;
if(subscriber.avatar_url) {
avatar = (
<img
className="avatar"
src={ subscriber.avatar_url }
title=""
width="32"
height="32"
/>
);
}
return (
<div>
<td className={ row_classes }>
<strong><Link to={ `/edit/${ subscriber.id }` }>
{ subscriber.email }
</Link></strong>
<p style={{margin: 0}}>
{ subscriber.first_name } { subscriber.last_name }
</p>
{ actions }
</td>
<td className="column" data-colname="Status">
{ status }
</td>
<td className="column" data-colname="Lists">
{ segments }
</td>
<td className="column-date" data-colname="Subscribed on">
<abbr>{ subscriber.created_at }</abbr>
</td>
<td className="column-date" data-colname="Last modified on">
<abbr>{ subscriber.updated_at }</abbr>
</td>
</div>
);
},
onGetItems: function(count) {
jQuery('#mailpoet_export_button')[(count > 0) ? 'show' : 'hide']();
},
render: function() {
return (
<div>
<h2 className="title">
Subscribers <Link className="add-new-h2" to="/new">New</Link>
<a className="add-new-h2" href="?page=mailpoet-import#step1">Import</a>
<a id="mailpoet_export_button" className="add-new-h2" href="?page=mailpoet-export">Export</a>
</h2>
<Listing
location={ this.props.location }
params={ this.props.params }
endpoint="subscribers"
onRenderItem={ this.renderItem }
columns={ columns }
bulk_actions={ bulk_actions }
messages={ messages }
onGetItems={ this.onGetItems }
/>
</div>
)
}
});
module.exports = SubscriberList;

View File

@ -5,15 +5,15 @@ import SubscriberList from 'subscribers/list.jsx'
import SubscriberForm from 'subscribers/form.jsx'
import createHashHistory from 'history/lib/createHashHistory'
let history = createHashHistory({ queryKey: false })
const history = createHashHistory({ queryKey: false })
const App = React.createClass({
render() {
return this.props.children
return this.props.children;
}
});
let container = document.getElementById('subscribers_container');
const container = document.getElementById('subscribers_container')
if(container) {
ReactDOM.render((

View File

@ -0,0 +1,264 @@
// Generated by CoffeeScript 1.9.2
/**
@license Sticky-kit v1.1.2 | WTFPL | Leaf Corcoran 2015 | http://leafo.net
*/
(function() {
var $, win;
$ = this.jQuery || window.jQuery;
win = $(window);
$.fn.stick_in_parent = function(opts) {
var doc, elm, enable_bottoming, fn, i, inner_scrolling, len, manual_spacer, offset_top, outer_width, parent_selector, recalc_every, sticky_class;
if (opts == null) {
opts = {};
}
sticky_class = opts.sticky_class, inner_scrolling = opts.inner_scrolling, recalc_every = opts.recalc_every, parent_selector = opts.parent, offset_top = opts.offset_top, manual_spacer = opts.spacer, enable_bottoming = opts.bottoming;
if (offset_top == null) {
offset_top = 0;
}
if (parent_selector == null) {
parent_selector = void 0;
}
if (inner_scrolling == null) {
inner_scrolling = true;
}
if (sticky_class == null) {
sticky_class = "is_stuck";
}
doc = $(document);
if (enable_bottoming == null) {
enable_bottoming = true;
}
outer_width = function(el) {
var _el, computed, w;
if (window.getComputedStyle) {
_el = el[0];
computed = window.getComputedStyle(el[0]);
w = parseFloat(computed.getPropertyValue("width")) + parseFloat(computed.getPropertyValue("margin-left")) + parseFloat(computed.getPropertyValue("margin-right"));
if (computed.getPropertyValue("box-sizing") !== "border-box") {
w += parseFloat(computed.getPropertyValue("border-left-width")) + parseFloat(computed.getPropertyValue("border-right-width")) + parseFloat(computed.getPropertyValue("padding-left")) + parseFloat(computed.getPropertyValue("padding-right"));
}
return w;
} else {
return el.outerWidth(true);
}
};
fn = function(elm, padding_bottom, parent_top, parent_height, top, height, el_float, detached) {
var bottomed, detach, fixed, last_pos, last_scroll_height, offset, parent, recalc, recalc_and_tick, recalc_counter, spacer, tick;
if (elm.data("sticky_kit")) {
return;
}
elm.data("sticky_kit", true);
last_scroll_height = doc.height();
parent = elm.parent();
if (parent_selector != null) {
parent = parent.closest(parent_selector);
}
if (!parent.length) {
throw "failed to find stick parent";
}
fixed = false;
bottomed = false;
spacer = manual_spacer != null ? manual_spacer && elm.closest(manual_spacer) : $("<div />");
if (spacer) {
spacer.css('position', elm.css('position'));
}
recalc = function() {
var border_top, padding_top, restore;
if (detached) {
return;
}
last_scroll_height = doc.height();
border_top = parseInt(parent.css("border-top-width"), 10);
padding_top = parseInt(parent.css("padding-top"), 10);
padding_bottom = parseInt(parent.css("padding-bottom"), 10);
parent_top = parent.offset().top + border_top + padding_top;
parent_height = parent.height();
if (fixed) {
fixed = false;
bottomed = false;
if (manual_spacer == null) {
elm.insertAfter(spacer);
spacer.detach();
}
elm.css({
position: "",
top: "",
width: "",
bottom: ""
}).removeClass(sticky_class);
restore = true;
}
top = elm.offset().top - (parseInt(elm.css("margin-top"), 10) || 0) - offset_top;
height = elm.outerHeight(true);
el_float = elm.css("float");
if (spacer) {
spacer.css({
width: outer_width(elm),
height: height,
display: elm.css("display"),
"vertical-align": elm.css("vertical-align"),
"float": el_float
});
}
if (restore) {
return tick();
}
};
recalc();
last_pos = void 0;
offset = offset_top;
recalc_counter = recalc_every;
tick = function() {
var css, delta, recalced, scroll, will_bottom, win_height;
if (detached) {
return;
}
recalced = false;
if (recalc_counter != null) {
recalc_counter -= 1;
if (recalc_counter <= 0) {
recalc_counter = recalc_every;
recalc();
recalced = true;
}
}
if (!recalced && doc.height() !== last_scroll_height) {
recalc();
recalced = true;
}
scroll = win.scrollTop();
if (last_pos != null) {
delta = scroll - last_pos;
}
last_pos = scroll;
if (fixed) {
if (enable_bottoming) {
will_bottom = scroll + height + offset > parent_height + parent_top;
if (bottomed && !will_bottom) {
bottomed = false;
elm.css({
position: "fixed",
bottom: "",
top: offset
}).trigger("sticky_kit:unbottom");
}
}
if (scroll < top) {
fixed = false;
offset = offset_top;
if (manual_spacer == null) {
if (el_float === "left" || el_float === "right") {
elm.insertAfter(spacer);
}
spacer.detach();
}
css = {
position: "",
width: "",
top: ""
};
elm.css(css).removeClass(sticky_class).trigger("sticky_kit:unstick");
}
if (inner_scrolling) {
win_height = win.height();
if (height + offset_top > win_height) {
if (!bottomed) {
offset -= delta;
offset = Math.max(win_height - height, offset);
offset = Math.min(offset_top, offset);
if (fixed) {
elm.css({
top: offset + "px"
});
}
}
}
}
} else {
if (scroll > top) {
fixed = true;
css = {
position: "fixed",
top: offset
};
css.width = elm.css("box-sizing") === "border-box" ? elm.outerWidth() + "px" : elm.width() + "px";
elm.css(css).addClass(sticky_class);
if (manual_spacer == null) {
elm.after(spacer);
if (el_float === "left" || el_float === "right") {
spacer.append(elm);
}
}
elm.trigger("sticky_kit:stick");
}
}
if (fixed && enable_bottoming) {
if (will_bottom == null) {
will_bottom = scroll + height + offset > parent_height + parent_top;
}
if (!bottomed && will_bottom) {
bottomed = true;
if (parent.css("position") === "static") {
parent.css({
position: "relative"
});
}
return elm.css({
position: "absolute",
bottom: padding_bottom,
top: "auto"
}).trigger("sticky_kit:bottom");
}
}
};
recalc_and_tick = function() {
recalc();
return tick();
};
detach = function() {
detached = true;
win.off("touchmove", tick);
win.off("scroll", tick);
win.off("resize", recalc_and_tick);
$(document.body).off("sticky_kit:recalc", recalc_and_tick);
elm.off("sticky_kit:detach", detach);
elm.removeData("sticky_kit");
elm.css({
position: "",
bottom: "",
top: "",
width: ""
});
parent.position("position", "");
if (fixed) {
if (manual_spacer == null) {
if (el_float === "left" || el_float === "right") {
elm.insertAfter(spacer);
}
spacer.remove();
}
return elm.removeClass(sticky_class);
}
};
win.on("touchmove", tick);
win.on("scroll", tick);
win.on("resize", recalc_and_tick);
$(document.body).on("sticky_kit:recalc", recalc_and_tick);
elm.on("sticky_kit:detach", detach);
return setTimeout(tick, 0);
};
for (i = 0, len = this.length; i < len; i++) {
elm = this[i];
fn($(elm));
}
return this;
};
}).call(this);

19
build
View File

@ -7,21 +7,20 @@ rm wysija-newsletters.zip;
mkdir wysija-newsletters;
# Production assets.
npm install;
./do compile:all;
# Production libraries.
rm -rf vendor;
rm composer.lock;
./composer.phar install --no-dev;
# Copy release folders.
cp -rf lang wysija-newsletters;
cp -rfL assets wysija-newsletters;
cp -rf lib wysija-newsletters;
cp -rf vendor wysija-newsletters;
cp -rf views wysija-newsletters;
rm -rf wysija-newsletters/assets/css/src;
rm -rf wysija-newsletters/assets/js/src;
cp -Rf lang wysija-newsletters;
cp -RfL assets wysija-newsletters;
cp -Rf lib wysija-newsletters;
cp -Rf vendor wysija-newsletters;
cp -Rf views wysija-newsletters;
rm -Rf wysija-newsletters/assets/css/src;
rm -Rf wysija-newsletters/assets/js/src;
# Copy release files.
cp LICENSE wysija-newsletters;
@ -37,6 +36,4 @@ zip -r wysija-newsletters.zip wysija-newsletters;
rm -rf wysija-newsletters;
# Reinstall dev dependencies.
rm composer.lock;
./composer.phar install;
./do install;

View File

@ -7,7 +7,8 @@
"sunra/php-simple-html-dom-parser": "*",
"tburry/pquery": "*",
"j4mie/paris": "1.5.4",
"swiftmailer/swiftmailer": "^5.4"
"swiftmailer/swiftmailer": "^5.4",
"phpseclib/phpseclib": "*"
},
"require-dev": {
"codeception/codeception": "*",

412
composer.lock generated
View File

@ -4,8 +4,8 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "92704d2679fce692438b9e6f1dc6e02f",
"content-hash": "3297411fcec47a02bc4f456fbf3751d1",
"hash": "7d7ef94b6e40ac2b2d594e5832d7e16d",
"content-hash": "2e70c335edf7429df0794ebf49e2f210",
"packages": [
{
"name": "cerdic/css-tidy",
@ -160,16 +160,16 @@
},
{
"name": "phpmailer/phpmailer",
"version": "v5.2.13",
"version": "v5.2.14",
"source": {
"type": "git",
"url": "https://github.com/PHPMailer/PHPMailer.git",
"reference": "45df3a88f7f46071e10d0b600f228d19f95911b3"
"reference": "e774bc9152de85547336e22b8926189e582ece95"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/45df3a88f7f46071e10d0b600f228d19f95911b3",
"reference": "45df3a88f7f46071e10d0b600f228d19f95911b3",
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/e774bc9152de85547336e22b8926189e582ece95",
"reference": "e774bc9152de85547336e22b8926189e582ece95",
"shasum": ""
},
"require": {
@ -180,7 +180,8 @@
"phpunit/phpunit": "4.7.*"
},
"suggest": {
"league/oauth2-client": "Needed for Gmail's XOAUTH2 authentication system"
"league/oauth2-client": "Needed for XOAUTH2 authentication",
"league/oauth2-google": "Needed for Gmail XOAUTH2"
},
"type": "library",
"autoload": {
@ -216,7 +217,95 @@
}
],
"description": "PHPMailer is a full-featured email creation and transfer class for PHP",
"time": "2015-09-14 09:18:12"
"time": "2015-11-01 10:15:28"
},
{
"name": "phpseclib/phpseclib",
"version": "2.0.0",
"source": {
"type": "git",
"url": "https://github.com/phpseclib/phpseclib.git",
"reference": "a74aa9efbe61430fcb60157c8e025a48ec8ff604"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/a74aa9efbe61430fcb60157c8e025a48ec8ff604",
"reference": "a74aa9efbe61430fcb60157c8e025a48ec8ff604",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"phing/phing": "~2.7",
"phpunit/phpunit": "~4.0",
"sami/sami": "~2.0",
"squizlabs/php_codesniffer": "~2.0"
},
"suggest": {
"ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.",
"ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.",
"ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.",
"ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations.",
"pear-pear/PHP_Compat": "Install PHP_Compat to get phpseclib working on PHP < 5.0.0."
},
"type": "library",
"autoload": {
"psr-4": {
"phpseclib\\": "phpseclib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"include-path": [
"phpseclib/"
],
"license": [
"MIT"
],
"authors": [
{
"name": "Jim Wigginton",
"email": "terrafrost@php.net",
"role": "Lead Developer"
},
{
"name": "Patrick Monnerat",
"email": "pm@datasphere.ch",
"role": "Developer"
},
{
"name": "Andreas Fischer",
"email": "bantu@phpbb.com",
"role": "Developer"
},
{
"name": "Hans-Jürgen Petrich",
"email": "petrich@tronic-media.com",
"role": "Developer"
}
],
"description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.",
"homepage": "http://phpseclib.sourceforge.net",
"keywords": [
"BigInteger",
"aes",
"asn.1",
"asn1",
"blowfish",
"crypto",
"cryptography",
"encryption",
"rsa",
"security",
"sftp",
"signature",
"signing",
"ssh",
"twofish",
"x.509",
"x509"
],
"time": "2015-08-04 04:48:03"
},
{
"name": "sunra/php-simple-html-dom-parser",
@ -368,16 +457,16 @@
},
{
"name": "twig/twig",
"version": "v1.22.3",
"version": "v1.23.1",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "ebfc36b7e77b0c1175afe30459cf943010245540"
"reference": "d9b6333ae8dd2c8e3fd256e127548def0bc614c6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/ebfc36b7e77b0c1175afe30459cf943010245540",
"reference": "ebfc36b7e77b0c1175afe30459cf943010245540",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/d9b6333ae8dd2c8e3fd256e127548def0bc614c6",
"reference": "d9b6333ae8dd2c8e3fd256e127548def0bc614c6",
"shasum": ""
},
"require": {
@ -390,7 +479,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.22-dev"
"dev-master": "1.23-dev"
}
},
"autoload": {
@ -425,22 +514,22 @@
"keywords": [
"templating"
],
"time": "2015-10-13 07:07:02"
"time": "2015-11-05 12:49:06"
}
],
"packages-dev": [
{
"name": "codeception/codeception",
"version": "2.1.3",
"version": "2.1.4",
"source": {
"type": "git",
"url": "https://github.com/Codeception/Codeception.git",
"reference": "cd810cb78a869408602e17271f9b7368b09a7ca8"
"reference": "6a812e8a0d1b1db939a29b4dc14cb398b21b6112"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Codeception/Codeception/zipball/cd810cb78a869408602e17271f9b7368b09a7ca8",
"reference": "cd810cb78a869408602e17271f9b7368b09a7ca8",
"url": "https://api.github.com/repos/Codeception/Codeception/zipball/6a812e8a0d1b1db939a29b4dc14cb398b21b6112",
"reference": "6a812e8a0d1b1db939a29b4dc14cb398b21b6112",
"shasum": ""
},
"require": {
@ -507,7 +596,7 @@
"functional testing",
"unit testing"
],
"time": "2015-10-02 09:38:59"
"time": "2015-11-12 03:57:06"
},
{
"name": "codeception/verify",
@ -545,16 +634,16 @@
},
{
"name": "codegyre/robo",
"version": "0.5.4",
"version": "0.6.0",
"source": {
"type": "git",
"url": "https://github.com/Codegyre/Robo.git",
"reference": "10aa223f6d1db182dc81d723bf1545dfc6ff380d"
"reference": "d18185f0494c854d36aa5ee0ad931ee23bbef552"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Codegyre/Robo/zipball/10aa223f6d1db182dc81d723bf1545dfc6ff380d",
"reference": "10aa223f6d1db182dc81d723bf1545dfc6ff380d",
"url": "https://api.github.com/repos/Codegyre/Robo/zipball/d18185f0494c854d36aa5ee0ad931ee23bbef552",
"reference": "d18185f0494c854d36aa5ee0ad931ee23bbef552",
"shasum": ""
},
"require": {
@ -568,6 +657,7 @@
"require-dev": {
"codeception/aspect-mock": "0.5.*",
"codeception/base": "~2.1",
"codeception/codeception": "2.1",
"codeception/verify": "0.2.*",
"natxet/cssmin": "~3.0",
"patchwork/jsqueeze": "~1.0"
@ -592,7 +682,7 @@
}
],
"description": "Modern task runner",
"time": "2015-08-31 17:35:30"
"time": "2015-10-30 11:29:52"
},
{
"name": "doctrine/instantiator",
@ -650,16 +740,16 @@
},
{
"name": "facebook/webdriver",
"version": "1.0.2",
"version": "1.0.4",
"source": {
"type": "git",
"url": "https://github.com/facebook/php-webdriver.git",
"reference": "fe1bbbc5dde804d08a8593f1d9d0d3b05f5c84f5"
"reference": "a6e209a309bf7cd71acf15476f40b11a25d5a79d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/facebook/php-webdriver/zipball/fe1bbbc5dde804d08a8593f1d9d0d3b05f5c84f5",
"reference": "fe1bbbc5dde804d08a8593f1d9d0d3b05f5c84f5",
"url": "https://api.github.com/repos/facebook/php-webdriver/zipball/a6e209a309bf7cd71acf15476f40b11a25d5a79d",
"reference": "a6e209a309bf7cd71acf15476f40b11a25d5a79d",
"shasum": ""
},
"require": {
@ -689,7 +779,7 @@
"selenium",
"webdriver"
],
"time": "2015-08-12 20:21:31"
"time": "2015-11-03 22:17:22"
},
{
"name": "guzzlehttp/guzzle",
@ -806,16 +896,16 @@
},
{
"name": "guzzlehttp/psr7",
"version": "1.2.0",
"version": "1.2.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "4ef919b0cf3b1989523138b60163bbcb7ba1ff7e"
"reference": "4d0bdbe1206df7440219ce14c972aa57cc5e4982"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/4ef919b0cf3b1989523138b60163bbcb7ba1ff7e",
"reference": "4ef919b0cf3b1989523138b60163bbcb7ba1ff7e",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/4d0bdbe1206df7440219ce14c972aa57cc5e4982",
"reference": "4d0bdbe1206df7440219ce14c972aa57cc5e4982",
"shasum": ""
},
"require": {
@ -860,19 +950,19 @@
"stream",
"uri"
],
"time": "2015-08-15 19:32:36"
"time": "2015-11-03 01:34:55"
},
{
"name": "henrikbjorn/lurker",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/henrikbjorn/Lurker.git",
"url": "https://github.com/flint/Lurker.git",
"reference": "a020d45b3bc37810aeafe27343c51af8a74c9419"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/henrikbjorn/Lurker/zipball/a020d45b3bc37810aeafe27343c51af8a74c9419",
"url": "https://api.github.com/repos/flint/Lurker/zipball/a020d45b3bc37810aeafe27343c51af8a74c9419",
"reference": "a020d45b3bc37810aeafe27343c51af8a74c9419",
"shasum": ""
},
@ -901,18 +991,16 @@
],
"authors": [
{
"name": "Henrik Bjornskov",
"email": "henrik@bjrnskov.dk",
"homepage": "http://henrik.bjrnskov.dk"
"name": "Yaroslav Kiliba",
"email": "om.dattaya@gmail.com"
},
{
"name": "Konstantin Kudryashov",
"email": "ever.zet@gmail.com",
"homepage": "http://everzet.com"
"email": "ever.zet@gmail.com"
},
{
"name": "Yaroslav Kiliba",
"email": "om.dattaya@gmail.com"
"name": "Henrik Bjrnskov",
"email": "henrik@bjrnskov.dk"
}
],
"description": "Resource Watcher.",
@ -1274,16 +1362,16 @@
},
{
"name": "phpunit/phpunit",
"version": "4.8.16",
"version": "4.8.18",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "625f8c345606ed0f3a141dfb88f4116f0e22978e"
"reference": "fa33d4ad96481b91df343d83e8c8aabed6b1dfd3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/625f8c345606ed0f3a141dfb88f4116f0e22978e",
"reference": "625f8c345606ed0f3a141dfb88f4116f0e22978e",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fa33d4ad96481b91df343d83e8c8aabed6b1dfd3",
"reference": "fa33d4ad96481b91df343d83e8c8aabed6b1dfd3",
"shasum": ""
},
"require": {
@ -1342,7 +1430,7 @@
"testing",
"xunit"
],
"time": "2015-10-23 06:48:33"
"time": "2015-11-11 11:32:49"
},
{
"name": "phpunit/phpunit-mock-objects",
@ -1873,16 +1961,16 @@
},
{
"name": "symfony/browser-kit",
"version": "v2.7.5",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/browser-kit.git",
"reference": "277a2457776d4cc25706fbdd9d1e4ab2dac884e4"
"reference": "07d664a052572ccc28eb2ab7dbbe82155b1ad367"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/browser-kit/zipball/277a2457776d4cc25706fbdd9d1e4ab2dac884e4",
"reference": "277a2457776d4cc25706fbdd9d1e4ab2dac884e4",
"url": "https://api.github.com/repos/symfony/browser-kit/zipball/07d664a052572ccc28eb2ab7dbbe82155b1ad367",
"reference": "07d664a052572ccc28eb2ab7dbbe82155b1ad367",
"shasum": ""
},
"require": {
@ -1891,8 +1979,7 @@
},
"require-dev": {
"symfony/css-selector": "~2.0,>=2.0.5",
"symfony/phpunit-bridge": "~2.7",
"symfony/process": "~2.0,>=2.0.5"
"symfony/process": "~2.3.34|~2.7,>=2.7.6"
},
"suggest": {
"symfony/process": ""
@ -1924,29 +2011,26 @@
],
"description": "Symfony BrowserKit Component",
"homepage": "https://symfony.com",
"time": "2015-09-06 08:36:38"
"time": "2015-10-23 14:47:27"
},
{
"name": "symfony/config",
"version": "v2.7.5",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/config.git",
"reference": "9698fdf0a750d6887d5e7729d5cf099765b20e61"
"reference": "831f88908b51b9ce945f5e6f402931d1ac544423"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/config/zipball/9698fdf0a750d6887d5e7729d5cf099765b20e61",
"reference": "9698fdf0a750d6887d5e7729d5cf099765b20e61",
"url": "https://api.github.com/repos/symfony/config/zipball/831f88908b51b9ce945f5e6f402931d1ac544423",
"reference": "831f88908b51b9ce945f5e6f402931d1ac544423",
"shasum": ""
},
"require": {
"php": ">=5.3.9",
"symfony/filesystem": "~2.3"
},
"require-dev": {
"symfony/phpunit-bridge": "~2.7"
},
"type": "library",
"extra": {
"branch-alias": {
@ -1974,20 +2058,20 @@
],
"description": "Symfony Config Component",
"homepage": "https://symfony.com",
"time": "2015-09-21 15:02:29"
"time": "2015-10-11 09:39:48"
},
{
"name": "symfony/console",
"version": "v2.7.5",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "06cb17c013a82f94a3d840682b49425cd00a2161"
"reference": "5efd632294c8320ea52492db22292ff853a43766"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/06cb17c013a82f94a3d840682b49425cd00a2161",
"reference": "06cb17c013a82f94a3d840682b49425cd00a2161",
"url": "https://api.github.com/repos/symfony/console/zipball/5efd632294c8320ea52492db22292ff853a43766",
"reference": "5efd632294c8320ea52492db22292ff853a43766",
"shasum": ""
},
"require": {
@ -1996,7 +2080,6 @@
"require-dev": {
"psr/log": "~1.0",
"symfony/event-dispatcher": "~2.1",
"symfony/phpunit-bridge": "~2.7",
"symfony/process": "~2.1"
},
"suggest": {
@ -2031,28 +2114,25 @@
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
"time": "2015-09-25 08:32:23"
"time": "2015-10-20 14:38:46"
},
{
"name": "symfony/css-selector",
"version": "v2.7.5",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
"reference": "abe19cc0429a06be0c133056d1f9859854860970"
"reference": "e1b865b26be4a56d22a8dee398375044a80c865b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/css-selector/zipball/abe19cc0429a06be0c133056d1f9859854860970",
"reference": "abe19cc0429a06be0c133056d1f9859854860970",
"url": "https://api.github.com/repos/symfony/css-selector/zipball/e1b865b26be4a56d22a8dee398375044a80c865b",
"reference": "e1b865b26be4a56d22a8dee398375044a80c865b",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"require-dev": {
"symfony/phpunit-bridge": "~2.7"
},
"type": "library",
"extra": {
"branch-alias": {
@ -2084,28 +2164,27 @@
],
"description": "Symfony CssSelector Component",
"homepage": "https://symfony.com",
"time": "2015-09-22 13:49:29"
"time": "2015-10-11 09:39:48"
},
{
"name": "symfony/dom-crawler",
"version": "v2.7.5",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/dom-crawler.git",
"reference": "2e185ca136399f902b948694987e62c80099c052"
"reference": "5fef7d8b80d8f9992df99d8ee283f420484c9612"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/2e185ca136399f902b948694987e62c80099c052",
"reference": "2e185ca136399f902b948694987e62c80099c052",
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/5fef7d8b80d8f9992df99d8ee283f420484c9612",
"reference": "5fef7d8b80d8f9992df99d8ee283f420484c9612",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"require-dev": {
"symfony/css-selector": "~2.3",
"symfony/phpunit-bridge": "~2.7"
"symfony/css-selector": "~2.3"
},
"suggest": {
"symfony/css-selector": ""
@ -2137,20 +2216,20 @@
],
"description": "Symfony DomCrawler Component",
"homepage": "https://symfony.com",
"time": "2015-09-20 21:13:58"
"time": "2015-10-11 09:39:48"
},
{
"name": "symfony/event-dispatcher",
"version": "v2.7.5",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "ae4dcc2a8d3de98bd794167a3ccda1311597c5d9"
"reference": "87a5db5ea887763fa3a31a5471b512ff1596d9b8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/ae4dcc2a8d3de98bd794167a3ccda1311597c5d9",
"reference": "ae4dcc2a8d3de98bd794167a3ccda1311597c5d9",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/87a5db5ea887763fa3a31a5471b512ff1596d9b8",
"reference": "87a5db5ea887763fa3a31a5471b512ff1596d9b8",
"shasum": ""
},
"require": {
@ -2161,7 +2240,6 @@
"symfony/config": "~2.0,>=2.0.5",
"symfony/dependency-injection": "~2.6",
"symfony/expression-language": "~2.6",
"symfony/phpunit-bridge": "~2.7",
"symfony/stopwatch": "~2.3"
},
"suggest": {
@ -2195,28 +2273,25 @@
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
"time": "2015-09-22 13:49:29"
"time": "2015-10-11 09:39:48"
},
{
"name": "symfony/filesystem",
"version": "v2.7.5",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "a17f8a17c20e8614c15b8e116e2f4bcde102cfab"
"reference": "56fd6df73be859323ff97418d97edc1d756df6df"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/a17f8a17c20e8614c15b8e116e2f4bcde102cfab",
"reference": "a17f8a17c20e8614c15b8e116e2f4bcde102cfab",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/56fd6df73be859323ff97418d97edc1d756df6df",
"reference": "56fd6df73be859323ff97418d97edc1d756df6df",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"require-dev": {
"symfony/phpunit-bridge": "~2.7"
},
"type": "library",
"extra": {
"branch-alias": {
@ -2244,28 +2319,25 @@
],
"description": "Symfony Filesystem Component",
"homepage": "https://symfony.com",
"time": "2015-09-09 17:42:36"
"time": "2015-10-18 20:23:18"
},
{
"name": "symfony/finder",
"version": "v2.7.5",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
"reference": "8262ab605973afbb3ef74b945daabf086f58366f"
"reference": "2ffb4e9598db3c48eb6d0ae73b04bbf09280c59d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/8262ab605973afbb3ef74b945daabf086f58366f",
"reference": "8262ab605973afbb3ef74b945daabf086f58366f",
"url": "https://api.github.com/repos/symfony/finder/zipball/2ffb4e9598db3c48eb6d0ae73b04bbf09280c59d",
"reference": "2ffb4e9598db3c48eb6d0ae73b04bbf09280c59d",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"require-dev": {
"symfony/phpunit-bridge": "~2.7"
},
"type": "library",
"extra": {
"branch-alias": {
@ -2293,20 +2365,20 @@
],
"description": "Symfony Finder Component",
"homepage": "https://symfony.com",
"time": "2015-09-19 19:59:23"
"time": "2015-10-11 09:39:48"
},
{
"name": "symfony/form",
"version": "v2.7.5",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/form.git",
"reference": "d4a990d2ebe4dd39cac52c5a40a5aac84b12b237"
"reference": "b93fcb816bec2b8470ea9d54e4b6658b2461b83c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/form/zipball/d4a990d2ebe4dd39cac52c5a40a5aac84b12b237",
"reference": "d4a990d2ebe4dd39cac52c5a40a5aac84b12b237",
"url": "https://api.github.com/repos/symfony/form/zipball/b93fcb816bec2b8470ea9d54e4b6658b2461b83c",
"reference": "b93fcb816bec2b8470ea9d54e4b6658b2461b83c",
"shasum": ""
},
"require": {
@ -2325,7 +2397,6 @@
"doctrine/collections": "~1.0",
"symfony/http-foundation": "~2.2",
"symfony/http-kernel": "~2.4",
"symfony/phpunit-bridge": "~2.7",
"symfony/security-csrf": "~2.4",
"symfony/translation": "~2.0,>=2.0.5",
"symfony/validator": "~2.6,>=2.6.8"
@ -2363,28 +2434,27 @@
],
"description": "Symfony Form Component",
"homepage": "https://symfony.com",
"time": "2015-09-22 13:49:29"
"time": "2015-10-27 15:38:06"
},
{
"name": "symfony/intl",
"version": "v2.7.5",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/intl.git",
"reference": "35f902b232c10056e17d94a842160d44bb540838"
"reference": "330f52a996749eb6a2fdc1506c7a4868e070d678"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/intl/zipball/35f902b232c10056e17d94a842160d44bb540838",
"reference": "35f902b232c10056e17d94a842160d44bb540838",
"url": "https://api.github.com/repos/symfony/intl/zipball/330f52a996749eb6a2fdc1506c7a4868e070d678",
"reference": "330f52a996749eb6a2fdc1506c7a4868e070d678",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"require-dev": {
"symfony/filesystem": "~2.1",
"symfony/phpunit-bridge": "~2.7"
"symfony/filesystem": "~2.1"
},
"suggest": {
"ext-intl": "to use the component with locales other than \"en\""
@ -2438,28 +2508,25 @@
"l10n",
"localization"
],
"time": "2015-09-09 17:53:06"
"time": "2015-10-11 09:39:48"
},
{
"name": "symfony/options-resolver",
"version": "v2.7.5",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/options-resolver.git",
"reference": "75389f6f948edfdf0c0ebdbe00c4f84ab5d1a03e"
"reference": "85fd10e551677d3c9a4632def78b8ec4670b247d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/75389f6f948edfdf0c0ebdbe00c4f84ab5d1a03e",
"reference": "75389f6f948edfdf0c0ebdbe00c4f84ab5d1a03e",
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/85fd10e551677d3c9a4632def78b8ec4670b247d",
"reference": "85fd10e551677d3c9a4632def78b8ec4670b247d",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"require-dev": {
"symfony/phpunit-bridge": "~2.7"
},
"type": "library",
"extra": {
"branch-alias": {
@ -2492,28 +2559,25 @@
"configuration",
"options"
],
"time": "2015-09-25 06:59:16"
"time": "2015-10-11 09:39:48"
},
{
"name": "symfony/process",
"version": "v2.7.5",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "b27c8e317922cd3cdd3600850273cf6b82b2e8e9"
"reference": "4a959dd4e19c2c5d7512689413921e0a74386ec7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/b27c8e317922cd3cdd3600850273cf6b82b2e8e9",
"reference": "b27c8e317922cd3cdd3600850273cf6b82b2e8e9",
"url": "https://api.github.com/repos/symfony/process/zipball/4a959dd4e19c2c5d7512689413921e0a74386ec7",
"reference": "4a959dd4e19c2c5d7512689413921e0a74386ec7",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"require-dev": {
"symfony/phpunit-bridge": "~2.7"
},
"type": "library",
"extra": {
"branch-alias": {
@ -2541,28 +2605,25 @@
],
"description": "Symfony Process Component",
"homepage": "https://symfony.com",
"time": "2015-09-19 19:59:23"
"time": "2015-10-23 14:47:27"
},
{
"name": "symfony/property-access",
"version": "v2.7.5",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/property-access.git",
"reference": "f8ea7aa472f0e3f8cdf43287caa72a70ff5c088c"
"reference": "368b784738fa932e6d86866038312b03e073a824"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/property-access/zipball/f8ea7aa472f0e3f8cdf43287caa72a70ff5c088c",
"reference": "f8ea7aa472f0e3f8cdf43287caa72a70ff5c088c",
"url": "https://api.github.com/repos/symfony/property-access/zipball/368b784738fa932e6d86866038312b03e073a824",
"reference": "368b784738fa932e6d86866038312b03e073a824",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"require-dev": {
"symfony/phpunit-bridge": "~2.7"
},
"type": "library",
"extra": {
"branch-alias": {
@ -2601,20 +2662,20 @@
"property path",
"reflection"
],
"time": "2015-08-24 07:13:45"
"time": "2015-10-23 14:47:27"
},
{
"name": "symfony/routing",
"version": "v2.7.5",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/routing.git",
"reference": "6c5fae83efa20baf166fcf4582f57094e9f60f16"
"reference": "f353e1f588679c3ec987624e6c617646bd01ba38"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/routing/zipball/6c5fae83efa20baf166fcf4582f57094e9f60f16",
"reference": "6c5fae83efa20baf166fcf4582f57094e9f60f16",
"url": "https://api.github.com/repos/symfony/routing/zipball/f353e1f588679c3ec987624e6c617646bd01ba38",
"reference": "f353e1f588679c3ec987624e6c617646bd01ba38",
"shasum": ""
},
"require": {
@ -2630,7 +2691,6 @@
"symfony/config": "~2.7",
"symfony/expression-language": "~2.4",
"symfony/http-foundation": "~2.3",
"symfony/phpunit-bridge": "~2.7",
"symfony/yaml": "~2.0,>=2.0.5"
},
"suggest": {
@ -2672,20 +2732,20 @@
"uri",
"url"
],
"time": "2015-09-14 14:14:09"
"time": "2015-10-27 15:38:06"
},
{
"name": "symfony/translation",
"version": "v2.7.5",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
"reference": "485877661835e188cd78345c6d4eef1290d17571"
"reference": "6ccd9289ec1c71d01a49d83480de3b5293ce30c8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/translation/zipball/485877661835e188cd78345c6d4eef1290d17571",
"reference": "485877661835e188cd78345c6d4eef1290d17571",
"url": "https://api.github.com/repos/symfony/translation/zipball/6ccd9289ec1c71d01a49d83480de3b5293ce30c8",
"reference": "6ccd9289ec1c71d01a49d83480de3b5293ce30c8",
"shasum": ""
},
"require": {
@ -2698,7 +2758,6 @@
"psr/log": "~1.0",
"symfony/config": "~2.7",
"symfony/intl": "~2.4",
"symfony/phpunit-bridge": "~2.7",
"symfony/yaml": "~2.2"
},
"suggest": {
@ -2733,20 +2792,20 @@
],
"description": "Symfony Translation Component",
"homepage": "https://symfony.com",
"time": "2015-09-06 08:36:38"
"time": "2015-10-27 15:38:06"
},
{
"name": "symfony/twig-bridge",
"version": "v2.7.5",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/twig-bridge.git",
"reference": "bce37975610a46bde48dbf2f67f724401251d199"
"reference": "3dd44937b1e08af8c8f6b14850f4b9c4d1039c6f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/twig-bridge/zipball/bce37975610a46bde48dbf2f67f724401251d199",
"reference": "bce37975610a46bde48dbf2f67f724401251d199",
"url": "https://api.github.com/repos/symfony/twig-bridge/zipball/3dd44937b1e08af8c8f6b14850f4b9c4d1039c6f",
"reference": "3dd44937b1e08af8c8f6b14850f4b9c4d1039c6f",
"shasum": ""
},
"require": {
@ -2758,10 +2817,9 @@
"symfony/console": "~2.7",
"symfony/expression-language": "~2.4",
"symfony/finder": "~2.3",
"symfony/form": "~2.7,>=2.7.2",
"symfony/form": "~2.7,>=2.7.6",
"symfony/http-kernel": "~2.3",
"symfony/intl": "~2.3",
"symfony/phpunit-bridge": "~2.7",
"symfony/routing": "~2.2",
"symfony/security": "~2.6",
"symfony/security-acl": "~2.6",
@ -2812,28 +2870,25 @@
],
"description": "Symfony Twig Bridge",
"homepage": "https://symfony.com",
"time": "2015-09-23 09:17:11"
"time": "2015-10-11 09:39:48"
},
{
"name": "symfony/yaml",
"version": "v2.7.5",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "31cb2ad0155c95b88ee55fe12bc7ff92232c1770"
"reference": "eca9019c88fbe250164affd107bc8057771f3f4d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/31cb2ad0155c95b88ee55fe12bc7ff92232c1770",
"reference": "31cb2ad0155c95b88ee55fe12bc7ff92232c1770",
"url": "https://api.github.com/repos/symfony/yaml/zipball/eca9019c88fbe250164affd107bc8057771f3f4d",
"reference": "eca9019c88fbe250164affd107bc8057771f3f4d",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"require-dev": {
"symfony/phpunit-bridge": "~2.7"
},
"type": "library",
"extra": {
"branch-alias": {
@ -2861,7 +2916,7 @@
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
"time": "2015-09-14 14:14:09"
"time": "2015-10-11 09:39:48"
},
{
"name": "twig/extensions",
@ -2966,25 +3021,30 @@
},
{
"name": "vlucas/phpdotenv",
"version": "v2.0.1",
"version": "v2.1.0",
"source": {
"type": "git",
"url": "https://github.com/vlucas/phpdotenv.git",
"reference": "91064290f5b53a09bdff1b939d7f69fb0e7531b5"
"reference": "c10040e0df17d2ee88e9212b50cbe9319e878f59"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/91064290f5b53a09bdff1b939d7f69fb0e7531b5",
"reference": "91064290f5b53a09bdff1b939d7f69fb0e7531b5",
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/c10040e0df17d2ee88e9212b50cbe9319e878f59",
"reference": "c10040e0df17d2ee88e9212b50cbe9319e878f59",
"shasum": ""
},
"require": {
"php": ">=5.3.2"
"php": ">=5.3.9"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.1-dev"
}
},
"autoload": {
"psr-4": {
"Dotenv\\": "src/"
@ -3008,7 +3068,7 @@
"env",
"environment"
],
"time": "2015-05-30 16:15:01"
"time": "2015-10-28 18:53:35"
}
],
"aliases": [],

View File

@ -0,0 +1,25 @@
<?php
namespace MailPoet\Analytics;
class Reporter {
private $fields = array(
'Plugin Version' => 'pluginVersion',
);
function __construct() {}
function getData() {
$_this = $this;
$analytics_data = array_map(function($func) use ($_this) {
return $_this->$func();
}, $this->fields);
return $analytics_data;
}
private function pluginVersion() {
return MAILPOET_VERSION;
}
}

32
lib/Config/Analytics.php Normal file
View File

@ -0,0 +1,32 @@
<?php
namespace MailPoet\Config;
use \MailPoet\Analytics\Reporter;
use \MailPoet\Models\Setting;
if(!defined('ABSPATH')) exit;
class Analytics {
function __construct() {
}
function init() {
add_action('admin_enqueue_scripts', array($this, 'setupAdminDependencies'));
}
function setupAdminDependencies() {
if(Setting::getValue('send_analytics_now', false)) {
$analytics = new Reporter();
wp_enqueue_script(
'analytics',
Env::$assets_url . '/js/lib/analytics.js',
array(),
Env::$version
);
wp_localize_script(
'analytics',
'mailpoet_analytics_data',
$analytics->getData()
);
}
}
}

61
lib/Config/Changelog.php Normal file
View File

@ -0,0 +1,61 @@
<?php
namespace MailPoet\Config;
use \MailPoet\Models\Setting;
class Changelog {
function init() {
$doing_ajax = (bool)(defined('DOING_AJAX') && DOING_AJAX);
// don't run any check when it's an ajax request
if($doing_ajax) {
return;
}
// don't run any check when we're not on our pages
if(
!(isset($_GET['page']))
or
(isset($_GET['page']) && strpos($_GET['page'], 'mailpoet') !== 0)
) {
return;
}
add_action(
'admin_init',
array($this, 'check')
);
}
function check() {
$version = Setting::getValue('version', null);
$redirect_url = null;
if($version === null) {
// new install
$redirect_url = admin_url('admin.php?page=mailpoet-welcome');
} else if($version !== Env::$version) {
// update
$redirect_url = admin_url('admin.php?page=mailpoet-update');
}
if($redirect_url !== null) {
// save version number
Setting::setValue('version', Env::$version);
global $wp;
$current_url = home_url(add_query_arg($wp->query_string, $wp->request));
if($redirect_url !== $current_url) {
wp_safe_redirect(
add_query_arg(
array(
'mailpoet_redirect' => urlencode($current_url)
),
$redirect_url
)
);
exit;
}
}
}
}

View File

@ -6,17 +6,22 @@ if(!defined('ABSPATH')) exit;
class Env {
public static $version;
public static $plugin_name;
public static $plugin_url;
public static $file;
public static $path;
public static $views_path;
public static $assets_path;
public static $assets_url;
public static $temp_name;
public static $temp_path;
public static $languages_path;
public static $lib_path;
public static $plugin_prefix;
public static $db_prefix;
public static $db_source_name;
public static $db_host;
public static $db_socket;
public static $db_port;
public static $db_name;
public static $db_username;
public static $db_password;
@ -25,32 +30,46 @@ class Env {
public static function init($file, $version) {
global $wpdb;
self::$version = $version;
self::$plugin_name = 'mailpoet';
self::$file = $file;
self::$path = dirname(self::$file);
self::$plugin_name = 'mailpoet';
self::$plugin_url = plugins_url() . '/' . basename(Env::$path);
self::$views_path = self::$path . '/views';
self::$assets_path = self::$path . '/assets';
self::$assets_url = plugins_url('/assets', $file);
self::$temp_name = 'temp';
self::$temp_path = self::$path . '/' . self::$temp_name;
self::$languages_path = self::$path . '/lang';
self::$lib_path = self::$path . '/lib';
self::$plugin_prefix = 'mailpoet_';
self::$db_prefix = $wpdb->prefix . self::$plugin_prefix;
self::$db_source_name = self::dbSourceName();
self::$db_host = DB_HOST;
self::$db_port = 3306;
self::$db_socket = false;
if (preg_match('/(?=:\d+$)/', DB_HOST)) {
list(self::$db_host, self::$db_port) = explode(':', DB_HOST);
}
else if (preg_match('/:/', DB_HOST)) {
self::$db_socket = true;
}
self::$db_name = DB_NAME;
self::$db_username = DB_USER;
self::$db_password = DB_PASSWORD;
self::$db_charset = $wpdb->get_charset_collate();
self::$db_source_name = self::dbSourceName(self::$db_host, self::$db_socket, self::$db_port);
}
private static function dbSourceName() {
private static function dbSourceName($host, $socket,$port) {
$source_name = array(
'mysql:host=',
DB_HOST,
(!$socket) ? 'mysql:host=' : 'mysql:unix_socket=',
$host,
';',
'port=',
$port,
';',
'dbname=',
DB_NAME
);
return implode('', $source_name);
}
}
}

View File

@ -22,7 +22,9 @@ class Initializer {
$this->setupMenu();
$this->setupRouter();
$this->setupWidget();
$this->setupAnalytics();
$this->setupPermissions();
$this->setupChangelog();
}
function setupDB() {
@ -39,6 +41,7 @@ class Initializer {
$newsletters = Env::$db_prefix . 'newsletters';
$newsletter_templates = Env::$db_prefix . 'newsletter_templates';
$segments = Env::$db_prefix . 'segments';
$forms = Env::$db_prefix . 'forms';
$subscriber_segment = Env::$db_prefix . 'subscriber_segment';
$newsletter_segment = Env::$db_prefix . 'newsletter_segment';
$custom_fields = Env::$db_prefix . 'custom_fields';
@ -50,6 +53,7 @@ class Initializer {
define('MP_SETTINGS_TABLE', $settings);
define('MP_NEWSLETTERS_TABLE', $newsletters);
define('MP_SEGMENTS_TABLE', $segments);
define('MP_FORMS_TABLE', $forms);
define('MP_SUBSCRIBER_SEGMENT_TABLE', $subscriber_segment);
define('MP_NEWSLETTER_TEMPLATES_TABLE', $newsletter_templates);
define('MP_NEWSLETTER_SEGMENT_TABLE', $newsletter_segment);
@ -92,8 +96,18 @@ class Initializer {
$widget->init();
}
function setupAnalytics() {
$widget = new Analytics();
$widget->init();
}
function setupPermissions() {
$permissions = new Permissions();
$permissions->init();
}
function setupChangelog() {
$changelog = new Changelog();
$changelog->init();
}
}

View File

@ -1,7 +1,12 @@
<?php
namespace MailPoet\Config;
use MailPoet\Subscribers\ImportExport\BootStrapMenu;
use \MailPoet\Models\Segment;
use \MailPoet\Models\Setting;
use \MailPoet\Models\Form;
use \MailPoet\Form\Block;
use \MailPoet\Form\Renderer as FormRenderer;
use \MailPoet\Settings\Hosts;
use \MailPoet\Settings\Pages;
use \MailPoet\Settings\Charsets;
@ -41,6 +46,14 @@ class Menu {
'mailpoet-newsletters',
array($this, 'newsletters')
);
add_submenu_page(
'mailpoet',
__('Forms'),
__('Forms'),
'manage_options',
'mailpoet-forms',
array($this, 'forms')
);
add_submenu_page(
'mailpoet',
__('Subscribers'),
@ -65,30 +78,58 @@ class Menu {
'mailpoet-settings',
array($this, 'settings')
);
// add_submenu_page(
// 'mailpoet',
// __('Newsletter editor'),
// __('Newsletter editor'),
// 'manage_options',
// 'mailpoet-newsletter-editor',
// array($this, 'newletterEditor')
// );
$this->registered_pages();
}
function registered_pages() {
global $_registered_pages;
$pages = array(
//'mailpoet-form-editor' => 'formEditor',
'mailpoet-newsletter-editor' => array($this, 'newletterForm')
add_submenu_page(
null,
__('Import'),
__('Import'),
'manage_options',
'mailpoet-import',
array($this, 'import')
);
add_submenu_page(
null,
__('Export'),
__('Export'),
'manage_options',
'mailpoet-export',
array($this, 'export')
);
add_submenu_page(
null,
__('Welcome'),
__('Welcome'),
'manage_options',
'mailpoet-welcome',
array($this, 'welcome')
);
add_submenu_page(
null,
__('Update'),
__('Update'),
'manage_options',
'mailpoet-update',
array($this, 'update')
);
add_submenu_page(
null,
__('Form editor'),
__('Form editor'),
'manage_options',
'mailpoet-form-editor',
array($this, 'formEditor')
);
add_submenu_page(
null,
__('Newsletter editor'),
__('Newsletter editor'),
'manage_options',
'mailpoet-newsletter-editor',
array($this, 'newletterEditor')
);
foreach($pages as $menu_slug => $callback) {
$hookname = get_plugin_page_hookname($menu_slug, null);
if(!empty($hookname)) {
add_action($hookname, $callback);
}
$_registered_pages[$hookname] = true;
}
}
function home() {
@ -96,26 +137,56 @@ class Menu {
echo $this->renderer->render('index.html', $data);
}
function settings() {
// flags (available features on WP install)
$flags = array();
function welcome() {
global $wp;
$current_url = home_url(add_query_arg($wp->query_string, $wp->request));
$redirect_url =
(!empty($_GET['mailpoet_redirect']))
? urldecode($_GET['mailpoet_redirect'])
: wp_get_referer();
if(is_multisite()) {
// get multisite registration option
$registration = apply_filters(
'wpmu_registration_enabled',
get_site_option('registration', 'all')
);
// check if users can register
$flags['registration_enabled'] =
!(in_array($registration, array('none', 'blog')));
} else {
// check if users can register
$flags['registration_enabled'] =
(bool)get_option('users_can_register', false);
if(
$redirect_url === $current_url
or
strpos($redirect_url, 'mailpoet') === false
) {
$redirect_url = admin_url('admin.php?page=mailpoet');
}
$data = array(
'settings' => Setting::getAll(),
'current_user' => wp_get_current_user(),
'redirect_url' => $redirect_url
);
echo $this->renderer->render('welcome.html', $data);
}
function update() {
global $wp;
$current_url = home_url(add_query_arg($wp->query_string, $wp->request));
$redirect_url =
(!empty($_GET['mailpoet_redirect']))
? urldecode($_GET['mailpoet_redirect'])
: wp_get_referer();
if(
$redirect_url === $current_url
or
strpos($redirect_url, 'mailpoet') === false
) {
$redirect_url = admin_url('admin.php?page=mailpoet');
}
$data = array(
'settings' => Setting::getAll(),
'current_user' => wp_get_current_user(),
'redirect_url' => $redirect_url
);
echo $this->renderer->render('update.html', $data);
}
function settings() {
$settings = Setting::getAll();
// dkim: check if public/private keys have been generated
@ -135,9 +206,9 @@ class Menu {
$data = array(
'settings' => $settings,
'segments' => Segment::findArray(),
'segments' => Segment::getPublished()->findArray(),
'pages' => Pages::getAll(),
'flags' => $flags,
'flags' => $this->_getFlags(),
'charsets' => Charsets::getAll(),
'current_user' => wp_get_current_user(),
'permissions' => Permissions::getAll(),
@ -150,6 +221,29 @@ class Menu {
echo $this->renderer->render('settings.html', $data);
}
private function _getFlags() {
// flags (available features on WP install)
$flags = array();
if(is_multisite()) {
// get multisite registration option
$registration = apply_filters(
'wpmu_registration_enabled',
get_site_option('registration', 'all')
);
// check if users can register
$flags['registration_enabled'] =
!(in_array($registration, array('none', 'blog')));
} else {
// check if users can register
$flags['registration_enabled'] =
(bool)get_option('users_can_register', false);
}
return $flags;
}
function subscribers() {
$data = array();
@ -163,6 +257,13 @@ class Menu {
echo $this->renderer->render('segments.html', $data);
}
function forms() {
$data = array();
$data['segments'] = Segment::findArray();
echo $this->renderer->render('forms.html', $data);
}
function newsletters() {
global $wp_roles;
@ -178,11 +279,42 @@ class Menu {
echo $this->renderer->render('newsletters.html', $data);
}
function newletterForm() {
function newletterEditor() {
$data = array();
wp_enqueue_media();
wp_enqueue_script('tinymce-wplink', includes_url('js/tinymce/plugins/wplink/plugin.js'));
wp_enqueue_style('editor', includes_url('css/editor.css'));
echo $this->renderer->render('newsletter/form.html', $data);
}
}
function import() {
$import = new BootStrapMenu('import');
$data = $import->bootstrap();
echo $this->renderer->render('import.html', $data);
}
function export() {
$export = new BootStrapMenu('export');
$data = $export->bootstrap();
echo $this->renderer->render('export.html', $data);
}
function formEditor() {
$id = (isset($_GET['id']) ? (int)$_GET['id'] : 0);
$form = Form::findOne($id);
if($form !== false) {
$form = $form->asArray();
}
$data = array(
'form' => $form,
'pages' => Pages::getAll(),
'segments' => Segment::getPublished()->findArray(),
'styles' => FormRenderer::getStyles($form),
'date_types' => Block\Date::getDateTypes(),
'date_formats' => Block\Date::getDateFormats()
);
echo $this->renderer->render('form/editor.html', $data);
}
}

View File

@ -21,6 +21,7 @@ class Migrator {
'subscriber_custom_field',
'newsletter_option_fields',
'newsletter_option',
'forms'
);
}
@ -66,7 +67,7 @@ class Migrator {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'name varchar(20) NOT NULL,',
'value varchar(255) NOT NULL,',
'value longtext,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
@ -107,6 +108,7 @@ class Migrator {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'name varchar(90) NOT NULL,',
'description varchar(250) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'deleted_at TIMESTAMP NULL DEFAULT NULL,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
@ -123,7 +125,8 @@ class Migrator {
'segment_id mediumint(9) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id)'
'PRIMARY KEY (id),',
'UNIQUE KEY subscriber_segment (subscriber_id,segment_id)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
@ -135,7 +138,8 @@ class Migrator {
'segment_id mediumint(9) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id)'
'PRIMARY KEY (id),',
'UNIQUE KEY newsletter_segment (newsletter_id,segment_id)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
@ -145,6 +149,7 @@ class Migrator {
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'name varchar(90) NOT NULL,',
'type varchar(90) NOT NULL,',
'params longtext NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
@ -161,7 +166,8 @@ class Migrator {
'value varchar(255) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id)'
'PRIMARY KEY (id),',
'UNIQUE KEY subscriber_id_custom_field_id (subscriber_id,custom_field_id)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
@ -187,6 +193,22 @@ class Migrator {
'value varchar(255) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY newsletter_id_option_field_id (newsletter_id,option_field_id)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function forms() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'name varchar(90) NOT NULL,',
'body longtext,',
'settings longtext,',
'styles longtext,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'deleted_at TIMESTAMP NULL DEFAULT NULL,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id)'
);
return $this->sqlify(__FUNCTION__, $attributes);

View File

@ -60,6 +60,27 @@ class Populator {
'name' => 'afterTimeType',
'newsletter_type' => 'welcome',
),
array(
'name' => 'intervalType',
'newsletter_type' => 'notification',
),
array(
'name' => 'timeOfDay',
'newsletter_type' => 'notification',
),
array(
'name' => 'weekDay',
'newsletter_type' => 'notification',
),
array(
'name' => 'monthDay',
'newsletter_type' => 'notification',
),
array(
'name' => 'nthWeekDay',
'newsletter_type' => 'notification',
),
);
}

View File

@ -1,5 +1,6 @@
<?php
namespace MailPoet\Config;
use \MailPoet\Models\Subscriber;
use \MailPoet\Util\Security;
if(!defined('ABSPATH')) exit;
@ -12,16 +13,39 @@ class Widget {
add_action('widgets_init', array($this, 'registerWidget'));
if(!is_admin()) {
add_action('widgets_init', array($this, 'setupActions'));
//$this->setupActions();
add_action('widgets_init', array($this, 'setupDependencies'));
} else {
add_action('widgets_init', array($this, 'setupAdminDependencies'));
}
}
function registerWidget() {
register_widget('\MailPoet\Form\Widget');
// subscribers count shortcode
add_shortcode('mailpoet_subscribers_count', array(
$this, 'getSubscribersCount'
));
add_shortcode('wysija_subscribers_count', array(
$this, 'getSubscribersCount'
));
}
function getSubscribersCount($params) {
return Subscriber::filter('subscribed')->count();
}
function setupDependencies() {
wp_enqueue_style('mailpoet_public', Env::$assets_url.'/css/public.css');
wp_enqueue_script('mailpoet_vendor',
Env::$assets_url.'/js/vendor.js',
array(),
Env::$version,
true
);
wp_enqueue_script('mailpoet_public',
Env::$assets_url.'/js/public.js',
array(),
@ -36,6 +60,22 @@ class Widget {
));
}
function setupAdminDependencies() {
wp_enqueue_script('mailpoet_vendor',
Env::$assets_url.'/js/vendor.js',
array(),
Env::$version,
true
);
wp_enqueue_script('mailpoet_admin',
Env::$assets_url.'/js/mailpoet.js',
array(),
Env::$version,
true
);
}
function setupActions() {
// ajax requests
add_action(
@ -55,9 +95,9 @@ class Widget {
'admin_post_mailpoet_form_subscribe',
'mailpoet_form_subscribe'
);
/*add_action(
add_action(
'init',
'mailpoet_form_subscribe'
);*/
);
}
}
}

103
lib/Form/Block/Base.php Normal file
View File

@ -0,0 +1,103 @@
<?php
namespace MailPoet\Form\Block;
abstract class Base {
protected static function getInputValidation($block) {
$rules = array();
if($block['id'] === 'email') {
$rules['required'] = true;
$rules['error-message'] = __('You need to specify a valid email address');
}
if($block['id'] === 'segments') {
$rules['required'] = true;
$rules['mincheck'] = 1;
$rules['error-message'] = __('You need to select a list');
}
if(!empty($block['params']['required'])) {
$rules['required'] = true;
}
if(!empty($block['params']['validate'])) {
if($block['params']['validate'] === 'phone') {
$rules['pattern'] = "^[\d\+\-\.\(\)\/\s]*$";
$rules['error-message'] = __('You need to specify a valid phone number');
} else {
$rules['type'] = $block['params']['validate'];
}
}
$validation = '';
if(!empty($rules)) {
$rules = array_unique($rules);
foreach($rules as $rule => $value) {
if(is_bool($value)) {
$value = ($value) ? 'true' : 'false';
}
$validation .= 'data-parsley-'.$rule.'="'.$value.'"';
}
}
return $validation;
}
protected static function renderLabel($block) {
$html = '';
if(
isset($block['params']['label_within'])
&& $block['params']['label_within']
) {
return $html;
}
if(isset($block['params']['label'])
&& strlen(trim($block['params']['label'])) > 0) {
$html .= '<label class="mailpoet_'.$block['type'].'_label">';
$html .= $block['params']['label'];
if(isset($block['params']['required']) && $block['params']['required']) {
$html .= ' <span class="mailpoet_required">*</span>';
}
$html .= '</label>';
}
return $html;
}
protected static function renderInputPlaceholder($block) {
$html = '';
// if the label is displayed as a placeholder,
if(
isset($block['params']['label_within'])
&& $block['params']['label_within']
) {
// display only label
$html .= ' placeholder="';
$html .= static::getFieldLabel($block);
// add an asterisk if it's a required field
if(isset($block['params']['required']) && $block['params']['required']) {
$html .= ' *';
}
$html .= '" ';
}
return $html;
}
// return field name depending on block data
protected static function getFieldName($block = array()) {
return $block['id'];
}
protected static function getFieldLabel($block = array()) {
return (isset($block['params']['label'])
&& strlen(trim($block['params']['label'])) > 0)
? trim($block['params']['label']) : '';
}
protected static function getFieldValue($block = array()) {
return (isset($block['params']['value'])
&& strlen(trim($block['params']['value'])) > 0)
? trim($block['params']['value']) : '';
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace MailPoet\Form\Block;
class Checkbox extends Base {
static function render($block) {
$html = '';
$field_name = static::getFieldName($block);
$field_validation = static::getInputValidation($block);
// TODO: check if it still makes sense
// create hidden default value
// $html .= '<input type="hidden"name="'.$field_name.'" value="0" '.static::getInputValidation($block).'/>';
$html .= '<p class="mailpoet_paragraph">';
$html .= static::renderLabel($block);
foreach($block['params']['values'] as $option) {
$html .= '<label class="mailpoet_checkbox_label">';
$html .= '<input type="checkbox" class="mailpoet_checkbox" ';
$html .= 'name="'.$field_name.'" ';
$html .= 'value="1" ';
$html .= (isset($option['is_checked']) && $option['is_checked'])
? 'checked="checked"' : '';
$html .= $field_validation;
$html .= ' />'.$option['value'];
$html .= '</label>';
}
$html .= '</p>';
return $html;
}
}

155
lib/Form/Block/Date.php Normal file
View File

@ -0,0 +1,155 @@
<?php
namespace MailPoet\Form\Block;
class Date extends Base {
static function render($block) {
$html = '';
$html .= '<p class="mailpoet_paragraph">';
$html .= static::renderLabel($block);
$html .= static::renderDateSelect($block);
$html .= '</p>';
return $html;
}
private static function renderDateSelect($block = array()) {
$html = '';
$field_name = static::getFieldName($block);
$field_validation = static::getInputValidation($block);
$date_formats = static::getDateFormats();
// automatically select first date format
$date_format = $date_formats[$block['params']['date_type']][0];
// set date format if specified
if(isset($block['params']['date_format'])
&& strlen(trim($block['params']['date_format'])) > 0) {
$date_format = $block['params']['date_format'];
}
// generate an array of selectors based on date format
$date_selectors = explode('/', $date_format);
foreach($date_selectors as $date_selector) {
if($date_selector === 'dd') {
$html .= '<select class="mailpoet_date_day" ';
$html .= 'name="'.$field_name.'[day]" placeholder="'.__('Day').'">';
$html .= static::getDays($block);
$html .= '</select>';
} else if($date_selector === 'mm') {
$html .= '<select class="mailpoet_date_month" ';
$html .= 'name="'.$field_name.'[month]" placeholder="'.__('Month').'">';
$html .= static::getMonths($block);
$html .= '</select>';
} else if($date_selector === 'yyyy') {
$html .= '<select class="mailpoet_date_year" ';
$html .= 'name="'.$field_name.'[year]" placeholder="'.__('Year').'">';
$html .= static::getYears($block);
$html .= '</select>';
}
}
return $html;
}
static function getDateTypes() {
return array(
'year_month_day' => __('Year, month, day'),
'year_month' => __('Year, month'),
'month' => __('Month (January, February,...)'),
'year' => __('Year')
);
}
static function getDateFormats() {
return array(
'year_month_day' => array('mm/dd/yyyy', 'dd/mm/yyyy', 'yyyy/mm/dd'),
'year_month' => array('mm/yyyy', 'yyyy/mm'),
'year' => array('yyyy'),
'month' => array('mm')
);
}
static function getMonthNames() {
return array(__('January'), __('February'), __('March'), __('April'),
__('May'), __('June'), __('July'), __('August'), __('September'),
__('October'), __('November'), __('December')
);
}
static function getMonths($block = array()) {
$defaults = array(
'selected' => null
);
// is default today
if(!empty($block['params']['is_default_today'])) {
$defaults['selected'] = (int)strftime('%m');
}
// merge block with defaults
$block = array_merge($defaults, $block);
$month_names = static::getMonthNames();
$html = '';
for($i = 1; $i < 13; $i++) {
$is_selected = ($i === $block['selected']) ? 'selected="selected"' : '';
$html .= '<option value="'.$i.'" '.$is_selected.'>';
$html .= $month_names[$i - 1];
$html .= '</option>';
}
return $html;
}
static function getYears($block = array()) {
$defaults = array(
'selected' => null,
'from' => (int)strftime('%Y') - 100,
'to' => (int)strftime('%Y')
);
// is default today
if(!empty($block['params']['is_default_today'])) {
$defaults['selected'] = (int)strftime('%Y');
}
// merge block with defaults
$block = array_merge($defaults, $block);
$html = '';
// return years as an array
for($i = (int)$block['to']; $i > (int)($block['from'] - 1); $i--) {
$is_selected = ($i === $block['selected']) ? 'selected="selected"' : '';
$html .= '<option value="'.$i.'" '.$is_selected.'>'.$i.'</option>';
}
return $html;
}
static function getDays($block = array()) {
$defaults = array(
'selected' => null
);
// is default today
if(!empty($block['params']['is_default_today'])) {
$defaults['selected'] = (int)strftime('%d');
}
// merge block with defaults
$block = array_merge($defaults, $block);
$html = '';
// return days as an array
for($i = 1; $i < 32; $i++) {
$is_selected = ($i === $block['selected']) ? 'selected="selected"' : '';
$html .= '<option value="'.$i.'" '.$is_selected.'>'.$i.'</option>';
}
return $html;
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace MailPoet\Form\Block;
class Divider {
static function render() {
return '<hr class="mailpoet_divider" />';
}
}

23
lib/Form/Block/Html.php Normal file
View File

@ -0,0 +1,23 @@
<?php
namespace MailPoet\Form\Block;
class Html {
static function render($block) {
$html = '';
if(isset($block['params']['text']) && $block['params']['text']) {
$text = html_entity_decode($block['params']['text'], ENT_QUOTES);
}
if(isset($block['params']['nl2br']) && $block['params']['nl2br']) {
$text = nl2br($text);
}
$html .= '<p class="mailpoet_paragraph">';
$html .= $text;
$html .= '</p>';
return $html;
}
}

36
lib/Form/Block/Input.php Normal file
View File

@ -0,0 +1,36 @@
<?php
namespace MailPoet\Form\Block;
class Input extends Base {
static function render($block) {
$type = 'text';
if($block['id'] === 'email') {
$type = 'email';
}
$html = '';
$html .= '<p class="mailpoet_paragraph">';
$html .= static::renderLabel($block);
$html .= '<input type="'.$type.'" class="mailpoet_input" ';
$html .= 'name="'.static::getFieldName($block).'" ';
$html .= 'title="'.static::getFieldLabel($block).'" ';
$html .= 'value="'.static::getFieldValue($block).'" ';
$html .= static::renderInputPlaceholder($block);
$html .= static::getInputValidation($block);
$html .= '/>';
$html .= '</p>';
return $html;
}
}

42
lib/Form/Block/Radio.php Normal file
View File

@ -0,0 +1,42 @@
<?php
namespace MailPoet\Form\Block;
class Radio extends Base {
static function render($block) {
$html = '';
$field_name = static::getFieldName($block);
$field_validation = static::getInputValidation($block);
// TODO: check if it still makes sense
// create hidden default value
// $html .= '<input type="hidden"name="'.$field_name.'" value="0" '.static::getInputValidation($block).'/>';
$html .= '<p class="mailpoet_paragraph">';
$html .= static::renderLabel($block);
foreach($block['params']['values'] as $option) {
$html .= '<label class="mailpoet_radio_label">';
$html .= '<input type="radio" class="mailpoet_radio" ';
$html .= 'name="'.$field_name.'" ';
$html .= 'value="'.$option['value'].'" ';
$html .= (isset($option['is_checked']) && $option['is_checked'])
? 'checked="checked"' : '';
$html .= $field_validation;
$html .= ' />&nbsp;'.$option['value'];
$html .= '</label>';
}
$html .= '</p>';
return $html;
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace MailPoet\Form\Block;
class Segment extends Base {
static function render($block) {
$html = '';
$field_name = static::getFieldName($block);
$field_validation = static::getInputValidation($block);
$html .= '<p class="mailpoet_paragraph">';
$html .= static::renderLabel($block);
if(!empty($block['params']['values'])) {
// display values
foreach($block['params']['values'] as $segment) {
if(!isset($segment['id']) || !isset($segment['name'])) continue;
$is_checked = (isset($segment['is_checked']) && $segment['is_checked']) ? 'checked="checked"' : '';
$html .= '<label class="mailpoet_checkbox_label">';
$html .= '<input type="checkbox" class="mailpoet_checkbox" ';
$html .= 'name="'.$field_name.'[]" ';
$html .= 'value="'.$segment['id'].'" '.$is_checked.' ';
$html .= $field_validation;
$html .= ' />'.$segment['name'];
$html .= '</label>';
}
}
$html .= '</p>';
return $html;
}
}

36
lib/Form/Block/Select.php Normal file
View File

@ -0,0 +1,36 @@
<?php
namespace MailPoet\Form\Block;
class Select extends Base {
static function render($block) {
$html = '';
$field_name = static::getFieldName($block);
$field_validation = static::getInputValidation($block);
$html .= '<p class="mailpoet_paragraph">';
$html .= static::renderLabel($block);
$html .= '<select class="mailpoet_select" name="'.$field_name.'">';
if(isset($block['params']['label_within'])
&& $block['params']['label_within']) {
$html .= '<option value="">'.static::getFieldLabel($block).'</option>';
}
foreach($block['params']['values'] as $option) {
$is_selected = (isset($option['is_checked']) && $option['is_checked'])
? 'selected="selected"' : '';
$html .= '<option value="'.$option['value'].'" '.$is_selected.'>';
$html .= $option['value'];
$html .= '</option>';
}
$html .= '</select>';
$html .= '</p>';
return $html;
}
}

17
lib/Form/Block/Submit.php Normal file
View File

@ -0,0 +1,17 @@
<?php
namespace MailPoet\Form\Block;
class Submit extends Base {
static function render($block) {
$html = '';
$html .= '<input class="mailpoet_submit" type="submit" ';
$html .= 'value="'.static::getFieldLabel($block).'" ';
$html .= '/>';
return $html;
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace MailPoet\Form\Block;
class Textarea extends Base {
static function render($block) {
$html = '';
$html .= '<p class="mailpoet_paragraph">';
$html .= static::renderLabel($block);
$lines = (isset($block['params']['lines']) ? (int)$block['params']['lines'] : 1);
$html .= '<textarea class="mailpoet_textarea" rows="'.$lines.'" ';
$html .= 'name="'.static::getFieldName($block).'"';
$html .= static::renderInputPlaceholder($block);
$html .= static::getInputValidation($block);
$html .= '></textarea>';
$html .= '</p>';
return $html;
}
}

95
lib/Form/Renderer.php Normal file
View File

@ -0,0 +1,95 @@
<?php
namespace MailPoet\Form;
use MailPoet\Form\Block;
use MailPoet\Form\Util;
if(!defined('ABSPATH')) exit;
class Renderer {
// public: rendering method
static function render($form = array()) {
$html = static::renderStyles($form);
$html .= static::renderHTML($form);
return $html;
}
static function renderStyles($form = array()) {
$html = '<style type="text/css">';
$html .= static::getStyles($form);
$html .= '</style>';
return $html;
}
static function renderHTML($form = array()) {
if(isset($form['body']) && !empty($form['body'])) {
return static::renderBlocks($form['body']);
}
return '';
}
static function getStyles($form = array()) {
if(isset($form['styles'])
&& strlen(trim($form['styles'])) > 0) {
return strip_tags($form['styles']);
} else {
return Util\Styles::$defaults;
}
}
// private: rendering methods
private static function renderBlocks($blocks = array()) {
$html = '';
foreach ($blocks as $key => $block) {
$html .= static::renderBlock($block)."\n";
}
return $html;
}
private static function renderBlock($block = array()) {
$html = '';
switch ($block['type']) {
case 'html':
$html .= Block\Html::render($block);
break;
case 'divider':
$html .= Block\Divider::render();
break;
case 'checkbox':
$html .= Block\Checkbox::render($block);
break;
case 'radio':
$html .= Block\Radio::render($block);
break;
case 'segment':
$html .= Block\Segment::render($block);
break;
case 'date':
$html .= Block\Date::render($block);
break;
case 'select':
$html .= Block\Select::render($block);
break;
case 'input':
$html .= Block\Input::render($block);
break;
case 'textarea':
$html .= Block\Textarea::render($block);
break;
case 'submit':
$html .= Block\Submit::render($block);
break;
}
return $html;
}
}

91
lib/Form/Util/Export.php Normal file
View File

@ -0,0 +1,91 @@
<?php
namespace MailPoet\Form\Util;
use MailPoet\Form\Widget;
class Export {
static function getAll($form = null) {
return array(
'html' => static::get('html', $form),
'php' => static::get('php', $form),
'iframe' => static::get('iframe', $form),
'shortcode' => static::get('shortcode', $form),
);
}
static function get($type = 'html', $form = null) {
switch($type) {
case 'iframe':
// generate url to load iframe's content
$iframe_url = add_query_arg(array(
'mailpoet_page' => 'mailpoet_form_iframe',
'mailpoet_form' => $form['id']
), site_url());
// generate iframe
return '<iframe '.
'width="100%" '.
'scrolling="no" '.
'frameborder="0" '.
'src="'.$iframe_url.'" '.
'class="mailpoet_form_iframe" '.
'vspace="0" '.
'tabindex="0" '.
'onload="javascript:(this.style.height = this.contentWindow.document.body.scrollHeight + \'px\');"'.
'marginwidth="0" '.
'marginheight="0" '.
'hspace="0" '.
'allowtransparency="true"></iframe>';
break;
case 'php':
$output = array(
'$form_widget = new \MailPoet\Form\Widget();',
'echo $form_widget->widget(array(\'form\' => '.(int)$form['id'].', \'form_type\' => \'php\'));'
);
return join("\n", $output);
break;
case 'html':
// TODO: get locale setting in order to load translations
$wp_locale = \get_locale();
$output = array();
$output[] = '<!-- BEGIN Scripts : you should place them in the header of your theme -->';
// jQuery
$output[] = '<script type="text/javascript" src="'.includes_url().'js/jquery/jquery.js'.'?mpv='.MAILPOET_VERSION.'"></script>';
// (JS) form validation
$output[] = '<script type="text/javascript" src="'.plugins_url('wysija-newsletters/'.'lib/jquery.validationEngine.js?mpv='.MAILPOET_VERSION).'"></script>';
$output[] = '<script type="text/javascript" src="'.plugins_url('wysija-newsletters/'.'lib/jquery.validationEngine-en.js?mpv='.MAILPOET_VERSION).'"></script>';
// (CSS) form validation styles
$output[] = '<link rel="stylesheet" type="text/css" href="'.plugins_url('wysija-newsletters/'.'lib/validationEngine.jquery.css?mpv='.MAILPOET_VERSION).'">';
// (JS) form submission
$output[] = '<script type="text/javascript" src="'.plugins_url('wysija-newsletters/'.'www/mailpoet_form_subscribe.js?mpv='.MAILPOET_VERSION).'"></script>';
// (JS) variables...
$output[] = '<script type="text/javascript">';
$output[] = ' var MailPoetData = MailPoetData || {';
$output[] = ' is_rtl: '.((int)is_rtl()).",";
$output[] = ' ajax_url: "'.admin_url('admin-ajax.php').'"';
$output[] = ' };';
$output[] = '</script>';
$output[] = '<!--END Scripts-->';
$form_widget = new Widget();
$output[] = $form_widget->widget(array(
'form' => (int)$form['id'],
'form_type' => 'php'
));
return join("\n", $output);
break;
case 'shortcode':
return '[mailpoet_form id="'.(int)$form['id'].'"]';
break;
}
}
}

181
lib/Form/Util/Styles.php Normal file
View File

@ -0,0 +1,181 @@
<?php
namespace MailPoet\Form\Util;
class Styles {
private $_stylesheet = null;
private $_styles = array();
static $defaults =<<<EOL
/* form */
.mailpoet_form {
}
/* paragraphs (label + input) */
.mailpoet_paragraph {
}
/* labels */
.mailpoet_input_label,
.mailpoet_textarea_label,
.mailpoet_select_label,
.mailpoet_radio_label,
.mailpoet_checkbox_label,
.mailpoet_list_label,
.mailpoet_date_label {
display:block;
}
/* inputs */
.mailpoet_input,
.mailpoet_textarea,
.mailpoet_select,
.mailpoet_date {
display:block;
}
.mailpoet_checkbox {
display:inline;
margin-right: 5px;
vertical-align:middle;
}
.mailpoet_validate_success {
color:#468847;
}
.mailpoet_validate_error {
color:#B94A48;
}
EOL;
function __construct($stylesheet = null) {
// store raw styles
$this->setStylesheet($stylesheet);
// extract rules/properties
$this->parseStyles();
return $this;
}
function render($prefix = '') {
$styles = $this->getStyles();
if(!empty($styles)) {
$output = array();
// add prefix on each selector
foreach($styles as $style) {
// check if selector is an array
if(is_array($style['selector'])) {
$selector = join(",\n", array_map(function($value) use ($prefix) {
return $prefix.' '.$value;
}, $style['selector']));
} else {
$selector = $prefix.' '.$style['selector'];
}
// format selector
$output[] = $selector . ' {';
// format rules
if(!empty($style['rules'])) {
$rules = join("\n", array_map(function($rule) {
return "\t".$rule['property'] . ': ' . $rule['value'].';';
}, $style['rules']));
$output[] = $rules;
}
$output[] = '}';
}
return join("\n", $output);
}
}
private function setStylesheet($stylesheet) {
$this->_stylesheet = $this->stripComments($stylesheet);
return $this;
}
private function stripComments($stylesheet) {
// remove comments
return preg_replace('!/\*.*?\*/!s', '', $stylesheet);
}
private function getStylesheet() {
return $this->_stylesheet;
}
private function setStyles($styles) {
$this->_styles = $styles;
return $this;
}
private function getStyles() {
return $this->_styles;
}
private function parseStyles() {
if($this->getStylesheet() !== null) {
// extract selectors and rules
preg_match_all( '/(?ims)([a-z0-9\s\.\:#_\-@,]+)\{([^\}]*)\}/',
$this->getStylesheet(),
$matches
);
$selectors = $matches[1];
$rules = $matches[2];
// extracted styles
$styles = array();
// loop through each selector
foreach($selectors as $index => $selector) {
// trim selector
$selector = trim($selector);
// get selector rules
$selector_rules = array_filter(array_map(function($value) {
if(strlen(trim($value)) > 0) {
// split property / value
$pair = explode(':', trim($value));
if(isset($pair[0]) && isset($pair[1])) {
return array(
'property' => $pair[0],
'value' => $pair[1]
);
}
}
}, explode(';', trim($rules[$index]))));
// check if we have multiple selectors
if(strpos($selector, ',') !== FALSE) {
$selectors_array = array_filter(array_map(function($value) {
return trim($value);
}, explode(',', $selector)));
// multiple selectors
$styles[$index] = array(
'selector' => $selectors_array,
'rules' => $selector_rules
);
} else {
// it's a single selector
$styles[$index] = array(
'selector' => $selector,
'rules' => $selector_rules
);
}
}
$this->setStyles($styles);
}
}
function __toString() {
$this->stripComments();
return $this->render();
}
}

View File

@ -1,120 +1,307 @@
<?php
namespace MailPoet\Form;
use \MailPoet\Config\Renderer;
use \MailPoet\Models\Form;
use \MailPoet\Models\Segment;
use \MailPoet\Models\Setting;
use \MailPoet\Models\Subscriber;
use \MailPoet\Form\Renderer as FormRenderer;
use \MailPoet\Form\Util;
if(!defined('ABSPATH')) exit;
class Widget extends \WP_Widget {
function __construct () {
// add_action(
// 'wp_ajax_mailpoet_form_subscribe',
// array($this, 'subscribe')
// );
// add_action(
// 'wp_ajax_nopriv_mailpoet_form_subscribe',
// array($this, 'subscribe')
// );
// add_action(
// 'admin_post_nopriv_mailpoet_form_subscribe',
// array($this, 'subscribe')
// );
// add_action(
// 'admin_post_mailpoet_form_subscribe',
// array($this, 'subscribe')
// );
// add_action(
// 'init',
// array($this, 'subscribe')
// );
function __construct() {
return parent::__construct(
'mailpoet_form',
__('MailPoet Subscription Form'),
__("MailPoet Subscription Form"),
array(
'title' => __('Newsletter subscription form')
'title' => __("Newsletter subscription form"),
)
);
}
public function update($new_instance, $old_instance) {
/**
* Save the new widget's title.
*/
public function update( $new_instance, $old_instance ) {
$instance = $old_instance;
$instance['title'] = strip_tags($new_instance['title']);
$instance['form'] = (int)$new_instance['form'];
return $instance;
}
/**
* Output the widget's option form.
*/
public function form($instance) {
$instance = wp_parse_args(
(array)$instance,
array(
'title' => __('Subscribe to our Newsletter')
'title' => __("Subscribe to our Newsletter")
)
);
// set title
$title = isset($instance['title']) ? strip_tags($instance['title']) : '';
$output = '';
// set form
$selected_form = isset($instance['form']) ? (int)($instance['form']) : 0;
$output .= '<p>';
$output .= ' <label for="'.$this->get_field_id('title').'">';
$output .= __('Title:' );
$output .= ' </label>';
$output .= ' <input type="text" class="widefat"';
$output .= ' id="'.$this->get_field_id('title').'"';
$output .= ' name="'.$this->get_field_name('title').'"';
$output .= ' value="'.esc_attr($title).'"';
$output .= ' />';
$output .= '</p>';
$output .= '<p>';
$output .= ' <a href="javascript:;" class="mailpoet_form_new">';
$output .= __('Create a new form');
$output .= ' </a>';
$output .= '</p>';
echo $output;
// get forms list
$forms = Form::getPublished()->orderByAsc('name')->findArray();
?><p>
<label for="<?php $this->get_field_id( 'title' ) ?>"><?php _e( 'Title:' ); ?></label>
<input
type="text"
class="widefat"
id="<?php echo $this->get_field_id('title') ?>"
name="<?php echo $this->get_field_name('title'); ?>"
value="<?php echo esc_attr($title); ?>"
/>
</p>
<p>
<select class="widefat" id="<?php echo $this->get_field_id('form') ?>" name="<?php echo $this->get_field_name('form'); ?>">
<?php
foreach ($forms as $form) {
$is_selected = ($selected_form === (int)$form['id']) ? 'selected="selected"' : '';
?>
<option value="<?php echo (int)$form['id']; ?>" <?php echo $is_selected; ?>><?php echo esc_html($form['name']); ?></option>
<?php } ?>
</select>
</p>
<p>
<a href="javascript:;" onClick="createSubscriptionForm()" class="mailpoet_form_new"><?php _e("Create a new form"); ?></a>
</p>
<script type="text/javascript">
function createSubscriptionForm() {
MailPoet.Ajax.post({
endpoint: 'forms',
action: 'create'
}).done(function(response) {
if(response !== false) {
window.location = response;
}
});
return false;
}
</script>
<?php
}
/**
* Output the widget itself.
*/
function widget($args, $instance = null) {
// turn $args into variables
extract($args);
if($instance === null) { $instance = $args; }
if($instance === null) {
$instance = $args;
}
$title = apply_filters(
'widget_title',
$instance['title'],
!empty($instance['title']) ? $instance['title'] : '',
$instance,
$this->id_base
);
$form_id = $this->id_base.'_'.$this->number;
$form_type = 'widget';
// get form
$form = Form::getPublished()->findOne($instance['form']);
$output = '';
// if the form was not found, return nothing.
if($form === false) {
return '';
} else {
$form = $form->asArray();
$form_type = 'widget';
if(isset($instance['form_type']) && in_array(
$instance['form_type'],
array('html', 'php', 'iframe', 'shortcode')
)) {
$form_type = $instance['form_type'];
}
// before widget
$output .= (isset($before_widget) ? $before_widget : '');
$settings = (isset($form['settings']) ? $form['settings'] : array());
$body = (isset($form['body']) ? $form['body'] : array());
$output = '';
// title
$output .= $before_title.$title.$after_title;
if(!empty($body)) {
$data = array(
'form_id' => $this->id_base.'_'.$this->number,
'form_type' => $form_type,
'form' => $form,
'title' => $title,
'styles' => FormRenderer::renderStyles($form),
'html' => FormRenderer::renderHTML($form),
'before_widget' => (!empty($before_widget) ? $before_widget : ''),
'after_widget' => (!empty($after_widget) ? $after_widget : ''),
'before_title' => (!empty($before_title) ? $before_title : ''),
'after_title' => (!empty($after_title) ? $after_title : '')
);
// container
$output .= '<div class="mailpoet_form mailpoet_form_'.$form_type.'">';
// if(isset($_GET['mailpoet_form']) && (int)$_GET['mailpoet_form'] === $form['id']) {
// // form messages (success / error)
// $output .= '<div class="mailpoet_message">';
// // success message
// if(isset($_GET['mailpoet_success'])) {
// $output .= '<p class="mailpoet_validate_success">'.strip_tags(urldecode($_GET['mailpoet_success']), '<a><strong><em><br><p>').'</p>';
// }
// // error message
// if(isset($_GET['mailpoet_error'])) {
// $output .= '<p class="mailpoet_validate_error">'.strip_tags(urldecode($_GET['mailpoet_error']), '<a><strong><em><br><p>').'</p>';
// }
// $output .= '</div>';
// } else {
// $output .= '<div class="mailpoet_message"></div>';
// }
// styles
$styles = '.mailpoet_validate_success { color:#468847; }';
$styles .= '.mailpoet_validate_error { color:#B94A48; }';
$output .= '<style type="text/css">'.$styles.'</style>';
// render form
$renderer = new Renderer();
$renderer = $renderer->init();
$output = $renderer->render('form/widget.html', $data);
$output = do_shortcode($output);
}
$output .= '<form '.
'id="'.$form_id.'" '.
'method="post" '.
'action="'.admin_url('admin-post.php?action=mailpoet_form_subscribe').'" '.
'class="mailpoet_form mailpoet_form_'.$form_type.'" novalidate>';
$output .= '<div class="mailpoet_message"></div>';
$output .= ' <p>';
$output .= ' <label>'.__('E-mail');
$output .= ' <input type="email" name="email"';
$output .= ' data-rule-required="true"';
$output .= ' data-rule-email="true"';
$output .= ' data-msg-required="'.__('Please enter your email address.').'"';
$output .= ' data-msg-email="'.__('Please enter a valid email address.').'"';
$output .= ' />';
$output .= ' </label>';
$output .= ' </p>';
$output .= ' <p>';
$output .= ' <label>';
$output .= ' <input type="submit" value="'.esc_attr('Subscribe!').'" />';
$output .= ' </label>';
$output .= ' </p>';
$output .= '</form>';
$output .= '</div>';
// after widget
$output .= (isset($after_widget) ? $after_widget : '');
echo $output;
if($form_type === 'widget') {
echo $output;
} else {
return $output;
}
}
}
}
// mailpoet shortcodes
// form shortcode
add_shortcode('mailpoet_form', 'mailpoet_form_shortcode');
add_shortcode('wysija_form', 'mailpoet_form_shortcode');
function mailpoet_form_shortcode($params = array()) {
// IMPORTANT: this is to make sure MagicMember won't scan our form and find [user_list] as a code they should replace.
remove_shortcode('user_list');
if(isset($params['id']) && (int)$params['id'] > 0) {
$form_widget = new \MailPoet\Form\Widget();
return $form_widget->widget(array(
'form' => (int)$params['id'],
'form_type' => 'shortcode'
));
}
}
// set the content filter to replace the shortcode
if(isset($_GET['mailpoet_page']) && strlen(trim($_GET['mailpoet_page'])) > 0) {
switch($_GET['mailpoet_page']) {
case 'mailpoet_form_iframe':
$id = (isset($_GET['mailpoet_form']) && (int)$_GET['mailpoet_form'] > 0) ? (int)$_GET['mailpoet_form'] : null;
$form = Form::findOne($id);
if($form !== false) {
// render form
$output = Util\Export::get('html', $form->asArray());
// $output = do_shortcode($output);
print $output;
exit;
}
break;
default:
// add_filter('wp_title', 'mailpoet_meta_page_title'));
add_filter('the_title', 'mailpoet_page_title', 10, 2);
add_filter('the_content', 'mailpoet_page_content', 98, 1);
break;
}
}
function mailpoet_page_title($title = '', $id = null) {
// get signup confirmation page id
$signup_confirmation = Setting::getValue('signup_confirmation');
$page_id = $signup_confirmation['page'];
// check if we're on the signup confirmation page
if((int)$page_id === (int)$id) {
global $post;
// disable comments
$post->comment_status = 'close';
// disable password
$post->post_password = '';
$subscriber = null;
// get subscriber key from url
$subscriber_digest = (isset($_GET['mailpoet_key']) && strlen(trim($_GET['mailpoet_key'])) === 32) ? trim($_GET['mailpoet_key']) : null;
if($subscriber_digest !== null) {
// get subscriber
// TODO: change select() to selectOne() once it's implemented
$subscribers = $mailpoet->subscribers()->select(array(
'filter' => array(
'subscriber_digest' => $subscriber_digest
),
'limit' => 1
));
if(!empty($subscribers)) {
$subscriber = array_shift($subscribers);
}
}
// check if we have a subscriber record
if($subscriber === null) {
return __('Your confirmation link expired, please subscribe again.');
} else {
// we have a subscriber, let's check its state
switch($subscriber['subscriber_state']) {
case MailPoetSubscribers::STATE_UNCONFIRMED:
case MailPoetSubscribers::STATE_UNSUBSCRIBED:
// set subscriber state as confirmed
$mailpoet->subscribers()->update(array(
'subscriber' => $subscriber['subscriber'],
'subscriber_state' => MailPoetSubscribers::STATE_SUBSCRIBED,
'subscriber_confirmed_at' => time()
));
return __("You've subscribed");
break;
case MailPoetSubscribers::STATE_SUBSCRIBED:
return __("You've already subscribed");
break;
}
}
} else {
return $title;
}
}
function mailpoet_page_content($content = '') {
if(strpos($content, '[mailpoet_page]') !== FALSE) {
$content = str_replace('[mailpoet_page]', '', $content);
}
return $content;
}

View File

@ -5,15 +5,17 @@ if(!defined('ABSPATH')) exit;
class BulkAction {
private $listing = null;
private $action = null;
private $data = null;
private $model_class = null;
function __construct($model_class, $data) {
$this->model_class = $model_class;
$this->action = $data['action'];
unset($data['action']);
$this->data = $data;
$this->model_class = $model_class;
$this->listing = new Handler(
$this->model_class,
$model_class,
$this->data['listing']
);
return $this;
@ -21,8 +23,9 @@ class BulkAction {
function apply() {
return call_user_func_array(
array($this->model_class, $this->data['action']),
array($this->listing, $this->data)
array($this->model_class, 'bulk'.ucfirst($this->action)),
array($this->listing->getSelection(), $this->data)
);
return $models->count();
}
}

View File

@ -4,16 +4,13 @@ namespace MailPoet\Listing;
if(!defined('ABSPATH')) exit;
class Handler {
private $data = array();
private $model = null;
function __construct($model_class, $data = array()) {
$class = new \ReflectionClass($model_class);
$this->table_name = $class->getStaticPropertyValue('_table');
$this->model = \Model::factory($model_class);
$this->model = $model_class::select('*');
$this->data = array(
// pagination
'offset' => (isset($data['offset']) ? (int)$data['offset'] : 0),
@ -31,7 +28,7 @@ class Handler {
'selection' => (isset($data['selection']) ? $data['selection'] : null)
);
$this->model = $this->setFilter();
$this->setFilter();
$this->setSearch();
$this->setGroup();
$this->setOrder();
@ -59,22 +56,18 @@ class Handler {
private function setFilter() {
if($this->data['filter'] === null) {
return $this->model;
return;
}
return $this->model->filter('filterBy', $this->data['filter']);
$this->model = $this->model->filter('filterBy', $this->data['filter']);
}
function getSelection() {
if(!empty($this->data['selection'])) {
$this->model->whereIn('id', $this->data['selection']);
$this->model->whereIn($this->table_name.'.id', $this->data['selection']);
}
return $this->model;
}
function count() {
return (int)$this->model->count();
}
function getSelectionIds() {
$models = $this->getSelection()
->select('id')
@ -86,14 +79,18 @@ class Handler {
}
function get() {
$count = $this->model->count();
$items = $this->model
->offset($this->data['offset'])
->limit($this->data['limit'])
->findArray();
return array(
'count' => $this->model->count(),
'count' => $count,
'filters' => $this->model->filter('filters'),
'groups' => $this->model->filter('groups'),
'items' => $this->model
->offset($this->data['offset'])
->limit($this->data['limit'])
->findArray()
'items' => $items
);
}
}

View File

@ -16,12 +16,62 @@ class CustomField extends Model {
));
}
function asArray() {
$model = parent::asArray();
$model['params'] = (
is_serialized($this->params)
? unserialize($this->params)
: $this->params
);
return $model;
}
function save() {
$this->set('params', (
is_serialized($this->params)
? $this->params
: serialize($this->params)
));
return parent::save();
}
function subscribers() {
return $this->has_many_through(
return $this->hasManyThrough(
__NAMESPACE__ . '\Subscriber',
__NAMESPACE__ . '\SubscriberCustomField',
'custom_field_id',
'subscriber_id'
)->select_expr(MP_SUBSCRIBER_CUSTOM_FIELD_TABLE.'.value');
)->selectExpr(MP_SUBSCRIBER_CUSTOM_FIELD_TABLE . '.value');
}
static function createOrUpdate($data = array()) {
$custom_field = false;
if(isset($data['id']) && (int)$data['id'] > 0) {
$custom_field = self::findOne((int)$data['id']);
}
// set name as label by default
if(empty($data['params']['label'])) {
$data['params']['label'] = $data['name'];
}
if($custom_field === false) {
$custom_field = self::create();
$custom_field->hydrate($data);
} else {
unset($data['id']);
$custom_field->set($data);
}
try {
$custom_field->save();
return $custom_field;
} catch(Exception $e) {
return $custom_field->getValidationErrors();
}
return false;
}
}

93
lib/Models/Form.php Normal file
View File

@ -0,0 +1,93 @@
<?php
namespace MailPoet\Models;
if(!defined('ABSPATH')) exit;
class Form extends Model {
static $_table = MP_FORMS_TABLE;
function __construct() {
parent::__construct();
$this->addValidations('name', array(
'required' => __('You need to specify a name.')
));
}
function asArray() {
$model = parent::asArray();
$model['body'] = (
is_serialized($this->body)
? unserialize($this->body)
: $this->body
);
$model['settings'] = (
is_serialized($this->settings)
? unserialize($this->settings)
: $this->settings
);
return $model;
}
function save() {
$this->set('body', (
is_serialized($this->body)
? $this->body
: serialize($this->body)
));
$this->set('settings', (
is_serialized($this->settings)
? $this->settings
: serialize($this->settings)
));
return parent::save();
}
static function search($orm, $search = '') {
return $orm->where_like('name', '%'.$search.'%');
}
static function groups() {
return array(
array(
'name' => 'all',
'label' => __('All'),
'count' => Form::getPublished()->count()
),
array(
'name' => 'trash',
'label' => __('Trash'),
'count' => Form::getTrashed()->count()
)
);
}
static function groupBy($orm, $group = null) {
if($group === 'trash') {
return $orm->whereNotNull('deleted_at');
} else {
$orm = $orm->whereNull('deleted_at');
}
}
static function createOrUpdate($data = array()) {
$form = false;
if(isset($data['id']) && (int)$data['id'] > 0) {
$form = self::findOne((int)$data['id']);
}
if($form === false) {
$form = self::create();
$form->hydrate($data);
} else {
unset($data['id']);
$form->set($data);
}
$form->save();
return $form;
}
}

View File

@ -9,6 +9,10 @@ class Model extends \Sudzy\ValidModel {
parent::__construct($customValidators->init());
}
static function create() {
return parent::create();
}
function save() {
$this->setTimestamp();
try {
@ -21,9 +25,57 @@ class Model extends \Sudzy\ValidModel {
}
}
function trash() {
return $this->set_expr('deleted_at', 'NOW()')->save();
}
static function bulkTrash($orm) {
$models = $orm->findResultSet();
$models->set_expr('deleted_at', 'NOW()')->save();
return $models->count();
}
static function bulkDelete($orm) {
$models = $orm->findMany();
$count = 0;
foreach($models as $model) {
$model->delete();
$count++;
}
return $count;
}
function restore() {
return $this->set_expr('deleted_at', 'NULl')->save();
}
static function bulkRestore($orm) {
$models = $orm->findResultSet();
$models->set_expr('deleted_at', 'NULL')->save();
return $models->count();
}
function duplicate($data = array()) {
$model = get_called_class();
$model_data = array_merge($this->asArray(), $data);
unset($model_data['id']);
$duplicate = $model::create();
$duplicate->hydrate($model_data);
$duplicate->set_expr('created_at', 'NOW()');
$duplicate->set_expr('updated_at', 'NOW()');
$duplicate->set_expr('deleted_at', 'NULL');
if($duplicate->save()) {
return $duplicate;
} else {
return false;
}
}
private function setTimestamp() {
if($this->created_at === null) {
$this->created_at = date('Y-m-d H:i:s');
$this->set_expr('created_at', 'NOW()');
}
}
@ -38,4 +90,12 @@ class Model extends \Sudzy\ValidModel {
}, $searchCriteria);
return $orm->having_raw(implode(' ' . $searchCondition . ' ', $havingFields), array_values($havingValues));
}
static function getPublished() {
return static::whereNull('deleted_at');
}
static function getTrashed() {
return static::whereNotNull('deleted_at');
}
}

View File

@ -10,6 +10,21 @@ class Newsletter extends Model {
parent::__construct();
}
function save() {
if(is_string($this->deleted_at) && strlen(trim($this->deleted_at)) === 0) {
$this->set_expr('deleted_at', 'NULL');
}
return parent::save();
}
function delete() {
// delete all relations to segments
NewsletterSegment::where('newsletter_id', $this->id)->deleteMany();
return parent::delete();
}
function segments() {
return $this->has_many_through(
__NAMESPACE__.'\Segment',
@ -39,10 +54,10 @@ class Newsletter extends Model {
'label' => __('All lists'),
'value' => ''
);
foreach($segments as $segment) {
$newsletters_count = $segment->newsletters()->count();
if($newsletters_count > 0) {
$segment_list[] = array(
'label' => sprintf('%s (%d)', $segment->name, $newsletters_count),
'value' => $segment->id()
@ -51,34 +66,21 @@ class Newsletter extends Model {
}
$filters = array(
array(
'name' => 'segment',
'options' => $segment_list
)
'segment' => $segment_list
);
return $filters;
}
static function filterBy($orm, $filters = null) {
if(empty($filters)) {
if(empty($filters)) {
return $orm;
}
foreach($filters as $filter) {
if($filter['name'] === 'segment') {
$segment = Segment::findOne($filter['value']);
foreach($filters as $key => $value) {
if($key === 'segment') {
$segment = Segment::findOne($value);
if($segment !== false) {
$orm = $orm
->select(MP_NEWSLETTERS_TABLE.'.*')
->select('newsletter_segment.id', 'newsletter_segment_id')
->join(
MP_NEWSLETTER_SEGMENT_TABLE,
MP_NEWSLETTERS_TABLE.'.id = newsletter_segment.newsletter_id',
'newsletter_segment'
)
->where('newsletter_segment.segment_id', (int)$filter['value']);
$orm = $segment->newsletters();
}
}
}
@ -112,19 +114,28 @@ class Newsletter extends Model {
array(
'name' => 'all',
'label' => __('All'),
'count' => Newsletter::count()
'count' => Newsletter::getPublished()->count()
),
array(
'name' => 'trash',
'label' => __('Trash'),
'count' => Newsletter::getTrashed()->count()
)
);
}
static function group($orm, $group = null) {
static function groupBy($orm, $group = null) {
if($group === 'trash') {
return $orm->whereNotNull('deleted_at');
}
return $orm->whereNull('deleted_at');
}
static function createOrUpdate($data = array()) {
$newsletter = false;
if(isset($data['id']) && (int) $data['id'] > 0) {
$newsletter = self::findOne((int) $data['id']);
if(isset($data['id']) && (int)$data['id'] > 0) {
$newsletter = self::findOne((int)$data['id']);
}
if($newsletter === false) {
@ -135,21 +146,7 @@ class Newsletter extends Model {
$newsletter->set($data);
}
$saved = $newsletter->save();
if($saved === true) {
return $newsletter->id();
} else {
$errors = $newsletter->getValidationErrors();
if(!empty($errors)) {
return $errors;
}
}
return false;
}
static function trash($listing) {
return $listing->getSelection()
->deleteMany();
$newsletter->save();
return $newsletter;
}
}

View File

@ -14,13 +14,10 @@ class Segment extends Model {
));
}
function subscribers() {
return $this->has_many_through(
__NAMESPACE__.'\Subscriber',
__NAMESPACE__.'\SubscriberSegment',
'segment_id',
'subscriber_id'
);
function delete() {
// delete all relations to subscribers
SubscriberSegment::where('segment_id', $this->id)->deleteMany();
return parent::delete();
}
function newsletters() {
@ -32,8 +29,46 @@ class Segment extends Model {
);
}
function subscribers() {
return $this->has_many_through(
__NAMESPACE__.'\Subscriber',
__NAMESPACE__.'\SubscriberSegment',
'segment_id',
'subscriber_id'
);
}
function duplicate($data = array()) {
$duplicate = parent::duplicate($data);
if($duplicate !== false) {
foreach($this->subscribers()->findResultSet() as $relation) {
$new_relation = SubscriberSegment::create();
$new_relation->set('subscriber_id', $relation->id);
$new_relation->set('segment_id', $duplicate->id);
$new_relation->save();
}
return $duplicate;
}
return false;
}
function addSubscriber($subscriber_id) {
$relation = SubscriberSegment::create();
$relation->set('subscriber_id', $subscriber_id);
$relation->set('segment_id', $this->id);
return $relation->save();
}
function removeSubscriber($subscriber_id) {
return SubscriberSegment::where('subscriber_id', $subscriber_id)
->where('segment_id', $this->id)
->delete();
}
static function search($orm, $search = '') {
return $orm->where_like('name', '%'.$search.'%');
return $orm->whereLike('name', '%'.$search.'%');
}
static function groups() {
@ -41,12 +76,57 @@ class Segment extends Model {
array(
'name' => 'all',
'label' => __('All'),
'count' => Segment::count()
'count' => Segment::getPublished()->count()
),
array(
'name' => 'trash',
'label' => __('Trash'),
'count' => Segment::getTrashed()->count()
)
);
}
static function group($orm, $group = null) {
static function groupBy($orm, $group = null) {
if($group === 'trash') {
return $orm->whereNotNull('deleted_at');
} else {
$orm = $orm->whereNull('deleted_at');
}
}
static function getSegmentsForImport() {
return self::selectMany(array(self::$_table.'.id', self::$_table.'.name'))
->select_expr(
'COUNT('.MP_SUBSCRIBER_SEGMENT_TABLE.'.subscriber_id)', 'subscribers'
)
->left_outer_join(
MP_SUBSCRIBER_SEGMENT_TABLE,
array(self::$_table.'.id', '=', MP_SUBSCRIBER_SEGMENT_TABLE.'.segment_id'))
->group_by(self::$_table.'.id')
->group_by(self::$_table.'.name')
->findArray();
}
static function getSegmentsForExport($withConfirmedSubscribers = false) {
return self::raw_query(
'(SELECT segments.id, segments.name, COUNT(relation.subscriber_id) as subscribers ' .
'FROM ' . MP_SUBSCRIBER_SEGMENT_TABLE . ' relation ' .
'LEFT JOIN ' . self::$_table . ' segments ON segments.id = relation.segment_id ' .
'LEFT JOIN ' . MP_SUBSCRIBERS_TABLE . ' subscribers ON subscribers.id = relation.subscriber_id ' .
(($withConfirmedSubscribers) ?
'WHERE subscribers.status = 1 ' :
'WHERE relation.segment_id IS NOT NULL ') .
'GROUP BY segments.id) ' .
'UNION ALL ' .
'(SELECT 0 as id, "' . __('Not In List') . '" as name, COUNT(*) as subscribers ' .
'FROM ' . MP_SUBSCRIBERS_TABLE . ' subscribers ' .
'LEFT JOIN ' . MP_SUBSCRIBER_SEGMENT_TABLE . ' relation on relation.subscriber_id = subscribers.id ' .
(($withConfirmedSubscribers) ?
'WHERE relation.subscriber_id is NULL AND subscribers.status = 1 ' :
'WHERE relation.subscriber_id is NULL ') .
'HAVING subscribers) ' .
'ORDER BY name'
)->findArray();
}
static function createOrUpdate($data = array()) {
@ -64,20 +144,7 @@ class Segment extends Model {
$segment->set($data);
}
$saved = $segment->save();
if($saved === true) {
return true;
} else {
$errors = $segment->getValidationErrors();
if(!empty($errors)) {
return $errors;
}
}
return false;
$segment->save();
return $segment;
}
static function trash($listing) {
return $listing->getSelection()->deleteMany();
}
}
}

View File

@ -28,6 +28,13 @@ class Setting extends Model {
}
}
public static function setValue($key, $value) {
return Setting::createOrUpdate(array(
'name' => $key,
'value' => $value
));
}
public static function getAll() {
$settingsCollection = self::findMany();
$settings = array();

View File

@ -1,6 +1,7 @@
<?php
namespace MailPoet\Models;
use MailPoet\Util\Helpers;
if(!defined('ABSPATH')) exit;
class Subscriber extends Model {
@ -15,11 +16,20 @@ class Subscriber extends Model {
));
}
function delete() {
function segments() {
return $this->has_many_through(
__NAMESPACE__.'\Segment',
__NAMESPACE__.'\SubscriberSegment',
'subscriber_id',
'segment_id'
);
}
function delete() {
// delete all relations to segments
SubscriberSegment::where('subscriber_id', $this->id)->deleteMany();
parent::delete();
return parent::delete();
}
static function search($orm, $search = '') {
@ -36,14 +46,15 @@ class Subscriber extends Model {
static function filters() {
$segments = Segment::orderByAsc('name')->findMany();
$segment_list = array();
$segment_list[] = array(
'label' => __('All lists'),
'value' => ''
);
foreach($segments as $segment) {
$subscribers_count = $segment->subscribers()->count();
$subscribers_count = $segment->subscribers()
->whereNull('deleted_at')
->count();
if($subscribers_count > 0) {
$segment_list[] = array(
'label' => sprintf('%s (%d)', $segment->name, $subscribers_count),
@ -53,10 +64,7 @@ class Subscriber extends Model {
}
$filters = array(
array(
'name' => 'segment',
'options' => $segment_list
)
'segment' => $segment_list
);
return $filters;
@ -66,12 +74,11 @@ class Subscriber extends Model {
if(empty($filters)) {
return $orm;
}
foreach($filters as $filter) {
if($filter['name'] === 'segment') {
$segment = Segment::findOne($filter['value']);
foreach($filters as $key => $value) {
if($key === 'segment') {
$segment = Segment::findOne($value);
if($segment !== false) {
$orm = $segment->subscribers();
return $segment->subscribers();
}
}
}
@ -83,33 +90,27 @@ class Subscriber extends Model {
array(
'name' => 'all',
'label' => __('All'),
'count' => Subscriber::whereNull('deleted_at')->count()
'count' => Subscriber::getPublished()->count()
),
array(
'name' => 'subscribed',
'label' => __('Subscribed'),
'count' => Subscriber::whereNull('deleted_at')
->where('status', 'subscribed')
->count()
'count' => Subscriber::filter('subscribed')->count()
),
array(
'name' => 'unconfirmed',
'label' => __('Unconfirmed'),
'count' => Subscriber::whereNull('deleted_at')
->where('status', 'unconfirmed')
->count()
'count' => Subscriber::filter('unconfirmed')->count()
),
array(
'name' => 'unsubscribed',
'label' => __('Unsubscribed'),
'count' => Subscriber::whereNull('deleted_at')
->where('status', 'unsubscribed')
->count()
'count' => Subscriber::filter('unsubscribed')->count()
),
array(
'name' => 'trash',
'label' => __('Trash'),
'count' => Subscriber::whereNotNull('deleted_at')->count()
'count' => Subscriber::getTrashed()->count()
)
);
}
@ -117,12 +118,10 @@ class Subscriber extends Model {
static function groupBy($orm, $group = null) {
if($group === 'trash') {
return $orm->whereNotNull('deleted_at');
} else if($group === 'all') {
return $orm->whereNull('deleted_at');
} else {
$orm = $orm->whereNull('deleted_at');
if(in_array($group, array('subscribed', 'unsubscribed', 'unconfirmed'))) {
return $orm->where('status', $group);
}
return $orm->filter($group);
}
}
@ -136,29 +135,41 @@ class Subscriber extends Model {
MP_SUBSCRIBER_CUSTOM_FIELD_TABLE . '.value END), NULL) as "' . $customField['name'].'"');
}
$orm = $orm
->left_outer_join(
->leftOuterJoin(
MP_SUBSCRIBER_CUSTOM_FIELD_TABLE,
array(MP_SUBSCRIBERS_TABLE.'.id', '=',
MP_SUBSCRIBER_CUSTOM_FIELD_TABLE.'.subscriber_id'))
->left_outer_join(
->leftOuterJoin(
MP_CUSTOM_FIELDS_TABLE,
array(MP_CUSTOM_FIELDS_TABLE.'.id','=',
MP_SUBSCRIBER_CUSTOM_FIELD_TABLE.'.custom_field_id'))
->group_by(MP_SUBSCRIBERS_TABLE.'.id');
->groupBy(MP_SUBSCRIBERS_TABLE.'.id');
return $orm;
}
function segments() {
return $this->has_many_through(
__NAMESPACE__.'\Segment',
__NAMESPACE__.'\SubscriberSegment',
'subscriber_id',
'segment_id'
);
static function filterWithCustomFieldsForExport($orm) {
$orm = $orm->select(MP_SUBSCRIBERS_TABLE.'.*');
$customFields = CustomField::findArray();
foreach ($customFields as $customField) {
$orm = $orm->selectExpr(
'CASE WHEN ' .
MP_CUSTOM_FIELDS_TABLE . '.id=' . $customField['id'] . ' THEN ' .
MP_SUBSCRIBER_CUSTOM_FIELD_TABLE . '.value END as "' . $customField['name'].'"');
}
$orm = $orm
->leftOuterJoin(
MP_SUBSCRIBER_CUSTOM_FIELD_TABLE,
array(MP_SUBSCRIBERS_TABLE.'.id', '=',
MP_SUBSCRIBER_CUSTOM_FIELD_TABLE.'.subscriber_id'))
->leftOuterJoin(
MP_CUSTOM_FIELDS_TABLE,
array(MP_CUSTOM_FIELDS_TABLE.'.id','=',
MP_SUBSCRIBER_CUSTOM_FIELD_TABLE.'.custom_field_id'));
return $orm;
}
function customFields() {
return $this->has_many_through(
return $this->hasManyThrough(
__NAMESPACE__.'\CustomField',
__NAMESPACE__.'\SubscriberCustomField',
'subscriber_id',
@ -171,36 +182,25 @@ class Subscriber extends Model {
if(isset($data['id']) && (int)$data['id'] > 0) {
$subscriber = self::findOne((int)$data['id']);
unset($data['id']);
}
if($subscriber === false) {
$subscriber = self::create();
$subscriber->hydrate($data);
} else {
unset($data['id']);
$subscriber->set($data);
}
$saved = $subscriber->save();
if($saved === true) {
return true;
} else {
$errors = $subscriber->getValidationErrors();
if(!empty($errors)) {
return $errors;
}
}
return false;
$subscriber->save();
return $subscriber;
}
static function moveToList($listing, $data = array()) {
static function bulkMoveToList($orm, $data = array()) {
$segment_id = (isset($data['segment_id']) ? (int)$data['segment_id'] : 0);
$segment = Segment::findOne($segment_id);
if($segment !== false) {
$subscribers_count = 0;
$subscribers = $listing->getSelection()->findMany();
$subscribers = $orm->findResultSet();
foreach($subscribers as $subscriber) {
// remove subscriber from all segments
SubscriberSegment::where('subscriber_id', $subscriber->id)->deleteMany();
@ -210,37 +210,37 @@ class Subscriber extends Model {
$association->subscriber_id = $subscriber->id;
$association->segment_id = $segment->id;
$association->save();
$subscribers_count++;
}
return array(
'subscribers' => $subscribers_count,
'subscribers' => $subscribers->count(),
'segment' => $segment->name
);
}
return false;
}
static function removeFromList($listing, $data = array()) {
static function bulkRemoveFromList($orm, $data = array()) {
$segment_id = (isset($data['segment_id']) ? (int)$data['segment_id'] : 0);
$segment = Segment::findOne($segment_id);
if($segment !== false) {
// delete relations with segment
$subscriber_ids = $listing->getSelectionIds();
SubscriberSegment::whereIn('subscriber_id', $subscriber_ids)
->where('segment_id', $segment->id)
->deleteMany();
$subscribers = $orm->findResultSet();
foreach($subscribers as $subscriber) {
SubscriberSegment::where('subscriber_id', $subscriber->id)
->where('segment_id', $segment->id)
->deleteMany();
}
return array(
'subscribers' => count($subscriber_ids),
'subscribers' => $subscribers->count(),
'segment' => $segment->name
);
}
return false;
}
static function removeFromAllLists($listing) {
static function bulkRemoveFromAllLists($orm) {
$segments = Segment::findMany();
$segment_ids = array_map(function($segment) {
return $segment->id();
@ -248,62 +248,48 @@ class Subscriber extends Model {
if(!empty($segment_ids)) {
// delete relations with segment
$subscriber_ids = $listing->getSelectionIds();
SubscriberSegment::whereIn('subscriber_id', $subscriber_ids)
->whereIn('segment_id', $segment_ids)
->deleteMany();
return array(
'subscribers' => count($subscriber_ids)
);
}
return false;
}
static function confirmUnconfirmed($listing) {
$subscriber_ids = $listing->getSelectionIds();
$subscribers = Subscriber::whereIn('id', $subscriber_ids)
->where('status', 'unconfirmed')
->findMany();
if(!empty($subscribers)) {
$subscribers_count = 0;
$subscribers = $orm->findResultSet();
foreach($subscribers as $subscriber) {
$subscriber->set('status', 'subscribed');
if($subscriber->save() === true) {
$subscribers_count++;
}
SubscriberSegment::where('subscriber_id', $subscriber->id)
->whereIn('segment_id', $segment_ids)
->deleteMany();
}
return array(
'subscribers' => $subscribers_count
);
return $subscribers->count();
}
return false;
}
static function resendConfirmationEmail($listing) {
$subscriber_ids = $listing->getSelectionIds();
$subscribers = Subscriber::whereIn('id', $subscriber_ids)
static function bulkConfirmUnconfirmed($orm) {
$subscribers = $orm->findResultSet();
$subscribers->set('status', 'subscribed')->save();
return $subscribers->count();
}
static function bulkResendConfirmationEmail($orm) {
$subscribers = $orm
->where('status', 'unconfirmed')
->findMany();
->findResultSet();
if(!empty($subscribers)) {
foreach($subscribers as $subscriber) {
// TODO: resend confirmation email
// TODO: send confirmation email
// $subscriber->sendConfirmationEmail()
}
return true;
return $subscribers->count();
}
return false;
}
static function addToList($listing, $data = array()) {
static function bulkAddToList($orm, $data = array()) {
$segment_id = (isset($data['segment_id']) ? (int)$data['segment_id'] : 0);
$segment = Segment::findOne($segment_id);
if($segment !== false) {
$subscribers_count = 0;
$subscribers = $listing->getSelection()->findMany();
$subscribers = $orm->findMany();
foreach($subscribers as $subscriber) {
// create relation with segment
$association = \MailPoet\Models\SubscriberSegment::create();
@ -321,45 +307,85 @@ class Subscriber extends Model {
return false;
}
static function trash($listing, $data = array()) {
$confirm_delete = filter_var($data['confirm'], FILTER_VALIDATE_BOOLEAN);
if($confirm_delete) {
// delete relations with all segments
$subscribers = $listing->getSelection()->findResultSet();
if(!empty($subscribers)) {
$subscribers_count = 0;
foreach($subscribers as $subscriber) {
if($subscriber->delete()) {
$subscribers_count++;
}
}
return array(
'subscribers' => $subscribers_count
);
}
return false;
} else {
// soft delete
$subscribers = $listing->getSelection()
->findResultSet()
->set_expr('deleted_at', 'NOW()')
->save();
return array(
'subscribers' => $subscribers->count()
);
}
static function subscribed($orm) {
return $orm
->whereNull('deleted_at')
->where('status', 'subscribed');
}
static function restore($listing, $data = array()) {
$subscribers = $listing->getSelection()
->findResultSet()
->set_expr('deleted_at', 'NULL')
->save();
static function unsubscribed($orm) {
return $orm
->whereNull('deleted_at')
->where('status', 'unsubscribed');
}
return array(
'subscribers' => $subscribers->count()
static function unconfirmed($orm) {
return $orm
->whereNull('deleted_at')
->where('status', 'unconfirmed');
}
static function createMultiple($columns, $values) {
return self::rawExecute(
'INSERT INTO `' . self::$_table . '` ' .
'(' . implode(', ', $columns) . ') ' .
'VALUES ' . rtrim(
str_repeat(
'(' . rtrim(str_repeat('?,', count($columns)), ',') . ')' . ', '
, count($values)
)
, ', '),
Helpers::flattenArray($values)
);
}
static function updateMultiple($columns, $subscribers, $currentTime = false) {
$ignoreColumnsOnUpdate = array(
'email',
'created_at'
);
$subscribers = array_map('array_values', $subscribers);
$emailPosition = array_search('email', $columns);
$sql =
function ($type) use (
$columns,
$subscribers,
$emailPosition,
$ignoreColumnsOnUpdate
) {
return array_filter(
array_map(function ($columnPosition, $columnName) use (
$type,
$subscribers,
$emailPosition,
$ignoreColumnsOnUpdate
) {
if(in_array($columnName, $ignoreColumnsOnUpdate)) return;
$query = array_map(
function ($subscriber) use ($type, $columnPosition, $emailPosition) {
return ($type === 'values') ?
array(
$subscriber[$emailPosition],
$subscriber[$columnPosition]
) :
'WHEN email = ? THEN ?';
}, $subscribers);
return ($type === 'values') ?
Helpers::flattenArray($query) :
$columnName . '= (CASE ' . implode(' ', $query) . ' END)';
}, array_keys($columns), $columns)
);
};
return self::rawExecute(
'UPDATE `' . self::$_table . '` ' .
'SET ' . implode(', ', $sql('statement')) . ' '.
(($currentTime) ? ', updated_at = "' . $currentTime . '" ' : '') .
'WHERE email IN ' .
'(' . rtrim(str_repeat('?,', count($subscribers)), ',') . ')',
array_merge(
Helpers::flattenArray($sql('values')),
Helpers::arrayColumn($subscribers, $emailPosition)
)
);
}
}

View File

@ -1,6 +1,8 @@
<?php
namespace MailPoet\Models;
use MailPoet\Util\Helpers;
if(!defined('ABSPATH')) exit;
class SubscriberCustomField extends Model {
@ -9,4 +11,40 @@ class SubscriberCustomField extends Model {
function __construct() {
parent::__construct();
}
static function createMultiple($values) {
$values = array_map('array_values', $values);
return self::rawExecute(
'INSERT IGNORE INTO `' . self::$_table . '` ' .
'(custom_field_id, subscriber_id, value) ' .
'VALUES ' . rtrim(
str_repeat(
'(?, ?, ?)' . ', '
, count($values)
), ', '
),
Helpers::flattenArray($values)
);
}
static function updateMultiple($values) {
self::createMultiple($values);
$values = array_map('array_values', $values);
self::rawExecute(
'UPDATE `' . self::$_table . '` ' .
'SET value = ' .
'(CASE ' .
str_repeat(
'WHEN custom_field_id = ? AND subscriber_id = ? THEN ? ',
count($values)
) .
'END) ' .
'WHERE subscriber_id IN (' .
implode(', ', Helpers::arrayColumn($values, 1)) .
') AND custom_field_id IN (' .
implode(', ', array_unique(Helpers::arrayColumn($values, 0)))
. ') ',
Helpers::flattenArray($values)
);
}
}

View File

@ -1,6 +1,8 @@
<?php
namespace MailPoet\Models;
use MailPoet\Util\Helpers;
if(!defined('ABSPATH')) exit;
class SubscriberSegment extends Model {
@ -9,4 +11,47 @@ class SubscriberSegment extends Model {
function __construct() {
parent::__construct();
}
}
static function filterWithCustomFields($orm) {
$orm = $orm->select(MP_SUBSCRIBERS_TABLE.'.*');
$customFields = CustomField::findArray();
foreach ($customFields as $customField) {
$orm = $orm->select_expr(
'CASE WHEN ' .
MP_CUSTOM_FIELDS_TABLE . '.id=' . $customField['id'] . ' THEN ' .
MP_SUBSCRIBER_CUSTOM_FIELD_TABLE . '.value END as "' . $customField['name'].'"');
}
$orm = $orm
->left_outer_join(
MP_SUBSCRIBER_CUSTOM_FIELD_TABLE,
array(MP_SUBSCRIBERS_TABLE.'.id', '=',
MP_SUBSCRIBER_CUSTOM_FIELD_TABLE.'.subscriber_id'))
->left_outer_join(
MP_CUSTOM_FIELDS_TABLE,
array(MP_CUSTOM_FIELDS_TABLE.'.id','=',
MP_SUBSCRIBER_CUSTOM_FIELD_TABLE.'.custom_field_id'));
return $orm;
}
static function createMultiple($segmnets, $subscribers) {
$values = Helpers::flattenArray(
array_map(function ($segment) use ($subscribers) {
return array_map(function ($subscriber) use ($segment) {
return array(
$segment,
$subscriber
);
}, $subscribers);
}, $segmnets)
);
return self::rawExecute(
'INSERT IGNORE INTO `' . self::$_table . '` ' .
'(segment_id, subscriber_id) ' .
'VALUES ' . rtrim(
str_repeat(
'(?, ?), ', count($subscribers) * count($segmnets)), ', '
),
$values
);
}
}

View File

@ -50,9 +50,11 @@ class MetaInformationManager {
// check if the user specified a label to be displayed before the author's name
if(strlen($preceded_by) > 0) {
$content = stripslashes($preceded_by) . ' ';
} else {
$content = '';
}
return join(', ', $categories);
return $content . join(', ', $categories);
} else {
return '';
}

View File

@ -14,7 +14,7 @@ class PostListTransformer {
function transform($posts) {
$results = array();
$use_divider = (bool)$this->args['showDivider'];
$use_divider = $this->args['showDivider'] === 'true';
foreach ($posts as $index => $post) {
if ($use_divider && $index > 0) {

View File

@ -22,16 +22,26 @@ class PostTransformer {
$content = $content_manager->filterContent($content);
$structure_transformer = new StructureTransformer();
$structure = $structure_transformer->transform($content, (bool)$this->args['imagePadded']);
$structure = $structure_transformer->transform($content, $this->args['imagePadded'] === 'true');
$structure = $this->appendFeaturedImage($post, (bool)$this->args['imagePadded'], $structure);
$structure = $this->appendFeaturedImage(
$post,
$this->args['displayType'],
$this->args['imagePadded'] === 'true',
$structure
);
$structure = $this->appendPostTitle($post, $structure);
$structure = $this->appendReadMore($post->ID, $structure);
return $structure;
}
private function appendFeaturedImage($post, $image_padded, $structure) {
private function appendFeaturedImage($post, $display_type, $image_padded, $structure) {
if ($display_type === 'full') {
// No featured images for full posts
return $structure;
}
$featured_image = $this->getFeaturedImage(
$post->ID,
$post->post_title,
@ -68,7 +78,7 @@ class PostTransformer {
return array(
'type' => 'image',
'link' => '',
'link' => get_permalink($post_id),
'src' => $image_info[0],
'alt' => $alt_text,
'padded' => $image_padded,
@ -84,6 +94,8 @@ class PostTransformer {
}
private function appendPostTitle($post, $structure) {
$title = $this->getPostTitle($post);
if ($this->args['titlePosition'] === 'inTextBlock') {
// Attach title to the first text block
$text_block_index = null;
@ -94,7 +106,6 @@ class PostTransformer {
}
}
$title = $this->getPostTitle($post);
if ($text_block_index === null) {
$structure[] = array(
'type' => 'text',
@ -103,6 +114,14 @@ class PostTransformer {
} else {
$structure[$text_block_index]['text'] = $title . $structure[$text_block_index]['text'];
}
} elseif ($this->args['titlePosition'] === 'aboveBlock') {
array_unshift(
$structure,
array(
'type' => 'text',
'text' => $title,
)
);
}
return $structure;
@ -113,6 +132,15 @@ class PostTransformer {
$button = $this->args['readMoreButton'];
$button['url'] = get_permalink($post_id);
$structure[] = $button;
} else {
$structure[] = array(
'type' => 'text',
'text' => sprintf(
'<a href="%s">%s</a>',
get_permalink($post_id),
$this->args['readMoreText']
),
);
}
return $structure;
@ -121,7 +149,7 @@ class PostTransformer {
private function getPostTitle($post) {
$title = $post->post_title;
if ((bool)$this->args['titleIsLink']) {
if ($this->args['titleIsLink'] === 'true') {
$title = '<a href="' . get_permalink($post->ID) . '">' . $title . '</a>';
}

Some files were not shown because too many files have changed in this diff Show More