Version Description
- 2016-12-27 =
- Improved: newsletter is saved prior to sending an email preview;
- Improved: subscription management page conditionally displays the "bounced" status;
- Improved: deleted lists are displayed in newsletter listings;
- Fixed: newsletter/subscriber/list/form dates are properly formatted according to WP settings;
- Fixed: emails' "Return-path" header is set to the bounce address configured in Settings->Advanced;
- Fixed: archived newsletters' shortcode works for site visitors;
- Fixed: unicode support for newsletters.
Download this release
Release Info
Developer | wysija |
Plugin | MailPoet Newsletters (New) |
Version | 3.0.0-beta.10 |
Comparing to | |
See all releases |
Code changes from version 3.0.0-beta.9 to 3.0.0-beta.10
- assets/css/admin.css +2 -2
- assets/js/admin.js +9 -3
- assets/js/form_editor.js +679 -676
- assets/js/mailpoet.js +1 -3
- assets/js/newsletter_editor.js +39 -24
- lang/index.php +0 -3
- lang/mailpoet.pot +44 -40
- lib/API/Endpoints/Newsletters.php +11 -5
- lib/Config/Env.php +1 -0
- lib/Config/Initializer.php +1 -2
- lib/Config/Menu.php +12 -12
- lib/Config/Migrator.php +1 -0
- lib/Config/Shortcodes.php +13 -8
- lib/Form/Block/Select.php +8 -2
- lib/Mailer/Mailer.php +16 -5
- lib/Mailer/Methods/AmazonSES.php +6 -3
- lib/Mailer/Methods/PHPMail.php +6 -1
- lib/Mailer/Methods/SMTP.php +6 -1
- lib/Models/Newsletter.php +65 -3
- lib/Models/SendingQueue.php +5 -2
- lib/Models/Subscriber.php +7 -3
- lib/Newsletter/Links/Links.php +58 -12
- lib/Newsletter/Renderer/PostProcess/OpenTracking.php +1 -1
- lib/Newsletter/Renderer/Renderer.php +8 -4
- lib/Newsletter/Shortcodes/Categories/Link.php +42 -16
- lib/Newsletter/Shortcodes/Shortcodes.php +8 -3
- lib/Newsletter/Url.php +59 -31
- lib/Newsletter/ViewInBrowser.php +25 -10
- lib/Router/Endpoints/Track.php +4 -3
- lib/Router/Endpoints/ViewInBrowser.php +45 -26
- lib/Subscription/Pages.php +4 -0
- mailpoet.php +2 -2
- readme.txt +18 -9
- vendor/autoload.php +1 -1
- vendor/composer/ClassLoader.php +31 -5
- vendor/composer/autoload_real.php +8 -8
- vendor/composer/autoload_static.php +5 -5
- vendor/composer/installed.json +11 -11
assets/css/admin.css
CHANGED
@@ -1799,8 +1799,8 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
|
|
1799 |
position: relative;
|
1800 |
overflow: visible;
|
1801 |
-webkit-tap-highlight-color: transparent;
|
1802 |
-
-webkit-font-variant-ligatures:
|
1803 |
-
font-variant-ligatures:
|
1804 |
}
|
1805 |
.CodeMirror-wrap pre {
|
1806 |
word-wrap: break-word;
|
1799 |
position: relative;
|
1800 |
overflow: visible;
|
1801 |
-webkit-tap-highlight-color: transparent;
|
1802 |
+
-webkit-font-variant-ligatures: contextual;
|
1803 |
+
font-variant-ligatures: contextual;
|
1804 |
}
|
1805 |
.CodeMirror-wrap pre {
|
1806 |
word-wrap: break-word;
|
assets/js/admin.js
CHANGED
@@ -839,12 +839,18 @@ webpackJsonp([0],[
|
|
839 |
* will remain to ensure logic does not differ in production.
|
840 |
*/
|
841 |
|
842 |
-
function
|
843 |
-
|
|
|
|
|
844 |
if (format === undefined) {
|
845 |
throw new Error('invariant requires an error message argument');
|
846 |
}
|
847 |
-
}
|
|
|
|
|
|
|
|
|
848 |
|
849 |
if (!condition) {
|
850 |
var error;
|
839 |
* will remain to ensure logic does not differ in production.
|
840 |
*/
|
841 |
|
842 |
+
var validateFormat = function validateFormat(format) {};
|
843 |
+
|
844 |
+
if (process.env.NODE_ENV !== 'production') {
|
845 |
+
validateFormat = function validateFormat(format) {
|
846 |
if (format === undefined) {
|
847 |
throw new Error('invariant requires an error message argument');
|
848 |
}
|
849 |
+
};
|
850 |
+
}
|
851 |
+
|
852 |
+
function invariant(condition, format, a, b, c, d, e, f) {
|
853 |
+
validateFormat(format);
|
854 |
|
855 |
if (!condition) {
|
856 |
var error;
|
assets/js/form_editor.js
CHANGED
@@ -2077,12 +2077,12 @@ webpackJsonp([1],{
|
|
2077 |
var bidiOrdering = (function() {
|
2078 |
// Character types for codepoints 0 to 0xff
|
2079 |
var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN"
|
2080 |
-
// Character types for codepoints 0x600 to
|
2081 |
-
var arabicTypes = "
|
2082 |
function charType(code) {
|
2083 |
if (code <= 0xf7) { return lowTypes.charAt(code) }
|
2084 |
else if (0x590 <= code && code <= 0x5f4) { return "R" }
|
2085 |
-
else if (0x600 <= code && code <=
|
2086 |
else if (0x6ee <= code && code <= 0x8ac) { return "r" }
|
2087 |
else if (0x2000 <= code && code <= 0x200b) { return "w" }
|
2088 |
else if (code == 0x200c) { return "b" }
|
@@ -4388,7 +4388,7 @@ webpackJsonp([1],{
|
|
4388 |
}
|
4389 |
}
|
4390 |
|
4391 |
-
|
4392 |
this.cm = cm
|
4393 |
var vert = this.vert = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar")
|
4394 |
var horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar")
|
@@ -4404,91 +4404,92 @@ webpackJsonp([1],{
|
|
4404 |
this.checkedZeroWidth = false
|
4405 |
// Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
|
4406 |
if (ie && ie_version < 8) { this.horiz.style.minHeight = this.vert.style.minWidth = "18px" }
|
4407 |
-
}
|
4408 |
-
|
4409 |
-
NativeScrollbars.prototype =
|
4410 |
-
|
4411 |
-
|
4412 |
-
|
4413 |
-
|
4414 |
-
|
4415 |
-
|
4416 |
-
|
4417 |
-
|
4418 |
-
|
4419 |
-
|
4420 |
-
|
4421 |
-
|
4422 |
-
|
4423 |
-
|
4424 |
-
this.vert.firstChild.style.height = "0"
|
4425 |
-
}
|
4426 |
-
|
4427 |
-
if (needsH) {
|
4428 |
-
this.horiz.style.display = "block"
|
4429 |
-
this.horiz.style.right = needsV ? sWidth + "px" : "0"
|
4430 |
-
this.horiz.style.left = measure.barLeft + "px"
|
4431 |
-
var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0)
|
4432 |
-
this.horiz.firstChild.style.width =
|
4433 |
-
(measure.scrollWidth - measure.clientWidth + totalWidth) + "px"
|
4434 |
-
} else {
|
4435 |
-
this.horiz.style.display = ""
|
4436 |
-
this.horiz.firstChild.style.width = "0"
|
4437 |
-
}
|
4438 |
-
|
4439 |
-
if (!this.checkedZeroWidth && measure.clientHeight > 0) {
|
4440 |
-
if (sWidth == 0) { this.zeroWidthHack() }
|
4441 |
-
this.checkedZeroWidth = true
|
4442 |
-
}
|
4443 |
-
|
4444 |
-
return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0}
|
4445 |
-
},
|
4446 |
-
setScrollLeft: function(pos) {
|
4447 |
-
if (this.horiz.scrollLeft != pos) { this.horiz.scrollLeft = pos }
|
4448 |
-
if (this.disableHoriz) { this.enableZeroWidthBar(this.horiz, this.disableHoriz) }
|
4449 |
-
},
|
4450 |
-
setScrollTop: function(pos) {
|
4451 |
-
if (this.vert.scrollTop != pos) { this.vert.scrollTop = pos }
|
4452 |
-
if (this.disableVert) { this.enableZeroWidthBar(this.vert, this.disableVert) }
|
4453 |
-
},
|
4454 |
-
zeroWidthHack: function() {
|
4455 |
-
var w = mac && !mac_geMountainLion ? "12px" : "18px"
|
4456 |
-
this.horiz.style.height = this.vert.style.width = w
|
4457 |
-
this.horiz.style.pointerEvents = this.vert.style.pointerEvents = "none"
|
4458 |
-
this.disableHoriz = new Delayed
|
4459 |
-
this.disableVert = new Delayed
|
4460 |
-
},
|
4461 |
-
enableZeroWidthBar: function(bar, delay) {
|
4462 |
-
bar.style.pointerEvents = "auto"
|
4463 |
-
function maybeDisable() {
|
4464 |
-
// To find out whether the scrollbar is still visible, we
|
4465 |
-
// check whether the element under the pixel in the bottom
|
4466 |
-
// left corner of the scrollbar box is the scrollbar box
|
4467 |
-
// itself (when the bar is still visible) or its filler child
|
4468 |
-
// (when the bar is hidden). If it is still visible, we keep
|
4469 |
-
// it enabled, if it's hidden, we disable pointer events.
|
4470 |
-
var box = bar.getBoundingClientRect()
|
4471 |
-
var elt = document.elementFromPoint(box.left + 1, box.bottom - 1)
|
4472 |
-
if (elt != bar) { bar.style.pointerEvents = "none" }
|
4473 |
-
else { delay.set(1000, maybeDisable) }
|
4474 |
-
}
|
4475 |
-
delay.set(1000, maybeDisable)
|
4476 |
-
},
|
4477 |
-
clear: function() {
|
4478 |
-
var parent = this.horiz.parentNode
|
4479 |
-
parent.removeChild(this.horiz)
|
4480 |
-
parent.removeChild(this.vert)
|
4481 |
}
|
4482 |
-
}, NativeScrollbars.prototype)
|
4483 |
-
|
4484 |
-
function NullScrollbars() {}
|
4485 |
|
4486 |
-
|
4487 |
-
|
4488 |
-
|
4489 |
-
|
4490 |
-
|
4491 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4492 |
|
4493 |
function updateScrollbars(cm, measure) {
|
4494 |
if (!measure) { measure = measureForScrollbars(cm) }
|
@@ -5067,7 +5068,7 @@ webpackJsonp([1],{
|
|
5067 |
|
5068 |
// DISPLAY DRAWING
|
5069 |
|
5070 |
-
|
5071 |
var display = cm.display
|
5072 |
|
5073 |
this.viewport = viewport
|
@@ -5080,18 +5081,18 @@ webpackJsonp([1],{
|
|
5080 |
this.force = force
|
5081 |
this.dims = getDimensions(cm)
|
5082 |
this.events = []
|
5083 |
-
}
|
5084 |
|
5085 |
-
DisplayUpdate.prototype.signal = function(emitter, type) {
|
5086 |
if (hasHandler(emitter, type))
|
5087 |
{ this.events.push(arguments) }
|
5088 |
-
}
|
5089 |
-
DisplayUpdate.prototype.finish = function() {
|
5090 |
-
|
5091 |
|
5092 |
for (var i = 0; i < this.events.length; i++)
|
5093 |
{ signal.apply(null, this$1.events[i]) }
|
5094 |
-
}
|
5095 |
|
5096 |
function maybeClipScrollbars(cm) {
|
5097 |
var display = cm.display
|
@@ -8219,7 +8220,7 @@ webpackJsonp([1],{
|
|
8219 |
for (var i = newBreaks.length - 1; i >= 0; i--)
|
8220 |
{ replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks[i].ch + val.length)) }
|
8221 |
})
|
8222 |
-
option("specialChars", /[\u0000-\u001f\u007f\u00ad\u200b-\u200f\u2028\u2029\ufeff]/g, function (cm, val, old) {
|
8223 |
cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g")
|
8224 |
if (old != Init) { cm.refresh() }
|
8225 |
})
|
@@ -8309,7 +8310,7 @@ webpackJsonp([1],{
|
|
8309 |
function guttersChanged(cm) {
|
8310 |
updateGutters(cm)
|
8311 |
regChange(cm)
|
8312 |
-
|
8313 |
}
|
8314 |
|
8315 |
function dragDropChanged(cm, value, old) {
|
@@ -8364,7 +8365,6 @@ webpackJsonp([1],{
|
|
8364 |
themeChanged(this)
|
8365 |
if (options.lineWrapping)
|
8366 |
{ this.display.wrapper.className += " CodeMirror-wrap" }
|
8367 |
-
if (options.autofocus && !mobile) { display.input.focus() }
|
8368 |
initScrollbars(this)
|
8369 |
|
8370 |
this.state = {
|
@@ -8383,6 +8383,8 @@ webpackJsonp([1],{
|
|
8383 |
specialChars: null
|
8384 |
}
|
8385 |
|
|
|
|
|
8386 |
// Override magic textarea content restore that IE sometimes does
|
8387 |
// on our hidden textarea on reload
|
8388 |
if (ie && ie_version < 11) { setTimeout(function () { return this$1.display.input.reset(true); }, 20) }
|
@@ -8738,6 +8740,7 @@ webpackJsonp([1],{
|
|
8738 |
options[option] = value
|
8739 |
if (optionHandlers.hasOwnProperty(option))
|
8740 |
{ operation(this, optionHandlers[option])(this, value, old) }
|
|
|
8741 |
},
|
8742 |
|
8743 |
getOption: function(option) {return this.options[option]},
|
@@ -9245,331 +9248,333 @@ webpackJsonp([1],{
|
|
9245 |
|
9246 |
// CONTENTEDITABLE INPUT STYLE
|
9247 |
|
9248 |
-
|
9249 |
this.cm = cm
|
9250 |
this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null
|
9251 |
this.polling = new Delayed()
|
9252 |
this.composing = null
|
9253 |
this.gracePeriod = false
|
9254 |
this.readDOMTimeout = null
|
9255 |
-
}
|
9256 |
|
9257 |
-
ContentEditableInput.prototype =
|
9258 |
-
init: function(display) {
|
9259 |
var this$1 = this;
|
9260 |
|
9261 |
-
|
9262 |
-
|
9263 |
-
|
9264 |
|
9265 |
-
|
9266 |
-
|
9267 |
-
|
9268 |
-
|
9269 |
-
|
9270 |
-
|
9271 |
-
|
9272 |
|
9273 |
-
|
9274 |
-
|
9275 |
-
|
9276 |
-
|
9277 |
-
|
9278 |
-
|
9279 |
-
|
9280 |
-
|
9281 |
-
|
9282 |
-
|
9283 |
-
|
9284 |
-
|
9285 |
|
9286 |
-
|
9287 |
|
9288 |
-
|
9289 |
-
|
9290 |
-
|
9291 |
|
9292 |
-
|
9293 |
-
|
9294 |
-
|
9295 |
-
|
9296 |
-
|
9297 |
-
|
9298 |
-
|
9299 |
-
|
9300 |
-
|
9301 |
-
|
9302 |
-
|
9303 |
-
|
9304 |
-
|
9305 |
-
|
9306 |
-
|
9307 |
-
}
|
9308 |
}
|
9309 |
-
|
9310 |
-
|
9311 |
-
|
9312 |
-
|
9313 |
-
|
9314 |
-
|
9315 |
-
|
9316 |
-
|
9317 |
-
|
9318 |
}
|
9319 |
-
// Old-fashioned briefly-focus-a-textarea hack
|
9320 |
-
var kludge = hiddenTextarea(), te = kludge.firstChild
|
9321 |
-
cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild)
|
9322 |
-
te.value = lastCopied.text.join("\n")
|
9323 |
-
var hadFocus = document.activeElement
|
9324 |
-
selectInput(te)
|
9325 |
-
setTimeout(function () {
|
9326 |
-
cm.display.lineSpace.removeChild(kludge)
|
9327 |
-
hadFocus.focus()
|
9328 |
-
if (hadFocus == div) { input.showPrimarySelection() }
|
9329 |
-
}, 50)
|
9330 |
}
|
9331 |
-
|
9332 |
-
|
9333 |
-
|
9334 |
-
|
9335 |
-
|
9336 |
-
|
9337 |
-
|
9338 |
-
|
9339 |
-
|
9340 |
-
|
9341 |
-
|
9342 |
-
|
9343 |
-
|
9344 |
-
|
9345 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9346 |
|
9347 |
-
|
9348 |
-
|
9349 |
-
|
9350 |
-
|
9351 |
-
|
9352 |
-
|
9353 |
-
|
9354 |
-
|
9355 |
-
|
9356 |
-
var
|
9357 |
-
var
|
9358 |
-
|
9359 |
-
|
9360 |
-
|
9361 |
-
|
9362 |
-
|
9363 |
-
|
9364 |
-
|
9365 |
-
|
9366 |
-
|
9367 |
-
|
9368 |
-
}
|
9369 |
-
|
9370 |
-
var rng
|
9371 |
-
try { rng = range(start.node, start.offset, end.offset, end.node) }
|
9372 |
-
catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible
|
9373 |
-
if (rng) {
|
9374 |
-
if (!gecko && this.cm.state.focused) {
|
9375 |
-
sel.collapse(start.node, start.offset)
|
9376 |
-
if (!rng.collapsed) {
|
9377 |
-
sel.removeAllRanges()
|
9378 |
-
sel.addRange(rng)
|
9379 |
-
}
|
9380 |
-
} else {
|
9381 |
sel.removeAllRanges()
|
9382 |
sel.addRange(rng)
|
9383 |
}
|
9384 |
-
|
9385 |
-
|
|
|
9386 |
}
|
9387 |
-
|
9388 |
-
|
|
|
|
|
|
|
9389 |
|
9390 |
-
|
9391 |
var this$1 = this;
|
9392 |
|
9393 |
-
|
9394 |
-
|
9395 |
-
|
9396 |
-
|
9397 |
-
|
9398 |
-
|
9399 |
-
|
9400 |
-
|
9401 |
-
|
9402 |
-
|
9403 |
-
|
9404 |
-
|
9405 |
-
|
9406 |
-
|
9407 |
-
|
9408 |
-
|
9409 |
-
|
9410 |
-
|
9411 |
-
|
9412 |
-
|
9413 |
-
|
9414 |
-
|
9415 |
-
|
9416 |
-
|
9417 |
-
|
9418 |
-
|
9419 |
-
|
9420 |
-
|
9421 |
-
|
9422 |
-
|
9423 |
-
|
9424 |
-
|
9425 |
-
|
9426 |
-
|
9427 |
-
|
9428 |
-
|
9429 |
-
supportsTouch: function() { return true },
|
9430 |
-
|
9431 |
-
receivedFocus: function() {
|
9432 |
-
var input = this
|
9433 |
-
if (this.selectionInEditor())
|
9434 |
-
{ this.pollSelection() }
|
9435 |
-
else
|
9436 |
-
{ runInOp(this.cm, function () { return input.cm.curOp.selectionChanged = true; }) }
|
9437 |
|
9438 |
-
|
9439 |
-
if (input.cm.state.focused) {
|
9440 |
-
input.pollSelection()
|
9441 |
-
input.polling.set(input.cm.options.pollInterval, poll)
|
9442 |
-
}
|
9443 |
-
}
|
9444 |
-
this.polling.set(this.cm.options.pollInterval, poll)
|
9445 |
-
},
|
9446 |
|
9447 |
-
|
9448 |
-
|
9449 |
-
|
9450 |
-
|
9451 |
-
|
|
|
9452 |
|
9453 |
-
|
9454 |
-
if (
|
9455 |
-
|
9456 |
-
|
9457 |
-
var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset)
|
9458 |
-
var head = domToPos(cm, sel.focusNode, sel.focusOffset)
|
9459 |
-
if (anchor && head) { runInOp(cm, function () {
|
9460 |
-
setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll)
|
9461 |
-
if (anchor.bad || head.bad) { cm.curOp.selectionChanged = true }
|
9462 |
-
}) }
|
9463 |
}
|
9464 |
-
}
|
|
|
|
|
9465 |
|
9466 |
-
|
9467 |
-
|
9468 |
-
|
9469 |
-
|
9470 |
-
|
9471 |
|
9472 |
-
|
9473 |
-
|
9474 |
-
|
9475 |
-
|
9476 |
-
|
9477 |
-
|
9478 |
-
if (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9479 |
|
9480 |
-
|
9481 |
-
|
9482 |
-
|
9483 |
-
|
9484 |
-
|
9485 |
-
|
9486 |
-
|
9487 |
-
|
9488 |
-
var toIndex = findViewIndex(cm, to.line)
|
9489 |
-
var toLine, toNode
|
9490 |
-
if (toIndex == display.view.length - 1) {
|
9491 |
-
toLine = display.viewTo - 1
|
9492 |
-
toNode = display.lineDiv.lastChild
|
9493 |
-
} else {
|
9494 |
-
toLine = lineNo(display.view[toIndex + 1].line) - 1
|
9495 |
-
toNode = display.view[toIndex + 1].node.previousSibling
|
9496 |
-
}
|
9497 |
-
|
9498 |
-
if (!fromNode) { return false }
|
9499 |
-
var newText = cm.doc.splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine))
|
9500 |
-
var oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc, toLine).text.length))
|
9501 |
-
while (newText.length > 1 && oldText.length > 1) {
|
9502 |
-
if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine-- }
|
9503 |
-
else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); fromLine++ }
|
9504 |
-
else { break }
|
9505 |
-
}
|
9506 |
-
|
9507 |
-
var cutFront = 0, cutEnd = 0
|
9508 |
-
var newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTop.length, oldTop.length)
|
9509 |
-
while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.charCodeAt(cutFront))
|
9510 |
-
{ ++cutFront }
|
9511 |
-
var newBot = lst(newText), oldBot = lst(oldText)
|
9512 |
-
var maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0),
|
9513 |
-
oldBot.length - (oldText.length == 1 ? cutFront : 0))
|
9514 |
-
while (cutEnd < maxCutEnd &&
|
9515 |
-
newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1))
|
9516 |
-
{ ++cutEnd }
|
9517 |
-
|
9518 |
-
newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd).replace(/^\u200b+/, "")
|
9519 |
-
newText[0] = newText[0].slice(cutFront).replace(/\u200b+$/, "")
|
9520 |
-
|
9521 |
-
var chFrom = Pos(fromLine, cutFront)
|
9522 |
-
var chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0)
|
9523 |
-
if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) {
|
9524 |
-
replaceRange(cm.doc, newText, chFrom, chTo, "+input")
|
9525 |
-
return true
|
9526 |
-
}
|
9527 |
-
},
|
9528 |
|
9529 |
-
|
9530 |
-
|
9531 |
-
|
9532 |
-
|
9533 |
-
|
9534 |
-
|
9535 |
-
|
9536 |
-
|
9537 |
-
|
9538 |
-
|
9539 |
-
|
9540 |
-
|
9541 |
-
|
9542 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9543 |
var this$1 = this;
|
9544 |
|
9545 |
-
|
9546 |
-
|
9547 |
-
|
9548 |
-
|
9549 |
-
if (this$1.
|
9550 |
-
|
9551 |
-
}
|
9552 |
-
|
|
|
|
|
|
|
9553 |
|
9554 |
-
|
9555 |
-
|
9556 |
-
|
9557 |
|
9558 |
-
|
9559 |
-
|
9560 |
-
|
9561 |
-
|
9562 |
-
|
9563 |
|
9564 |
-
|
9565 |
-
|
9566 |
-
|
9567 |
|
9568 |
-
|
9569 |
-
|
9570 |
|
9571 |
-
|
9572 |
-
}, ContentEditableInput.prototype)
|
9573 |
|
9574 |
function posToDOM(cm, pos) {
|
9575 |
var view = findViewForLine(cm, pos.line)
|
@@ -9706,7 +9711,7 @@ webpackJsonp([1],{
|
|
9706 |
|
9707 |
// TEXTAREA INPUT STYLE
|
9708 |
|
9709 |
-
|
9710 |
this.cm = cm
|
9711 |
// See input.poll and input.reset
|
9712 |
this.prevInput = ""
|
@@ -9723,335 +9728,333 @@ webpackJsonp([1],{
|
|
9723 |
// Used to work around IE issue with selection being forgotten when focus moves away from textarea
|
9724 |
this.hasSelection = false
|
9725 |
this.composing = null
|
9726 |
-
}
|
9727 |
|
9728 |
-
TextareaInput.prototype =
|
9729 |
-
init: function(display) {
|
9730 |
var this$1 = this;
|
9731 |
|
9732 |
-
|
9733 |
|
9734 |
-
|
9735 |
-
|
9736 |
-
|
9737 |
-
|
9738 |
-
|
9739 |
-
|
9740 |
|
9741 |
-
|
9742 |
-
|
9743 |
|
9744 |
-
|
9745 |
-
|
9746 |
-
|
9747 |
-
|
9748 |
|
9749 |
-
|
9750 |
-
|
9751 |
|
9752 |
-
|
9753 |
-
|
9754 |
-
|
9755 |
|
9756 |
-
|
9757 |
-
|
9758 |
-
|
9759 |
-
|
9760 |
-
|
9761 |
-
|
9762 |
-
|
9763 |
-
|
9764 |
-
|
9765 |
-
|
9766 |
-
|
9767 |
-
|
|
|
|
|
|
|
|
|
|
|
9768 |
} else {
|
9769 |
-
|
9770 |
-
|
9771 |
-
|
9772 |
-
cm.setSelections(ranges.ranges, null, sel_dontScroll)
|
9773 |
-
} else {
|
9774 |
-
input.prevInput = ""
|
9775 |
-
te.value = ranges.text.join("\n")
|
9776 |
-
selectInput(te)
|
9777 |
-
}
|
9778 |
}
|
9779 |
-
if (e.type == "cut") { cm.state.cutIncoming = true }
|
9780 |
}
|
9781 |
-
|
9782 |
-
|
9783 |
-
|
9784 |
-
|
9785 |
-
if (eventInWidget(display, e) || signalDOMEvent(cm, e)) { return }
|
9786 |
-
cm.state.pasteIncoming = true
|
9787 |
-
input.focus()
|
9788 |
-
})
|
9789 |
-
|
9790 |
-
// Prevent normal selection in the editor (we handle our own)
|
9791 |
-
on(display.lineSpace, "selectstart", function (e) {
|
9792 |
-
if (!eventInWidget(display, e)) { e_preventDefault(e) }
|
9793 |
-
})
|
9794 |
|
9795 |
-
|
9796 |
-
|
9797 |
-
|
9798 |
-
|
9799 |
-
|
9800 |
-
range: cm.markText(start, cm.getCursor("to"), {className: "CodeMirror-composing"})
|
9801 |
-
}
|
9802 |
-
})
|
9803 |
-
on(te, "compositionend", function () {
|
9804 |
-
if (input.composing) {
|
9805 |
-
input.poll()
|
9806 |
-
input.composing.range.clear()
|
9807 |
-
input.composing = null
|
9808 |
-
}
|
9809 |
-
})
|
9810 |
-
},
|
9811 |
|
9812 |
-
|
9813 |
-
|
9814 |
-
|
9815 |
-
|
9816 |
|
9817 |
-
|
9818 |
-
|
9819 |
-
|
9820 |
-
|
9821 |
-
|
9822 |
-
|
9823 |
-
result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10,
|
9824 |
-
headPos.left + lineOff.left - wrapOff.left))
|
9825 |
}
|
9826 |
-
|
9827 |
-
|
9828 |
-
|
9829 |
-
|
9830 |
-
|
9831 |
-
|
9832 |
-
removeChildrenAndAdd(display.cursorDiv, drawn.cursors)
|
9833 |
-
removeChildrenAndAdd(display.selectionDiv, drawn.selection)
|
9834 |
-
if (drawn.teTop != null) {
|
9835 |
-
this.wrapper.style.top = drawn.teTop + "px"
|
9836 |
-
this.wrapper.style.left = drawn.teLeft + "px"
|
9837 |
}
|
9838 |
-
}
|
9839 |
-
|
9840 |
-
// Reset the input to correspond to the selection (or to be empty,
|
9841 |
-
// when not typing and nothing is selected)
|
9842 |
-
reset: function(typing) {
|
9843 |
-
if (this.contextMenuPending) { return }
|
9844 |
-
var minimal, selected, cm = this.cm, doc = cm.doc
|
9845 |
-
if (cm.somethingSelected()) {
|
9846 |
-
this.prevInput = ""
|
9847 |
-
var range = doc.sel.primary()
|
9848 |
-
minimal = hasCopyEvent &&
|
9849 |
-
(range.to().line - range.from().line > 100 || (selected = cm.getSelection()).length > 1000)
|
9850 |
-
var content = minimal ? "-" : selected || cm.getSelection()
|
9851 |
-
this.textarea.value = content
|
9852 |
-
if (cm.state.focused) { selectInput(this.textarea) }
|
9853 |
-
if (ie && ie_version >= 9) { this.hasSelection = content }
|
9854 |
-
} else if (!typing) {
|
9855 |
-
this.prevInput = this.textarea.value = ""
|
9856 |
-
if (ie && ie_version >= 9) { this.hasSelection = null }
|
9857 |
-
}
|
9858 |
-
this.inaccurateSelection = minimal
|
9859 |
-
},
|
9860 |
|
9861 |
-
|
|
|
|
|
|
|
9862 |
|
9863 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9864 |
|
9865 |
-
|
9866 |
-
|
9867 |
-
|
9868 |
-
|
9869 |
-
|
9870 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9871 |
|
9872 |
-
|
9873 |
|
9874 |
-
|
9875 |
-
this.wrapper.style.top = this.wrapper.style.left = 0
|
9876 |
-
},
|
9877 |
|
9878 |
-
|
|
|
|
|
|
|
|
|
|
|
9879 |
|
9880 |
-
|
9881 |
-
// runs as long as the editor is focused.
|
9882 |
-
slowPoll: function() {
|
9883 |
-
var this$1 = this;
|
9884 |
|
9885 |
-
|
9886 |
-
|
9887 |
-
|
9888 |
-
if (this$1.cm.state.focused) { this$1.slowPoll() }
|
9889 |
-
})
|
9890 |
-
},
|
9891 |
|
9892 |
-
|
9893 |
-
// something in the input textarea, we poll faster, to ensure that
|
9894 |
-
// the change appears on the screen quickly.
|
9895 |
-
fastPoll: function() {
|
9896 |
-
var missed = false, input = this
|
9897 |
-
input.pollingFast = true
|
9898 |
-
function p() {
|
9899 |
-
var changed = input.poll()
|
9900 |
-
if (!changed && !missed) {missed = true; input.polling.set(60, p)}
|
9901 |
-
else {input.pollingFast = false; input.slowPoll()}
|
9902 |
-
}
|
9903 |
-
input.polling.set(20, p)
|
9904 |
-
},
|
9905 |
|
9906 |
-
|
9907 |
-
|
9908 |
-
|
9909 |
-
// used). When nothing is selected, the cursor sits after previously
|
9910 |
-
// seen text (can be empty), which is stored in prevInput (we must
|
9911 |
-
// not reset the textarea when typing, because that breaks IME).
|
9912 |
-
poll: function() {
|
9913 |
var this$1 = this;
|
9914 |
|
9915 |
-
|
9916 |
-
|
9917 |
-
|
9918 |
-
|
9919 |
-
|
9920 |
-
|
9921 |
-
|
9922 |
-
|
9923 |
-
|
9924 |
-
|
9925 |
-
|
9926 |
-
|
9927 |
-
|
9928 |
-
|
9929 |
-
|
9930 |
-
|
9931 |
-
|
9932 |
-
|
9933 |
-
|
9934 |
-
|
9935 |
-
|
9936 |
-
|
9937 |
-
|
9938 |
-
|
9939 |
-
|
9940 |
-
|
9941 |
-
|
9942 |
-
|
9943 |
-
var
|
9944 |
-
while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) { ++same }
|
9945 |
|
9946 |
-
|
9947 |
-
|
9948 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
9949 |
|
9950 |
-
|
9951 |
-
|
9952 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9953 |
|
9954 |
-
|
9955 |
-
|
9956 |
-
|
9957 |
-
|
9958 |
-
|
9959 |
-
|
9960 |
-
|
9961 |
-
}
|
9962 |
|
9963 |
-
|
9964 |
-
|
9965 |
-
|
9966 |
|
9967 |
-
|
9968 |
-
if (
|
9969 |
-
this.
|
9970 |
-
},
|
9971 |
|
9972 |
-
|
9973 |
-
|
9974 |
-
|
9975 |
-
|
9976 |
-
|
9977 |
-
// Reset the current text selection only if the click is done outside of the selection
|
9978 |
-
// and 'resetSelectionOnContextMenu' option is true.
|
9979 |
-
var reset = cm.options.resetSelectionOnContextMenu
|
9980 |
-
if (reset && cm.doc.sel.contains(pos) == -1)
|
9981 |
-
{ operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll) }
|
9982 |
-
|
9983 |
-
var oldCSS = te.style.cssText, oldWrapperCSS = input.wrapper.style.cssText
|
9984 |
-
input.wrapper.style.cssText = "position: absolute"
|
9985 |
-
var wrapperBox = input.wrapper.getBoundingClientRect()
|
9986 |
-
te.style.cssText = "position: absolute; width: 30px; height: 30px;\n top: " + (e.clientY - wrapperBox.top - 5) + "px; left: " + (e.clientX - wrapperBox.left - 5) + "px;\n z-index: 1000; background: " + (ie ? "rgba(255, 255, 255, .05)" : "transparent") + ";\n outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"
|
9987 |
-
var oldScrollY
|
9988 |
-
if (webkit) { oldScrollY = window.scrollY } // Work around Chrome issue (#2712)
|
9989 |
-
display.input.focus()
|
9990 |
-
if (webkit) { window.scrollTo(null, oldScrollY) }
|
9991 |
-
display.input.reset()
|
9992 |
-
// Adds "Select all" to context menu in FF
|
9993 |
-
if (!cm.somethingSelected()) { te.value = input.prevInput = " " }
|
9994 |
-
input.contextMenuPending = true
|
9995 |
-
display.selForContextMenu = cm.doc.sel
|
9996 |
-
clearTimeout(display.detectingSelectAll)
|
9997 |
-
|
9998 |
-
// Select-all will be greyed out if there's nothing to select, so
|
9999 |
-
// this adds a zero-width space so that we can later check whether
|
10000 |
-
// it got selected.
|
10001 |
-
function prepareSelectAllHack() {
|
10002 |
-
if (te.selectionStart != null) {
|
10003 |
-
var selected = cm.somethingSelected()
|
10004 |
-
var extval = "\u200b" + (selected ? te.value : "")
|
10005 |
-
te.value = "\u21da" // Used to catch context-menu undo
|
10006 |
-
te.value = extval
|
10007 |
-
input.prevInput = selected ? "" : "\u200b"
|
10008 |
-
te.selectionStart = 1; te.selectionEnd = extval.length
|
10009 |
-
// Re-set this, in case some other handler touched the
|
10010 |
-
// selection in the meantime.
|
10011 |
-
display.selForContextMenu = cm.doc.sel
|
10012 |
-
}
|
10013 |
}
|
10014 |
-
|
10015 |
-
|
10016 |
-
|
10017 |
-
|
10018 |
-
|
10019 |
-
|
10020 |
-
|
10021 |
-
|
10022 |
-
|
10023 |
-
|
10024 |
-
|
10025 |
-
|
10026 |
-
|
10027 |
-
|
10028 |
-
|
10029 |
-
|
10030 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10031 |
}
|
|
|
10032 |
}
|
|
|
10033 |
|
10034 |
-
|
10035 |
-
|
10036 |
-
|
10037 |
-
|
10038 |
-
|
10039 |
-
|
10040 |
-
}
|
10041 |
-
on(window, "mouseup", mouseup)
|
10042 |
-
} else {
|
10043 |
-
setTimeout(rehide, 50)
|
10044 |
}
|
10045 |
-
|
|
|
|
|
|
|
|
|
10046 |
|
10047 |
-
|
10048 |
-
|
10049 |
-
|
10050 |
|
10051 |
-
|
10052 |
|
10053 |
-
|
10054 |
-
}, TextareaInput.prototype)
|
10055 |
|
10056 |
function fromTextArea(textarea, options) {
|
10057 |
options = options ? copyObj(options) : {}
|
@@ -10202,7 +10205,7 @@ webpackJsonp([1],{
|
|
10202 |
|
10203 |
addLegacyProps(CodeMirror)
|
10204 |
|
10205 |
-
CodeMirror.version = "5.
|
10206 |
|
10207 |
return CodeMirror;
|
10208 |
|
@@ -10739,7 +10742,7 @@ webpackJsonp([1],{
|
|
10739 |
"transition-property", "transition-timing-function", "unicode-bidi",
|
10740 |
"user-select", "vertical-align", "visibility", "voice-balance", "voice-duration",
|
10741 |
"voice-family", "voice-pitch", "voice-range", "voice-rate", "voice-stress",
|
10742 |
-
"voice-volume", "volume", "white-space", "widows", "width", "word-break",
|
10743 |
"word-spacing", "word-wrap", "z-index",
|
10744 |
// SVG-specific
|
10745 |
"clip-path", "clip-rule", "mask", "enable-background", "filter", "flood-color",
|
@@ -10813,7 +10816,7 @@ webpackJsonp([1],{
|
|
10813 |
"cell", "center", "checkbox", "circle", "cjk-decimal", "cjk-earthly-branch",
|
10814 |
"cjk-heavenly-stem", "cjk-ideographic", "clear", "clip", "close-quote",
|
10815 |
"col-resize", "collapse", "color", "color-burn", "color-dodge", "column", "column-reverse",
|
10816 |
-
"compact", "condensed", "contain", "content",
|
10817 |
"content-box", "context-menu", "continuous", "copy", "counter", "counters", "cover", "crop",
|
10818 |
"cross", "crosshair", "currentcolor", "cursive", "cyclic", "darken", "dashed", "decimal",
|
10819 |
"decimal-leading-zero", "default", "default-button", "dense", "destination-atop",
|
@@ -10856,7 +10859,7 @@ webpackJsonp([1],{
|
|
10856 |
"mix", "mongolian", "monospace", "move", "multiple", "multiply", "myanmar", "n-resize",
|
10857 |
"narrower", "ne-resize", "nesw-resize", "no-close-quote", "no-drop",
|
10858 |
"no-open-quote", "no-repeat", "none", "normal", "not-allowed", "nowrap",
|
10859 |
-
"ns-resize", "numbers", "numeric", "nw-resize", "nwse-resize", "oblique", "octal", "open-quote",
|
10860 |
"optimizeLegibility", "optimizeSpeed", "oriya", "oromo", "outset",
|
10861 |
"outside", "outside-shape", "overlay", "overline", "padding", "padding-box",
|
10862 |
"painted", "page", "paused", "persian", "perspective", "plus-darker", "plus-lighter",
|
@@ -10868,7 +10871,7 @@ webpackJsonp([1],{
|
|
10868 |
"rgb", "rgba", "ridge", "right", "rotate", "rotate3d", "rotateX", "rotateY",
|
10869 |
"rotateZ", "round", "row", "row-resize", "row-reverse", "rtl", "run-in", "running",
|
10870 |
"s-resize", "sans-serif", "saturation", "scale", "scale3d", "scaleX", "scaleY", "scaleZ", "screen",
|
10871 |
-
"scroll", "scrollbar", "se-resize", "searchfield",
|
10872 |
"searchfield-cancel-button", "searchfield-decoration",
|
10873 |
"searchfield-results-button", "searchfield-results-decoration",
|
10874 |
"semi-condensed", "semi-expanded", "separate", "serif", "show", "sidama",
|
@@ -10886,9 +10889,9 @@ webpackJsonp([1],{
|
|
10886 |
"thick", "thin", "threeddarkshadow", "threedface", "threedhighlight",
|
10887 |
"threedlightshadow", "threedshadow", "tibetan", "tigre", "tigrinya-er",
|
10888 |
"tigrinya-er-abegede", "tigrinya-et", "tigrinya-et-abegede", "to", "top",
|
10889 |
-
"trad-chinese-formal", "trad-chinese-informal",
|
10890 |
"translate", "translate3d", "translateX", "translateY", "translateZ",
|
10891 |
-
"transparent", "ultra-condensed", "ultra-expanded", "underline", "up",
|
10892 |
"upper-alpha", "upper-armenian", "upper-greek", "upper-hexadecimal",
|
10893 |
"upper-latin", "upper-norwegian", "upper-roman", "uppercase", "urdu", "url",
|
10894 |
"var", "vertical", "vertical-text", "visible", "visibleFill", "visiblePainted",
|
2077 |
var bidiOrdering = (function() {
|
2078 |
// Character types for codepoints 0 to 0xff
|
2079 |
var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN"
|
2080 |
+
// Character types for codepoints 0x600 to 0x6f9
|
2081 |
+
var arabicTypes = "nnnnnnNNr%%r,rNNmmmmmmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmmmnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmnNmmmmmmrrmmNmmmmrr1111111111"
|
2082 |
function charType(code) {
|
2083 |
if (code <= 0xf7) { return lowTypes.charAt(code) }
|
2084 |
else if (0x590 <= code && code <= 0x5f4) { return "R" }
|
2085 |
+
else if (0x600 <= code && code <= 0x6f9) { return arabicTypes.charAt(code - 0x600) }
|
2086 |
else if (0x6ee <= code && code <= 0x8ac) { return "r" }
|
2087 |
else if (0x2000 <= code && code <= 0x200b) { return "w" }
|
2088 |
else if (code == 0x200c) { return "b" }
|
4388 |
}
|
4389 |
}
|
4390 |
|
4391 |
+
var NativeScrollbars = function(place, scroll, cm) {
|
4392 |
this.cm = cm
|
4393 |
var vert = this.vert = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar")
|
4394 |
var horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar")
|
4404 |
this.checkedZeroWidth = false
|
4405 |
// Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
|
4406 |
if (ie && ie_version < 8) { this.horiz.style.minHeight = this.vert.style.minWidth = "18px" }
|
4407 |
+
};
|
4408 |
+
|
4409 |
+
NativeScrollbars.prototype.update = function (measure) {
|
4410 |
+
var needsH = measure.scrollWidth > measure.clientWidth + 1
|
4411 |
+
var needsV = measure.scrollHeight > measure.clientHeight + 1
|
4412 |
+
var sWidth = measure.nativeBarWidth
|
4413 |
+
|
4414 |
+
if (needsV) {
|
4415 |
+
this.vert.style.display = "block"
|
4416 |
+
this.vert.style.bottom = needsH ? sWidth + "px" : "0"
|
4417 |
+
var totalHeight = measure.viewHeight - (needsH ? sWidth : 0)
|
4418 |
+
// A bug in IE8 can cause this value to be negative, so guard it.
|
4419 |
+
this.vert.firstChild.style.height =
|
4420 |
+
Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + "px"
|
4421 |
+
} else {
|
4422 |
+
this.vert.style.display = ""
|
4423 |
+
this.vert.firstChild.style.height = "0"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4424 |
}
|
|
|
|
|
|
|
4425 |
|
4426 |
+
if (needsH) {
|
4427 |
+
this.horiz.style.display = "block"
|
4428 |
+
this.horiz.style.right = needsV ? sWidth + "px" : "0"
|
4429 |
+
this.horiz.style.left = measure.barLeft + "px"
|
4430 |
+
var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0)
|
4431 |
+
this.horiz.firstChild.style.width =
|
4432 |
+
(measure.scrollWidth - measure.clientWidth + totalWidth) + "px"
|
4433 |
+
} else {
|
4434 |
+
this.horiz.style.display = ""
|
4435 |
+
this.horiz.firstChild.style.width = "0"
|
4436 |
+
}
|
4437 |
+
|
4438 |
+
if (!this.checkedZeroWidth && measure.clientHeight > 0) {
|
4439 |
+
if (sWidth == 0) { this.zeroWidthHack() }
|
4440 |
+
this.checkedZeroWidth = true
|
4441 |
+
}
|
4442 |
+
|
4443 |
+
return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0}
|
4444 |
+
};
|
4445 |
+
|
4446 |
+
NativeScrollbars.prototype.setScrollLeft = function (pos) {
|
4447 |
+
if (this.horiz.scrollLeft != pos) { this.horiz.scrollLeft = pos }
|
4448 |
+
if (this.disableHoriz) { this.enableZeroWidthBar(this.horiz, this.disableHoriz) }
|
4449 |
+
};
|
4450 |
+
|
4451 |
+
NativeScrollbars.prototype.setScrollTop = function (pos) {
|
4452 |
+
if (this.vert.scrollTop != pos) { this.vert.scrollTop = pos }
|
4453 |
+
if (this.disableVert) { this.enableZeroWidthBar(this.vert, this.disableVert) }
|
4454 |
+
};
|
4455 |
+
|
4456 |
+
NativeScrollbars.prototype.zeroWidthHack = function () {
|
4457 |
+
var w = mac && !mac_geMountainLion ? "12px" : "18px"
|
4458 |
+
this.horiz.style.height = this.vert.style.width = w
|
4459 |
+
this.horiz.style.pointerEvents = this.vert.style.pointerEvents = "none"
|
4460 |
+
this.disableHoriz = new Delayed
|
4461 |
+
this.disableVert = new Delayed
|
4462 |
+
};
|
4463 |
+
|
4464 |
+
NativeScrollbars.prototype.enableZeroWidthBar = function (bar, delay) {
|
4465 |
+
bar.style.pointerEvents = "auto"
|
4466 |
+
function maybeDisable() {
|
4467 |
+
// To find out whether the scrollbar is still visible, we
|
4468 |
+
// check whether the element under the pixel in the bottom
|
4469 |
+
// left corner of the scrollbar box is the scrollbar box
|
4470 |
+
// itself (when the bar is still visible) or its filler child
|
4471 |
+
// (when the bar is hidden). If it is still visible, we keep
|
4472 |
+
// it enabled, if it's hidden, we disable pointer events.
|
4473 |
+
var box = bar.getBoundingClientRect()
|
4474 |
+
var elt = document.elementFromPoint(box.left + 1, box.bottom - 1)
|
4475 |
+
if (elt != bar) { bar.style.pointerEvents = "none" }
|
4476 |
+
else { delay.set(1000, maybeDisable) }
|
4477 |
+
}
|
4478 |
+
delay.set(1000, maybeDisable)
|
4479 |
+
};
|
4480 |
+
|
4481 |
+
NativeScrollbars.prototype.clear = function () {
|
4482 |
+
var parent = this.horiz.parentNode
|
4483 |
+
parent.removeChild(this.horiz)
|
4484 |
+
parent.removeChild(this.vert)
|
4485 |
+
};
|
4486 |
+
|
4487 |
+
var NullScrollbars = function () {};
|
4488 |
+
|
4489 |
+
NullScrollbars.prototype.update = function () { return {bottom: 0, right: 0} };
|
4490 |
+
NullScrollbars.prototype.setScrollLeft = function () {};
|
4491 |
+
NullScrollbars.prototype.setScrollTop = function () {};
|
4492 |
+
NullScrollbars.prototype.clear = function () {};
|
4493 |
|
4494 |
function updateScrollbars(cm, measure) {
|
4495 |
if (!measure) { measure = measureForScrollbars(cm) }
|
5068 |
|
5069 |
// DISPLAY DRAWING
|
5070 |
|
5071 |
+
var DisplayUpdate = function(cm, viewport, force) {
|
5072 |
var display = cm.display
|
5073 |
|
5074 |
this.viewport = viewport
|
5081 |
this.force = force
|
5082 |
this.dims = getDimensions(cm)
|
5083 |
this.events = []
|
5084 |
+
};
|
5085 |
|
5086 |
+
DisplayUpdate.prototype.signal = function (emitter, type) {
|
5087 |
if (hasHandler(emitter, type))
|
5088 |
{ this.events.push(arguments) }
|
5089 |
+
};
|
5090 |
+
DisplayUpdate.prototype.finish = function () {
|
5091 |
+
var this$1 = this;
|
5092 |
|
5093 |
for (var i = 0; i < this.events.length; i++)
|
5094 |
{ signal.apply(null, this$1.events[i]) }
|
5095 |
+
};
|
5096 |
|
5097 |
function maybeClipScrollbars(cm) {
|
5098 |
var display = cm.display
|
8220 |
for (var i = newBreaks.length - 1; i >= 0; i--)
|
8221 |
{ replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks[i].ch + val.length)) }
|
8222 |
})
|
8223 |
+
option("specialChars", /[\u0000-\u001f\u007f\u00ad\u061c\u200b-\u200f\u2028\u2029\ufeff]/g, function (cm, val, old) {
|
8224 |
cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g")
|
8225 |
if (old != Init) { cm.refresh() }
|
8226 |
})
|
8310 |
function guttersChanged(cm) {
|
8311 |
updateGutters(cm)
|
8312 |
regChange(cm)
|
8313 |
+
alignHorizontally(cm)
|
8314 |
}
|
8315 |
|
8316 |
function dragDropChanged(cm, value, old) {
|
8365 |
themeChanged(this)
|
8366 |
if (options.lineWrapping)
|
8367 |
{ this.display.wrapper.className += " CodeMirror-wrap" }
|
|
|
8368 |
initScrollbars(this)
|
8369 |
|
8370 |
this.state = {
|
8383 |
specialChars: null
|
8384 |
}
|
8385 |
|
8386 |
+
if (options.autofocus && !mobile) { display.input.focus() }
|
8387 |
+
|
8388 |
// Override magic textarea content restore that IE sometimes does
|
8389 |
// on our hidden textarea on reload
|
8390 |
if (ie && ie_version < 11) { setTimeout(function () { return this$1.display.input.reset(true); }, 20) }
|
8740 |
options[option] = value
|
8741 |
if (optionHandlers.hasOwnProperty(option))
|
8742 |
{ operation(this, optionHandlers[option])(this, value, old) }
|
8743 |
+
signal(this, "optionChange", this, option)
|
8744 |
},
|
8745 |
|
8746 |
getOption: function(option) {return this.options[option]},
|
9248 |
|
9249 |
// CONTENTEDITABLE INPUT STYLE
|
9250 |
|
9251 |
+
var ContentEditableInput = function(cm) {
|
9252 |
this.cm = cm
|
9253 |
this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null
|
9254 |
this.polling = new Delayed()
|
9255 |
this.composing = null
|
9256 |
this.gracePeriod = false
|
9257 |
this.readDOMTimeout = null
|
9258 |
+
};
|
9259 |
|
9260 |
+
ContentEditableInput.prototype.init = function (display) {
|
|
|
9261 |
var this$1 = this;
|
9262 |
|
9263 |
+
var input = this, cm = input.cm
|
9264 |
+
var div = input.div = display.lineDiv
|
9265 |
+
disableBrowserMagic(div, cm.options.spellcheck)
|
9266 |
|
9267 |
+
on(div, "paste", function (e) {
|
9268 |
+
if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { return }
|
9269 |
+
// IE doesn't fire input events, so we schedule a read for the pasted content in this way
|
9270 |
+
if (ie_version <= 11) { setTimeout(operation(cm, function () {
|
9271 |
+
if (!input.pollContent()) { regChange(cm) }
|
9272 |
+
}), 20) }
|
9273 |
+
})
|
9274 |
|
9275 |
+
on(div, "compositionstart", function (e) {
|
9276 |
+
this$1.composing = {data: e.data, done: false}
|
9277 |
+
})
|
9278 |
+
on(div, "compositionupdate", function (e) {
|
9279 |
+
if (!this$1.composing) { this$1.composing = {data: e.data, done: false} }
|
9280 |
+
})
|
9281 |
+
on(div, "compositionend", function (e) {
|
9282 |
+
if (this$1.composing) {
|
9283 |
+
if (e.data != this$1.composing.data) { this$1.readFromDOMSoon() }
|
9284 |
+
this$1.composing.done = true
|
9285 |
+
}
|
9286 |
+
})
|
9287 |
|
9288 |
+
on(div, "touchstart", function () { return input.forceCompositionEnd(); })
|
9289 |
|
9290 |
+
on(div, "input", function () {
|
9291 |
+
if (!this$1.composing) { this$1.readFromDOMSoon() }
|
9292 |
+
})
|
9293 |
|
9294 |
+
function onCopyCut(e) {
|
9295 |
+
if (signalDOMEvent(cm, e)) { return }
|
9296 |
+
if (cm.somethingSelected()) {
|
9297 |
+
setLastCopied({lineWise: false, text: cm.getSelections()})
|
9298 |
+
if (e.type == "cut") { cm.replaceSelection("", null, "cut") }
|
9299 |
+
} else if (!cm.options.lineWiseCopyCut) {
|
9300 |
+
return
|
9301 |
+
} else {
|
9302 |
+
var ranges = copyableRanges(cm)
|
9303 |
+
setLastCopied({lineWise: true, text: ranges.text})
|
9304 |
+
if (e.type == "cut") {
|
9305 |
+
cm.operation(function () {
|
9306 |
+
cm.setSelections(ranges.ranges, 0, sel_dontScroll)
|
9307 |
+
cm.replaceSelection("", null, "cut")
|
9308 |
+
})
|
|
|
9309 |
}
|
9310 |
+
}
|
9311 |
+
if (e.clipboardData) {
|
9312 |
+
e.clipboardData.clearData()
|
9313 |
+
var content = lastCopied.text.join("\n")
|
9314 |
+
// iOS exposes the clipboard API, but seems to discard content inserted into it
|
9315 |
+
e.clipboardData.setData("Text", content)
|
9316 |
+
if (e.clipboardData.getData("Text") == content) {
|
9317 |
+
e.preventDefault()
|
9318 |
+
return
|
9319 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9320 |
}
|
9321 |
+
// Old-fashioned briefly-focus-a-textarea hack
|
9322 |
+
var kludge = hiddenTextarea(), te = kludge.firstChild
|
9323 |
+
cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild)
|
9324 |
+
te.value = lastCopied.text.join("\n")
|
9325 |
+
var hadFocus = document.activeElement
|
9326 |
+
selectInput(te)
|
9327 |
+
setTimeout(function () {
|
9328 |
+
cm.display.lineSpace.removeChild(kludge)
|
9329 |
+
hadFocus.focus()
|
9330 |
+
if (hadFocus == div) { input.showPrimarySelection() }
|
9331 |
+
}, 50)
|
9332 |
+
}
|
9333 |
+
on(div, "copy", onCopyCut)
|
9334 |
+
on(div, "cut", onCopyCut)
|
9335 |
+
};
|
9336 |
+
|
9337 |
+
ContentEditableInput.prototype.prepareSelection = function () {
|
9338 |
+
var result = prepareSelection(this.cm, false)
|
9339 |
+
result.focus = this.cm.state.focused
|
9340 |
+
return result
|
9341 |
+
};
|
9342 |
+
|
9343 |
+
ContentEditableInput.prototype.showSelection = function (info, takeFocus) {
|
9344 |
+
if (!info || !this.cm.display.view.length) { return }
|
9345 |
+
if (info.focus || takeFocus) { this.showPrimarySelection() }
|
9346 |
+
this.showMultipleSelections(info)
|
9347 |
+
};
|
9348 |
+
|
9349 |
+
ContentEditableInput.prototype.showPrimarySelection = function () {
|
9350 |
+
var sel = window.getSelection(), prim = this.cm.doc.sel.primary()
|
9351 |
+
var curAnchor = domToPos(this.cm, sel.anchorNode, sel.anchorOffset)
|
9352 |
+
var curFocus = domToPos(this.cm, sel.focusNode, sel.focusOffset)
|
9353 |
+
if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad &&
|
9354 |
+
cmp(minPos(curAnchor, curFocus), prim.from()) == 0 &&
|
9355 |
+
cmp(maxPos(curAnchor, curFocus), prim.to()) == 0)
|
9356 |
+
{ return }
|
9357 |
|
9358 |
+
var start = posToDOM(this.cm, prim.from())
|
9359 |
+
var end = posToDOM(this.cm, prim.to())
|
9360 |
+
if (!start && !end) { return }
|
9361 |
+
|
9362 |
+
var view = this.cm.display.view
|
9363 |
+
var old = sel.rangeCount && sel.getRangeAt(0)
|
9364 |
+
if (!start) {
|
9365 |
+
start = {node: view[0].measure.map[2], offset: 0}
|
9366 |
+
} else if (!end) { // FIXME dangerously hacky
|
9367 |
+
var measure = view[view.length - 1].measure
|
9368 |
+
var map = measure.maps ? measure.maps[measure.maps.length - 1] : measure.map
|
9369 |
+
end = {node: map[map.length - 1], offset: map[map.length - 2] - map[map.length - 3]}
|
9370 |
+
}
|
9371 |
+
|
9372 |
+
var rng
|
9373 |
+
try { rng = range(start.node, start.offset, end.offset, end.node) }
|
9374 |
+
catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible
|
9375 |
+
if (rng) {
|
9376 |
+
if (!gecko && this.cm.state.focused) {
|
9377 |
+
sel.collapse(start.node, start.offset)
|
9378 |
+
if (!rng.collapsed) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9379 |
sel.removeAllRanges()
|
9380 |
sel.addRange(rng)
|
9381 |
}
|
9382 |
+
} else {
|
9383 |
+
sel.removeAllRanges()
|
9384 |
+
sel.addRange(rng)
|
9385 |
}
|
9386 |
+
if (old && sel.anchorNode == null) { sel.addRange(old) }
|
9387 |
+
else if (gecko) { this.startGracePeriod() }
|
9388 |
+
}
|
9389 |
+
this.rememberSelection()
|
9390 |
+
};
|
9391 |
|
9392 |
+
ContentEditableInput.prototype.startGracePeriod = function () {
|
9393 |
var this$1 = this;
|
9394 |
|
9395 |
+
clearTimeout(this.gracePeriod)
|
9396 |
+
this.gracePeriod = setTimeout(function () {
|
9397 |
+
this$1.gracePeriod = false
|
9398 |
+
if (this$1.selectionChanged())
|
9399 |
+
{ this$1.cm.operation(function () { return this$1.cm.curOp.selectionChanged = true; }) }
|
9400 |
+
}, 20)
|
9401 |
+
};
|
9402 |
+
|
9403 |
+
ContentEditableInput.prototype.showMultipleSelections = function (info) {
|
9404 |
+
removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors)
|
9405 |
+
removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection)
|
9406 |
+
};
|
9407 |
+
|
9408 |
+
ContentEditableInput.prototype.rememberSelection = function () {
|
9409 |
+
var sel = window.getSelection()
|
9410 |
+
this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset
|
9411 |
+
this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset
|
9412 |
+
};
|
9413 |
+
|
9414 |
+
ContentEditableInput.prototype.selectionInEditor = function () {
|
9415 |
+
var sel = window.getSelection()
|
9416 |
+
if (!sel.rangeCount) { return false }
|
9417 |
+
var node = sel.getRangeAt(0).commonAncestorContainer
|
9418 |
+
return contains(this.div, node)
|
9419 |
+
};
|
9420 |
+
|
9421 |
+
ContentEditableInput.prototype.focus = function () {
|
9422 |
+
if (this.cm.options.readOnly != "nocursor") {
|
9423 |
+
if (!this.selectionInEditor())
|
9424 |
+
{ this.showSelection(this.prepareSelection(), true) }
|
9425 |
+
this.div.focus()
|
9426 |
+
}
|
9427 |
+
};
|
9428 |
+
ContentEditableInput.prototype.blur = function () { this.div.blur() };
|
9429 |
+
ContentEditableInput.prototype.getField = function () { return this.div };
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9430 |
|
9431 |
+
ContentEditableInput.prototype.supportsTouch = function () { return true };
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9432 |
|
9433 |
+
ContentEditableInput.prototype.receivedFocus = function () {
|
9434 |
+
var input = this
|
9435 |
+
if (this.selectionInEditor())
|
9436 |
+
{ this.pollSelection() }
|
9437 |
+
else
|
9438 |
+
{ runInOp(this.cm, function () { return input.cm.curOp.selectionChanged = true; }) }
|
9439 |
|
9440 |
+
function poll() {
|
9441 |
+
if (input.cm.state.focused) {
|
9442 |
+
input.pollSelection()
|
9443 |
+
input.polling.set(input.cm.options.pollInterval, poll)
|
|
|
|
|
|
|
|
|
|
|
|
|
9444 |
}
|
9445 |
+
}
|
9446 |
+
this.polling.set(this.cm.options.pollInterval, poll)
|
9447 |
+
};
|
9448 |
|
9449 |
+
ContentEditableInput.prototype.selectionChanged = function () {
|
9450 |
+
var sel = window.getSelection()
|
9451 |
+
return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset ||
|
9452 |
+
sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset
|
9453 |
+
};
|
9454 |
|
9455 |
+
ContentEditableInput.prototype.pollSelection = function () {
|
9456 |
+
if (!this.composing && this.readDOMTimeout == null && !this.gracePeriod && this.selectionChanged()) {
|
9457 |
+
var sel = window.getSelection(), cm = this.cm
|
9458 |
+
this.rememberSelection()
|
9459 |
+
var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset)
|
9460 |
+
var head = domToPos(cm, sel.focusNode, sel.focusOffset)
|
9461 |
+
if (anchor && head) { runInOp(cm, function () {
|
9462 |
+
setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll)
|
9463 |
+
if (anchor.bad || head.bad) { cm.curOp.selectionChanged = true }
|
9464 |
+
}) }
|
9465 |
+
}
|
9466 |
+
};
|
9467 |
+
|
9468 |
+
ContentEditableInput.prototype.pollContent = function () {
|
9469 |
+
if (this.readDOMTimeout != null) {
|
9470 |
+
clearTimeout(this.readDOMTimeout)
|
9471 |
+
this.readDOMTimeout = null
|
9472 |
+
}
|
9473 |
+
|
9474 |
+
var cm = this.cm, display = cm.display, sel = cm.doc.sel.primary()
|
9475 |
+
var from = sel.from(), to = sel.to()
|
9476 |
+
if (from.ch == 0 && from.line > cm.firstLine())
|
9477 |
+
{ from = Pos(from.line - 1, getLine(cm.doc, from.line - 1).length) }
|
9478 |
+
if (to.ch == getLine(cm.doc, to.line).text.length && to.line < cm.lastLine())
|
9479 |
+
{ to = Pos(to.line + 1, 0) }
|
9480 |
+
if (from.line < display.viewFrom || to.line > display.viewTo - 1) { return false }
|
9481 |
+
|
9482 |
+
var fromIndex, fromLine, fromNode
|
9483 |
+
if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.line)) == 0) {
|
9484 |
+
fromLine = lineNo(display.view[0].line)
|
9485 |
+
fromNode = display.view[0].node
|
9486 |
+
} else {
|
9487 |
+
fromLine = lineNo(display.view[fromIndex].line)
|
9488 |
+
fromNode = display.view[fromIndex - 1].node.nextSibling
|
9489 |
+
}
|
9490 |
+
var toIndex = findViewIndex(cm, to.line)
|
9491 |
+
var toLine, toNode
|
9492 |
+
if (toIndex == display.view.length - 1) {
|
9493 |
+
toLine = display.viewTo - 1
|
9494 |
+
toNode = display.lineDiv.lastChild
|
9495 |
+
} else {
|
9496 |
+
toLine = lineNo(display.view[toIndex + 1].line) - 1
|
9497 |
+
toNode = display.view[toIndex + 1].node.previousSibling
|
9498 |
+
}
|
9499 |
|
9500 |
+
if (!fromNode) { return false }
|
9501 |
+
var newText = cm.doc.splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine))
|
9502 |
+
var oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc, toLine).text.length))
|
9503 |
+
while (newText.length > 1 && oldText.length > 1) {
|
9504 |
+
if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine-- }
|
9505 |
+
else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); fromLine++ }
|
9506 |
+
else { break }
|
9507 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9508 |
|
9509 |
+
var cutFront = 0, cutEnd = 0
|
9510 |
+
var newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTop.length, oldTop.length)
|
9511 |
+
while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.charCodeAt(cutFront))
|
9512 |
+
{ ++cutFront }
|
9513 |
+
var newBot = lst(newText), oldBot = lst(oldText)
|
9514 |
+
var maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0),
|
9515 |
+
oldBot.length - (oldText.length == 1 ? cutFront : 0))
|
9516 |
+
while (cutEnd < maxCutEnd &&
|
9517 |
+
newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1))
|
9518 |
+
{ ++cutEnd }
|
9519 |
+
|
9520 |
+
newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd).replace(/^\u200b+/, "")
|
9521 |
+
newText[0] = newText[0].slice(cutFront).replace(/\u200b+$/, "")
|
9522 |
+
|
9523 |
+
var chFrom = Pos(fromLine, cutFront)
|
9524 |
+
var chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0)
|
9525 |
+
if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) {
|
9526 |
+
replaceRange(cm.doc, newText, chFrom, chTo, "+input")
|
9527 |
+
return true
|
9528 |
+
}
|
9529 |
+
};
|
9530 |
+
|
9531 |
+
ContentEditableInput.prototype.ensurePolled = function () {
|
9532 |
+
this.forceCompositionEnd()
|
9533 |
+
};
|
9534 |
+
ContentEditableInput.prototype.reset = function () {
|
9535 |
+
this.forceCompositionEnd()
|
9536 |
+
};
|
9537 |
+
ContentEditableInput.prototype.forceCompositionEnd = function () {
|
9538 |
+
if (!this.composing) { return }
|
9539 |
+
clearTimeout(this.readDOMTimeout)
|
9540 |
+
this.composing = null
|
9541 |
+
if (!this.pollContent()) { regChange(this.cm) }
|
9542 |
+
this.div.blur()
|
9543 |
+
this.div.focus()
|
9544 |
+
};
|
9545 |
+
ContentEditableInput.prototype.readFromDOMSoon = function () {
|
9546 |
var this$1 = this;
|
9547 |
|
9548 |
+
if (this.readDOMTimeout != null) { return }
|
9549 |
+
this.readDOMTimeout = setTimeout(function () {
|
9550 |
+
this$1.readDOMTimeout = null
|
9551 |
+
if (this$1.composing) {
|
9552 |
+
if (this$1.composing.done) { this$1.composing = null }
|
9553 |
+
else { return }
|
9554 |
+
}
|
9555 |
+
if (this$1.cm.isReadOnly() || !this$1.pollContent())
|
9556 |
+
{ runInOp(this$1.cm, function () { return regChange(this$1.cm); }) }
|
9557 |
+
}, 80)
|
9558 |
+
};
|
9559 |
|
9560 |
+
ContentEditableInput.prototype.setUneditable = function (node) {
|
9561 |
+
node.contentEditable = "false"
|
9562 |
+
};
|
9563 |
|
9564 |
+
ContentEditableInput.prototype.onKeyPress = function (e) {
|
9565 |
+
e.preventDefault()
|
9566 |
+
if (!this.cm.isReadOnly())
|
9567 |
+
{ operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0) }
|
9568 |
+
};
|
9569 |
|
9570 |
+
ContentEditableInput.prototype.readOnlyChanged = function (val) {
|
9571 |
+
this.div.contentEditable = String(val != "nocursor")
|
9572 |
+
};
|
9573 |
|
9574 |
+
ContentEditableInput.prototype.onContextMenu = function () {};
|
9575 |
+
ContentEditableInput.prototype.resetPosition = function () {};
|
9576 |
|
9577 |
+
ContentEditableInput.prototype.needsContentAttribute = true
|
|
|
9578 |
|
9579 |
function posToDOM(cm, pos) {
|
9580 |
var view = findViewForLine(cm, pos.line)
|
9711 |
|
9712 |
// TEXTAREA INPUT STYLE
|
9713 |
|
9714 |
+
var TextareaInput = function(cm) {
|
9715 |
this.cm = cm
|
9716 |
// See input.poll and input.reset
|
9717 |
this.prevInput = ""
|
9728 |
// Used to work around IE issue with selection being forgotten when focus moves away from textarea
|
9729 |
this.hasSelection = false
|
9730 |
this.composing = null
|
9731 |
+
};
|
9732 |
|
9733 |
+
TextareaInput.prototype.init = function (display) {
|
|
|
9734 |
var this$1 = this;
|
9735 |
|
9736 |
+
var input = this, cm = this.cm
|
9737 |
|
9738 |
+
// Wraps and hides input textarea
|
9739 |
+
var div = this.wrapper = hiddenTextarea()
|
9740 |
+
// The semihidden textarea that is focused when the editor is
|
9741 |
+
// focused, and receives input.
|
9742 |
+
var te = this.textarea = div.firstChild
|
9743 |
+
display.wrapper.insertBefore(div, display.wrapper.firstChild)
|
9744 |
|
9745 |
+
// Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to work in iOS 8 anymore)
|
9746 |
+
if (ios) { te.style.width = "0px" }
|
9747 |
|
9748 |
+
on(te, "input", function () {
|
9749 |
+
if (ie && ie_version >= 9 && this$1.hasSelection) { this$1.hasSelection = null }
|
9750 |
+
input.poll()
|
9751 |
+
})
|
9752 |
|
9753 |
+
on(te, "paste", function (e) {
|
9754 |
+
if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { return }
|
9755 |
|
9756 |
+
cm.state.pasteIncoming = true
|
9757 |
+
input.fastPoll()
|
9758 |
+
})
|
9759 |
|
9760 |
+
function prepareCopyCut(e) {
|
9761 |
+
if (signalDOMEvent(cm, e)) { return }
|
9762 |
+
if (cm.somethingSelected()) {
|
9763 |
+
setLastCopied({lineWise: false, text: cm.getSelections()})
|
9764 |
+
if (input.inaccurateSelection) {
|
9765 |
+
input.prevInput = ""
|
9766 |
+
input.inaccurateSelection = false
|
9767 |
+
te.value = lastCopied.text.join("\n")
|
9768 |
+
selectInput(te)
|
9769 |
+
}
|
9770 |
+
} else if (!cm.options.lineWiseCopyCut) {
|
9771 |
+
return
|
9772 |
+
} else {
|
9773 |
+
var ranges = copyableRanges(cm)
|
9774 |
+
setLastCopied({lineWise: true, text: ranges.text})
|
9775 |
+
if (e.type == "cut") {
|
9776 |
+
cm.setSelections(ranges.ranges, null, sel_dontScroll)
|
9777 |
} else {
|
9778 |
+
input.prevInput = ""
|
9779 |
+
te.value = ranges.text.join("\n")
|
9780 |
+
selectInput(te)
|
|
|
|
|
|
|
|
|
|
|
|
|
9781 |
}
|
|
|
9782 |
}
|
9783 |
+
if (e.type == "cut") { cm.state.cutIncoming = true }
|
9784 |
+
}
|
9785 |
+
on(te, "cut", prepareCopyCut)
|
9786 |
+
on(te, "copy", prepareCopyCut)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9787 |
|
9788 |
+
on(display.scroller, "paste", function (e) {
|
9789 |
+
if (eventInWidget(display, e) || signalDOMEvent(cm, e)) { return }
|
9790 |
+
cm.state.pasteIncoming = true
|
9791 |
+
input.focus()
|
9792 |
+
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9793 |
|
9794 |
+
// Prevent normal selection in the editor (we handle our own)
|
9795 |
+
on(display.lineSpace, "selectstart", function (e) {
|
9796 |
+
if (!eventInWidget(display, e)) { e_preventDefault(e) }
|
9797 |
+
})
|
9798 |
|
9799 |
+
on(te, "compositionstart", function () {
|
9800 |
+
var start = cm.getCursor("from")
|
9801 |
+
if (input.composing) { input.composing.range.clear() }
|
9802 |
+
input.composing = {
|
9803 |
+
start: start,
|
9804 |
+
range: cm.markText(start, cm.getCursor("to"), {className: "CodeMirror-composing"})
|
|
|
|
|
9805 |
}
|
9806 |
+
})
|
9807 |
+
on(te, "compositionend", function () {
|
9808 |
+
if (input.composing) {
|
9809 |
+
input.poll()
|
9810 |
+
input.composing.range.clear()
|
9811 |
+
input.composing = null
|
|
|
|
|
|
|
|
|
|
|
9812 |
}
|
9813 |
+
})
|
9814 |
+
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9815 |
|
9816 |
+
TextareaInput.prototype.prepareSelection = function () {
|
9817 |
+
// Redraw the selection and/or cursor
|
9818 |
+
var cm = this.cm, display = cm.display, doc = cm.doc
|
9819 |
+
var result = prepareSelection(cm)
|
9820 |
|
9821 |
+
// Move the hidden textarea near the cursor to prevent scrolling artifacts
|
9822 |
+
if (cm.options.moveInputWithCursor) {
|
9823 |
+
var headPos = cursorCoords(cm, doc.sel.primary().head, "div")
|
9824 |
+
var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect()
|
9825 |
+
result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10,
|
9826 |
+
headPos.top + lineOff.top - wrapOff.top))
|
9827 |
+
result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10,
|
9828 |
+
headPos.left + lineOff.left - wrapOff.left))
|
9829 |
+
}
|
9830 |
|
9831 |
+
return result
|
9832 |
+
};
|
9833 |
+
|
9834 |
+
TextareaInput.prototype.showSelection = function (drawn) {
|
9835 |
+
var cm = this.cm, display = cm.display
|
9836 |
+
removeChildrenAndAdd(display.cursorDiv, drawn.cursors)
|
9837 |
+
removeChildrenAndAdd(display.selectionDiv, drawn.selection)
|
9838 |
+
if (drawn.teTop != null) {
|
9839 |
+
this.wrapper.style.top = drawn.teTop + "px"
|
9840 |
+
this.wrapper.style.left = drawn.teLeft + "px"
|
9841 |
+
}
|
9842 |
+
};
|
9843 |
+
|
9844 |
+
// Reset the input to correspond to the selection (or to be empty,
|
9845 |
+
// when not typing and nothing is selected)
|
9846 |
+
TextareaInput.prototype.reset = function (typing) {
|
9847 |
+
if (this.contextMenuPending) { return }
|
9848 |
+
var minimal, selected, cm = this.cm, doc = cm.doc
|
9849 |
+
if (cm.somethingSelected()) {
|
9850 |
+
this.prevInput = ""
|
9851 |
+
var range = doc.sel.primary()
|
9852 |
+
minimal = hasCopyEvent &&
|
9853 |
+
(range.to().line - range.from().line > 100 || (selected = cm.getSelection()).length > 1000)
|
9854 |
+
var content = minimal ? "-" : selected || cm.getSelection()
|
9855 |
+
this.textarea.value = content
|
9856 |
+
if (cm.state.focused) { selectInput(this.textarea) }
|
9857 |
+
if (ie && ie_version >= 9) { this.hasSelection = content }
|
9858 |
+
} else if (!typing) {
|
9859 |
+
this.prevInput = this.textarea.value = ""
|
9860 |
+
if (ie && ie_version >= 9) { this.hasSelection = null }
|
9861 |
+
}
|
9862 |
+
this.inaccurateSelection = minimal
|
9863 |
+
};
|
9864 |
|
9865 |
+
TextareaInput.prototype.getField = function () { return this.textarea };
|
9866 |
|
9867 |
+
TextareaInput.prototype.supportsTouch = function () { return false };
|
|
|
|
|
9868 |
|
9869 |
+
TextareaInput.prototype.focus = function () {
|
9870 |
+
if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt() != this.textarea)) {
|
9871 |
+
try { this.textarea.focus() }
|
9872 |
+
catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM
|
9873 |
+
}
|
9874 |
+
};
|
9875 |
|
9876 |
+
TextareaInput.prototype.blur = function () { this.textarea.blur() };
|
|
|
|
|
|
|
9877 |
|
9878 |
+
TextareaInput.prototype.resetPosition = function () {
|
9879 |
+
this.wrapper.style.top = this.wrapper.style.left = 0
|
9880 |
+
};
|
|
|
|
|
|
|
9881 |
|
9882 |
+
TextareaInput.prototype.receivedFocus = function () { this.slowPoll() };
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9883 |
|
9884 |
+
// Poll for input changes, using the normal rate of polling. This
|
9885 |
+
// runs as long as the editor is focused.
|
9886 |
+
TextareaInput.prototype.slowPoll = function () {
|
|
|
|
|
|
|
|
|
9887 |
var this$1 = this;
|
9888 |
|
9889 |
+
if (this.pollingFast) { return }
|
9890 |
+
this.polling.set(this.cm.options.pollInterval, function () {
|
9891 |
+
this$1.poll()
|
9892 |
+
if (this$1.cm.state.focused) { this$1.slowPoll() }
|
9893 |
+
})
|
9894 |
+
};
|
9895 |
+
|
9896 |
+
// When an event has just come in that is likely to add or change
|
9897 |
+
// something in the input textarea, we poll faster, to ensure that
|
9898 |
+
// the change appears on the screen quickly.
|
9899 |
+
TextareaInput.prototype.fastPoll = function () {
|
9900 |
+
var missed = false, input = this
|
9901 |
+
input.pollingFast = true
|
9902 |
+
function p() {
|
9903 |
+
var changed = input.poll()
|
9904 |
+
if (!changed && !missed) {missed = true; input.polling.set(60, p)}
|
9905 |
+
else {input.pollingFast = false; input.slowPoll()}
|
9906 |
+
}
|
9907 |
+
input.polling.set(20, p)
|
9908 |
+
};
|
9909 |
+
|
9910 |
+
// Read input from the textarea, and update the document to match.
|
9911 |
+
// When something is selected, it is present in the textarea, and
|
9912 |
+
// selected (unless it is huge, in which case a placeholder is
|
9913 |
+
// used). When nothing is selected, the cursor sits after previously
|
9914 |
+
// seen text (can be empty), which is stored in prevInput (we must
|
9915 |
+
// not reset the textarea when typing, because that breaks IME).
|
9916 |
+
TextareaInput.prototype.poll = function () {
|
9917 |
+
var this$1 = this;
|
|
|
9918 |
|
9919 |
+
var cm = this.cm, input = this.textarea, prevInput = this.prevInput
|
9920 |
+
// Since this is called a *lot*, try to bail out as cheaply as
|
9921 |
+
// possible when it is clear that nothing happened. hasSelection
|
9922 |
+
// will be the case when there is a lot of text in the textarea,
|
9923 |
+
// in which case reading its value would be expensive.
|
9924 |
+
if (this.contextMenuPending || !cm.state.focused ||
|
9925 |
+
(hasSelection(input) && !prevInput && !this.composing) ||
|
9926 |
+
cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq)
|
9927 |
+
{ return false }
|
9928 |
|
9929 |
+
var text = input.value
|
9930 |
+
// If nothing changed, bail.
|
9931 |
+
if (text == prevInput && !cm.somethingSelected()) { return false }
|
9932 |
+
// Work around nonsensical selection resetting in IE9/10, and
|
9933 |
+
// inexplicable appearance of private area unicode characters on
|
9934 |
+
// some key combos in Mac (#2689).
|
9935 |
+
if (ie && ie_version >= 9 && this.hasSelection === text ||
|
9936 |
+
mac && /[\uf700-\uf7ff]/.test(text)) {
|
9937 |
+
cm.display.input.reset()
|
9938 |
+
return false
|
9939 |
+
}
|
9940 |
|
9941 |
+
if (cm.doc.sel == cm.display.selForContextMenu) {
|
9942 |
+
var first = text.charCodeAt(0)
|
9943 |
+
if (first == 0x200b && !prevInput) { prevInput = "\u200b" }
|
9944 |
+
if (first == 0x21da) { this.reset(); return this.cm.execCommand("undo") }
|
9945 |
+
}
|
9946 |
+
// Find the part of the input that is actually new
|
9947 |
+
var same = 0, l = Math.min(prevInput.length, text.length)
|
9948 |
+
while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) { ++same }
|
9949 |
|
9950 |
+
runInOp(cm, function () {
|
9951 |
+
applyTextInput(cm, text.slice(same), prevInput.length - same,
|
9952 |
+
null, this$1.composing ? "*compose" : null)
|
9953 |
|
9954 |
+
// Don't leave long text in the textarea, since it makes further polling slow
|
9955 |
+
if (text.length > 1000 || text.indexOf("\n") > -1) { input.value = this$1.prevInput = "" }
|
9956 |
+
else { this$1.prevInput = text }
|
|
|
9957 |
|
9958 |
+
if (this$1.composing) {
|
9959 |
+
this$1.composing.range.clear()
|
9960 |
+
this$1.composing.range = cm.markText(this$1.composing.start, cm.getCursor("to"),
|
9961 |
+
{className: "CodeMirror-composing"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9962 |
}
|
9963 |
+
})
|
9964 |
+
return true
|
9965 |
+
};
|
9966 |
+
|
9967 |
+
TextareaInput.prototype.ensurePolled = function () {
|
9968 |
+
if (this.pollingFast && this.poll()) { this.pollingFast = false }
|
9969 |
+
};
|
9970 |
+
|
9971 |
+
TextareaInput.prototype.onKeyPress = function () {
|
9972 |
+
if (ie && ie_version >= 9) { this.hasSelection = null }
|
9973 |
+
this.fastPoll()
|
9974 |
+
};
|
9975 |
+
|
9976 |
+
TextareaInput.prototype.onContextMenu = function (e) {
|
9977 |
+
var input = this, cm = input.cm, display = cm.display, te = input.textarea
|
9978 |
+
var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop
|
9979 |
+
if (!pos || presto) { return } // Opera is difficult.
|
9980 |
+
|
9981 |
+
// Reset the current text selection only if the click is done outside of the selection
|
9982 |
+
// and 'resetSelectionOnContextMenu' option is true.
|
9983 |
+
var reset = cm.options.resetSelectionOnContextMenu
|
9984 |
+
if (reset && cm.doc.sel.contains(pos) == -1)
|
9985 |
+
{ operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll) }
|
9986 |
+
|
9987 |
+
var oldCSS = te.style.cssText, oldWrapperCSS = input.wrapper.style.cssText
|
9988 |
+
input.wrapper.style.cssText = "position: absolute"
|
9989 |
+
var wrapperBox = input.wrapper.getBoundingClientRect()
|
9990 |
+
te.style.cssText = "position: absolute; width: 30px; height: 30px;\n top: " + (e.clientY - wrapperBox.top - 5) + "px; left: " + (e.clientX - wrapperBox.left - 5) + "px;\n z-index: 1000; background: " + (ie ? "rgba(255, 255, 255, .05)" : "transparent") + ";\n outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"
|
9991 |
+
var oldScrollY
|
9992 |
+
if (webkit) { oldScrollY = window.scrollY } // Work around Chrome issue (#2712)
|
9993 |
+
display.input.focus()
|
9994 |
+
if (webkit) { window.scrollTo(null, oldScrollY) }
|
9995 |
+
display.input.reset()
|
9996 |
+
// Adds "Select all" to context menu in FF
|
9997 |
+
if (!cm.somethingSelected()) { te.value = input.prevInput = " " }
|
9998 |
+
input.contextMenuPending = true
|
9999 |
+
display.selForContextMenu = cm.doc.sel
|
10000 |
+
clearTimeout(display.detectingSelectAll)
|
10001 |
+
|
10002 |
+
// Select-all will be greyed out if there's nothing to select, so
|
10003 |
+
// this adds a zero-width space so that we can later check whether
|
10004 |
+
// it got selected.
|
10005 |
+
function prepareSelectAllHack() {
|
10006 |
+
if (te.selectionStart != null) {
|
10007 |
+
var selected = cm.somethingSelected()
|
10008 |
+
var extval = "\u200b" + (selected ? te.value : "")
|
10009 |
+
te.value = "\u21da" // Used to catch context-menu undo
|
10010 |
+
te.value = extval
|
10011 |
+
input.prevInput = selected ? "" : "\u200b"
|
10012 |
+
te.selectionStart = 1; te.selectionEnd = extval.length
|
10013 |
+
// Re-set this, in case some other handler touched the
|
10014 |
+
// selection in the meantime.
|
10015 |
+
display.selForContextMenu = cm.doc.sel
|
10016 |
+
}
|
10017 |
+
}
|
10018 |
+
function rehide() {
|
10019 |
+
input.contextMenuPending = false
|
10020 |
+
input.wrapper.style.cssText = oldWrapperCSS
|
10021 |
+
te.style.cssText = oldCSS
|
10022 |
+
if (ie && ie_version < 9) { display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos) }
|
10023 |
+
|
10024 |
+
// Try to detect the user choosing select-all
|
10025 |
+
if (te.selectionStart != null) {
|
10026 |
+
if (!ie || (ie && ie_version < 9)) { prepareSelectAllHack() }
|
10027 |
+
var i = 0, poll = function () {
|
10028 |
+
if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 &&
|
10029 |
+
te.selectionEnd > 0 && input.prevInput == "\u200b")
|
10030 |
+
{ operation(cm, selectAll)(cm) }
|
10031 |
+
else if (i++ < 10) { display.detectingSelectAll = setTimeout(poll, 500) }
|
10032 |
+
else { display.input.reset() }
|
10033 |
}
|
10034 |
+
display.detectingSelectAll = setTimeout(poll, 200)
|
10035 |
}
|
10036 |
+
}
|
10037 |
|
10038 |
+
if (ie && ie_version >= 9) { prepareSelectAllHack() }
|
10039 |
+
if (captureRightClick) {
|
10040 |
+
e_stop(e)
|
10041 |
+
var mouseup = function () {
|
10042 |
+
off(window, "mouseup", mouseup)
|
10043 |
+
setTimeout(rehide, 20)
|
|
|
|
|
|
|
|
|
10044 |
}
|
10045 |
+
on(window, "mouseup", mouseup)
|
10046 |
+
} else {
|
10047 |
+
setTimeout(rehide, 50)
|
10048 |
+
}
|
10049 |
+
};
|
10050 |
|
10051 |
+
TextareaInput.prototype.readOnlyChanged = function (val) {
|
10052 |
+
if (!val) { this.reset() }
|
10053 |
+
};
|
10054 |
|
10055 |
+
TextareaInput.prototype.setUneditable = function () {};
|
10056 |
|
10057 |
+
TextareaInput.prototype.needsContentAttribute = false
|
|
|
10058 |
|
10059 |
function fromTextArea(textarea, options) {
|
10060 |
options = options ? copyObj(options) : {}
|
10205 |
|
10206 |
addLegacyProps(CodeMirror)
|
10207 |
|
10208 |
+
CodeMirror.version = "5.22.0"
|
10209 |
|
10210 |
return CodeMirror;
|
10211 |
|
10742 |
"transition-property", "transition-timing-function", "unicode-bidi",
|
10743 |
"user-select", "vertical-align", "visibility", "voice-balance", "voice-duration",
|
10744 |
"voice-family", "voice-pitch", "voice-range", "voice-rate", "voice-stress",
|
10745 |
+
"voice-volume", "volume", "white-space", "widows", "width", "will-change", "word-break",
|
10746 |
"word-spacing", "word-wrap", "z-index",
|
10747 |
// SVG-specific
|
10748 |
"clip-path", "clip-rule", "mask", "enable-background", "filter", "flood-color",
|
10816 |
"cell", "center", "checkbox", "circle", "cjk-decimal", "cjk-earthly-branch",
|
10817 |
"cjk-heavenly-stem", "cjk-ideographic", "clear", "clip", "close-quote",
|
10818 |
"col-resize", "collapse", "color", "color-burn", "color-dodge", "column", "column-reverse",
|
10819 |
+
"compact", "condensed", "contain", "content", "contents",
|
10820 |
"content-box", "context-menu", "continuous", "copy", "counter", "counters", "cover", "crop",
|
10821 |
"cross", "crosshair", "currentcolor", "cursive", "cyclic", "darken", "dashed", "decimal",
|
10822 |
"decimal-leading-zero", "default", "default-button", "dense", "destination-atop",
|
10859 |
"mix", "mongolian", "monospace", "move", "multiple", "multiply", "myanmar", "n-resize",
|
10860 |
"narrower", "ne-resize", "nesw-resize", "no-close-quote", "no-drop",
|
10861 |
"no-open-quote", "no-repeat", "none", "normal", "not-allowed", "nowrap",
|
10862 |
+
"ns-resize", "numbers", "numeric", "nw-resize", "nwse-resize", "oblique", "octal", "opacity", "open-quote",
|
10863 |
"optimizeLegibility", "optimizeSpeed", "oriya", "oromo", "outset",
|
10864 |
"outside", "outside-shape", "overlay", "overline", "padding", "padding-box",
|
10865 |
"painted", "page", "paused", "persian", "perspective", "plus-darker", "plus-lighter",
|
10871 |
"rgb", "rgba", "ridge", "right", "rotate", "rotate3d", "rotateX", "rotateY",
|
10872 |
"rotateZ", "round", "row", "row-resize", "row-reverse", "rtl", "run-in", "running",
|
10873 |
"s-resize", "sans-serif", "saturation", "scale", "scale3d", "scaleX", "scaleY", "scaleZ", "screen",
|
10874 |
+
"scroll", "scrollbar", "scroll-position", "se-resize", "searchfield",
|
10875 |
"searchfield-cancel-button", "searchfield-decoration",
|
10876 |
"searchfield-results-button", "searchfield-results-decoration",
|
10877 |
"semi-condensed", "semi-expanded", "separate", "serif", "show", "sidama",
|
10889 |
"thick", "thin", "threeddarkshadow", "threedface", "threedhighlight",
|
10890 |
"threedlightshadow", "threedshadow", "tibetan", "tigre", "tigrinya-er",
|
10891 |
"tigrinya-er-abegede", "tigrinya-et", "tigrinya-et-abegede", "to", "top",
|
10892 |
+
"trad-chinese-formal", "trad-chinese-informal", "transform",
|
10893 |
"translate", "translate3d", "translateX", "translateY", "translateZ",
|
10894 |
+
"transparent", "ultra-condensed", "ultra-expanded", "underline", "unset", "up",
|
10895 |
"upper-alpha", "upper-armenian", "upper-greek", "upper-hexadecimal",
|
10896 |
"upper-latin", "upper-norwegian", "upper-roman", "uppercase", "urdu", "url",
|
10897 |
"var", "vertical", "vertical-text", "visible", "visibleFill", "visiblePainted",
|
assets/js/mailpoet.js
CHANGED
@@ -17048,9 +17048,7 @@ webpackJsonp([2],[
|
|
17048 |
var convertedFormat = [];
|
17049 |
var escapeToken = false;
|
17050 |
|
17051 |
-
for
|
17052 |
-
var token = format[index];
|
17053 |
-
|
17054 |
if (escapeToken === true) {
|
17055 |
convertedFormat.push('['+token+']');
|
17056 |
escapeToken = false;
|
17048 |
var convertedFormat = [];
|
17049 |
var escapeToken = false;
|
17050 |
|
17051 |
+
for(var index = 0, token = ''; token = format.charAt(index); index++){
|
|
|
|
|
17052 |
if (escapeToken === true) {
|
17053 |
convertedFormat.push('['+token+']');
|
17054 |
escapeToken = false;
|
assets/js/newsletter_editor.js
CHANGED
@@ -14624,7 +14624,7 @@ webpackJsonp([3],{
|
|
14624 |
/***/ function(module, exports, __webpack_require__) {
|
14625 |
|
14626 |
/**
|
14627 |
-
* interact.js v1.2.
|
14628 |
*
|
14629 |
* Copyright (c) 2012-2015 Taye Adeyemi <dev@taye.me>
|
14630 |
* Open source under the MIT License.
|
@@ -14869,7 +14869,8 @@ webpackJsonp([3],{
|
|
14869 |
supportsTouch = (('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch),
|
14870 |
|
14871 |
// Does the browser support PointerEvents
|
14872 |
-
|
|
|
14873 |
|
14874 |
// Less Precision with touch input
|
14875 |
margin = supportsTouch || supportsPointerEvent? 20: 10,
|
@@ -16002,14 +16003,14 @@ webpackJsonp([3],{
|
|
16002 |
|
16003 |
this.pointerHover(pointer, event, this.matches, this.matchElements);
|
16004 |
events.add(eventTarget,
|
16005 |
-
|
16006 |
listeners.pointerHover);
|
16007 |
}
|
16008 |
else if (this.target) {
|
16009 |
if (nodeContains(prevTargetElement, eventTarget)) {
|
16010 |
this.pointerHover(pointer, event, this.matches, this.matchElements);
|
16011 |
events.add(this.element,
|
16012 |
-
|
16013 |
listeners.pointerHover);
|
16014 |
}
|
16015 |
else {
|
@@ -16061,7 +16062,7 @@ webpackJsonp([3],{
|
|
16061 |
// Remove temporary event listeners for selector Interactables
|
16062 |
if (!interactables.get(eventTarget)) {
|
16063 |
events.remove(eventTarget,
|
16064 |
-
|
16065 |
listeners.pointerHover);
|
16066 |
}
|
16067 |
|
@@ -16373,7 +16374,7 @@ webpackJsonp([3],{
|
|
16373 |
|
16374 |
// set the startCoords if there was no prepared action
|
16375 |
if (!this.prepared.name) {
|
16376 |
-
this.setEventXY(this.startCoords);
|
16377 |
}
|
16378 |
|
16379 |
this.prepared.name = action.name;
|
@@ -18555,7 +18556,7 @@ webpackJsonp([3],{
|
|
18555 |
|
18556 |
if (isElement(element, _window)) {
|
18557 |
|
18558 |
-
if (
|
18559 |
events.add(this._element, pEventTypes.down, listeners.pointerDown );
|
18560 |
events.add(this._element, pEventTypes.move, listeners.pointerHover);
|
18561 |
}
|
@@ -20400,7 +20401,7 @@ webpackJsonp([3],{
|
|
20400 |
events.add(doc, eventType, delegateUseCapture, true);
|
20401 |
}
|
20402 |
|
20403 |
-
if (
|
20404 |
if (PointerEvent === win.MSPointerEvent) {
|
20405 |
pEventTypes = {
|
20406 |
up: 'MSPointerUp', down: 'MSPointerDown', over: 'mouseover',
|
@@ -28413,19 +28414,25 @@ webpackJsonp([3],{
|
|
28413 |
// send test email
|
28414 |
MailPoet.Modal.loading(true);
|
28415 |
|
28416 |
-
|
28417 |
-
|
28418 |
-
|
28419 |
-
|
28420 |
-
|
28421 |
-
|
28422 |
-
|
28423 |
-
|
28424 |
-
MailPoet.Notice.
|
28425 |
-
|
28426 |
-
{ scroll: true
|
28427 |
-
|
28428 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
28429 |
});
|
28430 |
},
|
28431 |
});
|
@@ -28767,7 +28774,7 @@ webpackJsonp([3],{
|
|
28767 |
App.getChannel().trigger('beforeEditorSave', json);
|
28768 |
|
28769 |
// save newsletter
|
28770 |
-
CommunicationComponent.saveNewsletter(json).done(function(response) {
|
28771 |
if(response.success !== undefined && response.success === true) {
|
28772 |
// TODO: Handle translations
|
28773 |
//MailPoet.Notice.success("<?php _e('Newsletter has been saved.'); ?>");
|
@@ -28793,6 +28800,14 @@ webpackJsonp([3],{
|
|
28793 |
});
|
28794 |
};
|
28795 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
28796 |
Module.getThumbnail = function(element, options) {
|
28797 |
var promise = html2canvas(element, options || {});
|
28798 |
|
@@ -29062,12 +29077,12 @@ webpackJsonp([3],{
|
|
29062 |
};
|
29063 |
|
29064 |
App.on('before:start', function(options) {
|
29065 |
-
App.save = Module.
|
29066 |
App.getChannel().on('autoSave', Module.autoSave);
|
29067 |
|
29068 |
window.onbeforeunload = Module.beforeExitWithUnsavedChanges;
|
29069 |
|
29070 |
-
App.getChannel().on('save', function() { App.save(); });
|
29071 |
});
|
29072 |
|
29073 |
App.on('start', function(options) {
|
14624 |
/***/ function(module, exports, __webpack_require__) {
|
14625 |
|
14626 |
/**
|
14627 |
+
* interact.js v1.2.8
|
14628 |
*
|
14629 |
* Copyright (c) 2012-2015 Taye Adeyemi <dev@taye.me>
|
14630 |
* Open source under the MIT License.
|
14869 |
supportsTouch = (('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch),
|
14870 |
|
14871 |
// Does the browser support PointerEvents
|
14872 |
+
// Avoid PointerEvent bugs introduced in Chrome 55
|
14873 |
+
supportsPointerEvent = PointerEvent && !/Chrome/.test(navigator.userAgent),
|
14874 |
|
14875 |
// Less Precision with touch input
|
14876 |
margin = supportsTouch || supportsPointerEvent? 20: 10,
|
16003 |
|
16004 |
this.pointerHover(pointer, event, this.matches, this.matchElements);
|
16005 |
events.add(eventTarget,
|
16006 |
+
supportsPointerEvent? pEventTypes.move : 'mousemove',
|
16007 |
listeners.pointerHover);
|
16008 |
}
|
16009 |
else if (this.target) {
|
16010 |
if (nodeContains(prevTargetElement, eventTarget)) {
|
16011 |
this.pointerHover(pointer, event, this.matches, this.matchElements);
|
16012 |
events.add(this.element,
|
16013 |
+
supportsPointerEvent? pEventTypes.move : 'mousemove',
|
16014 |
listeners.pointerHover);
|
16015 |
}
|
16016 |
else {
|
16062 |
// Remove temporary event listeners for selector Interactables
|
16063 |
if (!interactables.get(eventTarget)) {
|
16064 |
events.remove(eventTarget,
|
16065 |
+
supportsPointerEvent? pEventTypes.move : 'mousemove',
|
16066 |
listeners.pointerHover);
|
16067 |
}
|
16068 |
|
16374 |
|
16375 |
// set the startCoords if there was no prepared action
|
16376 |
if (!this.prepared.name) {
|
16377 |
+
this.setEventXY(this.startCoords, this.pointers);
|
16378 |
}
|
16379 |
|
16380 |
this.prepared.name = action.name;
|
18556 |
|
18557 |
if (isElement(element, _window)) {
|
18558 |
|
18559 |
+
if (supportsPointerEvent) {
|
18560 |
events.add(this._element, pEventTypes.down, listeners.pointerDown );
|
18561 |
events.add(this._element, pEventTypes.move, listeners.pointerHover);
|
18562 |
}
|
20401 |
events.add(doc, eventType, delegateUseCapture, true);
|
20402 |
}
|
20403 |
|
20404 |
+
if (supportsPointerEvent) {
|
20405 |
if (PointerEvent === win.MSPointerEvent) {
|
20406 |
pEventTypes = {
|
20407 |
up: 'MSPointerUp', down: 'MSPointerDown', over: 'mouseover',
|
28414 |
// send test email
|
28415 |
MailPoet.Modal.loading(true);
|
28416 |
|
28417 |
+
// save before sending
|
28418 |
+
var saveResult = {promise: null};
|
28419 |
+
App.getChannel().trigger('save', saveResult);
|
28420 |
+
|
28421 |
+
saveResult.promise.always(function() {
|
28422 |
+
CommunicationComponent.previewNewsletter(data).always(function() {
|
28423 |
+
MailPoet.Modal.loading(false);
|
28424 |
+
}).done(function(response) {
|
28425 |
+
MailPoet.Notice.success(
|
28426 |
+
MailPoet.I18n.t('newsletterPreviewSent'),
|
28427 |
+
{ scroll: true });
|
28428 |
+
}).fail(function(response) {
|
28429 |
+
if (response.errors.length > 0) {
|
28430 |
+
MailPoet.Notice.error(
|
28431 |
+
response.errors.map(function(error) { return error.message; }),
|
28432 |
+
{ scroll: true, static: true }
|
28433 |
+
);
|
28434 |
+
}
|
28435 |
+
});
|
28436 |
});
|
28437 |
},
|
28438 |
});
|
28774 |
App.getChannel().trigger('beforeEditorSave', json);
|
28775 |
|
28776 |
// save newsletter
|
28777 |
+
return CommunicationComponent.saveNewsletter(json).done(function(response) {
|
28778 |
if(response.success !== undefined && response.success === true) {
|
28779 |
// TODO: Handle translations
|
28780 |
//MailPoet.Notice.success("<?php _e('Newsletter has been saved.'); ?>");
|
28800 |
});
|
28801 |
};
|
28802 |
|
28803 |
+
// For getting a promise after triggering save event
|
28804 |
+
Module.saveAndProvidePromise = function(saveResult) {
|
28805 |
+
var promise = Module.save();
|
28806 |
+
if (saveResult !== undefined) {
|
28807 |
+
saveResult.promise = promise;
|
28808 |
+
}
|
28809 |
+
};
|
28810 |
+
|
28811 |
Module.getThumbnail = function(element, options) {
|
28812 |
var promise = html2canvas(element, options || {});
|
28813 |
|
29077 |
};
|
29078 |
|
29079 |
App.on('before:start', function(options) {
|
29080 |
+
App.save = Module.saveAndProvidePromise;
|
29081 |
App.getChannel().on('autoSave', Module.autoSave);
|
29082 |
|
29083 |
window.onbeforeunload = Module.beforeExitWithUnsavedChanges;
|
29084 |
|
29085 |
+
App.getChannel().on('save', function(saveResult) { App.save(saveResult); });
|
29086 |
});
|
29087 |
|
29088 |
App.on('start', function(options) {
|
lang/index.php
CHANGED
@@ -1,3 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
// Silence is golden
|
|
|
|
|
|
lang/mailpoet.pot
CHANGED
@@ -4,7 +4,7 @@ msgid ""
|
|
4 |
msgstr ""
|
5 |
"Project-Id-Version: \n"
|
6 |
"Report-Msgid-Bugs-To: http://support.mailpoet.com/\n"
|
7 |
-
"POT-Creation-Date: 2016-12-
|
8 |
"MIME-Version: 1.0\n"
|
9 |
"Content-Type: text/plain; charset=utf-8\n"
|
10 |
"Content-Transfer-Encoding: 8bit\n"
|
@@ -79,7 +79,7 @@ msgstr ""
|
|
79 |
msgid "Copy of %s"
|
80 |
msgstr ""
|
81 |
|
82 |
-
#: lib/API/Endpoints/Mailer.php:26 lib/API/Endpoints/Newsletters.php:
|
83 |
msgid "The email could not be sent: %s"
|
84 |
msgstr ""
|
85 |
|
@@ -91,7 +91,7 @@ msgstr ""
|
|
91 |
#: lib/API/Endpoints/Newsletters.php:28 lib/API/Endpoints/Newsletters.php:117
|
92 |
#: lib/API/Endpoints/Newsletters.php:138 lib/API/Endpoints/Newsletters.php:154
|
93 |
#: lib/API/Endpoints/Newsletters.php:170 lib/API/Endpoints/Newsletters.php:184
|
94 |
-
#: lib/API/Endpoints/Newsletters.php:216 lib/API/Endpoints/Newsletters.php:
|
95 |
#: lib/API/Endpoints/SendingQueue.php:32 lib/API/Endpoints/SendingQueue.php:122
|
96 |
#: lib/API/Endpoints/SendingQueue.php:148
|
97 |
msgid "This newsletter does not exist."
|
@@ -105,7 +105,7 @@ msgstr ""
|
|
105 |
msgid "Newsletter data is missing."
|
106 |
msgstr ""
|
107 |
|
108 |
-
#: lib/API/Endpoints/Newsletters.php:
|
109 |
msgid "Please specify receiver information."
|
110 |
msgstr ""
|
111 |
|
@@ -959,11 +959,11 @@ msgid ""
|
|
959 |
"fix this issue."
|
960 |
msgstr ""
|
961 |
|
962 |
-
#: lib/Config/Shortcodes.php:
|
963 |
msgid "Oops! There are no newsletters to display."
|
964 |
msgstr ""
|
965 |
|
966 |
-
#: lib/Config/Shortcodes.php:
|
967 |
msgid "Preview in a new tab"
|
968 |
msgstr ""
|
969 |
|
@@ -1132,15 +1132,15 @@ msgstr ""
|
|
1132 |
msgid "Create a new form"
|
1133 |
msgstr ""
|
1134 |
|
1135 |
-
#: lib/Mailer/Mailer.php:
|
1136 |
msgid "Mailing method does not exist"
|
1137 |
msgstr ""
|
1138 |
|
1139 |
-
#: lib/Mailer/Mailer.php:
|
1140 |
msgid "Mailer is not configured"
|
1141 |
msgstr ""
|
1142 |
|
1143 |
-
#: lib/Mailer/Mailer.php:
|
1144 |
msgid "Sender name and email are not configured"
|
1145 |
msgstr ""
|
1146 |
|
@@ -1156,12 +1156,12 @@ msgstr ""
|
|
1156 |
msgid "Sending frequency limit has been reached."
|
1157 |
msgstr ""
|
1158 |
|
1159 |
-
#: lib/Mailer/Methods/AmazonSES.php:
|
1160 |
msgid "Unsupported Amazon SES region."
|
1161 |
msgstr ""
|
1162 |
|
1163 |
-
#: lib/Mailer/Methods/AmazonSES.php:
|
1164 |
-
#: lib/Mailer/Methods/SMTP.php:
|
1165 |
msgid "%s has returned an unknown error."
|
1166 |
msgstr ""
|
1167 |
|
@@ -1173,18 +1173,18 @@ msgstr ""
|
|
1173 |
msgid "Please specify a name"
|
1174 |
msgstr ""
|
1175 |
|
1176 |
-
#: lib/Models/CustomField.php:17 lib/Models/Newsletter.php:
|
1177 |
#: views/form/templates/settings/field_form.hbs:16
|
1178 |
msgid "Please specify a type"
|
1179 |
msgstr ""
|
1180 |
|
1181 |
-
#: lib/Models/Form.php:50 lib/Models/Newsletter.php:
|
1182 |
-
#: lib/Models/Segment.php:126 lib/Models/Subscriber.php:
|
1183 |
msgid "All"
|
1184 |
msgstr ""
|
1185 |
|
1186 |
-
#: lib/Models/Form.php:55 lib/Models/Newsletter.php:
|
1187 |
-
#: lib/Models/Segment.php:131 lib/Models/Subscriber.php:
|
1188 |
#: views/newsletters.html:75 views/segments.html:50
|
1189 |
#: views/subscribers/subscribers.html:34
|
1190 |
msgid "Trash"
|
@@ -1194,34 +1194,38 @@ msgstr ""
|
|
1194 |
msgid "Another record already exists. Please specify a different \"%1$s\"."
|
1195 |
msgstr ""
|
1196 |
|
1197 |
-
#: lib/Models/Newsletter.php:
|
|
|
|
|
|
|
|
|
1198 |
#: lib/Subscribers/ImportExport/Export/Export.php:170
|
1199 |
msgid "All Lists"
|
1200 |
msgstr ""
|
1201 |
|
1202 |
-
#: lib/Models/Newsletter.php:
|
1203 |
#: views/newsletter/templates/blocks/posts/settingsSelection.hbs:12
|
1204 |
msgid "Draft"
|
1205 |
msgstr ""
|
1206 |
|
1207 |
-
#: lib/Models/Newsletter.php:
|
1208 |
#: views/newsletter/templates/blocks/posts/settingsSelection.hbs:11
|
1209 |
msgid "Scheduled"
|
1210 |
msgstr ""
|
1211 |
|
1212 |
-
#: lib/Models/Newsletter.php:
|
1213 |
msgid "Sending"
|
1214 |
msgstr ""
|
1215 |
|
1216 |
-
#: lib/Models/Newsletter.php:
|
1217 |
msgid "Sent"
|
1218 |
msgstr ""
|
1219 |
|
1220 |
-
#: lib/Models/Newsletter.php:
|
1221 |
msgid "Active"
|
1222 |
msgstr ""
|
1223 |
|
1224 |
-
#: lib/Models/Newsletter.php:
|
1225 |
msgid "Not active"
|
1226 |
msgstr ""
|
1227 |
|
@@ -1273,30 +1277,30 @@ msgstr ""
|
|
1273 |
msgid "Your email address is invalid!"
|
1274 |
msgstr ""
|
1275 |
|
1276 |
-
#: lib/Models/Subscriber.php:
|
1277 |
msgid "You need to wait before subscribing again."
|
1278 |
msgstr ""
|
1279 |
|
1280 |
-
#: lib/Models/Subscriber.php:
|
1281 |
msgid "Subscribers without a list (%s)"
|
1282 |
msgstr ""
|
1283 |
|
1284 |
-
#: lib/Models/Subscriber.php:
|
1285 |
#: views/segments.html:30 views/subscribers/subscribers.html:51
|
1286 |
msgid "Subscribed"
|
1287 |
msgstr ""
|
1288 |
|
1289 |
-
#: lib/Models/Subscriber.php:
|
1290 |
#: views/subscribers/subscribers.html:50
|
1291 |
msgid "Unconfirmed"
|
1292 |
msgstr ""
|
1293 |
|
1294 |
-
#: lib/Models/Subscriber.php:
|
1295 |
#: views/segments.html:32 views/subscribers/subscribers.html:52
|
1296 |
msgid "Unsubscribed"
|
1297 |
msgstr ""
|
1298 |
|
1299 |
-
#: lib/Models/Subscriber.php:
|
1300 |
#: views/segments.html:33 views/subscribers/subscribers.html:53
|
1301 |
msgid "Bounced"
|
1302 |
msgstr ""
|
@@ -1305,17 +1309,17 @@ msgstr ""
|
|
1305 |
msgid "Click here to view media."
|
1306 |
msgstr ""
|
1307 |
|
1308 |
-
#: lib/Newsletter/Shortcodes/Categories/Link.php:
|
1309 |
#: views/newsletter/editor.html:1037
|
1310 |
msgid "Unsubscribe"
|
1311 |
msgstr ""
|
1312 |
|
1313 |
-
#: lib/Newsletter/Shortcodes/Categories/Link.php:
|
1314 |
#: views/newsletter/editor.html:1037
|
1315 |
msgid "Manage subscription"
|
1316 |
msgstr ""
|
1317 |
|
1318 |
-
#: lib/Newsletter/Shortcodes/Categories/Link.php:
|
1319 |
msgid "View in your browser"
|
1320 |
msgstr ""
|
1321 |
|
@@ -1546,7 +1550,7 @@ msgstr ""
|
|
1546 |
msgid "You are now subscribed!"
|
1547 |
msgstr ""
|
1548 |
|
1549 |
-
#: lib/Subscription/Pages.php:177 lib/Subscription/Pages.php:
|
1550 |
msgid "Manage your subscription"
|
1551 |
msgstr ""
|
1552 |
|
@@ -1558,31 +1562,31 @@ msgstr ""
|
|
1558 |
msgid "Yup, we've added you to our email list. You'll hear from us shortly."
|
1559 |
msgstr ""
|
1560 |
|
1561 |
-
#: lib/Subscription/Pages.php:
|
1562 |
msgid "Your lists"
|
1563 |
msgstr ""
|
1564 |
|
1565 |
-
#: lib/Subscription/Pages.php:
|
1566 |
#: views/form/editor.html:383 views/newsletter/templates/components/save.hbs:3
|
1567 |
#: views/segments.html:55 views/subscribers/subscribers.html:91
|
1568 |
msgid "Save"
|
1569 |
msgstr ""
|
1570 |
|
1571 |
-
#: lib/Subscription/Pages.php:
|
1572 |
msgid "[link]Edit your profile[/link] to update your email."
|
1573 |
msgstr ""
|
1574 |
|
1575 |
-
#: lib/Subscription/Pages.php:
|
1576 |
msgid "[link]Log in to your account[/link] to update your email."
|
1577 |
msgstr ""
|
1578 |
|
1579 |
-
#: lib/Subscription/Pages.php:
|
1580 |
msgid ""
|
1581 |
"Need to change your email address? Unsubscribe here, then simply sign up "
|
1582 |
"again."
|
1583 |
msgstr ""
|
1584 |
|
1585 |
-
#: lib/Subscription/Pages.php:
|
1586 |
msgid "Accidentally unsubscribed?"
|
1587 |
msgstr ""
|
1588 |
|
4 |
msgstr ""
|
5 |
"Project-Id-Version: \n"
|
6 |
"Report-Msgid-Bugs-To: http://support.mailpoet.com/\n"
|
7 |
+
"POT-Creation-Date: 2016-12-27 22:02:35+00:00\n"
|
8 |
"MIME-Version: 1.0\n"
|
9 |
"Content-Type: text/plain; charset=utf-8\n"
|
10 |
"Content-Transfer-Encoding: 8bit\n"
|
79 |
msgid "Copy of %s"
|
80 |
msgstr ""
|
81 |
|
82 |
+
#: lib/API/Endpoints/Mailer.php:26 lib/API/Endpoints/Newsletters.php:285
|
83 |
msgid "The email could not be sent: %s"
|
84 |
msgstr ""
|
85 |
|
91 |
#: lib/API/Endpoints/Newsletters.php:28 lib/API/Endpoints/Newsletters.php:117
|
92 |
#: lib/API/Endpoints/Newsletters.php:138 lib/API/Endpoints/Newsletters.php:154
|
93 |
#: lib/API/Endpoints/Newsletters.php:170 lib/API/Endpoints/Newsletters.php:184
|
94 |
+
#: lib/API/Endpoints/Newsletters.php:216 lib/API/Endpoints/Newsletters.php:247
|
95 |
#: lib/API/Endpoints/SendingQueue.php:32 lib/API/Endpoints/SendingQueue.php:122
|
96 |
#: lib/API/Endpoints/SendingQueue.php:148
|
97 |
msgid "This newsletter does not exist."
|
105 |
msgid "Newsletter data is missing."
|
106 |
msgstr ""
|
107 |
|
108 |
+
#: lib/API/Endpoints/Newsletters.php:238
|
109 |
msgid "Please specify receiver information."
|
110 |
msgstr ""
|
111 |
|
959 |
"fix this issue."
|
960 |
msgstr ""
|
961 |
|
962 |
+
#: lib/Config/Shortcodes.php:85
|
963 |
msgid "Oops! There are no newsletters to display."
|
964 |
msgstr ""
|
965 |
|
966 |
+
#: lib/Config/Shortcodes.php:125
|
967 |
msgid "Preview in a new tab"
|
968 |
msgstr ""
|
969 |
|
1132 |
msgid "Create a new form"
|
1133 |
msgstr ""
|
1134 |
|
1135 |
+
#: lib/Mailer/Mailer.php:83
|
1136 |
msgid "Mailing method does not exist"
|
1137 |
msgstr ""
|
1138 |
|
1139 |
+
#: lib/Mailer/Mailer.php:91
|
1140 |
msgid "Mailer is not configured"
|
1141 |
msgstr ""
|
1142 |
|
1143 |
+
#: lib/Mailer/Mailer.php:108
|
1144 |
msgid "Sender name and email are not configured"
|
1145 |
msgstr ""
|
1146 |
|
1156 |
msgid "Sending frequency limit has been reached."
|
1157 |
msgstr ""
|
1158 |
|
1159 |
+
#: lib/Mailer/Methods/AmazonSES.php:34
|
1160 |
msgid "Unsupported Amazon SES region."
|
1161 |
msgstr ""
|
1162 |
|
1163 |
+
#: lib/Mailer/Methods/AmazonSES.php:63 lib/Mailer/Methods/PHPMail.php:33
|
1164 |
+
#: lib/Mailer/Methods/SMTP.php:47 lib/Mailer/Methods/SendGrid.php:32
|
1165 |
msgid "%s has returned an unknown error."
|
1166 |
msgstr ""
|
1167 |
|
1173 |
msgid "Please specify a name"
|
1174 |
msgstr ""
|
1175 |
|
1176 |
+
#: lib/Models/CustomField.php:17 lib/Models/Newsletter.php:27
|
1177 |
#: views/form/templates/settings/field_form.hbs:16
|
1178 |
msgid "Please specify a type"
|
1179 |
msgstr ""
|
1180 |
|
1181 |
+
#: lib/Models/Form.php:50 lib/Models/Newsletter.php:459
|
1182 |
+
#: lib/Models/Segment.php:126 lib/Models/Subscriber.php:332
|
1183 |
msgid "All"
|
1184 |
msgstr ""
|
1185 |
|
1186 |
+
#: lib/Models/Form.php:55 lib/Models/Newsletter.php:529
|
1187 |
+
#: lib/Models/Segment.php:131 lib/Models/Subscriber.php:357 views/forms.html:56
|
1188 |
#: views/newsletters.html:75 views/segments.html:50
|
1189 |
#: views/subscribers/subscribers.html:34
|
1190 |
msgid "Trash"
|
1194 |
msgid "Another record already exists. Please specify a different \"%1$s\"."
|
1195 |
msgstr ""
|
1196 |
|
1197 |
+
#: lib/Models/Newsletter.php:229
|
1198 |
+
msgid "Deleted list"
|
1199 |
+
msgstr ""
|
1200 |
+
|
1201 |
+
#: lib/Models/Newsletter.php:360 lib/Models/Subscriber.php:270
|
1202 |
#: lib/Subscribers/ImportExport/Export/Export.php:170
|
1203 |
msgid "All Lists"
|
1204 |
msgstr ""
|
1205 |
|
1206 |
+
#: lib/Models/Newsletter.php:471
|
1207 |
#: views/newsletter/templates/blocks/posts/settingsSelection.hbs:12
|
1208 |
msgid "Draft"
|
1209 |
msgstr ""
|
1210 |
|
1211 |
+
#: lib/Models/Newsletter.php:479
|
1212 |
#: views/newsletter/templates/blocks/posts/settingsSelection.hbs:11
|
1213 |
msgid "Scheduled"
|
1214 |
msgstr ""
|
1215 |
|
1216 |
+
#: lib/Models/Newsletter.php:487
|
1217 |
msgid "Sending"
|
1218 |
msgstr ""
|
1219 |
|
1220 |
+
#: lib/Models/Newsletter.php:495
|
1221 |
msgid "Sent"
|
1222 |
msgstr ""
|
1223 |
|
1224 |
+
#: lib/Models/Newsletter.php:509 views/newsletters.html:82
|
1225 |
msgid "Active"
|
1226 |
msgstr ""
|
1227 |
|
1228 |
+
#: lib/Models/Newsletter.php:517
|
1229 |
msgid "Not active"
|
1230 |
msgstr ""
|
1231 |
|
1277 |
msgid "Your email address is invalid!"
|
1278 |
msgstr ""
|
1279 |
|
1280 |
+
#: lib/Models/Subscriber.php:207
|
1281 |
msgid "You need to wait before subscribing again."
|
1282 |
msgstr ""
|
1283 |
|
1284 |
+
#: lib/Models/Subscriber.php:276
|
1285 |
msgid "Subscribers without a list (%s)"
|
1286 |
msgstr ""
|
1287 |
|
1288 |
+
#: lib/Models/Subscriber.php:337 lib/Subscription/Pages.php:280
|
1289 |
#: views/segments.html:30 views/subscribers/subscribers.html:51
|
1290 |
msgid "Subscribed"
|
1291 |
msgstr ""
|
1292 |
|
1293 |
+
#: lib/Models/Subscriber.php:342 views/segments.html:31
|
1294 |
#: views/subscribers/subscribers.html:50
|
1295 |
msgid "Unconfirmed"
|
1296 |
msgstr ""
|
1297 |
|
1298 |
+
#: lib/Models/Subscriber.php:347 lib/Subscription/Pages.php:288
|
1299 |
#: views/segments.html:32 views/subscribers/subscribers.html:52
|
1300 |
msgid "Unsubscribed"
|
1301 |
msgstr ""
|
1302 |
|
1303 |
+
#: lib/Models/Subscriber.php:352 lib/Subscription/Pages.php:296
|
1304 |
#: views/segments.html:33 views/subscribers/subscribers.html:53
|
1305 |
msgid "Bounced"
|
1306 |
msgstr ""
|
1309 |
msgid "Click here to view media."
|
1310 |
msgstr ""
|
1311 |
|
1312 |
+
#: lib/Newsletter/Shortcodes/Categories/Link.php:31
|
1313 |
#: views/newsletter/editor.html:1037
|
1314 |
msgid "Unsubscribe"
|
1315 |
msgstr ""
|
1316 |
|
1317 |
+
#: lib/Newsletter/Shortcodes/Categories/Link.php:52
|
1318 |
#: views/newsletter/editor.html:1037
|
1319 |
msgid "Manage subscription"
|
1320 |
msgstr ""
|
1321 |
|
1322 |
+
#: lib/Newsletter/Shortcodes/Categories/Link.php:76
|
1323 |
msgid "View in your browser"
|
1324 |
msgstr ""
|
1325 |
|
1550 |
msgid "You are now subscribed!"
|
1551 |
msgstr ""
|
1552 |
|
1553 |
+
#: lib/Subscription/Pages.php:177 lib/Subscription/Pages.php:395
|
1554 |
msgid "Manage your subscription"
|
1555 |
msgstr ""
|
1556 |
|
1562 |
msgid "Yup, we've added you to our email list. You'll hear from us shortly."
|
1563 |
msgstr ""
|
1564 |
|
1565 |
+
#: lib/Subscription/Pages.php:319
|
1566 |
msgid "Your lists"
|
1567 |
msgstr ""
|
1568 |
|
1569 |
+
#: lib/Subscription/Pages.php:327 views/form/editor.html:29
|
1570 |
#: views/form/editor.html:383 views/newsletter/templates/components/save.hbs:3
|
1571 |
#: views/segments.html:55 views/subscribers/subscribers.html:91
|
1572 |
msgid "Save"
|
1573 |
msgstr ""
|
1574 |
|
1575 |
+
#: lib/Subscription/Pages.php:359
|
1576 |
msgid "[link]Edit your profile[/link] to update your email."
|
1577 |
msgstr ""
|
1578 |
|
1579 |
+
#: lib/Subscription/Pages.php:365
|
1580 |
msgid "[link]Log in to your account[/link] to update your email."
|
1581 |
msgstr ""
|
1582 |
|
1583 |
+
#: lib/Subscription/Pages.php:369
|
1584 |
msgid ""
|
1585 |
"Need to change your email address? Unsubscribe here, then simply sign up "
|
1586 |
"again."
|
1587 |
msgstr ""
|
1588 |
|
1589 |
+
#: lib/Subscription/Pages.php:383
|
1590 |
msgid "Accidentally unsubscribed?"
|
1591 |
msgstr ""
|
1592 |
|
lib/API/Endpoints/Newsletters.php
CHANGED
@@ -220,7 +220,9 @@ class Newsletters extends APIEndpoint {
|
|
220 |
$newsletter->save();
|
221 |
$subscriber = Subscriber::getCurrentWPUser();
|
222 |
$preview_url = NewsletterUrl::getViewInBrowserUrl(
|
223 |
-
|
|
|
|
|
224 |
);
|
225 |
|
226 |
return $this->successResponse(
|
@@ -310,7 +312,7 @@ class Newsletters extends APIEndpoint {
|
|
310 |
|
311 |
if($newsletter->type === Newsletter::TYPE_STANDARD) {
|
312 |
$newsletter
|
313 |
-
->withSegments()
|
314 |
->withSendingQueue()
|
315 |
->withStatistics();
|
316 |
} else if($newsletter->type === Newsletter::TYPE_WELCOME) {
|
@@ -321,11 +323,11 @@ class Newsletters extends APIEndpoint {
|
|
321 |
} else if($newsletter->type === Newsletter::TYPE_NOTIFICATION) {
|
322 |
$newsletter
|
323 |
->withOptions()
|
324 |
-
->withSegments()
|
325 |
->withChildrenCount();
|
326 |
} else if($newsletter->type === Newsletter::TYPE_NOTIFICATION_HISTORY) {
|
327 |
$newsletter
|
328 |
-
->withSegments()
|
329 |
->withSendingQueue()
|
330 |
->withStatistics();
|
331 |
}
|
@@ -339,7 +341,11 @@ class Newsletters extends APIEndpoint {
|
|
339 |
// get preview url
|
340 |
$subscriber = Subscriber::getCurrentWPUser();
|
341 |
$newsletter->preview_url = NewsletterUrl::getViewInBrowserUrl(
|
342 |
-
|
|
|
|
|
|
|
|
|
343 |
|
344 |
$data[] = $newsletter->asArray();
|
345 |
}
|
220 |
$newsletter->save();
|
221 |
$subscriber = Subscriber::getCurrentWPUser();
|
222 |
$preview_url = NewsletterUrl::getViewInBrowserUrl(
|
223 |
+
NewsletterUrl::TYPE_LISTING_EDITOR,
|
224 |
+
$newsletter,
|
225 |
+
$subscriber
|
226 |
);
|
227 |
|
228 |
return $this->successResponse(
|
312 |
|
313 |
if($newsletter->type === Newsletter::TYPE_STANDARD) {
|
314 |
$newsletter
|
315 |
+
->withSegments(true)
|
316 |
->withSendingQueue()
|
317 |
->withStatistics();
|
318 |
} else if($newsletter->type === Newsletter::TYPE_WELCOME) {
|
323 |
} else if($newsletter->type === Newsletter::TYPE_NOTIFICATION) {
|
324 |
$newsletter
|
325 |
->withOptions()
|
326 |
+
->withSegments(true)
|
327 |
->withChildrenCount();
|
328 |
} else if($newsletter->type === Newsletter::TYPE_NOTIFICATION_HISTORY) {
|
329 |
$newsletter
|
330 |
+
->withSegments(true)
|
331 |
->withSendingQueue()
|
332 |
->withStatistics();
|
333 |
}
|
341 |
// get preview url
|
342 |
$subscriber = Subscriber::getCurrentWPUser();
|
343 |
$newsletter->preview_url = NewsletterUrl::getViewInBrowserUrl(
|
344 |
+
NewsletterUrl::TYPE_LISTING_EDITOR,
|
345 |
+
$newsletter,
|
346 |
+
$subscriber,
|
347 |
+
$queue
|
348 |
+
);
|
349 |
|
350 |
$data[] = $newsletter->asArray();
|
351 |
}
|
lib/Config/Env.php
CHANGED
@@ -29,6 +29,7 @@ class Env {
|
|
29 |
static $db_password;
|
30 |
static $db_charset;
|
31 |
static $db_timezone_offset;
|
|
|
32 |
|
33 |
static function init($file, $version) {
|
34 |
global $wpdb;
|
29 |
static $db_password;
|
30 |
static $db_charset;
|
31 |
static $db_timezone_offset;
|
32 |
+
static $required_permission = 'manage_options';
|
33 |
|
34 |
static function init($file, $version) {
|
35 |
global $wpdb;
|
lib/Config/Initializer.php
CHANGED
@@ -53,8 +53,7 @@ class Initializer {
|
|
53 |
\ORM::configure('logging', WP_DEBUG);
|
54 |
\ORM::configure('driver_options', array(
|
55 |
\PDO::MYSQL_ATTR_INIT_COMMAND =>
|
56 |
-
'SET
|
57 |
-
'TIME_ZONE = "' . Env::$db_timezone_offset . '", ' .
|
58 |
'sql_mode=(SELECT REPLACE(@@sql_mode,"ONLY_FULL_GROUP_BY",""))'
|
59 |
));
|
60 |
|
53 |
\ORM::configure('logging', WP_DEBUG);
|
54 |
\ORM::configure('driver_options', array(
|
55 |
\PDO::MYSQL_ATTR_INIT_COMMAND =>
|
56 |
+
'SET TIME_ZONE = "' . Env::$db_timezone_offset . '", ' .
|
|
|
57 |
'sql_mode=(SELECT REPLACE(@@sql_mode,"ONLY_FULL_GROUP_BY",""))'
|
58 |
));
|
59 |
|
lib/Config/Menu.php
CHANGED
@@ -45,7 +45,7 @@ class Menu {
|
|
45 |
add_menu_page(
|
46 |
'MailPoet',
|
47 |
'MailPoet',
|
48 |
-
|
49 |
$main_page_slug,
|
50 |
null,
|
51 |
$this->assets_url . '/img/menu_icon.png',
|
@@ -56,7 +56,7 @@ class Menu {
|
|
56 |
$main_page_slug,
|
57 |
$this->setPageTitle(__('Emails', 'mailpoet')),
|
58 |
__('Emails', 'mailpoet'),
|
59 |
-
|
60 |
$main_page_slug,
|
61 |
array($this, 'newsletters')
|
62 |
);
|
@@ -76,7 +76,7 @@ class Menu {
|
|
76 |
$main_page_slug,
|
77 |
$this->setPageTitle(__('Forms', 'mailpoet')),
|
78 |
__('Forms', 'mailpoet'),
|
79 |
-
|
80 |
'mailpoet-forms',
|
81 |
array($this, 'forms')
|
82 |
);
|
@@ -95,7 +95,7 @@ class Menu {
|
|
95 |
$main_page_slug,
|
96 |
$this->setPageTitle(__('Subscribers', 'mailpoet')),
|
97 |
__('Subscribers', 'mailpoet'),
|
98 |
-
|
99 |
'mailpoet-subscribers',
|
100 |
array($this, 'subscribers')
|
101 |
);
|
@@ -114,7 +114,7 @@ class Menu {
|
|
114 |
$main_page_slug,
|
115 |
$this->setPageTitle(__('Lists', 'mailpoet')),
|
116 |
__('Lists', 'mailpoet'),
|
117 |
-
|
118 |
'mailpoet-segments',
|
119 |
array($this, 'segments')
|
120 |
);
|
@@ -134,7 +134,7 @@ class Menu {
|
|
134 |
$main_page_slug,
|
135 |
$this->setPageTitle( __('Settings', 'mailpoet')),
|
136 |
__('Settings', 'mailpoet'),
|
137 |
-
|
138 |
'mailpoet-settings',
|
139 |
array($this, 'settings')
|
140 |
);
|
@@ -142,7 +142,7 @@ class Menu {
|
|
142 |
'admin.php?page=mailpoet-subscribers',
|
143 |
$this->setPageTitle( __('Import', 'mailpoet')),
|
144 |
__('Import', 'mailpoet'),
|
145 |
-
|
146 |
'mailpoet-import',
|
147 |
array($this, 'import')
|
148 |
);
|
@@ -151,7 +151,7 @@ class Menu {
|
|
151 |
true,
|
152 |
$this->setPageTitle(__('Export', 'mailpoet')),
|
153 |
__('Export', 'mailpoet'),
|
154 |
-
|
155 |
'mailpoet-export',
|
156 |
array($this, 'export')
|
157 |
);
|
@@ -160,7 +160,7 @@ class Menu {
|
|
160 |
true,
|
161 |
$this->setPageTitle(__('Welcome', 'mailpoet')),
|
162 |
__('Welcome', 'mailpoet'),
|
163 |
-
|
164 |
'mailpoet-welcome',
|
165 |
array($this, 'welcome')
|
166 |
);
|
@@ -169,7 +169,7 @@ class Menu {
|
|
169 |
true,
|
170 |
$this->setPageTitle(__('Update', 'mailpoet')),
|
171 |
__('Update', 'mailpoet'),
|
172 |
-
|
173 |
'mailpoet-update',
|
174 |
array($this, 'update')
|
175 |
);
|
@@ -178,7 +178,7 @@ class Menu {
|
|
178 |
true,
|
179 |
$this->setPageTitle(__('Form Editor', 'mailpoet')),
|
180 |
__('Form Editor', 'mailpoet'),
|
181 |
-
|
182 |
'mailpoet-form-editor',
|
183 |
array($this, 'formEditor')
|
184 |
);
|
@@ -187,7 +187,7 @@ class Menu {
|
|
187 |
true,
|
188 |
$this->setPageTitle(__('Newsletter', 'mailpoet')),
|
189 |
__('Newsletter Editor', 'mailpoet'),
|
190 |
-
|
191 |
'mailpoet-newsletter-editor',
|
192 |
array($this, 'newletterEditor')
|
193 |
);
|
45 |
add_menu_page(
|
46 |
'MailPoet',
|
47 |
'MailPoet',
|
48 |
+
Env::$required_permission,
|
49 |
$main_page_slug,
|
50 |
null,
|
51 |
$this->assets_url . '/img/menu_icon.png',
|
56 |
$main_page_slug,
|
57 |
$this->setPageTitle(__('Emails', 'mailpoet')),
|
58 |
__('Emails', 'mailpoet'),
|
59 |
+
Env::$required_permission,
|
60 |
$main_page_slug,
|
61 |
array($this, 'newsletters')
|
62 |
);
|
76 |
$main_page_slug,
|
77 |
$this->setPageTitle(__('Forms', 'mailpoet')),
|
78 |
__('Forms', 'mailpoet'),
|
79 |
+
Env::$required_permission,
|
80 |
'mailpoet-forms',
|
81 |
array($this, 'forms')
|
82 |
);
|
95 |
$main_page_slug,
|
96 |
$this->setPageTitle(__('Subscribers', 'mailpoet')),
|
97 |
__('Subscribers', 'mailpoet'),
|
98 |
+
Env::$required_permission,
|
99 |
'mailpoet-subscribers',
|
100 |
array($this, 'subscribers')
|
101 |
);
|
114 |
$main_page_slug,
|
115 |
$this->setPageTitle(__('Lists', 'mailpoet')),
|
116 |
__('Lists', 'mailpoet'),
|
117 |
+
Env::$required_permission,
|
118 |
'mailpoet-segments',
|
119 |
array($this, 'segments')
|
120 |
);
|
134 |
$main_page_slug,
|
135 |
$this->setPageTitle( __('Settings', 'mailpoet')),
|
136 |
__('Settings', 'mailpoet'),
|
137 |
+
Env::$required_permission,
|
138 |
'mailpoet-settings',
|
139 |
array($this, 'settings')
|
140 |
);
|
142 |
'admin.php?page=mailpoet-subscribers',
|
143 |
$this->setPageTitle( __('Import', 'mailpoet')),
|
144 |
__('Import', 'mailpoet'),
|
145 |
+
Env::$required_permission,
|
146 |
'mailpoet-import',
|
147 |
array($this, 'import')
|
148 |
);
|
151 |
true,
|
152 |
$this->setPageTitle(__('Export', 'mailpoet')),
|
153 |
__('Export', 'mailpoet'),
|
154 |
+
Env::$required_permission,
|
155 |
'mailpoet-export',
|
156 |
array($this, 'export')
|
157 |
);
|
160 |
true,
|
161 |
$this->setPageTitle(__('Welcome', 'mailpoet')),
|
162 |
__('Welcome', 'mailpoet'),
|
163 |
+
Env::$required_permission,
|
164 |
'mailpoet-welcome',
|
165 |
array($this, 'welcome')
|
166 |
);
|
169 |
true,
|
170 |
$this->setPageTitle(__('Update', 'mailpoet')),
|
171 |
__('Update', 'mailpoet'),
|
172 |
+
Env::$required_permission,
|
173 |
'mailpoet-update',
|
174 |
array($this, 'update')
|
175 |
);
|
178 |
true,
|
179 |
$this->setPageTitle(__('Form Editor', 'mailpoet')),
|
180 |
__('Form Editor', 'mailpoet'),
|
181 |
+
Env::$required_permission,
|
182 |
'mailpoet-form-editor',
|
183 |
array($this, 'formEditor')
|
184 |
);
|
187 |
true,
|
188 |
$this->setPageTitle(__('Newsletter', 'mailpoet')),
|
189 |
__('Newsletter Editor', 'mailpoet'),
|
190 |
+
Env::$required_permission,
|
191 |
'mailpoet-newsletter-editor',
|
192 |
array($this, 'newletterEditor')
|
193 |
);
|
lib/Config/Migrator.php
CHANGED
@@ -177,6 +177,7 @@ class Migrator {
|
|
177 |
function newsletters() {
|
178 |
$attributes = array(
|
179 |
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
|
|
|
180 |
'parent_id mediumint(9) NULL,',
|
181 |
'subject varchar(250) NOT NULL DEFAULT "",',
|
182 |
'type varchar(20) NOT NULL DEFAULT "standard",',
|
177 |
function newsletters() {
|
178 |
$attributes = array(
|
179 |
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
|
180 |
+
'hash varchar(150) NULL DEFAULT NULL,',
|
181 |
'parent_id mediumint(9) NULL,',
|
182 |
'subject varchar(250) NOT NULL DEFAULT "",',
|
183 |
'type varchar(20) NOT NULL DEFAULT "standard",',
|
lib/Config/Shortcodes.php
CHANGED
@@ -3,7 +3,6 @@ namespace MailPoet\Config;
|
|
3 |
use \MailPoet\Models\Newsletter;
|
4 |
use \MailPoet\Models\Subscriber;
|
5 |
use \MailPoet\Models\SubscriberSegment;
|
6 |
-
use \MailPoet\Subscription;
|
7 |
use MailPoet\Newsletter\Url as NewsletterUrl;
|
8 |
|
9 |
class Shortcodes {
|
@@ -33,7 +32,7 @@ class Shortcodes {
|
|
33 |
), 2);
|
34 |
add_filter('mailpoet_archive_subject', array(
|
35 |
$this, 'renderArchiveSubject'
|
36 |
-
), 2);
|
37 |
}
|
38 |
|
39 |
function formWidget($params = array()) {
|
@@ -78,6 +77,8 @@ class Shortcodes {
|
|
78 |
|
79 |
$newsletters = Newsletter::getArchives($segment_ids);
|
80 |
|
|
|
|
|
81 |
if(empty($newsletters)) {
|
82 |
return apply_filters(
|
83 |
'mailpoet_archive_no_newsletters',
|
@@ -91,12 +92,13 @@ class Shortcodes {
|
|
91 |
|
92 |
$html .= '<ul class="mailpoet_archive">';
|
93 |
foreach($newsletters as $newsletter) {
|
|
|
94 |
$html .= '<li>'.
|
95 |
'<span class="mailpoet_archive_date">'.
|
96 |
apply_filters('mailpoet_archive_date', $newsletter).
|
97 |
'</span>
|
98 |
<span class="mailpoet_archive_subject">'.
|
99 |
-
apply_filters('mailpoet_archive_subject', $newsletter).
|
100 |
'</span>
|
101 |
</li>';
|
102 |
}
|
@@ -112,13 +114,16 @@ class Shortcodes {
|
|
112 |
);
|
113 |
}
|
114 |
|
115 |
-
function renderArchiveSubject($newsletter) {
|
116 |
-
$preview_url = NewsletterUrl::getViewInBrowserUrl(
|
117 |
-
|
|
|
|
|
|
|
|
|
118 |
return '<a href="'.esc_attr($preview_url).'" target="_blank" title="'
|
119 |
.esc_attr(__('Preview in a new tab', 'mailpoet')).'">'
|
120 |
.esc_attr($newsletter->subject).
|
121 |
'</a>';
|
122 |
}
|
123 |
-
|
124 |
-
}
|
3 |
use \MailPoet\Models\Newsletter;
|
4 |
use \MailPoet\Models\Subscriber;
|
5 |
use \MailPoet\Models\SubscriberSegment;
|
|
|
6 |
use MailPoet\Newsletter\Url as NewsletterUrl;
|
7 |
|
8 |
class Shortcodes {
|
32 |
), 2);
|
33 |
add_filter('mailpoet_archive_subject', array(
|
34 |
$this, 'renderArchiveSubject'
|
35 |
+
), 2, 3);
|
36 |
}
|
37 |
|
38 |
function formWidget($params = array()) {
|
77 |
|
78 |
$newsletters = Newsletter::getArchives($segment_ids);
|
79 |
|
80 |
+
$subscriber = Subscriber::getCurrentWPUser();
|
81 |
+
|
82 |
if(empty($newsletters)) {
|
83 |
return apply_filters(
|
84 |
'mailpoet_archive_no_newsletters',
|
92 |
|
93 |
$html .= '<ul class="mailpoet_archive">';
|
94 |
foreach($newsletters as $newsletter) {
|
95 |
+
$queue = $newsletter->queue()->findOne();
|
96 |
$html .= '<li>'.
|
97 |
'<span class="mailpoet_archive_date">'.
|
98 |
apply_filters('mailpoet_archive_date', $newsletter).
|
99 |
'</span>
|
100 |
<span class="mailpoet_archive_subject">'.
|
101 |
+
apply_filters('mailpoet_archive_subject', $newsletter, $subscriber, $queue).
|
102 |
'</span>
|
103 |
</li>';
|
104 |
}
|
114 |
);
|
115 |
}
|
116 |
|
117 |
+
function renderArchiveSubject($newsletter, $subscriber, $queue) {
|
118 |
+
$preview_url = NewsletterUrl::getViewInBrowserUrl(
|
119 |
+
NewsletterUrl::TYPE_ARCHIVE,
|
120 |
+
$newsletter,
|
121 |
+
$subscriber,
|
122 |
+
$queue
|
123 |
+
);
|
124 |
return '<a href="'.esc_attr($preview_url).'" target="_blank" title="'
|
125 |
.esc_attr(__('Preview in a new tab', 'mailpoet')).'">'
|
126 |
.esc_attr($newsletter->subject).
|
127 |
'</a>';
|
128 |
}
|
129 |
+
}
|
|
lib/Form/Block/Select.php
CHANGED
@@ -29,11 +29,17 @@ class Select extends Base {
|
|
29 |
);
|
30 |
|
31 |
foreach($options as $option) {
|
|
|
|
|
|
|
|
|
32 |
$is_selected = (
|
33 |
(isset($option['is_checked']) && $option['is_checked'])
|
34 |
||
|
35 |
(self::getFieldValue($block) === $option['value'])
|
36 |
-
) ? 'selected="selected"' : '';
|
|
|
|
|
37 |
|
38 |
if(is_array($option['value'])) {
|
39 |
$value = key($option['value']);
|
@@ -43,7 +49,7 @@ class Select extends Base {
|
|
43 |
$label = $option['value'];
|
44 |
}
|
45 |
|
46 |
-
$html .= '<option value="'.$value.'"
|
47 |
$html .= esc_attr($label);
|
48 |
$html .= '</option>';
|
49 |
}
|
29 |
);
|
30 |
|
31 |
foreach($options as $option) {
|
32 |
+
if(!empty($option['is_hidden'])) {
|
33 |
+
continue;
|
34 |
+
}
|
35 |
+
|
36 |
$is_selected = (
|
37 |
(isset($option['is_checked']) && $option['is_checked'])
|
38 |
||
|
39 |
(self::getFieldValue($block) === $option['value'])
|
40 |
+
) ? ' selected="selected"' : '';
|
41 |
+
|
42 |
+
$is_disabled = (!empty($option['is_disabled'])) ? ' disabled="disabled"' : '';
|
43 |
|
44 |
if(is_array($option['value'])) {
|
45 |
$value = key($option['value']);
|
49 |
$label = $option['value'];
|
50 |
}
|
51 |
|
52 |
+
$html .= '<option value="'.$value.'"' . $is_selected . $is_disabled . '>';
|
53 |
$html .= esc_attr($label);
|
54 |
$html .= '</option>';
|
55 |
}
|
lib/Mailer/Mailer.php
CHANGED
@@ -10,6 +10,7 @@ class Mailer {
|
|
10 |
public $mailer_config;
|
11 |
public $sender;
|
12 |
public $reply_to;
|
|
|
13 |
public $mailer_instance;
|
14 |
const MAILER_CONFIG_SETTING_NAME = 'mta';
|
15 |
const SENDING_LIMIT_INTERVAL_MULTIPLIER = 60;
|
@@ -19,10 +20,11 @@ class Mailer {
|
|
19 |
const METHOD_PHPMAIL = 'PHPMail';
|
20 |
const METHOD_SMTP = 'SMTP';
|
21 |
|
22 |
-
function __construct($mailer = false, $sender = false, $reply_to = false) {
|
23 |
$this->mailer_config = self::getMailerConfig($mailer);
|
24 |
$this->sender = $this->getSenderNameAndAddress($sender);
|
25 |
$this->reply_to = $this->getReplyToNameAndAddress($reply_to);
|
|
|
26 |
$this->mailer_instance = $this->buildMailer();
|
27 |
}
|
28 |
|
@@ -39,7 +41,8 @@ class Mailer {
|
|
39 |
$this->mailer_config['access_key'],
|
40 |
$this->mailer_config['secret_key'],
|
41 |
$this->sender,
|
42 |
-
$this->reply_to
|
|
|
43 |
);
|
44 |
break;
|
45 |
case self::METHOD_MAILPOET:
|
@@ -59,7 +62,8 @@ class Mailer {
|
|
59 |
case self::METHOD_PHPMAIL:
|
60 |
$mailer_instance = new $this->mailer_config['class'](
|
61 |
$this->sender,
|
62 |
-
$this->reply_to
|
|
|
63 |
);
|
64 |
break;
|
65 |
case self::METHOD_SMTP:
|
@@ -71,7 +75,8 @@ class Mailer {
|
|
71 |
$this->mailer_config['password'],
|
72 |
$this->mailer_config['encryption'],
|
73 |
$this->sender,
|
74 |
-
$this->reply_to
|
|
|
75 |
);
|
76 |
break;
|
77 |
default:
|
@@ -112,7 +117,7 @@ class Mailer {
|
|
112 |
|
113 |
function getReplyToNameAndAddress($reply_to = array()) {
|
114 |
if(!$reply_to) {
|
115 |
-
$reply_to = Setting::getValue('reply_to'
|
116 |
$reply_to['name'] = (!empty($reply_to['name'])) ?
|
117 |
$reply_to['name'] :
|
118 |
$this->sender['from_name'];
|
@@ -131,6 +136,12 @@ class Mailer {
|
|
131 |
);
|
132 |
}
|
133 |
|
|
|
|
|
|
|
|
|
|
|
|
|
134 |
function formatSubscriberNameAndEmailAddress($subscriber) {
|
135 |
$subscriber = (is_object($subscriber)) ? $subscriber->asArray() : $subscriber;
|
136 |
if(!is_array($subscriber)) return $subscriber;
|
10 |
public $mailer_config;
|
11 |
public $sender;
|
12 |
public $reply_to;
|
13 |
+
public $return_path;
|
14 |
public $mailer_instance;
|
15 |
const MAILER_CONFIG_SETTING_NAME = 'mta';
|
16 |
const SENDING_LIMIT_INTERVAL_MULTIPLIER = 60;
|
20 |
const METHOD_PHPMAIL = 'PHPMail';
|
21 |
const METHOD_SMTP = 'SMTP';
|
22 |
|
23 |
+
function __construct($mailer = false, $sender = false, $reply_to = false, $return_path = false) {
|
24 |
$this->mailer_config = self::getMailerConfig($mailer);
|
25 |
$this->sender = $this->getSenderNameAndAddress($sender);
|
26 |
$this->reply_to = $this->getReplyToNameAndAddress($reply_to);
|
27 |
+
$this->return_path = $this->getReturnPathAddress($return_path);
|
28 |
$this->mailer_instance = $this->buildMailer();
|
29 |
}
|
30 |
|
41 |
$this->mailer_config['access_key'],
|
42 |
$this->mailer_config['secret_key'],
|
43 |
$this->sender,
|
44 |
+
$this->reply_to,
|
45 |
+
$this->return_path
|
46 |
);
|
47 |
break;
|
48 |
case self::METHOD_MAILPOET:
|
62 |
case self::METHOD_PHPMAIL:
|
63 |
$mailer_instance = new $this->mailer_config['class'](
|
64 |
$this->sender,
|
65 |
+
$this->reply_to,
|
66 |
+
$this->return_path
|
67 |
);
|
68 |
break;
|
69 |
case self::METHOD_SMTP:
|
75 |
$this->mailer_config['password'],
|
76 |
$this->mailer_config['encryption'],
|
77 |
$this->sender,
|
78 |
+
$this->reply_to,
|
79 |
+
$this->return_path
|
80 |
);
|
81 |
break;
|
82 |
default:
|
117 |
|
118 |
function getReplyToNameAndAddress($reply_to = array()) {
|
119 |
if(!$reply_to) {
|
120 |
+
$reply_to = Setting::getValue('reply_to');
|
121 |
$reply_to['name'] = (!empty($reply_to['name'])) ?
|
122 |
$reply_to['name'] :
|
123 |
$this->sender['from_name'];
|
136 |
);
|
137 |
}
|
138 |
|
139 |
+
function getReturnPathAddress($return_path) {
|
140 |
+
return ($return_path) ?
|
141 |
+
$return_path :
|
142 |
+
Setting::getValue('bounce.address');
|
143 |
+
}
|
144 |
+
|
145 |
function formatSubscriberNameAndEmailAddress($subscriber) {
|
146 |
$subscriber = (is_object($subscriber)) ? $subscriber->asArray() : $subscriber;
|
147 |
if(!is_array($subscriber)) return $subscriber;
|
lib/Mailer/Methods/AmazonSES.php
CHANGED
@@ -17,6 +17,7 @@ class AmazonSES {
|
|
17 |
public $url;
|
18 |
public $sender;
|
19 |
public $reply_to;
|
|
|
20 |
public $date;
|
21 |
public $date_without_time;
|
22 |
private $available_regions = array(
|
@@ -25,7 +26,7 @@ class AmazonSES {
|
|
25 |
'EU (Ireland)' => 'eu-west-1'
|
26 |
);
|
27 |
|
28 |
-
function __construct($region, $access_key, $secret_key, $sender, $reply_to) {
|
29 |
$this->aws_access_key = $access_key;
|
30 |
$this->aws_secret_key = $secret_key;
|
31 |
$this->aws_region = (in_array($region, $this->available_regions)) ? $region : false;
|
@@ -40,11 +41,13 @@ class AmazonSES {
|
|
40 |
$this->url = 'https://' . $this->aws_endpoint;
|
41 |
$this->sender = $sender;
|
42 |
$this->reply_to = $reply_to;
|
|
|
|
|
|
|
43 |
$this->date = gmdate('Ymd\THis\Z');
|
44 |
$this->date_without_time = gmdate('Ymd');
|
45 |
}
|
46 |
|
47 |
-
|
48 |
function send($newsletter, $subscriber) {
|
49 |
$result = wp_remote_post(
|
50 |
$this->url,
|
@@ -71,7 +74,7 @@ class AmazonSES {
|
|
71 |
'Source' => $this->sender['from_name_email'],
|
72 |
'ReplyToAddresses.member.1' => $this->reply_to['reply_to_name_email'],
|
73 |
'Message.Subject.Data' => $newsletter['subject'],
|
74 |
-
'ReturnPath' => $this->
|
75 |
);
|
76 |
if(!empty($newsletter['body']['html'])) {
|
77 |
$body['Message.Body.Html.Data'] = $newsletter['body']['html'];
|
17 |
public $url;
|
18 |
public $sender;
|
19 |
public $reply_to;
|
20 |
+
public $return_path;
|
21 |
public $date;
|
22 |
public $date_without_time;
|
23 |
private $available_regions = array(
|
26 |
'EU (Ireland)' => 'eu-west-1'
|
27 |
);
|
28 |
|
29 |
+
function __construct($region, $access_key, $secret_key, $sender, $reply_to, $return_path) {
|
30 |
$this->aws_access_key = $access_key;
|
31 |
$this->aws_secret_key = $secret_key;
|
32 |
$this->aws_region = (in_array($region, $this->available_regions)) ? $region : false;
|
41 |
$this->url = 'https://' . $this->aws_endpoint;
|
42 |
$this->sender = $sender;
|
43 |
$this->reply_to = $reply_to;
|
44 |
+
$this->return_path = ($return_path) ?
|
45 |
+
$return_path :
|
46 |
+
$this->sender['from_email'];
|
47 |
$this->date = gmdate('Ymd\THis\Z');
|
48 |
$this->date_without_time = gmdate('Ymd');
|
49 |
}
|
50 |
|
|
|
51 |
function send($newsletter, $subscriber) {
|
52 |
$result = wp_remote_post(
|
53 |
$this->url,
|
74 |
'Source' => $this->sender['from_name_email'],
|
75 |
'ReplyToAddresses.member.1' => $this->reply_to['reply_to_name_email'],
|
76 |
'Message.Subject.Data' => $newsletter['subject'],
|
77 |
+
'ReturnPath' => $this->return_path
|
78 |
);
|
79 |
if(!empty($newsletter['body']['html'])) {
|
80 |
$body['Message.Body.Html.Data'] = $newsletter['body']['html'];
|
lib/Mailer/Methods/PHPMail.php
CHANGED
@@ -8,11 +8,15 @@ if(!defined('ABSPATH')) exit;
|
|
8 |
class PHPMail {
|
9 |
public $sender;
|
10 |
public $reply_to;
|
|
|
11 |
public $mailer;
|
12 |
|
13 |
-
function __construct($sender, $reply_to) {
|
14 |
$this->sender = $sender;
|
15 |
$this->reply_to = $reply_to;
|
|
|
|
|
|
|
16 |
$this->mailer = $this->buildMailer();
|
17 |
}
|
18 |
|
@@ -44,6 +48,7 @@ class PHPMail {
|
|
44 |
->setReplyTo(array(
|
45 |
$this->reply_to['reply_to_email'] => $this->reply_to['reply_to_name']
|
46 |
))
|
|
|
47 |
->setSubject($newsletter['subject']);
|
48 |
if(!empty($newsletter['body']['html'])) {
|
49 |
$message = $message->setBody($newsletter['body']['html'], 'text/html');
|
8 |
class PHPMail {
|
9 |
public $sender;
|
10 |
public $reply_to;
|
11 |
+
public $return_path;
|
12 |
public $mailer;
|
13 |
|
14 |
+
function __construct($sender, $reply_to, $return_path) {
|
15 |
$this->sender = $sender;
|
16 |
$this->reply_to = $reply_to;
|
17 |
+
$this->return_path = ($return_path) ?
|
18 |
+
$return_path :
|
19 |
+
$this->sender['from_email'];
|
20 |
$this->mailer = $this->buildMailer();
|
21 |
}
|
22 |
|
48 |
->setReplyTo(array(
|
49 |
$this->reply_to['reply_to_email'] => $this->reply_to['reply_to_name']
|
50 |
))
|
51 |
+
->setReturnPath($this->return_path)
|
52 |
->setSubject($newsletter['subject']);
|
53 |
if(!empty($newsletter['body']['html'])) {
|
54 |
$message = $message->setBody($newsletter['body']['html'], 'text/html');
|
lib/Mailer/Methods/SMTP.php
CHANGED
@@ -14,11 +14,12 @@ class SMTP {
|
|
14 |
public $encryption;
|
15 |
public $sender;
|
16 |
public $reply_to;
|
|
|
17 |
public $mailer;
|
18 |
|
19 |
function __construct(
|
20 |
$host, $port, $authentication, $login = null, $password = null, $encryption,
|
21 |
-
$sender, $reply_to) {
|
22 |
$this->host = $host;
|
23 |
$this->port = $port;
|
24 |
$this->authentication = $authentication;
|
@@ -27,6 +28,9 @@ class SMTP {
|
|
27 |
$this->encryption = $encryption;
|
28 |
$this->sender = $sender;
|
29 |
$this->reply_to = $reply_to;
|
|
|
|
|
|
|
30 |
$this->mailer = $this->buildMailer();
|
31 |
}
|
32 |
|
@@ -65,6 +69,7 @@ class SMTP {
|
|
65 |
->setReplyTo(array(
|
66 |
$this->reply_to['reply_to_email'] => $this->reply_to['reply_to_name']
|
67 |
))
|
|
|
68 |
->setSubject($newsletter['subject']);
|
69 |
if(!empty($newsletter['body']['html'])) {
|
70 |
$message = $message->setBody($newsletter['body']['html'], 'text/html');
|
14 |
public $encryption;
|
15 |
public $sender;
|
16 |
public $reply_to;
|
17 |
+
public $return_path;
|
18 |
public $mailer;
|
19 |
|
20 |
function __construct(
|
21 |
$host, $port, $authentication, $login = null, $password = null, $encryption,
|
22 |
+
$sender, $reply_to, $return_path) {
|
23 |
$this->host = $host;
|
24 |
$this->port = $port;
|
25 |
$this->authentication = $authentication;
|
28 |
$this->encryption = $encryption;
|
29 |
$this->sender = $sender;
|
30 |
$this->reply_to = $reply_to;
|
31 |
+
$this->return_path = ($return_path) ?
|
32 |
+
$return_path :
|
33 |
+
$this->sender['from_email'];
|
34 |
$this->mailer = $this->buildMailer();
|
35 |
}
|
36 |
|
69 |
->setReplyTo(array(
|
70 |
$this->reply_to['reply_to_email'] => $this->reply_to['reply_to_name']
|
71 |
))
|
72 |
+
->setReturnPath($this->return_path)
|
73 |
->setSubject($newsletter['subject']);
|
74 |
if(!empty($newsletter['body']['html'])) {
|
75 |
$message = $message->setBody($newsletter['body']['html'], 'text/html');
|
lib/Models/Newsletter.php
CHANGED
@@ -2,6 +2,7 @@
|
|
2 |
namespace MailPoet\Models;
|
3 |
use MailPoet\Newsletter\Renderer\Renderer;
|
4 |
use MailPoet\Util\Helpers;
|
|
|
5 |
|
6 |
if(!defined('ABSPATH')) exit;
|
7 |
|
@@ -11,7 +12,6 @@ class Newsletter extends Model {
|
|
11 |
const TYPE_WELCOME = 'welcome';
|
12 |
const TYPE_NOTIFICATION = 'notification';
|
13 |
const TYPE_NOTIFICATION_HISTORY = 'notification_history';
|
14 |
-
|
15 |
// standard newsletters
|
16 |
const STATUS_DRAFT = 'draft';
|
17 |
const STATUS_SCHEDULED = 'scheduled';
|
@@ -19,6 +19,7 @@ class Newsletter extends Model {
|
|
19 |
const STATUS_SENT = 'sent';
|
20 |
// automatic newsletters status
|
21 |
const STATUS_ACTIVE = 'active';
|
|
|
22 |
|
23 |
function __construct() {
|
24 |
parent::__construct();
|
@@ -27,6 +28,10 @@ class Newsletter extends Model {
|
|
27 |
));
|
28 |
}
|
29 |
|
|
|
|
|
|
|
|
|
30 |
function save() {
|
31 |
if(is_string($this->deleted_at) && strlen(trim($this->deleted_at)) === 0) {
|
32 |
$this->set_expr('deleted_at', 'NULL');
|
@@ -37,6 +42,12 @@ class Newsletter extends Model {
|
|
37 |
? json_encode($this->body)
|
38 |
: $this->body
|
39 |
);
|
|
|
|
|
|
|
|
|
|
|
|
|
40 |
return parent::save();
|
41 |
}
|
42 |
|
@@ -74,6 +85,9 @@ class Newsletter extends Model {
|
|
74 |
// reset status
|
75 |
$duplicate->set('status', self::STATUS_DRAFT);
|
76 |
|
|
|
|
|
|
|
77 |
$duplicate->save();
|
78 |
|
79 |
if($duplicate->getErrors() === false) {
|
@@ -130,6 +144,9 @@ class Newsletter extends Model {
|
|
130 |
$notification_history->set_expr('updated_at', 'NOW()');
|
131 |
$notification_history->set_expr('deleted_at', 'NULL');
|
132 |
|
|
|
|
|
|
|
133 |
$notification_history->save();
|
134 |
|
135 |
if($notification_history->getErrors() === false) {
|
@@ -182,8 +199,39 @@ class Newsletter extends Model {
|
|
182 |
);
|
183 |
}
|
184 |
|
185 |
-
function withSegments() {
|
186 |
$this->segments = $this->segments()->findArray();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
187 |
return $this;
|
188 |
}
|
189 |
|
@@ -634,6 +682,7 @@ class Newsletter extends Model {
|
|
634 |
'queues'
|
635 |
)
|
636 |
->where('queues.status', SendingQueue::STATUS_COMPLETED)
|
|
|
637 |
->select('queues.processed_at')
|
638 |
->orderByDesc('queues.processed_at');
|
639 |
|
@@ -647,4 +696,17 @@ class Newsletter extends Model {
|
|
647 |
}
|
648 |
return $orm->findMany();
|
649 |
}
|
650 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
namespace MailPoet\Models;
|
3 |
use MailPoet\Newsletter\Renderer\Renderer;
|
4 |
use MailPoet\Util\Helpers;
|
5 |
+
use MailPoet\Util\Security;
|
6 |
|
7 |
if(!defined('ABSPATH')) exit;
|
8 |
|
12 |
const TYPE_WELCOME = 'welcome';
|
13 |
const TYPE_NOTIFICATION = 'notification';
|
14 |
const TYPE_NOTIFICATION_HISTORY = 'notification_history';
|
|
|
15 |
// standard newsletters
|
16 |
const STATUS_DRAFT = 'draft';
|
17 |
const STATUS_SCHEDULED = 'scheduled';
|
19 |
const STATUS_SENT = 'sent';
|
20 |
// automatic newsletters status
|
21 |
const STATUS_ACTIVE = 'active';
|
22 |
+
const NEWSLETTER_HASH_LENGTH = 6;
|
23 |
|
24 |
function __construct() {
|
25 |
parent::__construct();
|
28 |
));
|
29 |
}
|
30 |
|
31 |
+
function queue() {
|
32 |
+
return $this->has_one(__NAMESPACE__ . '\SendingQueue', 'newsletter_id', 'id');
|
33 |
+
}
|
34 |
+
|
35 |
function save() {
|
36 |
if(is_string($this->deleted_at) && strlen(trim($this->deleted_at)) === 0) {
|
37 |
$this->set_expr('deleted_at', 'NULL');
|
42 |
? json_encode($this->body)
|
43 |
: $this->body
|
44 |
);
|
45 |
+
|
46 |
+
$this->set('hash',
|
47 |
+
($this->hash)
|
48 |
+
? $this->hash
|
49 |
+
: self::generateHash()
|
50 |
+
);
|
51 |
return parent::save();
|
52 |
}
|
53 |
|
85 |
// reset status
|
86 |
$duplicate->set('status', self::STATUS_DRAFT);
|
87 |
|
88 |
+
// reset hash
|
89 |
+
$duplicate->set('hash', null);
|
90 |
+
|
91 |
$duplicate->save();
|
92 |
|
93 |
if($duplicate->getErrors() === false) {
|
144 |
$notification_history->set_expr('updated_at', 'NOW()');
|
145 |
$notification_history->set_expr('deleted_at', 'NULL');
|
146 |
|
147 |
+
// reset hash
|
148 |
+
$notification_history->set('hash', null);
|
149 |
+
|
150 |
$notification_history->save();
|
151 |
|
152 |
if($notification_history->getErrors() === false) {
|
199 |
);
|
200 |
}
|
201 |
|
202 |
+
function withSegments($incl_deleted = false) {
|
203 |
$this->segments = $this->segments()->findArray();
|
204 |
+
if($incl_deleted) {
|
205 |
+
$this->withDeletedSegments();
|
206 |
+
}
|
207 |
+
return $this;
|
208 |
+
}
|
209 |
+
|
210 |
+
// Intermediary table only
|
211 |
+
function segmentLinks() {
|
212 |
+
return $this->has_many(
|
213 |
+
__NAMESPACE__.'\NewsletterSegment',
|
214 |
+
'newsletter_id',
|
215 |
+
'id'
|
216 |
+
);
|
217 |
+
}
|
218 |
+
|
219 |
+
function withDeletedSegments() {
|
220 |
+
if(!empty($this->segments)) {
|
221 |
+
$segment_ids = Helpers::arrayColumn($this->segments, 'id');
|
222 |
+
$links = $this->segmentLinks()
|
223 |
+
->whereNotIn('segment_id', $segment_ids)->findArray();
|
224 |
+
$deleted_segments = array();
|
225 |
+
|
226 |
+
foreach($links as $link) {
|
227 |
+
$deleted_segments[] = array(
|
228 |
+
'id' => $link['segment_id'],
|
229 |
+
'name' => __('Deleted list', 'mailpoet')
|
230 |
+
);
|
231 |
+
}
|
232 |
+
$this->segments = array_merge($this->segments, $deleted_segments);
|
233 |
+
}
|
234 |
+
|
235 |
return $this;
|
236 |
}
|
237 |
|
682 |
'queues'
|
683 |
)
|
684 |
->where('queues.status', SendingQueue::STATUS_COMPLETED)
|
685 |
+
->whereNull('newsletters.deleted_at')
|
686 |
->select('queues.processed_at')
|
687 |
->orderByDesc('queues.processed_at');
|
688 |
|
696 |
}
|
697 |
return $orm->findMany();
|
698 |
}
|
699 |
+
|
700 |
+
static function getByHash($hash) {
|
701 |
+
return parent::where('hash', $hash)
|
702 |
+
->findOne();
|
703 |
+
}
|
704 |
+
|
705 |
+
static function generateHash() {
|
706 |
+
return substr(
|
707 |
+
md5(AUTH_KEY . Security::generateRandomString(15)),
|
708 |
+
0,
|
709 |
+
self::NEWSLETTER_HASH_LENGTH
|
710 |
+
);
|
711 |
+
}
|
712 |
+
}
|
lib/Models/SendingQueue.php
CHANGED
@@ -70,10 +70,13 @@ class SendingQueue extends Model {
|
|
70 |
return $subscribers;
|
71 |
}
|
72 |
|
73 |
-
function getNewsletterRenderedBody() {
|
74 |
-
|
75 |
$this->newsletter_rendered_body :
|
76 |
unserialize($this->newsletter_rendered_body);
|
|
|
|
|
|
|
77 |
}
|
78 |
|
79 |
function isSubscriberProcessed($subscriber_id) {
|
70 |
return $subscribers;
|
71 |
}
|
72 |
|
73 |
+
function getNewsletterRenderedBody($type = false) {
|
74 |
+
$rendered_newsletter = (!is_serialized($this->newsletter_rendered_body)) ?
|
75 |
$this->newsletter_rendered_body :
|
76 |
unserialize($this->newsletter_rendered_body);
|
77 |
+
return ($type && !empty($rendered_newsletter[$type])) ?
|
78 |
+
$rendered_newsletter[$type] :
|
79 |
+
$rendered_newsletter;
|
80 |
}
|
81 |
|
82 |
function isSubscriberProcessed($subscriber_id) {
|
lib/Models/Subscriber.php
CHANGED
@@ -14,8 +14,8 @@ class Subscriber extends Model {
|
|
14 |
const STATUS_UNSUBSCRIBED = 'unsubscribed';
|
15 |
const STATUS_UNCONFIRMED = 'unconfirmed';
|
16 |
const STATUS_BOUNCED = 'bounced';
|
17 |
-
|
18 |
const SUBSCRIPTION_LIMIT_COOLDOWN = 60;
|
|
|
19 |
|
20 |
function __construct() {
|
21 |
parent::__construct();
|
@@ -154,13 +154,17 @@ class Subscriber extends Model {
|
|
154 |
|
155 |
static function generateToken($email = null) {
|
156 |
if($email !== null) {
|
157 |
-
return md5(AUTH_KEY
|
158 |
}
|
159 |
return false;
|
160 |
}
|
161 |
|
162 |
static function verifyToken($email, $token) {
|
163 |
-
return call_user_func(
|
|
|
|
|
|
|
|
|
164 |
}
|
165 |
|
166 |
static function subscribe($subscriber_data = array(), $segment_ids = array()) {
|
14 |
const STATUS_UNSUBSCRIBED = 'unsubscribed';
|
15 |
const STATUS_UNCONFIRMED = 'unconfirmed';
|
16 |
const STATUS_BOUNCED = 'bounced';
|
|
|
17 |
const SUBSCRIPTION_LIMIT_COOLDOWN = 60;
|
18 |
+
const SUBSCRIBER_TOKEN_LENGTH = 6;
|
19 |
|
20 |
function __construct() {
|
21 |
parent::__construct();
|
154 |
|
155 |
static function generateToken($email = null) {
|
156 |
if($email !== null) {
|
157 |
+
return substr(md5(AUTH_KEY . $email), 0, self::SUBSCRIBER_TOKEN_LENGTH);
|
158 |
}
|
159 |
return false;
|
160 |
}
|
161 |
|
162 |
static function verifyToken($email, $token) {
|
163 |
+
return call_user_func(
|
164 |
+
'hash_equals',
|
165 |
+
self::generateToken($email),
|
166 |
+
substr($token, 0, self::SUBSCRIBER_TOKEN_LENGTH)
|
167 |
+
);
|
168 |
}
|
169 |
|
170 |
static function subscribe($subscriber_data = array(), $segment_ids = array()) {
|
lib/Newsletter/Links/Links.php
CHANGED
@@ -6,6 +6,7 @@ use MailPoet\Router\Router;
|
|
6 |
use MailPoet\Router\Endpoints\Track as TrackEndpoint;
|
7 |
use MailPoet\Models\NewsletterLink;
|
8 |
use MailPoet\Newsletter\Shortcodes\Shortcodes;
|
|
|
9 |
use MailPoet\Util\Security;
|
10 |
|
11 |
class Links {
|
@@ -101,24 +102,19 @@ class Links {
|
|
101 |
$preview = false
|
102 |
) {
|
103 |
// match data tags
|
104 |
-
$regex = sprintf(
|
105 |
-
'/((%s|%s)(?:-\w+)?)/',
|
106 |
-
preg_quote(self::DATA_TAG_CLICK),
|
107 |
-
preg_quote(self::DATA_TAG_OPEN)
|
108 |
-
);
|
109 |
$subscriber = Subscriber::findOne($subscriber_id);
|
110 |
-
preg_match_all(
|
111 |
foreach($matches[1] as $index => $match) {
|
112 |
$hash = null;
|
113 |
if(preg_match('/-/', $match)) {
|
114 |
list(, $hash) = explode('-', $match);
|
115 |
}
|
116 |
-
$data =
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
);
|
123 |
$router_action = ($matches[2][$index] === self::DATA_TAG_CLICK) ?
|
124 |
TrackEndpoint::ACTION_CLICK :
|
@@ -144,4 +140,54 @@ class Links {
|
|
144 |
$newsletter_link->save();
|
145 |
}
|
146 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
147 |
}
|
6 |
use MailPoet\Router\Endpoints\Track as TrackEndpoint;
|
7 |
use MailPoet\Models\NewsletterLink;
|
8 |
use MailPoet\Newsletter\Shortcodes\Shortcodes;
|
9 |
+
use MailPoet\Util\Helpers;
|
10 |
use MailPoet\Util\Security;
|
11 |
|
12 |
class Links {
|
102 |
$preview = false
|
103 |
) {
|
104 |
// match data tags
|
|
|
|
|
|
|
|
|
|
|
105 |
$subscriber = Subscriber::findOne($subscriber_id);
|
106 |
+
preg_match_all(self::getLinkRegex(), $content, $matches);
|
107 |
foreach($matches[1] as $index => $match) {
|
108 |
$hash = null;
|
109 |
if(preg_match('/-/', $match)) {
|
110 |
list(, $hash) = explode('-', $match);
|
111 |
}
|
112 |
+
$data = self::createUrlDataObject(
|
113 |
+
$subscriber->id,
|
114 |
+
$subscriber->email,
|
115 |
+
$queue_id,
|
116 |
+
$hash,
|
117 |
+
$preview
|
118 |
);
|
119 |
$router_action = ($matches[2][$index] === self::DATA_TAG_CLICK) ?
|
120 |
TrackEndpoint::ACTION_CLICK :
|
140 |
$newsletter_link->save();
|
141 |
}
|
142 |
}
|
143 |
+
|
144 |
+
static function convertHashedLinksToShortcodesAndUrls($content, $convert_all = false) {
|
145 |
+
preg_match_all(self::getLinkRegex(), $content, $links);
|
146 |
+
$links = array_unique(Helpers::flattenArray($links));
|
147 |
+
foreach($links as $link) {
|
148 |
+
$link_hash = explode('-', $link);
|
149 |
+
if(!isset($link_hash[1])) continue;
|
150 |
+
$newsletter_link = NewsletterLink::getByHash($link_hash[1]);
|
151 |
+
// convert either only link shortcodes or all hashes links if "convert all"
|
152 |
+
// option is specified
|
153 |
+
if($newsletter_link &&
|
154 |
+
(preg_match('/\[link:/', $newsletter_link->url) || $convert_all)
|
155 |
+
) {
|
156 |
+
$content = str_replace($link, $newsletter_link->url, $content);
|
157 |
+
}
|
158 |
+
}
|
159 |
+
return $content;
|
160 |
+
}
|
161 |
+
|
162 |
+
static function getLinkRegex() {
|
163 |
+
return sprintf(
|
164 |
+
'/((%s|%s)(?:-\w+)?)/',
|
165 |
+
preg_quote(self::DATA_TAG_CLICK),
|
166 |
+
preg_quote(self::DATA_TAG_OPEN)
|
167 |
+
);
|
168 |
+
}
|
169 |
+
|
170 |
+
static function createUrlDataObject(
|
171 |
+
$subscriber_id, $subscriber_email, $queue_id, $link_hash, $preview
|
172 |
+
) {
|
173 |
+
return array(
|
174 |
+
$subscriber_id,
|
175 |
+
Subscriber::generateToken($subscriber_email),
|
176 |
+
$queue_id,
|
177 |
+
$link_hash,
|
178 |
+
$preview
|
179 |
+
);
|
180 |
+
}
|
181 |
+
|
182 |
+
static function transformUrlDataObject($data) {
|
183 |
+
reset($data);
|
184 |
+
if(!is_int(key($data))) return $data;
|
185 |
+
$transformed_data = array();
|
186 |
+
$transformed_data['subscriber_id'] = (!empty($data[0])) ? $data[0] : false;
|
187 |
+
$transformed_data['subscriber_token'] = (!empty($data[1])) ? $data[1] : false;
|
188 |
+
$transformed_data['queue_id'] = (!empty($data[2])) ? $data[2] : false;
|
189 |
+
$transformed_data['link_hash'] = (!empty($data[3])) ? $data[3] : false;
|
190 |
+
$transformed_data['preview'] = (!empty($data[4])) ? $data[4] : false;
|
191 |
+
return $transformed_data;
|
192 |
+
}
|
193 |
}
|
lib/Newsletter/Renderer/PostProcess/OpenTracking.php
CHANGED
@@ -21,7 +21,7 @@ class OpenTracking {
|
|
21 |
}
|
22 |
|
23 |
static function addTrackingImage() {
|
24 |
-
add_filter(Renderer::
|
25 |
return OpenTracking::process($template);
|
26 |
});
|
27 |
return true;
|
21 |
}
|
22 |
|
23 |
static function addTrackingImage() {
|
24 |
+
add_filter(Renderer::FILTER_POST_PROCESS, function ($template) {
|
25 |
return OpenTracking::process($template);
|
26 |
});
|
27 |
return true;
|
lib/Newsletter/Renderer/Renderer.php
CHANGED
@@ -11,7 +11,7 @@ class Renderer {
|
|
11 |
public $newsletter;
|
12 |
public $preview;
|
13 |
const NEWSLETTER_TEMPLATE = 'Template.html';
|
14 |
-
const
|
15 |
|
16 |
function __construct($newsletter, $preview = false) {
|
17 |
// TODO: remove ternary condition, refactor to use model objects
|
@@ -24,7 +24,7 @@ class Renderer {
|
|
24 |
$this->template = file_get_contents(dirname(__FILE__) . '/' . self::NEWSLETTER_TEMPLATE);
|
25 |
}
|
26 |
|
27 |
-
function render() {
|
28 |
$newsletter = $this->newsletter;
|
29 |
$body = (is_array($newsletter['body']))
|
30 |
? $newsletter['body']
|
@@ -48,10 +48,14 @@ class Renderer {
|
|
48 |
$template = $this->inlineCSSStyles($template);
|
49 |
$template = $this->postProcessTemplate($template);
|
50 |
|
51 |
-
|
52 |
'html' => $template,
|
53 |
'text' => $this->renderTextVersion($template)
|
54 |
);
|
|
|
|
|
|
|
|
|
55 |
}
|
56 |
|
57 |
function renderBody($content) {
|
@@ -124,7 +128,7 @@ class Renderer {
|
|
124 |
str_replace('&', '&', $template->html())
|
125 |
);
|
126 |
$template = apply_filters(
|
127 |
-
self::
|
128 |
$DOM->__toString()
|
129 |
);
|
130 |
return $template;
|
11 |
public $newsletter;
|
12 |
public $preview;
|
13 |
const NEWSLETTER_TEMPLATE = 'Template.html';
|
14 |
+
const FILTER_POST_PROCESS = 'mailpoet_rendering_post_process';
|
15 |
|
16 |
function __construct($newsletter, $preview = false) {
|
17 |
// TODO: remove ternary condition, refactor to use model objects
|
24 |
$this->template = file_get_contents(dirname(__FILE__) . '/' . self::NEWSLETTER_TEMPLATE);
|
25 |
}
|
26 |
|
27 |
+
function render($type = false) {
|
28 |
$newsletter = $this->newsletter;
|
29 |
$body = (is_array($newsletter['body']))
|
30 |
? $newsletter['body']
|
48 |
$template = $this->inlineCSSStyles($template);
|
49 |
$template = $this->postProcessTemplate($template);
|
50 |
|
51 |
+
$rendered_newsletter = array(
|
52 |
'html' => $template,
|
53 |
'text' => $this->renderTextVersion($template)
|
54 |
);
|
55 |
+
|
56 |
+
return ($type && !empty($rendered_newsletter[$type])) ?
|
57 |
+
$rendered_newsletter[$type] :
|
58 |
+
$rendered_newsletter;
|
59 |
}
|
60 |
|
61 |
function renderBody($content) {
|
128 |
str_replace('&', '&', $template->html())
|
129 |
);
|
130 |
$template = apply_filters(
|
131 |
+
self::FILTER_POST_PROCESS,
|
132 |
$DOM->__toString()
|
133 |
);
|
134 |
return $template;
|
lib/Newsletter/Shortcodes/Categories/Link.php
CHANGED
@@ -7,19 +7,23 @@ use MailPoet\Statistics\Track\Unsubscribes;
|
|
7 |
use MailPoet\Subscription\Url as SubscriptionUrl;
|
8 |
|
9 |
class Link {
|
10 |
-
static function process(
|
|
|
11 |
$default_value,
|
12 |
$newsletter,
|
13 |
$subscriber,
|
14 |
-
$queue
|
|
|
|
|
15 |
) {
|
16 |
switch($action) {
|
17 |
case 'subscription_unsubscribe':
|
18 |
$action = 'subscription_unsubscribe_url';
|
19 |
$url = self::processUrl(
|
20 |
$action,
|
21 |
-
|
22 |
-
$queue
|
|
|
23 |
);
|
24 |
return sprintf(
|
25 |
'<a target="_blank" href="%s">%s</a>',
|
@@ -31,14 +35,16 @@ class Link {
|
|
31 |
return self::processUrl(
|
32 |
$action,
|
33 |
SubscriptionUrl::getUnsubscribeUrl($subscriber),
|
34 |
-
$queue
|
|
|
35 |
);
|
36 |
|
37 |
case 'subscription_manage':
|
38 |
$url = self::processUrl(
|
39 |
$action = 'subscription_manage_url',
|
40 |
-
|
41 |
-
$queue
|
|
|
42 |
);
|
43 |
return sprintf(
|
44 |
'<a target="_blank" href="%s">%s</a>',
|
@@ -50,13 +56,20 @@ class Link {
|
|
50 |
return self::processUrl(
|
51 |
$action,
|
52 |
SubscriptionUrl::getManageUrl($subscriber),
|
53 |
-
$queue
|
|
|
54 |
);
|
55 |
|
56 |
case 'newsletter_view_in_browser':
|
57 |
$action = 'newsletter_view_in_browser_url';
|
58 |
-
$url =
|
59 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
60 |
return sprintf(
|
61 |
'<a target="_blank" href="%s">%s</a>',
|
62 |
$url,
|
@@ -64,8 +77,14 @@ class Link {
|
|
64 |
);
|
65 |
|
66 |
case 'newsletter_view_in_browser_url':
|
67 |
-
$url = NewsletterUrl::getViewInBrowserUrl(
|
68 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
69 |
|
70 |
default:
|
71 |
$shortcode = self::getShortcode($action);
|
@@ -74,15 +93,17 @@ class Link {
|
|
74 |
$shortcode,
|
75 |
$newsletter,
|
76 |
$subscriber,
|
77 |
-
$queue
|
|
|
78 |
);
|
79 |
return ($url !== $shortcode) ?
|
80 |
-
self::processUrl($action, $url, $queue) :
|
81 |
false;
|
82 |
}
|
83 |
}
|
84 |
|
85 |
-
static function processUrl($action, $url, $queue) {
|
|
|
86 |
return ($queue !== false && (boolean)Setting::getValue('tracking.enabled')) ?
|
87 |
self::getShortcode($action) :
|
88 |
$url;
|
@@ -104,7 +125,12 @@ class Link {
|
|
104 |
$url = SubscriptionUrl::getManageUrl($subscriber);
|
105 |
break;
|
106 |
case 'newsletter_view_in_browser_url':
|
107 |
-
$url = NewsletterUrl::getViewInBrowserUrl(
|
|
|
|
|
|
|
|
|
|
|
108 |
break;
|
109 |
default:
|
110 |
$shortcode = self::getShortcode($shortcode_action);
|
7 |
use MailPoet\Subscription\Url as SubscriptionUrl;
|
8 |
|
9 |
class Link {
|
10 |
+
static function process(
|
11 |
+
$action,
|
12 |
$default_value,
|
13 |
$newsletter,
|
14 |
$subscriber,
|
15 |
+
$queue,
|
16 |
+
$content,
|
17 |
+
$wp_user_preview
|
18 |
) {
|
19 |
switch($action) {
|
20 |
case 'subscription_unsubscribe':
|
21 |
$action = 'subscription_unsubscribe_url';
|
22 |
$url = self::processUrl(
|
23 |
$action,
|
24 |
+
SubscriptionUrl::getUnsubscribeUrl($subscriber),
|
25 |
+
$queue,
|
26 |
+
$wp_user_preview
|
27 |
);
|
28 |
return sprintf(
|
29 |
'<a target="_blank" href="%s">%s</a>',
|
35 |
return self::processUrl(
|
36 |
$action,
|
37 |
SubscriptionUrl::getUnsubscribeUrl($subscriber),
|
38 |
+
$queue,
|
39 |
+
$wp_user_preview
|
40 |
);
|
41 |
|
42 |
case 'subscription_manage':
|
43 |
$url = self::processUrl(
|
44 |
$action = 'subscription_manage_url',
|
45 |
+
SubscriptionUrl::getManageUrl($subscriber),
|
46 |
+
$queue,
|
47 |
+
$wp_user_preview
|
48 |
);
|
49 |
return sprintf(
|
50 |
'<a target="_blank" href="%s">%s</a>',
|
56 |
return self::processUrl(
|
57 |
$action,
|
58 |
SubscriptionUrl::getManageUrl($subscriber),
|
59 |
+
$queue,
|
60 |
+
$wp_user_preview
|
61 |
);
|
62 |
|
63 |
case 'newsletter_view_in_browser':
|
64 |
$action = 'newsletter_view_in_browser_url';
|
65 |
+
$url = NewsletterUrl::getViewInBrowserUrl(
|
66 |
+
$type = null,
|
67 |
+
$newsletter,
|
68 |
+
$subscriber,
|
69 |
+
$queue,
|
70 |
+
$wp_user_preview
|
71 |
+
);
|
72 |
+
$url = self::processUrl($action, $url, $queue, $wp_user_preview);
|
73 |
return sprintf(
|
74 |
'<a target="_blank" href="%s">%s</a>',
|
75 |
$url,
|
77 |
);
|
78 |
|
79 |
case 'newsletter_view_in_browser_url':
|
80 |
+
$url = NewsletterUrl::getViewInBrowserUrl(
|
81 |
+
$type = null,
|
82 |
+
$newsletter,
|
83 |
+
$subscriber,
|
84 |
+
$queue,
|
85 |
+
$wp_user_preview
|
86 |
+
);
|
87 |
+
return self::processUrl($action, $url, $queue, $wp_user_preview);
|
88 |
|
89 |
default:
|
90 |
$shortcode = self::getShortcode($action);
|
93 |
$shortcode,
|
94 |
$newsletter,
|
95 |
$subscriber,
|
96 |
+
$queue,
|
97 |
+
$wp_user_preview
|
98 |
);
|
99 |
return ($url !== $shortcode) ?
|
100 |
+
self::processUrl($action, $url, $queue, $wp_user_preview) :
|
101 |
false;
|
102 |
}
|
103 |
}
|
104 |
|
105 |
+
static function processUrl($action, $url, $queue, $wp_user_preview = false) {
|
106 |
+
if($wp_user_preview) return '#';
|
107 |
return ($queue !== false && (boolean)Setting::getValue('tracking.enabled')) ?
|
108 |
self::getShortcode($action) :
|
109 |
$url;
|
125 |
$url = SubscriptionUrl::getManageUrl($subscriber);
|
126 |
break;
|
127 |
case 'newsletter_view_in_browser_url':
|
128 |
+
$url = NewsletterUrl::getViewInBrowserUrl(
|
129 |
+
$type = null,
|
130 |
+
$newsletter,
|
131 |
+
$subscriber,
|
132 |
+
$queue
|
133 |
+
);
|
134 |
break;
|
135 |
default:
|
136 |
$shortcode = self::getShortcode($shortcode_action);
|
lib/Newsletter/Shortcodes/Shortcodes.php
CHANGED
@@ -5,16 +5,19 @@ class Shortcodes {
|
|
5 |
public $newsletter;
|
6 |
public $subscriber;
|
7 |
public $queue;
|
|
|
8 |
const SHORTCODE_CATEGORY_NAMESPACE = 'MailPoet\Newsletter\Shortcodes\Categories\\';
|
9 |
|
10 |
function __construct(
|
11 |
$newsletter = false,
|
12 |
$subscriber = false,
|
13 |
-
$queue = false
|
|
|
14 |
) {
|
15 |
$this->newsletter = $newsletter;
|
16 |
$this->subscriber = $subscriber;
|
17 |
$this->queue = $queue;
|
|
|
18 |
}
|
19 |
|
20 |
function extract($content, $categories = false) {
|
@@ -64,7 +67,8 @@ class Shortcodes {
|
|
64 |
$_this->newsletter,
|
65 |
$_this->subscriber,
|
66 |
$_this->queue,
|
67 |
-
$content
|
|
|
68 |
);
|
69 |
return ($custom_shortcode === $shortcode) ?
|
70 |
false :
|
@@ -76,7 +80,8 @@ class Shortcodes {
|
|
76 |
$_this->newsletter,
|
77 |
$_this->subscriber,
|
78 |
$_this->queue,
|
79 |
-
$content
|
|
|
80 |
);
|
81 |
}, $shortcodes);
|
82 |
return $processed_shortcodes;
|
5 |
public $newsletter;
|
6 |
public $subscriber;
|
7 |
public $queue;
|
8 |
+
public $wp_user_preview;
|
9 |
const SHORTCODE_CATEGORY_NAMESPACE = 'MailPoet\Newsletter\Shortcodes\Categories\\';
|
10 |
|
11 |
function __construct(
|
12 |
$newsletter = false,
|
13 |
$subscriber = false,
|
14 |
+
$queue = false,
|
15 |
+
$wp_user_preview = false
|
16 |
) {
|
17 |
$this->newsletter = $newsletter;
|
18 |
$this->subscriber = $subscriber;
|
19 |
$this->queue = $queue;
|
20 |
+
$this->wp_user_preview = $wp_user_preview;
|
21 |
}
|
22 |
|
23 |
function extract($content, $categories = false) {
|
67 |
$_this->newsletter,
|
68 |
$_this->subscriber,
|
69 |
$_this->queue,
|
70 |
+
$content,
|
71 |
+
$_this->wp_user_preview
|
72 |
);
|
73 |
return ($custom_shortcode === $shortcode) ?
|
74 |
false :
|
80 |
$_this->newsletter,
|
81 |
$_this->subscriber,
|
82 |
$_this->queue,
|
83 |
+
$content,
|
84 |
+
$_this->wp_user_preview
|
85 |
);
|
86 |
}, $shortcodes);
|
87 |
return $processed_shortcodes;
|
lib/Newsletter/Url.php
CHANGED
@@ -1,52 +1,80 @@
|
|
1 |
<?php
|
2 |
namespace MailPoet\Newsletter;
|
3 |
|
4 |
-
use MailPoet\Models\SendingQueue;
|
5 |
use MailPoet\Router\Router;
|
6 |
use MailPoet\Router\Endpoints\ViewInBrowser as ViewInBrowserEndpoint;
|
7 |
-
use MailPoet\Models\
|
|
|
8 |
|
9 |
class Url {
|
|
|
|
|
|
|
10 |
static function getViewInBrowserUrl(
|
11 |
-
$
|
|
|
12 |
$subscriber = false,
|
13 |
$queue = false,
|
14 |
$preview = false
|
15 |
) {
|
16 |
-
if(
|
17 |
-
$
|
18 |
-
}
|
19 |
-
if(is_object($subscriber)) {
|
20 |
-
$subscriber = $subscriber->asArray();
|
21 |
-
} else if(!$subscriber) {
|
22 |
-
$subscriber = Subscriber::getCurrentWPUser();
|
23 |
-
$subscriber = ($subscriber) ? $subscriber->asArray() : false;
|
24 |
}
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
}
|
31 |
-
$data =
|
32 |
-
'newsletter_id' => (!empty($newsletter['id'])) ?
|
33 |
-
$newsletter['id'] :
|
34 |
-
$newsletter,
|
35 |
-
'subscriber_id' => (!empty($subscriber['id'])) ?
|
36 |
-
$subscriber['id'] :
|
37 |
-
$subscriber,
|
38 |
-
'subscriber_token' => (!empty($subscriber['id'])) ?
|
39 |
-
Subscriber::generateToken($subscriber['email']) :
|
40 |
-
false,
|
41 |
-
'queue_id' => (!empty($queue['id'])) ?
|
42 |
-
$queue['id'] :
|
43 |
-
$queue,
|
44 |
-
'preview' => $preview
|
45 |
-
);
|
46 |
return Router::buildRequest(
|
47 |
ViewInBrowserEndpoint::ENDPOINT,
|
48 |
ViewInBrowserEndpoint::ACTION_VIEW,
|
49 |
$data
|
50 |
);
|
51 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
52 |
}
|
1 |
<?php
|
2 |
namespace MailPoet\Newsletter;
|
3 |
|
|
|
4 |
use MailPoet\Router\Router;
|
5 |
use MailPoet\Router\Endpoints\ViewInBrowser as ViewInBrowserEndpoint;
|
6 |
+
use MailPoet\Models\Newsletter as NewsletterModel;
|
7 |
+
use MailPoet\Models\Subscriber as SubscriberModel;
|
8 |
|
9 |
class Url {
|
10 |
+
const TYPE_ARCHIVE = 'display_archive';
|
11 |
+
const TYPE_LISTING_EDITOR = 'display_listing_editor';
|
12 |
+
|
13 |
static function getViewInBrowserUrl(
|
14 |
+
$type,
|
15 |
+
NewsletterModel $newsletter,
|
16 |
$subscriber = false,
|
17 |
$queue = false,
|
18 |
$preview = false
|
19 |
) {
|
20 |
+
if($subscriber instanceof SubscriberModel) {
|
21 |
+
$subscriber->token = SubscriberModel::generateToken($subscriber->email);
|
|
|
|
|
|
|
|
|
|
|
|
|
22 |
}
|
23 |
+
switch($type) {
|
24 |
+
case self::TYPE_ARCHIVE:
|
25 |
+
// do not expose newsletter id when displaying archive newsletters
|
26 |
+
$newsletter->id = null;
|
27 |
+
$preview = true;
|
28 |
+
break;
|
29 |
+
case self::TYPE_LISTING_EDITOR:
|
30 |
+
// enable preview and hide newsletter hash when displaying from editor or listings
|
31 |
+
$newsletter->hash = null;
|
32 |
+
$preview = true;
|
33 |
+
break;
|
34 |
+
default:
|
35 |
+
// hide hash for all other display types
|
36 |
+
$newsletter->hash = null;
|
37 |
+
break;
|
38 |
}
|
39 |
+
$data = self::createUrlDataObject($newsletter, $subscriber, $queue, $preview);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
40 |
return Router::buildRequest(
|
41 |
ViewInBrowserEndpoint::ENDPOINT,
|
42 |
ViewInBrowserEndpoint::ACTION_VIEW,
|
43 |
$data
|
44 |
);
|
45 |
}
|
46 |
+
|
47 |
+
static function createUrlDataObject($newsletter, $subscriber, $queue, $preview) {
|
48 |
+
return array(
|
49 |
+
(!empty($newsletter->id)) ?
|
50 |
+
(int)$newsletter->id :
|
51 |
+
0,
|
52 |
+
(!empty($newsletter->hash)) ?
|
53 |
+
$newsletter->hash :
|
54 |
+
0,
|
55 |
+
(!empty($subscriber->id)) ?
|
56 |
+
(int)$subscriber->id :
|
57 |
+
0,
|
58 |
+
(!empty($subscriber->token)) ?
|
59 |
+
$subscriber->token :
|
60 |
+
0,
|
61 |
+
(!empty($queue->id)) ?
|
62 |
+
(int)$queue->id :
|
63 |
+
0,
|
64 |
+
(int)$preview
|
65 |
+
);
|
66 |
+
}
|
67 |
+
|
68 |
+
static function transformUrlDataObject($data) {
|
69 |
+
reset($data);
|
70 |
+
if (!is_int(key($data))) return $data;
|
71 |
+
$transformed_data = array();
|
72 |
+
$transformed_data['newsletter_id'] = (!empty($data[0])) ? $data[0] : false;
|
73 |
+
$transformed_data['newsletter_hash'] = (!empty($data[1])) ? $data[1] : false;
|
74 |
+
$transformed_data['subscriber_id'] = (!empty($data[2])) ? $data[2] : false;
|
75 |
+
$transformed_data['subscriber_token'] = (!empty($data[3])) ? $data[3] : false;
|
76 |
+
$transformed_data['queue_id'] = (!empty($data[4])) ? $data[4] : false;
|
77 |
+
$transformed_data['preview'] = (!empty($data[5])) ? $data[5] : false;
|
78 |
+
return $transformed_data;
|
79 |
+
}
|
80 |
}
|
lib/Newsletter/ViewInBrowser.php
CHANGED
@@ -2,13 +2,17 @@
|
|
2 |
namespace MailPoet\Newsletter;
|
3 |
|
4 |
use MailPoet\Models\Setting;
|
|
|
5 |
use MailPoet\Newsletter\Links\Links;
|
6 |
use MailPoet\Newsletter\Renderer\Renderer;
|
7 |
use MailPoet\Newsletter\Shortcodes\Shortcodes;
|
8 |
|
9 |
class ViewInBrowser {
|
10 |
function view($data) {
|
11 |
-
$wp_user_preview = (
|
|
|
|
|
|
|
12 |
return $this->renderNewsletter(
|
13 |
$data->newsletter,
|
14 |
$data->subscriber,
|
@@ -18,25 +22,36 @@ class ViewInBrowser {
|
|
18 |
}
|
19 |
|
20 |
function renderNewsletter($newsletter, $subscriber, $queue, $wp_user_preview) {
|
21 |
-
if($queue && $queue->
|
22 |
-
$newsletter_body = $queue->getNewsletterRenderedBody();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
23 |
} else {
|
24 |
$renderer = new Renderer($newsletter, $wp_user_preview);
|
25 |
-
$newsletter_body = $renderer->render();
|
26 |
}
|
27 |
$shortcodes = new Shortcodes(
|
28 |
$newsletter,
|
29 |
$subscriber,
|
30 |
-
$queue
|
31 |
-
|
32 |
);
|
33 |
-
$rendered_newsletter = $shortcodes->replace($newsletter_body
|
34 |
-
if($queue && (boolean)Setting::getValue('tracking.enabled')) {
|
35 |
$rendered_newsletter = Links::replaceSubscriberData(
|
36 |
$subscriber->id,
|
37 |
$queue->id,
|
38 |
-
$rendered_newsletter
|
39 |
-
$wp_user_preview
|
40 |
);
|
41 |
}
|
42 |
return $rendered_newsletter;
|
2 |
namespace MailPoet\Newsletter;
|
3 |
|
4 |
use MailPoet\Models\Setting;
|
5 |
+
use MailPoet\Models\Subscriber;
|
6 |
use MailPoet\Newsletter\Links\Links;
|
7 |
use MailPoet\Newsletter\Renderer\Renderer;
|
8 |
use MailPoet\Newsletter\Shortcodes\Shortcodes;
|
9 |
|
10 |
class ViewInBrowser {
|
11 |
function view($data) {
|
12 |
+
$wp_user_preview = (
|
13 |
+
($data->subscriber && $data->subscriber->isWPUser() && $data->preview) ||
|
14 |
+
($data->preview && $data->newsletter_hash)
|
15 |
+
);
|
16 |
return $this->renderNewsletter(
|
17 |
$data->newsletter,
|
18 |
$data->subscriber,
|
22 |
}
|
23 |
|
24 |
function renderNewsletter($newsletter, $subscriber, $queue, $wp_user_preview) {
|
25 |
+
if($queue && $queue->getNewsletterRenderedBody()) {
|
26 |
+
$newsletter_body = $queue->getNewsletterRenderedBody('html');
|
27 |
+
// rendered newsletter body has shortcodes converted to links; we need to
|
28 |
+
// isolate "view in browser", "unsubscribe" and "manage subscription" links
|
29 |
+
// and convert them to shortcodes, which later will be replaced with "#" when
|
30 |
+
// newsletter is previewed
|
31 |
+
if($wp_user_preview && preg_match(Links::getLinkRegex(), $newsletter_body)) {
|
32 |
+
$newsletter_body = Links::convertHashedLinksToShortcodesAndUrls(
|
33 |
+
$newsletter_body,
|
34 |
+
$convert_all = true
|
35 |
+
);
|
36 |
+
// remove open tracking link
|
37 |
+
$newsletter_body = str_replace(Links::DATA_TAG_OPEN, '', $newsletter_body);
|
38 |
+
}
|
39 |
} else {
|
40 |
$renderer = new Renderer($newsletter, $wp_user_preview);
|
41 |
+
$newsletter_body = $renderer->render('html');
|
42 |
}
|
43 |
$shortcodes = new Shortcodes(
|
44 |
$newsletter,
|
45 |
$subscriber,
|
46 |
+
$queue,
|
47 |
+
$wp_user_preview
|
48 |
);
|
49 |
+
$rendered_newsletter = $shortcodes->replace($newsletter_body);
|
50 |
+
if(!$wp_user_preview && $queue && $subscriber && (boolean)Setting::getValue('tracking.enabled')) {
|
51 |
$rendered_newsletter = Links::replaceSubscriberData(
|
52 |
$subscriber->id,
|
53 |
$queue->id,
|
54 |
+
$rendered_newsletter
|
|
|
55 |
);
|
56 |
}
|
57 |
return $rendered_newsletter;
|
lib/Router/Endpoints/Track.php
CHANGED
@@ -5,6 +5,7 @@ use MailPoet\Models\Newsletter;
|
|
5 |
use MailPoet\Models\NewsletterLink;
|
6 |
use MailPoet\Models\SendingQueue;
|
7 |
use MailPoet\Models\Subscriber;
|
|
|
8 |
use MailPoet\Statistics\Track\Clicks;
|
9 |
use MailPoet\Statistics\Track\Opens;
|
10 |
|
@@ -35,10 +36,10 @@ class Track {
|
|
35 |
}
|
36 |
|
37 |
function _processTrackData($data) {
|
38 |
-
$data = (object)$data;
|
39 |
if(empty($data->queue_id) ||
|
40 |
-
|
41 |
-
|
42 |
) {
|
43 |
return false;
|
44 |
}
|
5 |
use MailPoet\Models\NewsletterLink;
|
6 |
use MailPoet\Models\SendingQueue;
|
7 |
use MailPoet\Models\Subscriber;
|
8 |
+
use MailPoet\Newsletter\Links\Links;
|
9 |
use MailPoet\Statistics\Track\Clicks;
|
10 |
use MailPoet\Statistics\Track\Opens;
|
11 |
|
36 |
}
|
37 |
|
38 |
function _processTrackData($data) {
|
39 |
+
$data = (object)Links::transformUrlDataObject($data);
|
40 |
if(empty($data->queue_id) ||
|
41 |
+
empty($data->subscriber_id) ||
|
42 |
+
empty($data->subscriber_token)
|
43 |
) {
|
44 |
return false;
|
45 |
}
|
lib/Router/Endpoints/ViewInBrowser.php
CHANGED
@@ -1,13 +1,17 @@
|
|
1 |
<?php
|
2 |
namespace MailPoet\Router\Endpoints;
|
3 |
|
|
|
4 |
use MailPoet\Models\Newsletter;
|
5 |
use MailPoet\Models\SendingQueue;
|
6 |
use MailPoet\Models\Subscriber;
|
|
|
7 |
use MailPoet\Newsletter\ViewInBrowser as NewsletterViewInBrowser;
|
8 |
|
9 |
if(!defined('ABSPATH')) exit;
|
10 |
|
|
|
|
|
11 |
class ViewInBrowser {
|
12 |
const ENDPOINT = 'view_in_browser';
|
13 |
const ACTION_VIEW = 'view';
|
@@ -24,37 +28,52 @@ class ViewInBrowser {
|
|
24 |
}
|
25 |
|
26 |
function _processBrowserPreviewData($data) {
|
27 |
-
$data = (object)$data;
|
28 |
-
|
29 |
-
|
30 |
-
empty($data->newsletter_id)
|
31 |
-
) {
|
32 |
$this->_abort();
|
33 |
-
} else {
|
34 |
-
$data->newsletter = Newsletter::findOne($data->newsletter_id);
|
35 |
-
$data->subscriber = Subscriber::findOne($data->subscriber_id);
|
36 |
-
$data->queue = ($data->queue_id) ?
|
37 |
-
SendingQueue::findOne($data->queue_id) :
|
38 |
-
false;
|
39 |
-
return ($this->_validateBrowserPreviewData($data)) ?
|
40 |
-
$data :
|
41 |
-
$this->_abort();
|
42 |
-
}
|
43 |
}
|
44 |
|
45 |
function _validateBrowserPreviewData($data) {
|
46 |
-
|
47 |
-
$
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
if(
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
|
|
|
|
|
|
|
|
57 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
58 |
return $data;
|
59 |
}
|
60 |
|
1 |
<?php
|
2 |
namespace MailPoet\Router\Endpoints;
|
3 |
|
4 |
+
use MailPoet\Config\Env;
|
5 |
use MailPoet\Models\Newsletter;
|
6 |
use MailPoet\Models\SendingQueue;
|
7 |
use MailPoet\Models\Subscriber;
|
8 |
+
use MailPoet\Newsletter\Url as NewsletterUrl;
|
9 |
use MailPoet\Newsletter\ViewInBrowser as NewsletterViewInBrowser;
|
10 |
|
11 |
if(!defined('ABSPATH')) exit;
|
12 |
|
13 |
+
require_once(ABSPATH . 'wp-includes/pluggable.php');
|
14 |
+
|
15 |
class ViewInBrowser {
|
16 |
const ENDPOINT = 'view_in_browser';
|
17 |
const ACTION_VIEW = 'view';
|
28 |
}
|
29 |
|
30 |
function _processBrowserPreviewData($data) {
|
31 |
+
$data = (object)NewsletterUrl::transformUrlDataObject($data);
|
32 |
+
return ($this->_validateBrowserPreviewData($data)) ?
|
33 |
+
$data :
|
|
|
|
|
34 |
$this->_abort();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
35 |
}
|
36 |
|
37 |
function _validateBrowserPreviewData($data) {
|
38 |
+
// either newsletter ID or hash must be defined, and newsletter must exist
|
39 |
+
if(empty($data->newsletter_id) && empty($data->newsletter_hash)) return false;
|
40 |
+
$data->newsletter = (!empty($data->newsletter_hash)) ?
|
41 |
+
Newsletter::getByHash($data->newsletter_hash) :
|
42 |
+
Newsletter::findOne($data->newsletter_id);
|
43 |
+
if(!$data->newsletter) return false;
|
44 |
+
|
45 |
+
// subscriber is optional; if exists, token must validate
|
46 |
+
$data->subscriber = (!empty($data->subscriber_id)) ?
|
47 |
+
Subscriber::findOne($data->subscriber_id) :
|
48 |
+
false;
|
49 |
+
if($data->subscriber) {
|
50 |
+
if(empty($data->subscriber_token) ||
|
51 |
+
!Subscriber::verifyToken($data->subscriber->email, $data->subscriber_token)
|
52 |
+
) return false;
|
53 |
}
|
54 |
+
|
55 |
+
// if newsletter ID is defined then subscriber must exist
|
56 |
+
if($data->newsletter_id && !$data->subscriber) return false;
|
57 |
+
|
58 |
+
// queue is optional; if defined, get it
|
59 |
+
$data->queue = (!empty($data->queue_id)) ?
|
60 |
+
SendingQueue::findOne($data->queue_id) :
|
61 |
+
SendingQueue::where('newsletter_id', $data->newsletter->id)->findOne();
|
62 |
+
|
63 |
+
// allow users with 'manage_options' permission to preview any newsletter
|
64 |
+
if(!empty($data->preview) && current_user_can(Env::$required_permission)
|
65 |
+
) return $data;
|
66 |
+
|
67 |
+
// allow others to preview newsletters only when newsletter hash is defined
|
68 |
+
if(!empty($data->preview) && empty($data->newsletter_hash)
|
69 |
+
) return false;
|
70 |
+
|
71 |
+
// if queue and subscriber exist, subscriber must have received the newsletter
|
72 |
+
if($data->queue &&
|
73 |
+
$data->subscriber &&
|
74 |
+
!$data->queue->isSubscriberProcessed($data->subscriber->id)
|
75 |
+
) return false;
|
76 |
+
|
77 |
return $data;
|
78 |
}
|
79 |
|
lib/Subscription/Pages.php
CHANGED
@@ -297,6 +297,10 @@ class Pages {
|
|
297 |
),
|
298 |
'is_checked' => (
|
299 |
$subscriber->status === Subscriber::STATUS_BOUNCED
|
|
|
|
|
|
|
|
|
300 |
)
|
301 |
)
|
302 |
)
|
297 |
),
|
298 |
'is_checked' => (
|
299 |
$subscriber->status === Subscriber::STATUS_BOUNCED
|
300 |
+
),
|
301 |
+
'is_disabled' => true,
|
302 |
+
'is_hidden' => (
|
303 |
+
$subscriber->status !== Subscriber::STATUS_BOUNCED
|
304 |
)
|
305 |
)
|
306 |
)
|
mailpoet.php
CHANGED
@@ -5,7 +5,7 @@ use MailPoet\Config\Initializer;
|
|
5 |
|
6 |
/*
|
7 |
* Plugin Name: MailPoet
|
8 |
-
* Version: 3.0.0-beta.
|
9 |
* Plugin URI: http://www.mailpoet.com
|
10 |
* Description: Create and send beautiful email newsletters, autoresponders, and post notifications without leaving WordPress. This is a beta version of our brand new plugin!
|
11 |
* Author: MailPoet
|
@@ -24,7 +24,7 @@ use MailPoet\Config\Initializer;
|
|
24 |
$mailpoet_loader = dirname(__FILE__) . '/vendor/autoload.php';
|
25 |
if(file_exists($mailpoet_loader)) {
|
26 |
require $mailpoet_loader;
|
27 |
-
define('MAILPOET_VERSION', '3.0.0-beta.
|
28 |
$initializer = new Initializer(
|
29 |
array(
|
30 |
'file' => __FILE__,
|
5 |
|
6 |
/*
|
7 |
* Plugin Name: MailPoet
|
8 |
+
* Version: 3.0.0-beta.10
|
9 |
* Plugin URI: http://www.mailpoet.com
|
10 |
* Description: Create and send beautiful email newsletters, autoresponders, and post notifications without leaving WordPress. This is a beta version of our brand new plugin!
|
11 |
* Author: MailPoet
|
24 |
$mailpoet_loader = dirname(__FILE__) . '/vendor/autoload.php';
|
25 |
if(file_exists($mailpoet_loader)) {
|
26 |
require $mailpoet_loader;
|
27 |
+
define('MAILPOET_VERSION', '3.0.0-beta.10');
|
28 |
$initializer = new Initializer(
|
29 |
array(
|
30 |
'file' => __FILE__,
|
readme.txt
CHANGED
@@ -3,7 +3,7 @@ Contributors: mailpoet, wysija
|
|
3 |
Tags: newsletter, email, welcome email, post notification, autoresponder, mailchimp, signup, smtp
|
4 |
Requires at least: 4.6
|
5 |
Tested up to: 4.7
|
6 |
-
Stable tag: 3.0.0-beta.
|
7 |
Create and send beautiful emails and newsletters from WordPress.
|
8 |
|
9 |
== Description ==
|
@@ -83,13 +83,22 @@ Our [support site](https://docs.mailpoet.com/) has plenty of articles. You can w
|
|
83 |
|
84 |
== Changelog ==
|
85 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
86 |
= 3.0.0-beta.9 - 2016-12-20 =
|
87 |
-
* Improved: the plugin is now tested up to WP 4.7
|
88 |
-
* Improved: MailPoet's sending service bounce status API update
|
89 |
-
* Improved: change duplicate subscribers import message to be more descriptive
|
90 |
-
* Fixed: database character set and time zone setup
|
91 |
-
* Fixed: alignment of post titles inside notificaiton emails
|
92 |
-
* Fixed: partially generated or missing translations from .pot file
|
93 |
|
94 |
= 3.0.0-beta.8 - 2016-12-13 =
|
95 |
* Added: MailPoet's sending service can now sync hard bounced addresses with the plugin to keep your lists tidy and clean;
|
@@ -151,12 +160,12 @@ Our [support site](https://docs.mailpoet.com/) has plenty of articles. You can w
|
|
151 |
* Fixed newsletter number shortcode for notification newsletters;
|
152 |
* Enhanced HelpScout support beacon report with extra support data;
|
153 |
* Fixed email renderer to not throw entity warnings on earlier PHP versions;
|
154 |
-
* Fixed newsletter preview incompatibility errors for earlier PHP versions
|
155 |
|
156 |
= 3.0.0-beta.2 - 2016-10 =
|
157 |
|
158 |
* Fixed compatibility issues with PHP versions earlier than PHP 5.6;
|
159 |
-
* Renamed 'Emails' email type to 'Newsletters'
|
160 |
|
161 |
= 3.0.0-beta.1 - 2016-10 =
|
162 |
|
3 |
Tags: newsletter, email, welcome email, post notification, autoresponder, mailchimp, signup, smtp
|
4 |
Requires at least: 4.6
|
5 |
Tested up to: 4.7
|
6 |
+
Stable tag: 3.0.0-beta.10
|
7 |
Create and send beautiful emails and newsletters from WordPress.
|
8 |
|
9 |
== Description ==
|
83 |
|
84 |
== Changelog ==
|
85 |
|
86 |
+
= 3.0.0-beta.10 - 2016-12-27 =
|
87 |
+
* Improved: newsletter is saved prior to sending an email preview;
|
88 |
+
* Improved: subscription management page conditionally displays the "bounced" status;
|
89 |
+
* Improved: deleted lists are displayed in newsletter listings;
|
90 |
+
* Fixed: newsletter/subscriber/list/form dates are properly formatted according to WP settings;
|
91 |
+
* Fixed: emails' "Return-path" header is set to the bounce address configured in Settings->Advanced;
|
92 |
+
* Fixed: archived newsletters' shortcode works for site visitors;
|
93 |
+
* Fixed: unicode support for newsletters.
|
94 |
+
|
95 |
= 3.0.0-beta.9 - 2016-12-20 =
|
96 |
+
* Improved: the plugin is now tested up to WP 4.7;
|
97 |
+
* Improved: MailPoet's sending service bounce status API update;
|
98 |
+
* Improved: change duplicate subscribers import message to be more descriptive;
|
99 |
+
* Fixed: database character set and time zone setup;
|
100 |
+
* Fixed: alignment of post titles inside notificaiton emails;
|
101 |
+
* Fixed: partially generated or missing translations from .pot file.
|
102 |
|
103 |
= 3.0.0-beta.8 - 2016-12-13 =
|
104 |
* Added: MailPoet's sending service can now sync hard bounced addresses with the plugin to keep your lists tidy and clean;
|
160 |
* Fixed newsletter number shortcode for notification newsletters;
|
161 |
* Enhanced HelpScout support beacon report with extra support data;
|
162 |
* Fixed email renderer to not throw entity warnings on earlier PHP versions;
|
163 |
+
* Fixed newsletter preview incompatibility errors for earlier PHP versions.
|
164 |
|
165 |
= 3.0.0-beta.2 - 2016-10 =
|
166 |
|
167 |
* Fixed compatibility issues with PHP versions earlier than PHP 5.6;
|
168 |
+
* Renamed 'Emails' email type to 'Newsletters'.
|
169 |
|
170 |
= 3.0.0-beta.1 - 2016-10 =
|
171 |
|
vendor/autoload.php
CHANGED
@@ -4,4 +4,4 @@
|
|
4 |
|
5 |
require_once __DIR__ . '/composer/autoload_real.php';
|
6 |
|
7 |
-
return
|
4 |
|
5 |
require_once __DIR__ . '/composer/autoload_real.php';
|
6 |
|
7 |
+
return ComposerAutoloaderInit9478c93be8e1a8371b13c6b1b2795224::getLoader();
|
vendor/composer/ClassLoader.php
CHANGED
@@ -55,6 +55,7 @@ class ClassLoader
|
|
55 |
private $classMap = array();
|
56 |
private $classMapAuthoritative = false;
|
57 |
private $missingClasses = array();
|
|
|
58 |
|
59 |
public function getPrefixes()
|
60 |
{
|
@@ -271,6 +272,26 @@ class ClassLoader
|
|
271 |
return $this->classMapAuthoritative;
|
272 |
}
|
273 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
274 |
/**
|
275 |
* Registers this instance as an autoloader.
|
276 |
*
|
@@ -313,11 +334,6 @@ class ClassLoader
|
|
313 |
*/
|
314 |
public function findFile($class)
|
315 |
{
|
316 |
-
// work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
|
317 |
-
if ('\\' == $class[0]) {
|
318 |
-
$class = substr($class, 1);
|
319 |
-
}
|
320 |
-
|
321 |
// class map lookup
|
322 |
if (isset($this->classMap[$class])) {
|
323 |
return $this->classMap[$class];
|
@@ -325,6 +341,12 @@ class ClassLoader
|
|
325 |
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
|
326 |
return false;
|
327 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
328 |
|
329 |
$file = $this->findFileWithExtension($class, '.php');
|
330 |
|
@@ -333,6 +355,10 @@ class ClassLoader
|
|
333 |
$file = $this->findFileWithExtension($class, '.hh');
|
334 |
}
|
335 |
|
|
|
|
|
|
|
|
|
336 |
if (false === $file) {
|
337 |
// Remember that this class does not exist.
|
338 |
$this->missingClasses[$class] = true;
|
55 |
private $classMap = array();
|
56 |
private $classMapAuthoritative = false;
|
57 |
private $missingClasses = array();
|
58 |
+
private $apcuPrefix;
|
59 |
|
60 |
public function getPrefixes()
|
61 |
{
|
272 |
return $this->classMapAuthoritative;
|
273 |
}
|
274 |
|
275 |
+
/**
|
276 |
+
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
|
277 |
+
*
|
278 |
+
* @param string|null $apcuPrefix
|
279 |
+
*/
|
280 |
+
public function setApcuPrefix($apcuPrefix)
|
281 |
+
{
|
282 |
+
$this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null;
|
283 |
+
}
|
284 |
+
|
285 |
+
/**
|
286 |
+
* The APCu prefix in use, or null if APCu caching is not enabled.
|
287 |
+
*
|
288 |
+
* @return string|null
|
289 |
+
*/
|
290 |
+
public function getApcuPrefix()
|
291 |
+
{
|
292 |
+
return $this->apcuPrefix;
|
293 |
+
}
|
294 |
+
|
295 |
/**
|
296 |
* Registers this instance as an autoloader.
|
297 |
*
|
334 |
*/
|
335 |
public function findFile($class)
|
336 |
{
|
|
|
|
|
|
|
|
|
|
|
337 |
// class map lookup
|
338 |
if (isset($this->classMap[$class])) {
|
339 |
return $this->classMap[$class];
|
341 |
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
|
342 |
return false;
|
343 |
}
|
344 |
+
if (null !== $this->apcuPrefix) {
|
345 |
+
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
|
346 |
+
if ($hit) {
|
347 |
+
return $file;
|
348 |
+
}
|
349 |
+
}
|
350 |
|
351 |
$file = $this->findFileWithExtension($class, '.php');
|
352 |
|
355 |
$file = $this->findFileWithExtension($class, '.hh');
|
356 |
}
|
357 |
|
358 |
+
if (null !== $this->apcuPrefix) {
|
359 |
+
apcu_add($this->apcuPrefix.$class, $file);
|
360 |
+
}
|
361 |
+
|
362 |
if (false === $file) {
|
363 |
// Remember that this class does not exist.
|
364 |
$this->missingClasses[$class] = true;
|
vendor/composer/autoload_real.php
CHANGED
@@ -2,7 +2,7 @@
|
|
2 |
|
3 |
// autoload_real.php @generated by Composer
|
4 |
|
5 |
-
class
|
6 |
{
|
7 |
private static $loader;
|
8 |
|
@@ -19,15 +19,15 @@ class ComposerAutoloaderInitba2ce543f2a04ae11d74280bc49a2040
|
|
19 |
return self::$loader;
|
20 |
}
|
21 |
|
22 |
-
spl_autoload_register(array('
|
23 |
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
|
24 |
-
spl_autoload_unregister(array('
|
25 |
|
26 |
-
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION');
|
27 |
if ($useStaticLoader) {
|
28 |
require_once __DIR__ . '/autoload_static.php';
|
29 |
|
30 |
-
call_user_func(\Composer\Autoload\
|
31 |
} else {
|
32 |
$map = require __DIR__ . '/autoload_namespaces.php';
|
33 |
foreach ($map as $namespace => $path) {
|
@@ -48,19 +48,19 @@ class ComposerAutoloaderInitba2ce543f2a04ae11d74280bc49a2040
|
|
48 |
$loader->register(true);
|
49 |
|
50 |
if ($useStaticLoader) {
|
51 |
-
$includeFiles = Composer\Autoload\
|
52 |
} else {
|
53 |
$includeFiles = require __DIR__ . '/autoload_files.php';
|
54 |
}
|
55 |
foreach ($includeFiles as $fileIdentifier => $file) {
|
56 |
-
|
57 |
}
|
58 |
|
59 |
return $loader;
|
60 |
}
|
61 |
}
|
62 |
|
63 |
-
function
|
64 |
{
|
65 |
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
|
66 |
require $file;
|
2 |
|
3 |
// autoload_real.php @generated by Composer
|
4 |
|
5 |
+
class ComposerAutoloaderInit9478c93be8e1a8371b13c6b1b2795224
|
6 |
{
|
7 |
private static $loader;
|
8 |
|
19 |
return self::$loader;
|
20 |
}
|
21 |
|
22 |
+
spl_autoload_register(array('ComposerAutoloaderInit9478c93be8e1a8371b13c6b1b2795224', 'loadClassLoader'), true, true);
|
23 |
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
|
24 |
+
spl_autoload_unregister(array('ComposerAutoloaderInit9478c93be8e1a8371b13c6b1b2795224', 'loadClassLoader'));
|
25 |
|
26 |
+
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
|
27 |
if ($useStaticLoader) {
|
28 |
require_once __DIR__ . '/autoload_static.php';
|
29 |
|
30 |
+
call_user_func(\Composer\Autoload\ComposerStaticInit9478c93be8e1a8371b13c6b1b2795224::getInitializer($loader));
|
31 |
} else {
|
32 |
$map = require __DIR__ . '/autoload_namespaces.php';
|
33 |
foreach ($map as $namespace => $path) {
|
48 |
$loader->register(true);
|
49 |
|
50 |
if ($useStaticLoader) {
|
51 |
+
$includeFiles = Composer\Autoload\ComposerStaticInit9478c93be8e1a8371b13c6b1b2795224::$files;
|
52 |
} else {
|
53 |
$includeFiles = require __DIR__ . '/autoload_files.php';
|
54 |
}
|
55 |
foreach ($includeFiles as $fileIdentifier => $file) {
|
56 |
+
composerRequire9478c93be8e1a8371b13c6b1b2795224($fileIdentifier, $file);
|
57 |
}
|
58 |
|
59 |
return $loader;
|
60 |
}
|
61 |
}
|
62 |
|
63 |
+
function composerRequire9478c93be8e1a8371b13c6b1b2795224($fileIdentifier, $file)
|
64 |
{
|
65 |
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
|
66 |
require $file;
|
vendor/composer/autoload_static.php
CHANGED
@@ -4,7 +4,7 @@
|
|
4 |
|
5 |
namespace Composer\Autoload;
|
6 |
|
7 |
-
class
|
8 |
{
|
9 |
public static $files = array (
|
10 |
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
|
@@ -555,10 +555,10 @@ class ComposerStaticInitba2ce543f2a04ae11d74280bc49a2040
|
|
555 |
public static function getInitializer(ClassLoader $loader)
|
556 |
{
|
557 |
return \Closure::bind(function () use ($loader) {
|
558 |
-
$loader->prefixLengthsPsr4 =
|
559 |
-
$loader->prefixDirsPsr4 =
|
560 |
-
$loader->prefixesPsr0 =
|
561 |
-
$loader->classMap =
|
562 |
|
563 |
}, null, ClassLoader::class);
|
564 |
}
|
4 |
|
5 |
namespace Composer\Autoload;
|
6 |
|
7 |
+
class ComposerStaticInit9478c93be8e1a8371b13c6b1b2795224
|
8 |
{
|
9 |
public static $files = array (
|
10 |
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
|
555 |
public static function getInitializer(ClassLoader $loader)
|
556 |
{
|
557 |
return \Closure::bind(function () use ($loader) {
|
558 |
+
$loader->prefixLengthsPsr4 = ComposerStaticInit9478c93be8e1a8371b13c6b1b2795224::$prefixLengthsPsr4;
|
559 |
+
$loader->prefixDirsPsr4 = ComposerStaticInit9478c93be8e1a8371b13c6b1b2795224::$prefixDirsPsr4;
|
560 |
+
$loader->prefixesPsr0 = ComposerStaticInit9478c93be8e1a8371b13c6b1b2795224::$prefixesPsr0;
|
561 |
+
$loader->classMap = ComposerStaticInit9478c93be8e1a8371b13c6b1b2795224::$classMap;
|
562 |
|
563 |
}, null, ClassLoader::class);
|
564 |
}
|
vendor/composer/installed.json
CHANGED
@@ -14,7 +14,7 @@
|
|
14 |
"reference": "b0c1bda3be5a35da44ba1ac28cc61c67d2ada465",
|
15 |
"shasum": ""
|
16 |
},
|
17 |
-
"time": "2015-11-
|
18 |
"type": "library",
|
19 |
"installation-source": "dist",
|
20 |
"autoload": {
|
@@ -52,7 +52,7 @@
|
|
52 |
"require": {
|
53 |
"php": ">=5.2.0"
|
54 |
},
|
55 |
-
"time": "2014-06-
|
56 |
"type": "library",
|
57 |
"installation-source": "dist",
|
58 |
"autoload": {
|
@@ -113,7 +113,7 @@
|
|
113 |
"j4mie/idiorm": "1.5.*",
|
114 |
"php": ">=5.2.0"
|
115 |
},
|
116 |
-
"time": "2014-09-
|
117 |
"type": "library",
|
118 |
"installation-source": "dist",
|
119 |
"autoload": {
|
@@ -177,7 +177,7 @@
|
|
177 |
"require-dev": {
|
178 |
"phpunit/phpunit": "~4.0|~5.0"
|
179 |
},
|
180 |
-
"time": "2016-01-
|
181 |
"type": "library",
|
182 |
"installation-source": "dist",
|
183 |
"autoload": {
|
@@ -223,7 +223,7 @@
|
|
223 |
"suggest": {
|
224 |
"ext-mbstring": "For best performance"
|
225 |
},
|
226 |
-
"time": "2016-05-
|
227 |
"type": "library",
|
228 |
"extra": {
|
229 |
"branch-alias": {
|
@@ -296,7 +296,7 @@
|
|
296 |
"symfony/config": "",
|
297 |
"symfony/yaml": ""
|
298 |
},
|
299 |
-
"time": "2016-10-
|
300 |
"type": "library",
|
301 |
"extra": {
|
302 |
"branch-alias": {
|
@@ -351,7 +351,7 @@
|
|
351 |
"require-dev": {
|
352 |
"phpunit/phpunit": "~4.0|~5.0"
|
353 |
},
|
354 |
-
"time": "2015-11-
|
355 |
"type": "library",
|
356 |
"installation-source": "dist",
|
357 |
"autoload": {
|
@@ -402,7 +402,7 @@
|
|
402 |
"phpunit/phpunit": ">=4.0",
|
403 |
"soundasleep/component-tests": "dev-master"
|
404 |
},
|
405 |
-
"time": "2016-07-
|
406 |
"type": "library",
|
407 |
"installation-source": "dist",
|
408 |
"autoload": {
|
@@ -454,7 +454,7 @@
|
|
454 |
"require-dev": {
|
455 |
"mockery/mockery": "~0.9.1"
|
456 |
},
|
457 |
-
"time": "2016-07-
|
458 |
"type": "library",
|
459 |
"extra": {
|
460 |
"branch-alias": {
|
@@ -509,7 +509,7 @@
|
|
509 |
"require-dev": {
|
510 |
"htmlawed/htmlawed": "dev-master"
|
511 |
},
|
512 |
-
"time": "2016-01-
|
513 |
"type": "library",
|
514 |
"installation-source": "dist",
|
515 |
"autoload": {
|
@@ -564,7 +564,7 @@
|
|
564 |
"symfony/debug": "~2.7",
|
565 |
"symfony/phpunit-bridge": "~2.7"
|
566 |
},
|
567 |
-
"time": "2016-10-
|
568 |
"type": "library",
|
569 |
"extra": {
|
570 |
"branch-alias": {
|
14 |
"reference": "b0c1bda3be5a35da44ba1ac28cc61c67d2ada465",
|
15 |
"shasum": ""
|
16 |
},
|
17 |
+
"time": "2015-11-28T21:47:43+00:00",
|
18 |
"type": "library",
|
19 |
"installation-source": "dist",
|
20 |
"autoload": {
|
52 |
"require": {
|
53 |
"php": ">=5.2.0"
|
54 |
},
|
55 |
+
"time": "2014-06-23T13:08:57+00:00",
|
56 |
"type": "library",
|
57 |
"installation-source": "dist",
|
58 |
"autoload": {
|
113 |
"j4mie/idiorm": "1.5.*",
|
114 |
"php": ">=5.2.0"
|
115 |
},
|
116 |
+
"time": "2014-09-23T10:49:36+00:00",
|
117 |
"type": "library",
|
118 |
"installation-source": "dist",
|
119 |
"autoload": {
|
177 |
"require-dev": {
|
178 |
"phpunit/phpunit": "~4.0|~5.0"
|
179 |
},
|
180 |
+
"time": "2016-01-26T21:23:30+00:00",
|
181 |
"type": "library",
|
182 |
"installation-source": "dist",
|
183 |
"autoload": {
|
223 |
"suggest": {
|
224 |
"ext-mbstring": "For best performance"
|
225 |
},
|
226 |
+
"time": "2016-05-18T14:26:46+00:00",
|
227 |
"type": "library",
|
228 |
"extra": {
|
229 |
"branch-alias": {
|
296 |
"symfony/config": "",
|
297 |
"symfony/yaml": ""
|
298 |
},
|
299 |
+
"time": "2016-10-18T04:28:30+00:00",
|
300 |
"type": "library",
|
301 |
"extra": {
|
302 |
"branch-alias": {
|
351 |
"require-dev": {
|
352 |
"phpunit/phpunit": "~4.0|~5.0"
|
353 |
},
|
354 |
+
"time": "2015-11-04T20:07:17+00:00",
|
355 |
"type": "library",
|
356 |
"installation-source": "dist",
|
357 |
"autoload": {
|
402 |
"phpunit/phpunit": ">=4.0",
|
403 |
"soundasleep/component-tests": "dev-master"
|
404 |
},
|
405 |
+
"time": "2016-07-28T01:09:53+00:00",
|
406 |
"type": "library",
|
407 |
"installation-source": "dist",
|
408 |
"autoload": {
|
454 |
"require-dev": {
|
455 |
"mockery/mockery": "~0.9.1"
|
456 |
},
|
457 |
+
"time": "2016-07-08T11:51:25+00:00",
|
458 |
"type": "library",
|
459 |
"extra": {
|
460 |
"branch-alias": {
|
509 |
"require-dev": {
|
510 |
"htmlawed/htmlawed": "dev-master"
|
511 |
},
|
512 |
+
"time": "2016-01-14T20:55:00+00:00",
|
513 |
"type": "library",
|
514 |
"installation-source": "dist",
|
515 |
"autoload": {
|
564 |
"symfony/debug": "~2.7",
|
565 |
"symfony/phpunit-bridge": "~2.7"
|
566 |
},
|
567 |
+
"time": "2016-10-25T19:17:17+00:00",
|
568 |
"type": "library",
|
569 |
"extra": {
|
570 |
"branch-alias": {
|