Page Builder by SiteOrigin - Version 2.10.7

Version Description

  • 20 August 2019 =
  • Added setting for mobile specific margin.
  • Prevent Welcome Page Redirect During Bulk Install and TGMPA
  • Added support for password settings field.
  • Layout Block: Add filter to control whether Add Layout Block button is shown or not.
  • Fixed issue with widget duplication after moving a widget.
  • Fixed Read More Custom Text issue.
Download this release

Release Info

Developer gpriday
Plugin Icon 128x128 Page Builder by SiteOrigin
Version 2.10.7
Comparing to
See all releases

Code changes from version 2.10.6 to 2.10.7

compat/js/siteorigin-panels-layout-block.js CHANGED
@@ -1,352 +1,352 @@
1
- "use strict";
2
-
3
- function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
4
-
5
- function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
6
-
7
- function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
8
-
9
- function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
10
-
11
- function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
12
-
13
- function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
14
-
15
- function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
16
-
17
- function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
18
-
19
- function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
20
-
21
- var _lodash = lodash,
22
- isEqual = _lodash.isEqual,
23
- debounce = _lodash.debounce,
24
- isEmpty = _lodash.isEmpty,
25
- isFunction = _lodash.isFunction;
26
- var registerBlockType = wp.blocks.registerBlockType;
27
- var _wp$element = wp.element,
28
- Component = _wp$element.Component,
29
- Fragment = _wp$element.Fragment,
30
- RawHTML = _wp$element.RawHTML,
31
- createRef = _wp$element.createRef;
32
- var BlockControls = wp.editor.BlockControls;
33
- var _wp$components = wp.components,
34
- Toolbar = _wp$components.Toolbar,
35
- IconButton = _wp$components.IconButton,
36
- Spinner = _wp$components.Spinner;
37
- var __ = wp.i18n.__;
38
- var _window = window,
39
- soPanelsBlockEditorAdmin = _window.soPanelsBlockEditorAdmin;
40
-
41
- var SiteOriginPanelsLayoutBlock =
42
- /*#__PURE__*/
43
- function (_Component) {
44
- _inherits(SiteOriginPanelsLayoutBlock, _Component);
45
-
46
- function SiteOriginPanelsLayoutBlock(props) {
47
- var _this;
48
-
49
- _classCallCheck(this, SiteOriginPanelsLayoutBlock);
50
-
51
- _this = _possibleConstructorReturn(this, _getPrototypeOf(SiteOriginPanelsLayoutBlock).call(this, props));
52
- var editMode = soPanelsBlockEditorAdmin.defaultMode === 'edit' || isEmpty(props.panelsData);
53
- _this.state = {
54
- editing: editMode,
55
- loadingPreview: !editMode,
56
- previewHtml: ''
57
- };
58
- _this.panelsContainer = createRef();
59
- _this.previewContainer = createRef();
60
- _this.panelsInitialized = false;
61
- _this.previewInitialized = false;
62
- return _this;
63
- }
64
-
65
- _createClass(SiteOriginPanelsLayoutBlock, [{
66
- key: "componentDidMount",
67
- value: function componentDidMount() {
68
- this.isStillMounted = true;
69
-
70
- if (this.state.editing) {
71
- this.setupPanels();
72
- } else if (!this.state.editing && !this.previewInitialized) {
73
- this.fetchPreview(this.props);
74
- this.fetchPreview = debounce(this.fetchPreview, 500);
75
- }
76
- }
77
- }, {
78
- key: "componentWillUnmount",
79
- value: function componentWillUnmount() {
80
- this.isStillMounted = false;
81
-
82
- if (this.builderView) {
83
- this.builderView.off('content_change');
84
- }
85
- }
86
- }, {
87
- key: "componentDidUpdate",
88
- value: function componentDidUpdate(prevProps) {
89
- // let propsChanged = !isEqual( prevProps.panelsData, this.props.panelsData );
90
- if (this.state.editing && !this.panelsInitialized) {
91
- this.setupPanels();
92
- } else if (this.state.loadingPreview) {
93
- this.fetchPreview(this.props);
94
- } else if (!this.previewInitialized && this.previewContainer.current) {
95
- jQuery(document).trigger('panels_setup_preview');
96
- this.previewInitialized = true;
97
- }
98
- }
99
- }, {
100
- key: "setupPanels",
101
- value: function setupPanels() {
102
- var _this2 = this;
103
-
104
- var $panelsContainer = jQuery(this.panelsContainer.current);
105
- var config = {
106
- editorType: 'standalone'
107
- };
108
- var builderModel = new panels.model.builder();
109
- this.builderView = new panels.view.builder({
110
- model: builderModel,
111
- config: config
112
- }); // Make sure panelsData is defined and clone so that we don't alter the underlying attribute.
113
-
114
- var panelsData = JSON.parse(JSON.stringify(jQuery.extend({}, this.props.panelsData))); // Disable block selection while dragging rows or widgets.
115
-
116
- var rowOrWidgetMouseDown = function rowOrWidgetMouseDown() {
117
- if (isFunction(_this2.props.onRowOrWidgetMouseDown)) {
118
- _this2.props.onRowOrWidgetMouseDown();
119
- }
120
-
121
- var rowOrWidgetMouseUp = function rowOrWidgetMouseUp() {
122
- jQuery(document).off('mouseup', rowOrWidgetMouseUp);
123
-
124
- if (isFunction(_this2.props.onRowOrWidgetMouseUp)) {
125
- _this2.props.onRowOrWidgetMouseUp();
126
- }
127
- };
128
-
129
- jQuery(document).on('mouseup', rowOrWidgetMouseUp);
130
- };
131
-
132
- this.builderView.on('row_added', function () {
133
- _this2.builderView.$('.so-row-move').off('mousedown', rowOrWidgetMouseDown);
134
-
135
- _this2.builderView.$('.so-row-move').on('mousedown', rowOrWidgetMouseDown);
136
-
137
- _this2.builderView.$('.so-widget').off('mousedown', rowOrWidgetMouseDown);
138
-
139
- _this2.builderView.$('.so-widget').on('mousedown', rowOrWidgetMouseDown);
140
- });
141
- this.builderView.on('widget_added', function () {
142
- _this2.builderView.$('.so-widget').off('mousedown', rowOrWidgetMouseDown);
143
-
144
- _this2.builderView.$('.so-widget').on('mousedown', rowOrWidgetMouseDown);
145
- });
146
- this.builderView.render().attach({
147
- container: $panelsContainer
148
- }).setData(panelsData);
149
- this.builderView.trigger('builder_resize');
150
- this.builderView.on('content_change', function () {
151
- var newPanelsData = _this2.builderView.getData();
152
-
153
- _this2.panelsDataChanged = !isEqual(panelsData, newPanelsData);
154
-
155
- if (_this2.panelsDataChanged) {
156
- if (_this2.props.onContentChange && isFunction(_this2.props.onContentChange)) {
157
- _this2.props.onContentChange(newPanelsData);
158
- }
159
-
160
- _this2.setState({
161
- loadingPreview: true,
162
- previewHtml: ''
163
- });
164
- }
165
- });
166
- jQuery(document).trigger('panels_setup', this.builderView);
167
- this.panelsInitialized = true;
168
- }
169
- }, {
170
- key: "fetchPreview",
171
- value: function fetchPreview(props) {
172
- var _this3 = this;
173
-
174
- if (!this.isStillMounted) {
175
- return;
176
- }
177
-
178
- this.previewInitialized = false;
179
- var fetchRequest = this.currentFetchRequest = jQuery.post({
180
- url: soPanelsBlockEditorAdmin.previewUrl,
181
- data: {
182
- action: 'so_panels_layout_block_preview',
183
- panelsData: JSON.stringify(props.panelsData)
184
- }
185
- }).then(function (preview) {
186
- if (_this3.isStillMounted && fetchRequest === _this3.currentFetchRequest && preview) {
187
- _this3.setState({
188
- previewHtml: preview,
189
- loadingPreview: false
190
- });
191
- }
192
- });
193
- return fetchRequest;
194
- }
195
- }, {
196
- key: "render",
197
- value: function render() {
198
- var _this4 = this;
199
-
200
- var panelsData = this.props.panelsData;
201
-
202
- var switchToEditing = function switchToEditing() {
203
- _this4.panelsInitialized = false;
204
-
205
- _this4.setState({
206
- editing: true
207
- });
208
- };
209
-
210
- var switchToPreview = function switchToPreview() {
211
- if (panelsData) {
212
- _this4.setState({
213
- editing: false
214
- });
215
- }
216
- };
217
-
218
- if (this.state.editing) {
219
- return React.createElement(Fragment, null, React.createElement(BlockControls, null, React.createElement(Toolbar, null, React.createElement(IconButton, {
220
- icon: "visibility",
221
- className: "components-icon-button components-toolbar__control",
222
- label: __('Preview layout.', 'siteorigin-panels'),
223
- onClick: switchToPreview
224
- }))), React.createElement("div", {
225
- key: "layout-block",
226
- className: "siteorigin-panels-layout-block-container",
227
- ref: this.panelsContainer
228
- }));
229
- } else {
230
- var loadingPreview = this.state.loadingPreview;
231
- return React.createElement(Fragment, null, React.createElement(BlockControls, null, React.createElement(Toolbar, null, React.createElement(IconButton, {
232
- icon: "edit",
233
- className: "components-icon-button components-toolbar__control",
234
- label: __('Edit layout.', 'siteorigin-panels'),
235
- onClick: switchToEditing
236
- }))), React.createElement("div", {
237
- key: "preview",
238
- className: "so-panels-block-layout-preview-container"
239
- }, loadingPreview ? React.createElement("div", {
240
- className: "so-panels-spinner-container"
241
- }, React.createElement("span", null, React.createElement(Spinner, null))) : React.createElement("div", {
242
- className: "so-panels-raw-html-container",
243
- ref: this.previewContainer
244
- }, React.createElement(RawHTML, null, this.state.previewHtml))));
245
- }
246
- }
247
- }]);
248
-
249
- return SiteOriginPanelsLayoutBlock;
250
- }(Component);
251
-
252
- registerBlockType('siteorigin-panels/layout-block', {
253
- title: __('SiteOrigin Layout', 'siteorigin-panels'),
254
- description: __("Build a layout using SiteOrigin's Page Builder.", 'siteorigin-panels'),
255
- icon: function icon() {
256
- return React.createElement("span", {
257
- className: "siteorigin-panels-block-icon"
258
- });
259
- },
260
- category: 'layout',
261
- keywords: ['page builder', 'column,grid', 'panel'],
262
- supports: {
263
- html: false
264
- },
265
- attributes: {
266
- panelsData: {
267
- type: 'object'
268
- }
269
- },
270
- edit: function edit(_ref) {
271
- var attributes = _ref.attributes,
272
- setAttributes = _ref.setAttributes,
273
- toggleSelection = _ref.toggleSelection;
274
-
275
- var onLayoutBlockContentChange = function onLayoutBlockContentChange(newPanelsData) {
276
- if (!_.isEmpty(newPanelsData.widgets)) {
277
- // Send panelsData to server for sanitization.
278
- jQuery.post(soPanelsBlockEditorAdmin.sanitizeUrl, {
279
- action: 'so_panels_layout_block_sanitize',
280
- panelsData: JSON.stringify(newPanelsData)
281
- }, function (sanitizedPanelsData) {
282
- if (sanitizedPanelsData !== '') {
283
- setAttributes({
284
- panelsData: sanitizedPanelsData
285
- });
286
- }
287
- });
288
- }
289
- };
290
-
291
- var disableSelection = function disableSelection() {
292
- toggleSelection(false);
293
- };
294
-
295
- var enableSelection = function enableSelection() {
296
- toggleSelection(true);
297
- };
298
-
299
- return React.createElement(SiteOriginPanelsLayoutBlock, {
300
- panelsData: attributes.panelsData,
301
- onContentChange: onLayoutBlockContentChange,
302
- onRowOrWidgetMouseDown: disableSelection,
303
- onRowOrWidgetMouseUp: enableSelection
304
- });
305
- },
306
- save: function save() {
307
- // Render in PHP
308
- return null;
309
- }
310
- });
311
-
312
- (function (jQuery) {
313
- if (soPanelsBlockEditorAdmin.showAddButton) {
314
- jQuery(function () {
315
- setTimeout(function () {
316
- var editorDispatch = wp.data.dispatch('core/editor');
317
- var editorSelect = wp.data.select('core/editor');
318
- var tmpl = jQuery('#siteorigin-panels-add-layout-block-button').html();
319
- var $addButton = jQuery(tmpl).insertAfter('.editor-writing-flow > div:first');
320
- $addButton.on('click', function () {
321
- var layoutBlock = wp.blocks.createBlock('siteorigin-panels/layout-block', {});
322
- var isEmpty = editorSelect.isEditedPostEmpty();
323
-
324
- if (isEmpty) {
325
- var blocks = editorSelect.getBlocks();
326
-
327
- if (blocks.length) {
328
- editorDispatch.replaceBlock(blocks[0].clientId, layoutBlock);
329
- } else {
330
- editorDispatch.insertBlock(layoutBlock);
331
- }
332
- } else {
333
- editorDispatch.insertBlock(layoutBlock);
334
- }
335
- });
336
-
337
- var hideButtonIfBlocks = function hideButtonIfBlocks() {
338
- var isEmpty = wp.data.select('core/editor').isEditedPostEmpty();
339
-
340
- if (isEmpty) {
341
- $addButton.show();
342
- } else {
343
- $addButton.hide();
344
- }
345
- };
346
-
347
- wp.data.subscribe(hideButtonIfBlocks);
348
- hideButtonIfBlocks();
349
- }, 100);
350
- });
351
- }
352
  })(jQuery);
1
+ "use strict";
2
+
3
+ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
4
+
5
+ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
6
+
7
+ function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
8
+
9
+ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
10
+
11
+ function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
12
+
13
+ function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
14
+
15
+ function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
16
+
17
+ function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
18
+
19
+ function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
20
+
21
+ var _lodash = lodash,
22
+ isEqual = _lodash.isEqual,
23
+ debounce = _lodash.debounce,
24
+ isEmpty = _lodash.isEmpty,
25
+ isFunction = _lodash.isFunction;
26
+ var registerBlockType = wp.blocks.registerBlockType;
27
+ var _wp$element = wp.element,
28
+ Component = _wp$element.Component,
29
+ Fragment = _wp$element.Fragment,
30
+ RawHTML = _wp$element.RawHTML,
31
+ createRef = _wp$element.createRef;
32
+ var BlockControls = wp.editor.BlockControls;
33
+ var _wp$components = wp.components,
34
+ Toolbar = _wp$components.Toolbar,
35
+ IconButton = _wp$components.IconButton,
36
+ Spinner = _wp$components.Spinner;
37
+ var __ = wp.i18n.__;
38
+ var _window = window,
39
+ soPanelsBlockEditorAdmin = _window.soPanelsBlockEditorAdmin;
40
+
41
+ var SiteOriginPanelsLayoutBlock =
42
+ /*#__PURE__*/
43
+ function (_Component) {
44
+ _inherits(SiteOriginPanelsLayoutBlock, _Component);
45
+
46
+ function SiteOriginPanelsLayoutBlock(props) {
47
+ var _this;
48
+
49
+ _classCallCheck(this, SiteOriginPanelsLayoutBlock);
50
+
51
+ _this = _possibleConstructorReturn(this, _getPrototypeOf(SiteOriginPanelsLayoutBlock).call(this, props));
52
+ var editMode = soPanelsBlockEditorAdmin.defaultMode === 'edit' || isEmpty(props.panelsData);
53
+ _this.state = {
54
+ editing: editMode,
55
+ loadingPreview: !editMode,
56
+ previewHtml: ''
57
+ };
58
+ _this.panelsContainer = createRef();
59
+ _this.previewContainer = createRef();
60
+ _this.panelsInitialized = false;
61
+ _this.previewInitialized = false;
62
+ return _this;
63
+ }
64
+
65
+ _createClass(SiteOriginPanelsLayoutBlock, [{
66
+ key: "componentDidMount",
67
+ value: function componentDidMount() {
68
+ this.isStillMounted = true;
69
+
70
+ if (this.state.editing) {
71
+ this.setupPanels();
72
+ } else if (!this.state.editing && !this.previewInitialized) {
73
+ this.fetchPreview(this.props);
74
+ this.fetchPreview = debounce(this.fetchPreview, 500);
75
+ }
76
+ }
77
+ }, {
78
+ key: "componentWillUnmount",
79
+ value: function componentWillUnmount() {
80
+ this.isStillMounted = false;
81
+
82
+ if (this.builderView) {
83
+ this.builderView.off('content_change');
84
+ }
85
+ }
86
+ }, {
87
+ key: "componentDidUpdate",
88
+ value: function componentDidUpdate(prevProps) {
89
+ // let propsChanged = !isEqual( prevProps.panelsData, this.props.panelsData );
90
+ if (this.state.editing && !this.panelsInitialized) {
91
+ this.setupPanels();
92
+ } else if (this.state.loadingPreview) {
93
+ this.fetchPreview(this.props);
94
+ } else if (!this.previewInitialized && this.previewContainer.current) {
95
+ jQuery(document).trigger('panels_setup_preview');
96
+ this.previewInitialized = true;
97
+ }
98
+ }
99
+ }, {
100
+ key: "setupPanels",
101
+ value: function setupPanels() {
102
+ var _this2 = this;
103
+
104
+ var $panelsContainer = jQuery(this.panelsContainer.current);
105
+ var config = {
106
+ editorType: 'standalone'
107
+ };
108
+ var builderModel = new panels.model.builder();
109
+ this.builderView = new panels.view.builder({
110
+ model: builderModel,
111
+ config: config
112
+ }); // Make sure panelsData is defined and clone so that we don't alter the underlying attribute.
113
+
114
+ var panelsData = JSON.parse(JSON.stringify(jQuery.extend({}, this.props.panelsData))); // Disable block selection while dragging rows or widgets.
115
+
116
+ var rowOrWidgetMouseDown = function rowOrWidgetMouseDown() {
117
+ if (isFunction(_this2.props.onRowOrWidgetMouseDown)) {
118
+ _this2.props.onRowOrWidgetMouseDown();
119
+ }
120
+
121
+ var rowOrWidgetMouseUp = function rowOrWidgetMouseUp() {
122
+ jQuery(document).off('mouseup', rowOrWidgetMouseUp);
123
+
124
+ if (isFunction(_this2.props.onRowOrWidgetMouseUp)) {
125
+ _this2.props.onRowOrWidgetMouseUp();
126
+ }
127
+ };
128
+
129
+ jQuery(document).on('mouseup', rowOrWidgetMouseUp);
130
+ };
131
+
132
+ this.builderView.on('row_added', function () {
133
+ _this2.builderView.$('.so-row-move').off('mousedown', rowOrWidgetMouseDown);
134
+
135
+ _this2.builderView.$('.so-row-move').on('mousedown', rowOrWidgetMouseDown);
136
+
137
+ _this2.builderView.$('.so-widget').off('mousedown', rowOrWidgetMouseDown);
138
+
139
+ _this2.builderView.$('.so-widget').on('mousedown', rowOrWidgetMouseDown);
140
+ });
141
+ this.builderView.on('widget_added', function () {
142
+ _this2.builderView.$('.so-widget').off('mousedown', rowOrWidgetMouseDown);
143
+
144
+ _this2.builderView.$('.so-widget').on('mousedown', rowOrWidgetMouseDown);
145
+ });
146
+ this.builderView.render().attach({
147
+ container: $panelsContainer
148
+ }).setData(panelsData);
149
+ this.builderView.trigger('builder_resize');
150
+ this.builderView.on('content_change', function () {
151
+ var newPanelsData = _this2.builderView.getData();
152
+
153
+ _this2.panelsDataChanged = !isEqual(panelsData, newPanelsData);
154
+
155
+ if (_this2.panelsDataChanged) {
156
+ if (_this2.props.onContentChange && isFunction(_this2.props.onContentChange)) {
157
+ _this2.props.onContentChange(newPanelsData);
158
+ }
159
+
160
+ _this2.setState({
161
+ loadingPreview: true,
162
+ previewHtml: ''
163
+ });
164
+ }
165
+ });
166
+ jQuery(document).trigger('panels_setup', this.builderView);
167
+ this.panelsInitialized = true;
168
+ }
169
+ }, {
170
+ key: "fetchPreview",
171
+ value: function fetchPreview(props) {
172
+ var _this3 = this;
173
+
174
+ if (!this.isStillMounted) {
175
+ return;
176
+ }
177
+
178
+ this.previewInitialized = false;
179
+ var fetchRequest = this.currentFetchRequest = jQuery.post({
180
+ url: soPanelsBlockEditorAdmin.previewUrl,
181
+ data: {
182
+ action: 'so_panels_layout_block_preview',
183
+ panelsData: JSON.stringify(props.panelsData)
184
+ }
185
+ }).then(function (preview) {
186
+ if (_this3.isStillMounted && fetchRequest === _this3.currentFetchRequest && preview) {
187
+ _this3.setState({
188
+ previewHtml: preview,
189
+ loadingPreview: false
190
+ });
191
+ }
192
+ });
193
+ return fetchRequest;
194
+ }
195
+ }, {
196
+ key: "render",
197
+ value: function render() {
198
+ var _this4 = this;
199
+
200
+ var panelsData = this.props.panelsData;
201
+
202
+ var switchToEditing = function switchToEditing() {
203
+ _this4.panelsInitialized = false;
204
+
205
+ _this4.setState({
206
+ editing: true
207
+ });
208
+ };
209
+
210
+ var switchToPreview = function switchToPreview() {
211
+ if (panelsData) {
212
+ _this4.setState({
213
+ editing: false
214
+ });
215
+ }
216
+ };
217
+
218
+ if (this.state.editing) {
219
+ return React.createElement(Fragment, null, React.createElement(BlockControls, null, React.createElement(Toolbar, null, React.createElement(IconButton, {
220
+ icon: "visibility",
221
+ className: "components-icon-button components-toolbar__control",
222
+ label: __('Preview layout.', 'siteorigin-panels'),
223
+ onClick: switchToPreview
224
+ }))), React.createElement("div", {
225
+ key: "layout-block",
226
+ className: "siteorigin-panels-layout-block-container",
227
+ ref: this.panelsContainer
228
+ }));
229
+ } else {
230
+ var loadingPreview = this.state.loadingPreview;
231
+ return React.createElement(Fragment, null, React.createElement(BlockControls, null, React.createElement(Toolbar, null, React.createElement(IconButton, {
232
+ icon: "edit",
233
+ className: "components-icon-button components-toolbar__control",
234
+ label: __('Edit layout.', 'siteorigin-panels'),
235
+ onClick: switchToEditing
236
+ }))), React.createElement("div", {
237
+ key: "preview",
238
+ className: "so-panels-block-layout-preview-container"
239
+ }, loadingPreview ? React.createElement("div", {
240
+ className: "so-panels-spinner-container"
241
+ }, React.createElement("span", null, React.createElement(Spinner, null))) : React.createElement("div", {
242
+ className: "so-panels-raw-html-container",
243
+ ref: this.previewContainer
244
+ }, React.createElement(RawHTML, null, this.state.previewHtml))));
245
+ }
246
+ }
247
+ }]);
248
+
249
+ return SiteOriginPanelsLayoutBlock;
250
+ }(Component);
251
+
252
+ registerBlockType('siteorigin-panels/layout-block', {
253
+ title: __('SiteOrigin Layout', 'siteorigin-panels'),
254
+ description: __("Build a layout using SiteOrigin's Page Builder.", 'siteorigin-panels'),
255
+ icon: function icon() {
256
+ return React.createElement("span", {
257
+ className: "siteorigin-panels-block-icon"
258
+ });
259
+ },
260
+ category: 'layout',
261
+ keywords: ['page builder', 'column,grid', 'panel'],
262
+ supports: {
263
+ html: false
264
+ },
265
+ attributes: {
266
+ panelsData: {
267
+ type: 'object'
268
+ }
269
+ },
270
+ edit: function edit(_ref) {
271
+ var attributes = _ref.attributes,
272
+ setAttributes = _ref.setAttributes,
273
+ toggleSelection = _ref.toggleSelection;
274
+
275
+ var onLayoutBlockContentChange = function onLayoutBlockContentChange(newPanelsData) {
276
+ if (!_.isEmpty(newPanelsData.widgets)) {
277
+ // Send panelsData to server for sanitization.
278
+ jQuery.post(soPanelsBlockEditorAdmin.sanitizeUrl, {
279
+ action: 'so_panels_layout_block_sanitize',
280
+ panelsData: JSON.stringify(newPanelsData)
281
+ }, function (sanitizedPanelsData) {
282
+ if (sanitizedPanelsData !== '') {
283
+ setAttributes({
284
+ panelsData: sanitizedPanelsData
285
+ });
286
+ }
287
+ });
288
+ }
289
+ };
290
+
291
+ var disableSelection = function disableSelection() {
292
+ toggleSelection(false);
293
+ };
294
+
295
+ var enableSelection = function enableSelection() {
296
+ toggleSelection(true);
297
+ };
298
+
299
+ return React.createElement(SiteOriginPanelsLayoutBlock, {
300
+ panelsData: attributes.panelsData,
301
+ onContentChange: onLayoutBlockContentChange,
302
+ onRowOrWidgetMouseDown: disableSelection,
303
+ onRowOrWidgetMouseUp: enableSelection
304
+ });
305
+ },
306
+ save: function save() {
307
+ // Render in PHP
308
+ return null;
309
+ }
310
+ });
311
+
312
+ (function (jQuery) {
313
+ if (soPanelsBlockEditorAdmin.showAddButton) {
314
+ jQuery(function () {
315
+ setTimeout(function () {
316
+ var editorDispatch = wp.data.dispatch('core/editor');
317
+ var editorSelect = wp.data.select('core/editor');
318
+ var tmpl = jQuery('#siteorigin-panels-add-layout-block-button').html();
319
+ var $addButton = jQuery(tmpl).insertAfter('.editor-writing-flow > div:first');
320
+ $addButton.on('click', function () {
321
+ var layoutBlock = wp.blocks.createBlock('siteorigin-panels/layout-block', {});
322
+ var isEmpty = editorSelect.isEditedPostEmpty();
323
+
324
+ if (isEmpty) {
325
+ var blocks = editorSelect.getBlocks();
326
+
327
+ if (blocks.length) {
328
+ editorDispatch.replaceBlock(blocks[0].clientId, layoutBlock);
329
+ } else {
330
+ editorDispatch.insertBlock(layoutBlock);
331
+ }
332
+ } else {
333
+ editorDispatch.insertBlock(layoutBlock);
334
+ }
335
+ });
336
+
337
+ var hideButtonIfBlocks = function hideButtonIfBlocks() {
338
+ var isEmpty = wp.data.select('core/editor').isEditedPostEmpty();
339
+
340
+ if (isEmpty) {
341
+ $addButton.show();
342
+ } else {
343
+ $addButton.hide();
344
+ }
345
+ };
346
+
347
+ wp.data.subscribe(hideButtonIfBlocks);
348
+ hideButtonIfBlocks();
349
+ }, 100);
350
+ });
351
+ }
352
  })(jQuery);
compat/js/siteorigin-panels-layout-block.min.js CHANGED
@@ -1 +1 @@
1
- "use strict";function _typeof(e){return(_typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _defineProperties(e,t){for(var n=0;n<t.length;n++){var i=t[n];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}function _createClass(e,t,n){return t&&_defineProperties(e.prototype,t),n&&_defineProperties(e,n),e}function _possibleConstructorReturn(e,t){return!t||"object"!==_typeof(t)&&"function"!=typeof t?_assertThisInitialized(e):t}function _assertThisInitialized(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function _getPrototypeOf(e){return(_getPrototypeOf=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&_setPrototypeOf(e,t)}function _setPrototypeOf(e,t){return(_setPrototypeOf=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}var _lodash=lodash,isEqual=_lodash.isEqual,debounce=_lodash.debounce,isEmpty=_lodash.isEmpty,isFunction=_lodash.isFunction,registerBlockType=wp.blocks.registerBlockType,_wp$element=wp.element,Component=_wp$element.Component,Fragment=_wp$element.Fragment,RawHTML=_wp$element.RawHTML,createRef=_wp$element.createRef,BlockControls=wp.editor.BlockControls,_wp$components=wp.components,Toolbar=_wp$components.Toolbar,IconButton=_wp$components.IconButton,Spinner=_wp$components.Spinner,__=wp.i18n.__,_window=window,soPanelsBlockEditorAdmin=_window.soPanelsBlockEditorAdmin,SiteOriginPanelsLayoutBlock=function(e){function i(e){var t;_classCallCheck(this,i),t=_possibleConstructorReturn(this,_getPrototypeOf(i).call(this,e));var n="edit"===soPanelsBlockEditorAdmin.defaultMode||isEmpty(e.panelsData);return t.state={editing:n,loadingPreview:!n,previewHtml:""},t.panelsContainer=createRef(),t.previewContainer=createRef(),t.panelsInitialized=!1,t.previewInitialized=!1,t}return _inherits(i,Component),_createClass(i,[{key:"componentDidMount",value:function(){this.isStillMounted=!0,this.state.editing?this.setupPanels():this.state.editing||this.previewInitialized||(this.fetchPreview(this.props),this.fetchPreview=debounce(this.fetchPreview,500))}},{key:"componentWillUnmount",value:function(){this.isStillMounted=!1,this.builderView&&this.builderView.off("content_change")}},{key:"componentDidUpdate",value:function(e){this.state.editing&&!this.panelsInitialized?this.setupPanels():this.state.loadingPreview?this.fetchPreview(this.props):!this.previewInitialized&&this.previewContainer.current&&(jQuery(document).trigger("panels_setup_preview"),this.previewInitialized=!0)}},{key:"setupPanels",value:function(){var t=this,e=jQuery(this.panelsContainer.current),n=new panels.model.builder;this.builderView=new panels.view.builder({model:n,config:{editorType:"standalone"}});var i=JSON.parse(JSON.stringify(jQuery.extend({},this.props.panelsData))),o=function(){isFunction(t.props.onRowOrWidgetMouseDown)&&t.props.onRowOrWidgetMouseDown();jQuery(document).on("mouseup",function e(){jQuery(document).off("mouseup",e),isFunction(t.props.onRowOrWidgetMouseUp)&&t.props.onRowOrWidgetMouseUp()})};this.builderView.on("row_added",function(){t.builderView.$(".so-row-move").off("mousedown",o),t.builderView.$(".so-row-move").on("mousedown",o),t.builderView.$(".so-widget").off("mousedown",o),t.builderView.$(".so-widget").on("mousedown",o)}),this.builderView.on("widget_added",function(){t.builderView.$(".so-widget").off("mousedown",o),t.builderView.$(".so-widget").on("mousedown",o)}),this.builderView.render().attach({container:e}).setData(i),this.builderView.trigger("builder_resize"),this.builderView.on("content_change",function(){var e=t.builderView.getData();t.panelsDataChanged=!isEqual(i,e),t.panelsDataChanged&&(t.props.onContentChange&&isFunction(t.props.onContentChange)&&t.props.onContentChange(e),t.setState({loadingPreview:!0,previewHtml:""}))}),jQuery(document).trigger("panels_setup",this.builderView),this.panelsInitialized=!0}},{key:"fetchPreview",value:function(e){var t=this;if(this.isStillMounted){this.previewInitialized=!1;var n=this.currentFetchRequest=jQuery.post({url:soPanelsBlockEditorAdmin.previewUrl,data:{action:"so_panels_layout_block_preview",panelsData:JSON.stringify(e.panelsData)}}).then(function(e){t.isStillMounted&&n===t.currentFetchRequest&&e&&t.setState({previewHtml:e,loadingPreview:!1})});return n}}},{key:"render",value:function(){var e=this,t=this.props.panelsData;if(this.state.editing)return React.createElement(Fragment,null,React.createElement(BlockControls,null,React.createElement(Toolbar,null,React.createElement(IconButton,{icon:"visibility",className:"components-icon-button components-toolbar__control",label:__("Preview layout.","siteorigin-panels"),onClick:function(){t&&e.setState({editing:!1})}}))),React.createElement("div",{key:"layout-block",className:"siteorigin-panels-layout-block-container",ref:this.panelsContainer}));var n=this.state.loadingPreview;return React.createElement(Fragment,null,React.createElement(BlockControls,null,React.createElement(Toolbar,null,React.createElement(IconButton,{icon:"edit",className:"components-icon-button components-toolbar__control",label:__("Edit layout.","siteorigin-panels"),onClick:function(){e.panelsInitialized=!1,e.setState({editing:!0})}}))),React.createElement("div",{key:"preview",className:"so-panels-block-layout-preview-container"},n?React.createElement("div",{className:"so-panels-spinner-container"},React.createElement("span",null,React.createElement(Spinner,null))):React.createElement("div",{className:"so-panels-raw-html-container",ref:this.previewContainer},React.createElement(RawHTML,null,this.state.previewHtml))))}}]),i}();registerBlockType("siteorigin-panels/layout-block",{title:__("SiteOrigin Layout","siteorigin-panels"),description:__("Build a layout using SiteOrigin's Page Builder.","siteorigin-panels"),icon:function(){return React.createElement("span",{className:"siteorigin-panels-block-icon"})},category:"layout",keywords:["page builder","column,grid","panel"],supports:{html:!1},attributes:{panelsData:{type:"object"}},edit:function(e){var t=e.attributes,n=e.setAttributes,i=e.toggleSelection;return React.createElement(SiteOriginPanelsLayoutBlock,{panelsData:t.panelsData,onContentChange:function(e){_.isEmpty(e.widgets)||jQuery.post(soPanelsBlockEditorAdmin.sanitizeUrl,{action:"so_panels_layout_block_sanitize",panelsData:JSON.stringify(e)},function(e){""!==e&&n({panelsData:e})})},onRowOrWidgetMouseDown:function(){i(!1)},onRowOrWidgetMouseUp:function(){i(!0)}})},save:function(){return null}}),function(r){soPanelsBlockEditorAdmin.showAddButton&&r(function(){setTimeout(function(){var n=wp.data.dispatch("core/editor"),i=wp.data.select("core/editor"),e=r("#siteorigin-panels-add-layout-block-button").html(),t=r(e).insertAfter(".editor-writing-flow > div:first");t.on("click",function(){var e=wp.blocks.createBlock("siteorigin-panels/layout-block",{});if(i.isEditedPostEmpty()){var t=i.getBlocks();t.length?n.replaceBlock(t[0].clientId,e):n.insertBlock(e)}else n.insertBlock(e)});var o=function(){wp.data.select("core/editor").isEditedPostEmpty()?t.show():t.hide()};wp.data.subscribe(o),o()},100)})}(jQuery);
1
+ "use strict";function _typeof(e){return(_typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _defineProperties(e,t){for(var n=0;n<t.length;n++){var i=t[n];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}function _createClass(e,t,n){return t&&_defineProperties(e.prototype,t),n&&_defineProperties(e,n),e}function _possibleConstructorReturn(e,t){return!t||"object"!==_typeof(t)&&"function"!=typeof t?_assertThisInitialized(e):t}function _assertThisInitialized(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function _getPrototypeOf(e){return(_getPrototypeOf=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&_setPrototypeOf(e,t)}function _setPrototypeOf(e,t){return(_setPrototypeOf=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}var _lodash=lodash,isEqual=_lodash.isEqual,debounce=_lodash.debounce,isEmpty=_lodash.isEmpty,isFunction=_lodash.isFunction,registerBlockType=wp.blocks.registerBlockType,_wp$element=wp.element,Component=_wp$element.Component,Fragment=_wp$element.Fragment,RawHTML=_wp$element.RawHTML,createRef=_wp$element.createRef,BlockControls=wp.editor.BlockControls,_wp$components=wp.components,Toolbar=_wp$components.Toolbar,IconButton=_wp$components.IconButton,Spinner=_wp$components.Spinner,__=wp.i18n.__,_window=window,soPanelsBlockEditorAdmin=_window.soPanelsBlockEditorAdmin,SiteOriginPanelsLayoutBlock=function(){function i(e){var t;_classCallCheck(this,i),t=_possibleConstructorReturn(this,_getPrototypeOf(i).call(this,e));var n="edit"===soPanelsBlockEditorAdmin.defaultMode||isEmpty(e.panelsData);return t.state={editing:n,loadingPreview:!n,previewHtml:""},t.panelsContainer=createRef(),t.previewContainer=createRef(),t.panelsInitialized=!1,t.previewInitialized=!1,t}return _inherits(i,Component),_createClass(i,[{key:"componentDidMount",value:function(){this.isStillMounted=!0,this.state.editing?this.setupPanels():this.state.editing||this.previewInitialized||(this.fetchPreview(this.props),this.fetchPreview=debounce(this.fetchPreview,500))}},{key:"componentWillUnmount",value:function(){this.isStillMounted=!1,this.builderView&&this.builderView.off("content_change")}},{key:"componentDidUpdate",value:function(e){this.state.editing&&!this.panelsInitialized?this.setupPanels():this.state.loadingPreview?this.fetchPreview(this.props):!this.previewInitialized&&this.previewContainer.current&&(jQuery(document).trigger("panels_setup_preview"),this.previewInitialized=!0)}},{key:"setupPanels",value:function(){var t=this,e=jQuery(this.panelsContainer.current),n=new panels.model.builder;this.builderView=new panels.view.builder({model:n,config:{editorType:"standalone"}});function i(){isFunction(t.props.onRowOrWidgetMouseDown)&&t.props.onRowOrWidgetMouseDown(),jQuery(document).on("mouseup",function e(){jQuery(document).off("mouseup",e),isFunction(t.props.onRowOrWidgetMouseUp)&&t.props.onRowOrWidgetMouseUp()})}var o=JSON.parse(JSON.stringify(jQuery.extend({},this.props.panelsData)));this.builderView.on("row_added",function(){t.builderView.$(".so-row-move").off("mousedown",i),t.builderView.$(".so-row-move").on("mousedown",i),t.builderView.$(".so-widget").off("mousedown",i),t.builderView.$(".so-widget").on("mousedown",i)}),this.builderView.on("widget_added",function(){t.builderView.$(".so-widget").off("mousedown",i),t.builderView.$(".so-widget").on("mousedown",i)}),this.builderView.render().attach({container:e}).setData(o),this.builderView.trigger("builder_resize"),this.builderView.on("content_change",function(){var e=t.builderView.getData();t.panelsDataChanged=!isEqual(o,e),t.panelsDataChanged&&(t.props.onContentChange&&isFunction(t.props.onContentChange)&&t.props.onContentChange(e),t.setState({loadingPreview:!0,previewHtml:""}))}),jQuery(document).trigger("panels_setup",this.builderView),this.panelsInitialized=!0}},{key:"fetchPreview",value:function(e){var t=this;if(this.isStillMounted){this.previewInitialized=!1;var n=this.currentFetchRequest=jQuery.post({url:soPanelsBlockEditorAdmin.previewUrl,data:{action:"so_panels_layout_block_preview",panelsData:JSON.stringify(e.panelsData)}}).then(function(e){t.isStillMounted&&n===t.currentFetchRequest&&e&&t.setState({previewHtml:e,loadingPreview:!1})});return n}}},{key:"render",value:function(){var e=this,t=this.props.panelsData;if(this.state.editing)return React.createElement(Fragment,null,React.createElement(BlockControls,null,React.createElement(Toolbar,null,React.createElement(IconButton,{icon:"visibility",className:"components-icon-button components-toolbar__control",label:__("Preview layout.","siteorigin-panels"),onClick:function(){t&&e.setState({editing:!1})}}))),React.createElement("div",{key:"layout-block",className:"siteorigin-panels-layout-block-container",ref:this.panelsContainer}));var n=this.state.loadingPreview;return React.createElement(Fragment,null,React.createElement(BlockControls,null,React.createElement(Toolbar,null,React.createElement(IconButton,{icon:"edit",className:"components-icon-button components-toolbar__control",label:__("Edit layout.","siteorigin-panels"),onClick:function(){e.panelsInitialized=!1,e.setState({editing:!0})}}))),React.createElement("div",{key:"preview",className:"so-panels-block-layout-preview-container"},n?React.createElement("div",{className:"so-panels-spinner-container"},React.createElement("span",null,React.createElement(Spinner,null))):React.createElement("div",{className:"so-panels-raw-html-container",ref:this.previewContainer},React.createElement(RawHTML,null,this.state.previewHtml))))}}]),i}();registerBlockType("siteorigin-panels/layout-block",{title:__("SiteOrigin Layout","siteorigin-panels"),description:__("Build a layout using SiteOrigin's Page Builder.","siteorigin-panels"),icon:function(){return React.createElement("span",{className:"siteorigin-panels-block-icon"})},category:"layout",keywords:["page builder","column,grid","panel"],supports:{html:!1},attributes:{panelsData:{type:"object"}},edit:function(e){var t=e.attributes,n=e.setAttributes,i=e.toggleSelection;return React.createElement(SiteOriginPanelsLayoutBlock,{panelsData:t.panelsData,onContentChange:function(e){_.isEmpty(e.widgets)||jQuery.post(soPanelsBlockEditorAdmin.sanitizeUrl,{action:"so_panels_layout_block_sanitize",panelsData:JSON.stringify(e)},function(e){""!==e&&n({panelsData:e})})},onRowOrWidgetMouseDown:function(){i(!1)},onRowOrWidgetMouseUp:function(){i(!0)}})},save:function(){return null}}),function(r){soPanelsBlockEditorAdmin.showAddButton&&r(function(){setTimeout(function(){var n=wp.data.dispatch("core/editor"),i=wp.data.select("core/editor"),e=r("#siteorigin-panels-add-layout-block-button").html(),t=r(e).insertAfter(".editor-writing-flow > div:first");t.on("click",function(){var e=wp.blocks.createBlock("siteorigin-panels/layout-block",{});if(i.isEditedPostEmpty()){var t=i.getBlocks();t.length?n.replaceBlock(t[0].clientId,e):n.insertBlock(e)}else n.insertBlock(e)});function o(){wp.data.select("core/editor").isEditedPostEmpty()?t.show():t.hide()}wp.data.subscribe(o),o()},100)})}(jQuery);
compat/layout-block.php CHANGED
@@ -1,114 +1,114 @@
1
- <?php
2
-
3
- class SiteOrigin_Panels_Compat_Layout_Block {
4
-
5
- const BLOCK_NAME = 'siteorigin-panels/layout-block';
6
-
7
- /**
8
- * Get the singleton instance
9
- *
10
- * @return SiteOrigin_Panels_Compat_Layout_Block
11
- */
12
- public static function single() {
13
- static $single;
14
-
15
- return empty( $single ) ? $single = new self() : $single;
16
- }
17
-
18
- public function __construct() {
19
- add_action( 'init', array( $this, 'register_layout_block' ) );
20
- // This action is slightly later than `enqueue_block_editor_assets`,
21
- // which we need to use to ensure our templates are loaded at the right time.
22
- add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_layout_block_editor_assets' ) );
23
- }
24
-
25
- public function register_layout_block() {
26
- register_block_type( self::BLOCK_NAME, array(
27
- 'render_callback' => array( $this, 'render_layout_block' ),
28
- ) );
29
- }
30
-
31
- public function enqueue_layout_block_editor_assets() {
32
- if ( SiteOrigin_Panels_Admin::is_block_editor() ) {
33
- $panels_admin = SiteOrigin_Panels_Admin::single();
34
- $panels_admin->enqueue_admin_scripts();
35
- $panels_admin->enqueue_admin_styles();
36
- $panels_admin->js_templates();
37
-
38
- wp_enqueue_script(
39
- 'siteorigin-panels-layout-block',
40
- plugins_url( 'js/siteorigin-panels-layout-block' . SITEORIGIN_PANELS_JS_SUFFIX . '.js', __FILE__ ),
41
- array(
42
- 'wp-editor',
43
- 'wp-blocks',
44
- 'wp-i18n',
45
- 'wp-element',
46
- 'wp-components',
47
- 'wp-compose',
48
- 'so-panels-admin'
49
- ),
50
- SITEORIGIN_PANELS_VERSION
51
- );
52
-
53
- $current_screen = get_current_screen();
54
- $is_panels_post_type = in_array( $current_screen->id, siteorigin_panels_setting( 'post-types' ) );
55
- wp_localize_script(
56
- 'siteorigin-panels-layout-block',
57
- 'soPanelsBlockEditorAdmin',
58
- array(
59
- 'sanitizeUrl' => wp_nonce_url( admin_url( 'admin-ajax.php' ), 'layout-block-sanitize', '_panelsnonce' ),
60
- 'previewUrl' => wp_nonce_url( admin_url( 'admin-ajax.php' ), 'layout-block-preview', '_panelsnonce' ),
61
- 'defaultMode' => siteorigin_panels_setting( 'layout-block-default-mode' ),
62
- 'showAddButton' => $is_panels_post_type,
63
- )
64
- );
65
- // This is only available in WP5.
66
- if ( function_exists( 'wp_set_script_translations' ) ) {
67
- wp_set_script_translations( 'siteorigin-panels-layout-block', 'siteorigin-panels' );
68
- }
69
- SiteOrigin_Panels_Styles::register_scripts();
70
- wp_enqueue_script( 'siteorigin-panels-front-styles' );
71
-
72
- // Enqueue front end scripts for our widgets bundle.
73
- if ( class_exists( 'SiteOrigin_Widgets_Bundle' ) ) {
74
- $sowb = SiteOrigin_Widgets_Bundle::single();
75
- $sowb->register_general_scripts();
76
- if ( method_exists( $sowb, 'enqueue_registered_widgets_scripts' ) ) {
77
- $sowb->enqueue_registered_widgets_scripts( true, false );
78
- }
79
- }
80
- }
81
- }
82
-
83
- public function render_layout_block( $attributes ) {
84
-
85
- if ( empty( $attributes['panelsData'] ) ) {
86
- return '<div>'.
87
- __( "You need to add a widget, row, or prebuilt layout before you'll see anything here. :)", 'siteorigin-panels' ) .
88
- '</div>';
89
- }
90
- $panels_data = $attributes['panelsData'];
91
- $panels_data = $this->sanitize_panels_data( $panels_data );
92
- $builder_id = isset( $attributes['builder_id'] ) ? $attributes['builder_id'] : uniqid( 'gb' . get_the_ID() . '-' );
93
-
94
- // Support for custom CSS classes
95
- $add_custom_class_name = function( $class_names ) use ($attributes) {
96
- if ( ! empty( $attributes['className'] ) ) {
97
- $class_names[] = $attributes['className'];
98
- }
99
- return $class_names;
100
- };
101
- add_filter( 'siteorigin_panels_layout_classes', $add_custom_class_name );
102
- $rendered_layout = SiteOrigin_Panels::renderer()->render( $builder_id, true, $panels_data );
103
- remove_filter( 'siteorigin_panels_layout_classes', $add_custom_class_name );
104
- return $rendered_layout;
105
- }
106
-
107
- private function sanitize_panels_data( $panels_data ) {
108
- // We force calling widgets' update functions here, but a better solution is to ensure these are called when
109
- // the block is saved, but there is currently no simple method to do so.
110
- $panels_data['widgets'] = SiteOrigin_Panels_Admin::single()->process_raw_widgets( $panels_data['widgets'], false, true );
111
- $panels_data = SiteOrigin_Panels_Styles_Admin::single()->sanitize_all( $panels_data );
112
- return $panels_data;
113
- }
114
- }
1
+ <?php
2
+
3
+ class SiteOrigin_Panels_Compat_Layout_Block {
4
+
5
+ const BLOCK_NAME = 'siteorigin-panels/layout-block';
6
+
7
+ /**
8
+ * Get the singleton instance
9
+ *
10
+ * @return SiteOrigin_Panels_Compat_Layout_Block
11
+ */
12
+ public static function single() {
13
+ static $single;
14
+
15
+ return empty( $single ) ? $single = new self() : $single;
16
+ }
17
+
18
+ public function __construct() {
19
+ add_action( 'init', array( $this, 'register_layout_block' ) );
20
+ // This action is slightly later than `enqueue_block_editor_assets`,
21
+ // which we need to use to ensure our templates are loaded at the right time.
22
+ add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_layout_block_editor_assets' ) );
23
+ }
24
+
25
+ public function register_layout_block() {
26
+ register_block_type( self::BLOCK_NAME, array(
27
+ 'render_callback' => array( $this, 'render_layout_block' ),
28
+ ) );
29
+ }
30
+
31
+ public function enqueue_layout_block_editor_assets() {
32
+ if ( SiteOrigin_Panels_Admin::is_block_editor() ) {
33
+ $panels_admin = SiteOrigin_Panels_Admin::single();
34
+ $panels_admin->enqueue_admin_scripts();
35
+ $panels_admin->enqueue_admin_styles();
36
+ $panels_admin->js_templates();
37
+
38
+ wp_enqueue_script(
39
+ 'siteorigin-panels-layout-block',
40
+ plugins_url( 'js/siteorigin-panels-layout-block' . SITEORIGIN_PANELS_JS_SUFFIX . '.js', __FILE__ ),
41
+ array(
42
+ 'wp-editor',
43
+ 'wp-blocks',
44
+ 'wp-i18n',
45
+ 'wp-element',
46
+ 'wp-components',
47
+ 'wp-compose',
48
+ 'so-panels-admin'
49
+ ),
50
+ SITEORIGIN_PANELS_VERSION
51
+ );
52
+
53
+ $current_screen = get_current_screen();
54
+ $is_panels_post_type = in_array( $current_screen->id, siteorigin_panels_setting( 'post-types' ) );
55
+ wp_localize_script(
56
+ 'siteorigin-panels-layout-block',
57
+ 'soPanelsBlockEditorAdmin',
58
+ array(
59
+ 'sanitizeUrl' => wp_nonce_url( admin_url( 'admin-ajax.php' ), 'layout-block-sanitize', '_panelsnonce' ),
60
+ 'previewUrl' => wp_nonce_url( admin_url( 'admin-ajax.php' ), 'layout-block-preview', '_panelsnonce' ),
61
+ 'defaultMode' => siteorigin_panels_setting( 'layout-block-default-mode' ),
62
+ 'showAddButton' => apply_filters( 'siteorigin_layout_block_show_add_button', $is_panels_post_type ),
63
+ )
64
+ );
65
+ // This is only available in WP5.
66
+ if ( function_exists( 'wp_set_script_translations' ) ) {
67
+ wp_set_script_translations( 'siteorigin-panels-layout-block', 'siteorigin-panels' );
68
+ }
69
+ SiteOrigin_Panels_Styles::register_scripts();
70
+ wp_enqueue_script( 'siteorigin-panels-front-styles' );
71
+
72
+ // Enqueue front end scripts for our widgets bundle.
73
+ if ( class_exists( 'SiteOrigin_Widgets_Bundle' ) ) {
74
+ $sowb = SiteOrigin_Widgets_Bundle::single();
75
+ $sowb->register_general_scripts();
76
+ if ( method_exists( $sowb, 'enqueue_registered_widgets_scripts' ) ) {
77
+ $sowb->enqueue_registered_widgets_scripts( true, false );
78
+ }
79
+ }
80
+ }
81
+ }
82
+
83
+ public function render_layout_block( $attributes ) {
84
+
85
+ if ( empty( $attributes['panelsData'] ) ) {
86
+ return '<div>'.
87
+ __( "You need to add a widget, row, or prebuilt layout before you'll see anything here. :)", 'siteorigin-panels' ) .
88
+ '</div>';
89
+ }
90
+ $panels_data = $attributes['panelsData'];
91
+ $panels_data = $this->sanitize_panels_data( $panels_data );
92
+ $builder_id = isset( $attributes['builder_id'] ) ? $attributes['builder_id'] : uniqid( 'gb' . get_the_ID() . '-' );
93
+
94
+ // Support for custom CSS classes
95
+ $add_custom_class_name = function( $class_names ) use ($attributes) {
96
+ if ( ! empty( $attributes['className'] ) ) {
97
+ $class_names[] = $attributes['className'];
98
+ }
99
+ return $class_names;
100
+ };
101
+ add_filter( 'siteorigin_panels_layout_classes', $add_custom_class_name );
102
+ $rendered_layout = SiteOrigin_Panels::renderer()->render( $builder_id, true, $panels_data );
103
+ remove_filter( 'siteorigin_panels_layout_classes', $add_custom_class_name );
104
+ return $rendered_layout;
105
+ }
106
+
107
+ private function sanitize_panels_data( $panels_data ) {
108
+ // We force calling widgets' update functions here, but a better solution is to ensure these are called when
109
+ // the block is saved, but there is currently no simple method to do so.
110
+ $panels_data['widgets'] = SiteOrigin_Panels_Admin::single()->process_raw_widgets( $panels_data['widgets'], false, true );
111
+ $panels_data = SiteOrigin_Panels_Styles_Admin::single()->sanitize_all( $panels_data );
112
+ return $panels_data;
113
+ }
114
+ }
compat/pb-icon.svg DELETED
@@ -1 +0,0 @@
1
- <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 703.29 745.09"><defs><style>.cls-1{opacity:0.5;}.cls-2{opacity:0.2;}</style></defs><title>pb-icon</title><path d="M731.52,232.52,397,430.73a17.11,17.11,0,0,1-17.45,0L45,232.52a17.12,17.12,0,0,1,0-29.46L379.54,4.85a17.11,17.11,0,0,1,17.45,0L731.52,203.06A17.12,17.12,0,0,1,731.52,232.52Z" transform="translate(-36.62 -2.46)"/><path class="cls-1" d="M731.52,399.94,397,598.15a17.11,17.11,0,0,1-17.45,0L45,399.94a17.12,17.12,0,0,1,0-29.46L379.54,172.27a17.11,17.11,0,0,1,17.45,0L731.52,370.48A17.12,17.12,0,0,1,731.52,399.94Z" transform="translate(-36.62 -2.46)"/><path class="cls-2" d="M731.52,546.94,397,745.15a17.11,17.11,0,0,1-17.45,0L45,546.94a17.12,17.12,0,0,1,0-29.46L379.54,319.27a17.11,17.11,0,0,1,17.45,0L731.52,517.48A17.12,17.12,0,0,1,731.52,546.94Z" transform="translate(-36.62 -2.46)"/></svg>
 
compat/pb-icon_white.svg DELETED
@@ -1 +0,0 @@
1
- <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 703.29 745.09"><defs><style>.cls-1{opacity:0.5;}.cls-2{opacity:0.2;}</style></defs><title>pb-icon</title><path fill="white" d="M731.52,232.52,397,430.73a17.11,17.11,0,0,1-17.45,0L45,232.52a17.12,17.12,0,0,1,0-29.46L379.54,4.85a17.11,17.11,0,0,1,17.45,0L731.52,203.06A17.12,17.12,0,0,1,731.52,232.52Z" transform="translate(-36.62 -2.46)"/><path class="cls-1" fill="white" d="M731.52,399.94,397,598.15a17.11,17.11,0,0,1-17.45,0L45,399.94a17.12,17.12,0,0,1,0-29.46L379.54,172.27a17.11,17.11,0,0,1,17.45,0L731.52,370.48A17.12,17.12,0,0,1,731.52,399.94Z" transform="translate(-36.62 -2.46)"/><path fill="white" class="cls-2" d="M731.52,546.94,397,745.15a17.11,17.11,0,0,1-17.45,0L45,546.94a17.12,17.12,0,0,1,0-29.46L379.54,319.27a17.11,17.11,0,0,1,17.45,0L731.52,517.48A17.12,17.12,0,0,1,731.52,546.94Z" transform="translate(-36.62 -2.46)"/></svg>
 
css/front.css DELETED
@@ -1,47 +0,0 @@
1
- .panel-grid.panel-no-style,
2
- .panel-grid.panel-has-style > .panel-row-style {
3
- display: -webkit-flex;
4
- display: flex;
5
- -ms-flex-wrap: wrap;
6
- -webkit-flex-wrap: wrap;
7
- flex-wrap: nowrap;
8
- -ms-justify-content: space-between;
9
- -webkit-justify-content: space-between;
10
- justify-content: space-between;
11
- }
12
- .panel-grid-cell {
13
- -ms-box-sizing: border-box;
14
- -moz-box-sizing: border-box;
15
- -webkit-box-sizing: border-box;
16
- box-sizing: border-box;
17
- }
18
- .panel-grid-cell .panel-cell-style {
19
- height: 100%;
20
- }
21
- .panel-grid-cell .so-panel {
22
- zoom: 1;
23
- }
24
- .panel-grid-cell .so-panel:before {
25
- content: '';
26
- display: block;
27
- }
28
- .panel-grid-cell .so-panel:after {
29
- content: '';
30
- display: table;
31
- clear: both;
32
- }
33
- .panel-grid-cell .panel-last-child {
34
- margin-bottom: 0;
35
- }
36
- .panel-grid-cell .widget-title {
37
- margin-top: 0;
38
- }
39
- body.siteorigin-panels-before-js {
40
- overflow-x: hidden;
41
- }
42
- body.siteorigin-panels-before-js .siteorigin-panels-stretch {
43
- margin-right: -1000px !important;
44
- margin-left: -1000px !important;
45
- padding-right: 1000px !important;
46
- padding-left: 1000px !important;
47
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
css/front.min.css DELETED
@@ -1 +0,0 @@
1
- .panel-grid.panel-has-style>.panel-row-style,.panel-grid.panel-no-style{display:flex;-ms-flex-wrap:wrap;flex-wrap:nowrap;-ms-justify-content:space-between;justify-content:space-between}.panel-grid-cell{-ms-box-sizing:border-box;box-sizing:border-box}.panel-grid-cell .panel-cell-style{height:100%}.panel-grid-cell .so-panel{zoom:1}.panel-grid-cell .so-panel:before{content:"";display:block}.panel-grid-cell .so-panel:after{content:"";display:table;clear:both}.panel-grid-cell .panel-last-child{margin-bottom:0}.panel-grid-cell .widget-title{margin-top:0}body.siteorigin-panels-before-js{overflow-x:hidden}body.siteorigin-panels-before-js .siteorigin-panels-stretch{margin-right:-1000px!important;margin-left:-1000px!important;padding-right:1000px!important;padding-left:1000px!important}
 
css/icons/readme.txt DELETED
@@ -1,5 +0,0 @@
1
- Icons are a subset of FontAwesome
2
- Font Awesome by Dave Gandy - http://fontawesome.io
3
-
4
- License: SIL OFL 1.1
5
- URL: http://scripts.sil.org/OFL
 
 
 
 
 
css/images/pb-icon_white.svg CHANGED
@@ -1 +1 @@
1
- <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 703.29 745.09"><defs><style>.cls-1{opacity:0.5;}.cls-2{opacity:0.2;}</style></defs><title>pb-icon</title><path fill="white" d="M731.52,232.52,397,430.73a17.11,17.11,0,0,1-17.45,0L45,232.52a17.12,17.12,0,0,1,0-29.46L379.54,4.85a17.11,17.11,0,0,1,17.45,0L731.52,203.06A17.12,17.12,0,0,1,731.52,232.52Z" transform="translate(-36.62 -2.46)"/><path class="cls-1" fill="white" d="M731.52,399.94,397,598.15a17.11,17.11,0,0,1-17.45,0L45,399.94a17.12,17.12,0,0,1,0-29.46L379.54,172.27a17.11,17.11,0,0,1,17.45,0L731.52,370.48A17.12,17.12,0,0,1,731.52,399.94Z" transform="translate(-36.62 -2.46)"/><path fill="white" class="cls-2" d="M731.52,546.94,397,745.15a17.11,17.11,0,0,1-17.45,0L45,546.94a17.12,17.12,0,0,1,0-29.46L379.54,319.27a17.11,17.11,0,0,1,17.45,0L731.52,517.48A17.12,17.12,0,0,1,731.52,546.94Z" transform="translate(-36.62 -2.46)"/></svg>
1
+ <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 703.29 745.09"><defs><style>.cls-1{opacity:0.5;}.cls-2{opacity:0.2;}</style></defs><title>pb-icon</title><path fill="white" d="M731.52,232.52,397,430.73a17.11,17.11,0,0,1-17.45,0L45,232.52a17.12,17.12,0,0,1,0-29.46L379.54,4.85a17.11,17.11,0,0,1,17.45,0L731.52,203.06A17.12,17.12,0,0,1,731.52,232.52Z" transform="translate(-36.62 -2.46)"/><path class="cls-1" fill="white" d="M731.52,399.94,397,598.15a17.11,17.11,0,0,1-17.45,0L45,399.94a17.12,17.12,0,0,1,0-29.46L379.54,172.27a17.11,17.11,0,0,1,17.45,0L731.52,370.48A17.12,17.12,0,0,1,731.52,399.94Z" transform="translate(-36.62 -2.46)"/><path fill="white" class="cls-2" d="M731.52,546.94,397,745.15a17.11,17.11,0,0,1-17.45,0L45,546.94a17.12,17.12,0,0,1,0-29.46L379.54,319.27a17.11,17.11,0,0,1,17.45,0L731.52,517.48A17.12,17.12,0,0,1,731.52,546.94Z" transform="translate(-36.62 -2.46)"/></svg>
css/mixins.less DELETED
@@ -1,173 +0,0 @@
1
- .gradient(@color: #F5F5F5, @start: #EEE, @stop: #FFF) {
2
- background: @color;
3
- background: -webkit-gradient(linear, left bottom, left top, color-stop(0, @start), color-stop(1, @stop));
4
- background: -ms-linear-gradient(bottom,@start,@stop);
5
- background: -moz-linear-gradient(center bottom,@start 0%,@stop 100%);
6
- background: -o-linear-gradient(@stop,@start);
7
- filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)", @stop, @start));
8
- }
9
-
10
- .bw-gradient(@color: #F5F5F5, @start: 0, @stop: 255) {
11
- background: @color;
12
- background: -webkit-gradient(linear, left bottom, left top, color-stop(0, rgb(@start,@start,@start)), color-stop(1, rgb(@stop,@stop,@stop)));
13
- background: -ms-linear-gradient(bottom, rgb(@start,@start,@start) 0%, rgb(@stop,@stop,@stop) 100%);
14
- background: -moz-linear-gradient(center bottom, rgb(@start,@start,@start) 0%, rgb(@stop,@stop,@stop) 100%);
15
- background: -o-linear-gradient(rgb(@stop,@stop,@stop), rgb(@start,@start,@start));
16
- background: linear-gradient(rgb(@stop,@stop,@stop), rgb(@start,@start,@start));
17
-
18
- filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",rgb(@stop,@stop,@stop), rgb(@start,@start,@start)));
19
- }
20
-
21
- .linear-gradient(@color, @gradient) {
22
- background: @color;
23
- background: -moz-linear-gradient(@gradient);
24
- background: -webkit-linear-gradient(@gradient);
25
- background: -o-linear-gradient(@gradient);
26
- background: -ms-linear-gradient(@gradient);
27
- background: linear-gradient(@gradient);
28
- }
29
-
30
- .bordered(@top-color: #EEE, @right-color: #EEE, @bottom-color: #EEE, @left-color: #EEE) {
31
- border-top: solid 1px @top-color;
32
- border-left: solid 1px @left-color;
33
- border-right: solid 1px @right-color;
34
- border-bottom: solid 1px @bottom-color;
35
- }
36
-
37
- .drop-shadow(@x-axis: 0, @y-axis: 1px, @blur: 2px, @alpha: 0.1) {
38
- -webkit-box-shadow: @x-axis @y-axis @blur rgba(0, 0, 0, @alpha);
39
- -moz-box-shadow: @x-axis @y-axis @blur rgba(0, 0, 0, @alpha);
40
- box-shadow: @x-axis @y-axis @blur rgba(0, 0, 0, @alpha);
41
- }
42
-
43
- .box-shadow(@shadow) {
44
- -webkit-box-shadow: @shadow;
45
- -moz-box-shadow: @shadow;
46
- box-shadow: @shadow;
47
- }
48
-
49
- .rounded(@radius: 2px) {
50
- -webkit-border-radius: @radius;
51
- -moz-border-radius: @radius;
52
- border-radius: @radius;
53
- }
54
-
55
- .border-radius(@topright: 0, @bottomright: 0, @bottomleft: 0, @topleft: 0) {
56
- -webkit-border-top-right-radius: @topright;
57
- -webkit-border-bottom-right-radius: @bottomright;
58
- -webkit-border-bottom-left-radius: @bottomleft;
59
- -webkit-border-top-left-radius: @topleft;
60
- -moz-border-radius-topright: @topright;
61
- -moz-border-radius-bottomright: @bottomright;
62
- -moz-border-radius-bottomleft: @bottomleft;
63
- -moz-border-radius-topleft: @topleft;
64
- border-top-right-radius: @topright;
65
- border-bottom-right-radius: @bottomright;
66
- border-bottom-left-radius: @bottomleft;
67
- border-top-left-radius: @topleft;
68
- .background-clip(padding-box);
69
- }
70
-
71
- .opacity(@opacity: 0.5) {
72
- -moz-opacity: @opacity;
73
- -khtml-opacity: @opacity;
74
- -webkit-opacity: @opacity;
75
- opacity: @opacity;
76
- @opperc: @opacity * 100;
77
- -ms-filter: ~"progid:DXImageTransform.Microsoft.Alpha(opacity=@{opperc})";
78
- filter: ~"alpha(opacity=@{opperc})";
79
- }
80
-
81
- .transition-duration(@duration: 0.2s) {
82
- -moz-transition-duration: @duration;
83
- -webkit-transition-duration: @duration;
84
- -o-transition-duration: @duration;
85
- transition-duration: @duration;
86
- }
87
-
88
- .transform(...) {
89
- -webkit-transform: @arguments;
90
- -moz-transform: @arguments;
91
- -o-transform: @arguments;
92
- -ms-transform: @arguments;
93
- transform: @arguments;
94
- }
95
-
96
- .rotation(@deg:5deg) {
97
- .transform(rotate(@deg));
98
- }
99
-
100
- .scale(@ratio:1.5) {
101
- .transform(scale(@ratio));
102
- }
103
-
104
- .transition(@duration:0.2s, @on: all, @ease:ease) {
105
- -webkit-transition: @on @duration @ease;
106
- -moz-transition: @on @duration @ease;
107
- -o-transition: @on @duration @ease;
108
- transition: @on @duration @ease;
109
- }
110
-
111
- .inner-shadow(@horizontal:0, @vertical:1px, @blur:2px, @alpha: 0.4) {
112
- -webkit-box-shadow: inset @horizontal @vertical @blur rgba(0, 0, 0, @alpha);
113
- -moz-box-shadow: inset @horizontal @vertical @blur rgba(0, 0, 0, @alpha);
114
- box-shadow: inset @horizontal @vertical @blur rgba(0, 0, 0, @alpha);
115
- }
116
-
117
- .box-sizing(@sizing: border-box) {
118
- -ms-box-sizing: @sizing;
119
- -moz-box-sizing: @sizing;
120
- -webkit-box-sizing: @sizing;
121
- box-sizing: @sizing;
122
- }
123
-
124
- .user-select(@argument: none) {
125
- -webkit-user-select: @argument;
126
- -moz-user-select: @argument;
127
- -ms-user-select: @argument;
128
- user-select: @argument;
129
- }
130
-
131
- .columns(@colwidth: 250px, @colcount: 0, @colgap: 50px, @columnRuleColor: #EEE, @columnRuleStyle: solid, @columnRuleWidth: 1px) {
132
- -moz-column-width: @colwidth;
133
- -moz-column-count: @colcount;
134
- -moz-column-gap: @colgap;
135
- -moz-column-rule-color: @columnRuleColor;
136
- -moz-column-rule-style: @columnRuleStyle;
137
- -moz-column-rule-width: @columnRuleWidth;
138
- -webkit-column-width: @colwidth;
139
- -webkit-column-count: @colcount;
140
- -webkit-column-gap: @colgap;
141
- -webkit-column-rule-color: @columnRuleColor;
142
- -webkit-column-rule-style: @columnRuleStyle;
143
- -webkit-column-rule-width: @columnRuleWidth;
144
- column-width: @colwidth;
145
- column-count: @colcount;
146
- column-gap: @colgap;
147
- column-rule-color: @columnRuleColor;
148
- column-rule-style: @columnRuleStyle;
149
- column-rule-width: @columnRuleWidth;
150
- }
151
-
152
- .translate(@x:0, @y:0) {
153
- .transform(translate(@x, @y));
154
- }
155
-
156
- .background-clip(@argument: padding-box) {
157
- -moz-background-clip: @argument;
158
- -webkit-background-clip: @argument;
159
- background-clip: @argument;
160
- }
161
-
162
- .clearfix() {
163
- zoom: 1;
164
- &:before {
165
- content: '';
166
- display: block;
167
- }
168
- &:after {
169
- content: '';
170
- display: table;
171
- clear: both;
172
- }
173
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
inc/admin-dashboard.php CHANGED
@@ -1,117 +1,117 @@
1
- <?php
2
-
3
- class SiteOrigin_Panels_Admin_Dashboard {
4
-
5
- function __construct() {
6
- add_action( 'wp_dashboard_setup', array( $this, 'register_dashboard_widgets' ), 15 );
7
- add_action( 'admin_print_styles', array( $this, 'enqueue_admin_styles' ) );
8
- }
9
-
10
- /**
11
- * @return SiteOrigin_Panels_Admin_Dashboard
12
- */
13
- public static function single() {
14
- static $single;
15
- return empty( $single ) ? $single = new self() : $single;
16
- }
17
-
18
- /**
19
- * Register the dashboard widget
20
- */
21
- public function register_dashboard_widgets(){
22
- if( function_exists( 'wp_dashboard_primary_output' ) ) {
23
- wp_add_dashboard_widget( 'so-dashboard-news', __( 'SiteOrigin Page Builder News', 'siteorigin-panels' ), array(
24
- $this,
25
- 'dashboard_overview_widget'
26
- ) );
27
-
28
- // Move Page Builder widget to the top
29
- global $wp_meta_boxes;
30
-
31
- $dashboard = $wp_meta_boxes['dashboard']['normal']['core'];
32
- $ours = array( 'so-dashboard-news' => $dashboard['so-dashboard-news'] );
33
-
34
- $wp_meta_boxes['dashboard']['normal']['core'] = array_merge( $ours, $dashboard ); // WPCS: override ok.
35
- }
36
- }
37
-
38
- /**
39
- * Enqueue the dashboard styles
40
- */
41
- public function enqueue_admin_styles( $page ){
42
- $screen = get_current_screen();
43
- if( ! empty( $screen ) && $screen->id == 'dashboard' ) {
44
- wp_enqueue_style(
45
- 'so-panels-dashboard',
46
- siteorigin_panels_url( 'css/dashboard.css' ),
47
- array( 'wp-color-picker' ),
48
- SITEORIGIN_PANELS_VERSION
49
- );
50
- }
51
- }
52
-
53
- /**
54
- * Display the actual widget
55
- */
56
- public function dashboard_overview_widget(){
57
- $feeds = array(
58
- array(
59
- 'url' => 'https://siteorigin.com/feed/',
60
- 'items' => 4,
61
- 'show_summary' => 0,
62
- 'show_author' => 0,
63
- 'show_date' => 1,
64
- ),
65
- );
66
-
67
- wp_dashboard_primary_output( 'so_dashboard_widget_news', $feeds );
68
-
69
- if( function_exists( 'wp_print_community_events_markup' ) ) {
70
- ?>
71
- <p class="community-events-footer">
72
- <?php
73
- printf(
74
- '<a href="%1$s" target="_blank" rel="noopener noreferrer">%2$s <span class="screen-reader-text">%3$s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a>',
75
- esc_url( 'https://siteorigin.com/blog/' ),
76
- __( 'Blog', 'siteorigin-panels' ),
77
- /* translators: accessibility text */
78
- __( '(opens in a new window)', 'siteorigin-panels' )
79
- );
80
- echo ' | ';
81
-
82
- if( class_exists( 'SiteOrigin_Premium' ) ) {
83
- printf(
84
- '<a href="%1$s" target="_blank" rel="noopener noreferrer">%2$s <span class="screen-reader-text">%3$s</span><span aria-hidden="true" class="dashicons dashicons-email-alt"></span></a>',
85
- esc_url( 'mailto:support@siteorigin.com' ),
86
- __( 'Email Support', 'siteorigin-panels' ),
87
- /* translators: accessibility text */
88
- __( '(email SiteOrigin support)', 'siteorigin-panels' )
89
- );
90
- }
91
- else {
92
- printf(
93
- '<a href="%1$s" target="_blank" rel="noopener noreferrer">%2$s <span class="screen-reader-text">%3$s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a>',
94
- esc_url( 'https://siteorigin.com/thread/' ),
95
- __( 'Support Forum', 'siteorigin-panels' ),
96
- /* translators: accessibility text */
97
- __( '(opens in a new window)', 'siteorigin-panels' )
98
- );
99
- }
100
-
101
- if ( SiteOrigin_Panels::display_premium_teaser() ) {
102
- echo ' | ';
103
- printf(
104
- '<a href="%1$s" target="_blank" rel="noopener noreferrer" style="color: #2ebd59">%2$s <span class="screen-reader-text">%3$s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a>',
105
- /* translators: If a Rosetta site exists (e.g. https://es.wordpress.org/news/), then use that. Otherwise, leave untranslated. */
106
- esc_url( 'https://siteorigin.com/downloads/premium/' ),
107
- __( 'Get Premium', 'siteorigin-panels' ),
108
- /* translators: accessibility text */
109
- __( '(opens in a new window)', 'siteorigin-panels' )
110
- );
111
- }
112
- ?>
113
- </p>
114
- <?php
115
- }
116
- }
117
- }
1
+ <?php
2
+
3
+ class SiteOrigin_Panels_Admin_Dashboard {
4
+
5
+ function __construct() {
6
+ add_action( 'wp_dashboard_setup', array( $this, 'register_dashboard_widgets' ), 15 );
7
+ add_action( 'admin_print_styles', array( $this, 'enqueue_admin_styles' ) );
8
+ }
9
+
10
+ /**
11
+ * @return SiteOrigin_Panels_Admin_Dashboard
12
+ */
13
+ public static function single() {
14
+ static $single;
15
+ return empty( $single ) ? $single = new self() : $single;
16
+ }
17
+
18
+ /**
19
+ * Register the dashboard widget
20
+ */
21
+ public function register_dashboard_widgets(){
22
+ if( function_exists( 'wp_dashboard_primary_output' ) ) {
23
+ wp_add_dashboard_widget( 'so-dashboard-news', __( 'SiteOrigin Page Builder News', 'siteorigin-panels' ), array(
24
+ $this,
25
+ 'dashboard_overview_widget'
26
+ ) );
27
+
28
+ // Move Page Builder widget to the top
29
+ global $wp_meta_boxes;
30
+
31
+ $dashboard = $wp_meta_boxes['dashboard']['normal']['core'];
32
+ $ours = array( 'so-dashboard-news' => $dashboard['so-dashboard-news'] );
33
+
34
+ $wp_meta_boxes['dashboard']['normal']['core'] = array_merge( $ours, $dashboard ); // WPCS: override ok.
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Enqueue the dashboard styles
40
+ */
41
+ public function enqueue_admin_styles( $page ){
42
+ $screen = get_current_screen();
43
+ if( ! empty( $screen ) && $screen->id == 'dashboard' ) {
44
+ wp_enqueue_style(
45
+ 'so-panels-dashboard',
46
+ siteorigin_panels_url( 'css/dashboard.css' ),
47
+ array( 'wp-color-picker' ),
48
+ SITEORIGIN_PANELS_VERSION
49
+ );
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Display the actual widget
55
+ */
56
+ public function dashboard_overview_widget(){
57
+ $feeds = array(
58
+ array(
59
+ 'url' => 'https://siteorigin.com/feed/',
60
+ 'items' => 4,
61
+ 'show_summary' => 0,
62
+ 'show_author' => 0,
63
+ 'show_date' => 1,
64
+ ),
65
+ );
66
+
67
+ wp_dashboard_primary_output( 'so_dashboard_widget_news', $feeds );
68
+
69
+ if( function_exists( 'wp_print_community_events_markup' ) ) {
70
+ ?>
71
+ <p class="community-events-footer">
72
+ <?php
73
+ printf(
74
+ '<a href="%1$s" target="_blank" rel="noopener noreferrer">%2$s <span class="screen-reader-text">%3$s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a>',
75
+ esc_url( 'https://siteorigin.com/blog/' ),
76
+ __( 'Blog', 'siteorigin-panels' ),
77
+ /* translators: accessibility text */
78
+ __( '(opens in a new window)', 'siteorigin-panels' )
79
+ );
80
+ echo ' | ';
81
+
82
+ if( class_exists( 'SiteOrigin_Premium' ) ) {
83
+ printf(
84
+ '<a href="%1$s" target="_blank" rel="noopener noreferrer">%2$s <span class="screen-reader-text">%3$s</span><span aria-hidden="true" class="dashicons dashicons-email-alt"></span></a>',
85
+ esc_url( 'mailto:support@siteorigin.com' ),
86
+ __( 'Email Support', 'siteorigin-panels' ),
87
+ /* translators: accessibility text */
88
+ __( '(email SiteOrigin support)', 'siteorigin-panels' )
89
+ );
90
+ }
91
+ else {
92
+ printf(
93
+ '<a href="%1$s" target="_blank" rel="noopener noreferrer">%2$s <span class="screen-reader-text">%3$s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a>',
94
+ esc_url( 'https://siteorigin.com/thread/' ),
95
+ __( 'Support Forum', 'siteorigin-panels' ),
96
+ /* translators: accessibility text */
97
+ __( '(opens in a new window)', 'siteorigin-panels' )
98
+ );
99
+ }
100
+
101
+ if ( SiteOrigin_Panels::display_premium_teaser() ) {
102
+ echo ' | ';
103
+ printf(
104
+ '<a href="%1$s" target="_blank" rel="noopener noreferrer" style="color: #2ebd59">%2$s <span class="screen-reader-text">%3$s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a>',
105
+ /* translators: If a Rosetta site exists (e.g. https://es.wordpress.org/news/), then use that. Otherwise, leave untranslated. */
106
+ esc_url( 'https://siteorigin.com/downloads/premium/' ),
107
+ __( 'Get Premium', 'siteorigin-panels' ),
108
+ /* translators: accessibility text */
109
+ __( '(opens in a new window)', 'siteorigin-panels' )
110
+ );
111
+ }
112
+ ?>
113
+ </p>
114
+ <?php
115
+ }
116
+ }
117
+ }
inc/admin-layouts.php CHANGED
@@ -1,494 +1,494 @@
1
- <?php
2
-
3
- /**
4
- * Class SiteOrigin_Panels_Admin
5
- *
6
- * Handles all the admin and database interactions.
7
- */
8
- class SiteOrigin_Panels_Admin_Layouts {
9
-
10
- const LAYOUT_URL = 'https://layouts.siteorigin.com/';
11
-
12
- function __construct() {
13
- // Filter all the available external layout directories.
14
- add_filter( 'siteorigin_panels_external_layout_directories', array( $this, 'filter_directories' ), 8 );
15
- // Filter all the available local layout folders.
16
- add_filter( 'siteorigin_panels_prebuilt_layouts', array( $this, 'get_local_layouts' ), 8 );
17
-
18
- add_action( 'wp_ajax_so_panels_layouts_query', array( $this, 'action_get_prebuilt_layouts' ) );
19
- add_action( 'wp_ajax_so_panels_get_layout', array( $this, 'action_get_prebuilt_layout' ) );
20
- add_action( 'wp_ajax_so_panels_import_layout', array( $this, 'action_import_layout' ) );
21
- add_action( 'wp_ajax_so_panels_export_layout', array( $this, 'action_export_layout' ) );
22
- add_action( 'wp_ajax_so_panels_directory_enable', array( $this, 'action_directory_enable' ) );
23
- }
24
-
25
- /**
26
- * @return SiteOrigin_Panels_Admin_Layouts
27
- */
28
- public static function single() {
29
- static $single;
30
- return empty( $single ) ? $single = new self() : $single;
31
- }
32
-
33
- /**
34
- * Add the main SiteOrigin layout directory
35
- */
36
- public function filter_directories( $directories ){
37
- if ( apply_filters( 'siteorigin_panels_layouts_directory_enabled', true ) ) {
38
- $directories['siteorigin'] = array(
39
- // The title of the layouts directory in the sidebar.
40
- 'title' => __( 'Layouts Directory', 'siteorigin-panels' ),
41
- // The URL of the directory.
42
- 'url' => self::LAYOUT_URL,
43
- // Any additional arguments to pass to the layouts server
44
- 'args' => array()
45
- );
46
- }
47
-
48
- return $directories;
49
- }
50
-
51
- /**
52
- * Get all the layout directories.
53
- *
54
- * @return array
55
- */
56
- public function get_directories(){
57
- $directories = apply_filters( 'siteorigin_panels_external_layout_directories', array() );
58
- if( empty( $directories ) || ! is_array( $directories ) ) {
59
- $directories = array();
60
- }
61
-
62
- return $directories;
63
- }
64
-
65
-
66
- /**
67
- * Looks through local folders in the active theme and any others filtered in by theme and plugins, to find JSON
68
- * prebuilt layouts.
69
- *
70
- */
71
- public function get_local_layouts() {
72
-
73
- // By default we'll look for layouts in a directory in the active theme
74
- $layout_folders = array( get_template_directory() . '/siteorigin-page-builder-layouts' );
75
-
76
- // And the child theme if there is one.
77
- if ( is_child_theme() ) {
78
- $layout_folders[] = get_stylesheet_directory() . '/siteorigin-page-builder-layouts';
79
- }
80
-
81
- // This allows themes and plugins to customize where we look for layouts.
82
- $layout_folders = apply_filters( 'siteorigin_panels_local_layouts_directories', $layout_folders );
83
-
84
- $layouts = array();
85
- foreach ( $layout_folders as $folder ) {
86
- $folder = realpath($folder);
87
- if ( file_exists( $folder ) && is_dir( $folder ) ) {
88
- $files = list_files( $folder, 1 );
89
- if ( empty( $files ) ) {
90
- continue;
91
- }
92
-
93
- foreach ( $files as $file ) {
94
-
95
- if ( function_exists( 'mime_content_type' ) ) {
96
- // get file mime type
97
- $mime_type = mime_content_type( $file );
98
-
99
- // Valid if text files.
100
- $valid_file_type = strpos( $mime_type, 'text/' ) === 0;
101
- } else {
102
- // If `mime_content_type` isn't available, just check file extension.
103
- $ext = pathinfo( $file, PATHINFO_EXTENSION );
104
-
105
- // skip files which don't have a `.json` extension.
106
- $valid_file_type = ! empty( $ext ) && $ext === 'json';
107
- }
108
-
109
- if ( ! $valid_file_type ) {
110
- continue;
111
- }
112
-
113
- // get file contents
114
- $file_contents = file_get_contents( $file );
115
-
116
- // skip if file_get_contents fails
117
- if ( $file_contents === false ) {
118
- continue;
119
- }
120
-
121
- // json decode
122
- $panels_data = json_decode( $file_contents, true );
123
-
124
- if ( ! empty( $panels_data ) ) {
125
- // get file name by stripping out folder path and .json extension
126
- $file_name = str_replace( array( $folder . '/', '.json' ), '', $file );
127
-
128
- // get name: check for id or name else use filename
129
- $panels_data['id'] = sanitize_title_with_dashes( $this->get_layout_id( $panels_data, $file_name ) );
130
-
131
- if ( empty( $panels_data['name'] ) ) {
132
- $panels_data['name'] = $file_name;
133
- }
134
-
135
- $panels_data['name'] = sanitize_text_field( $panels_data['name'] );
136
-
137
- // get screenshot: check for screenshot prop else try use image file with same filename.
138
- $panels_data['screenshot'] = $this->get_layout_file_screenshot( $panels_data, $folder, $file_name );
139
-
140
- // set item on layouts array
141
- $layouts[ $panels_data['id'] ] = $panels_data;
142
- }
143
- }
144
- }
145
- }
146
-
147
- return $layouts;
148
- }
149
-
150
- private function get_layout_id( $layout_data, $fallback ) {
151
- if ( ! empty( $layout_data['id'] ) ) {
152
- return $layout_data['id'];
153
- } else if ( ! empty( $layout_data['name'] ) ) {
154
- return $layout_data['name'];
155
- } else {
156
- return $fallback;
157
- }
158
- }
159
-
160
- private function get_layout_file_screenshot( $panels_data, $folder_path, $file_name ) {
161
- if ( ! empty( $panels_data['screenshot'] ) ) {
162
- return $panels_data['screenshot'];
163
- } else {
164
- $paths = glob( $folder_path . "/$file_name.{jpg,jpeg,gif,png}", GLOB_BRACE );
165
- // Highlander Condition. There can be only one.
166
- $screenshot_path = empty( $paths ) ? '' : wp_normalize_path( $paths[0] );
167
- $wp_content_dir = wp_normalize_path( WP_CONTENT_DIR );
168
- $screenshot_url = '';
169
- if ( file_exists( $screenshot_path ) &&
170
- strrpos( $screenshot_path, $wp_content_dir ) === 0 ) {
171
-
172
- $screenshot_url = str_replace( $wp_content_dir, content_url(), $screenshot_path );
173
- }
174
-
175
- return $screenshot_url;
176
- }
177
- }
178
-
179
- /**
180
- * Gets all the prebuilt layouts.
181
- */
182
- function action_get_prebuilt_layouts() {
183
- if ( empty( $_REQUEST['_panelsnonce'] ) || ! wp_verify_nonce( $_REQUEST['_panelsnonce'], 'panels_action' ) ) {
184
- wp_die( __( 'Invalid request.', 'siteorigin-panels' ), 403 );
185
- }
186
-
187
- // Get any layouts that the current user could edit.
188
- header( 'content-type: application/json' );
189
-
190
- $type = ! empty( $_REQUEST['type'] ) ? $_REQUEST['type'] : 'directory-siteorigin';
191
- $search = ! empty( $_REQUEST['search'] ) ? trim( strtolower( $_REQUEST['search'] ) ) : '';
192
- $page_num = ! empty( $_REQUEST['page'] ) ? intval( $_REQUEST['page'] ) : 1;
193
-
194
- $return = array(
195
- 'title' => '',
196
- 'items' => array()
197
- );
198
- if ( $type == 'prebuilt' ) {
199
- $return['title'] = __( 'Theme Defined Layouts', 'siteorigin-panels' );
200
-
201
- // This is for theme bundled prebuilt directories
202
- $layouts = apply_filters( 'siteorigin_panels_prebuilt_layouts', array() );
203
-
204
- foreach ( $layouts as $id => $vals ) {
205
- if ( ! empty( $search ) && strpos( strtolower( $vals['name'] ), $search ) === false ) {
206
- continue;
207
- }
208
-
209
- $return['items'][] = array(
210
- 'title' => $vals['name'],
211
- 'id' => $id,
212
- 'type' => 'prebuilt',
213
- 'description' => isset( $vals['description'] ) ? $vals['description'] : '',
214
- 'screenshot' => ! empty( $vals['screenshot'] ) ? $vals['screenshot'] : ''
215
- );
216
- }
217
-
218
- $return['max_num_pages'] = 1;
219
- } elseif ( substr( $type, 0, 10 ) == 'directory-' ) {
220
- $return['title'] = __( 'Layouts Directory', 'siteorigin-panels' );
221
-
222
- // This is a query of the prebuilt layout directory
223
- $query = array();
224
- if ( ! empty( $search ) ) {
225
- $query['search'] = $search;
226
- }
227
- $query['page'] = $page_num;
228
-
229
- $directory_id = str_replace( 'directory-', '', $type );
230
- $directories = $this->get_directories();
231
- $directory = ! empty( $directories[ $directory_id ] ) ? $directories[ $directory_id ] : false;
232
-
233
- if( empty( $directory ) ) {
234
- return false;
235
- }
236
-
237
- $url = add_query_arg( $query, $directory[ 'url' ] . 'wp-admin/admin-ajax.php?action=query_layouts' );
238
- if( ! empty( $directory[ 'args' ] ) && is_array( $directory[ 'args' ] ) ) {
239
- $url = add_query_arg( $directory[ 'args' ], $url );
240
- }
241
-
242
- $url = apply_filters( 'siteorigin_panels_layouts_directory_url', $url );
243
- $response = wp_remote_get( $url );
244
-
245
- if ( is_array( $response ) && $response['response']['code'] == 200 ) {
246
- $results = json_decode( $response['body'], true );
247
- if ( ! empty( $results ) && ! empty( $results['items'] ) ) {
248
- foreach ( $results['items'] as $item ) {
249
- $item['id'] = $item['slug'];
250
- $item['type'] = $type;
251
-
252
- if( empty( $item['screenshot'] ) && ! empty( $item['preview'] ) ) {
253
- $preview_url = add_query_arg( 'screenshot', 'true', $item[ 'preview' ] );
254
- $item['screenshot'] = 'https://s.wordpress.com/mshots/v1/' . urlencode( $preview_url ) . '?w=700';
255
- }
256
-
257
- $return['items'][] = $item;
258
- }
259
- }
260
-
261
- $return['max_num_pages'] = $results['max_num_pages'];
262
- }
263
- } elseif ( strpos( $type, 'clone_' ) !== false ) {
264
- // Check that the user can view the given page types
265
- $post_type = get_post_type_object( str_replace( 'clone_', '', $type ) );
266
- if( empty( $post_type ) ) {
267
- return;
268
- }
269
-
270
- $return['title'] = sprintf( __( 'Clone %s', 'siteorigin-panels' ), esc_html( $post_type->labels->singular_name ) );
271
-
272
- global $wpdb;
273
- $user_can_read_private = ( $post_type->name == 'post' && current_user_can( 'read_private_posts' ) || ( $post_type->name == 'page' && current_user_can( 'read_private_pages' ) ) );
274
- $include_private = $user_can_read_private ? "OR posts.post_status = 'private' " : "";
275
-
276
- // Select only the posts with the given post type that also have panels_data
277
- $results = $wpdb->get_results( "
278
- SELECT SQL_CALC_FOUND_ROWS DISTINCT ID, post_title, meta.meta_value
279
- FROM {$wpdb->posts} AS posts
280
- JOIN {$wpdb->postmeta} AS meta ON posts.ID = meta.post_id
281
- WHERE
282
- posts.post_type = '" . esc_sql( $post_type->name ) . "'
283
- AND meta.meta_key = 'panels_data'
284
- " . ( ! empty( $search ) ? 'AND posts.post_title LIKE "%' . esc_sql( $search ) . '%"' : '' ) . "
285
- AND ( posts.post_status = 'publish' OR posts.post_status = 'draft' " . $include_private . ")
286
- ORDER BY post_date DESC
287
- LIMIT 16 OFFSET " . intval( ( $page_num - 1 ) * 16 ) );
288
- $total_posts = $wpdb->get_var( "SELECT FOUND_ROWS();" );
289
-
290
- foreach ( $results as $result ) {
291
- $thumbnail = get_the_post_thumbnail_url( $result->ID, array( 400, 300 ) );
292
- $return['items'][] = array(
293
- 'id' => $result->ID,
294
- 'title' => $result->post_title,
295
- 'type' => $type,
296
- 'screenshot' => ! empty( $thumbnail ) ? $thumbnail : ''
297
- );
298
- }
299
-
300
- $return['max_num_pages'] = ceil( $total_posts / 16 );
301
-
302
- } else {
303
- // An invalid type. Display an error message.
304
- }
305
-
306
- // Add the search part to the title
307
- if ( ! empty( $search ) ) {
308
- $return['title'] .= __( ' - Results For:', 'siteorigin-panels' ) . ' <em>' . esc_html( $search ) . '</em>';
309
- }
310
-
311
- echo json_encode( $return );
312
-
313
- wp_die();
314
- }
315
-
316
- /**
317
- * Ajax handler to get an individual prebuilt layout
318
- */
319
- function action_get_prebuilt_layout() {
320
- if ( empty( $_REQUEST['type'] ) ) {
321
- wp_die();
322
- }
323
- if ( empty( $_REQUEST['lid'] ) ) {
324
- wp_die();
325
- }
326
- if ( empty( $_REQUEST['_panelsnonce'] ) || ! wp_verify_nonce( $_REQUEST['_panelsnonce'], 'panels_action' ) ) {
327
- wp_die();
328
- }
329
-
330
- header( 'content-type: application/json' );
331
- $panels_data = array();
332
- $raw_panels_data = false;
333
-
334
- if ( $_REQUEST['type'] == 'prebuilt' ) {
335
- $layouts = apply_filters( 'siteorigin_panels_prebuilt_layouts', array() );
336
- $lid = ! empty( $_REQUEST['lid'] ) ? $_REQUEST['lid'] : false;
337
-
338
- if ( empty( $lid ) || empty( $layouts[ $lid ] ) ) {
339
- wp_send_json_error( array(
340
- 'error' => true,
341
- 'message' => __( 'Missing layout ID or no such layout exists', 'siteorigin-panels' ),
342
- ) );
343
- }
344
-
345
- $layout = $layouts[ $_REQUEST['lid'] ];
346
-
347
- // Fix the format of this layout
348
- if( !empty( $layout[ 'filename' ] ) ) {
349
- $filename = $layout[ 'filename' ];
350
- // Only accept filenames that end with .json
351
- if( substr( $filename, -5, 5 ) === '.json' && file_exists( $filename ) ) {
352
- $panels_data = json_decode( file_get_contents( $filename ), true );
353
- $layout[ 'widgets' ] = ! empty( $panels_data[ 'widgets' ] ) ? $panels_data[ 'widgets' ] : array();
354
- $layout[ 'grids' ] = ! empty( $panels_data[ 'grids' ] ) ? $panels_data[ 'grids' ] : array();
355
- $layout[ 'grid_cells' ] = ! empty( $panels_data[ 'grid_cells' ] ) ? $panels_data[ 'grid_cells' ] : array();
356
- }
357
- }
358
-
359
- // A theme or plugin could use this to change the data in the layout
360
- $panels_data = apply_filters( 'siteorigin_panels_prebuilt_layout', $layout, $lid );
361
-
362
- // Remove all the layout specific attributes
363
- if ( isset( $panels_data['name'] ) ) unset( $panels_data['name'] );
364
- if ( isset( $panels_data['screenshot'] ) ) unset( $panels_data['screenshot'] );
365
- if ( isset( $panels_data['filename'] ) ) unset( $panels_data['filename'] );
366
-
367
- $raw_panels_data = true;
368
-
369
- } elseif ( substr( $_REQUEST['type'], 0, 10 ) == 'directory-' ) {
370
- $directory_id = str_replace( 'directory-', '', $_REQUEST['type'] );
371
- $directories = $this->get_directories();
372
- $directory = ! empty( $directories[ $directory_id ] ) ? $directories[ $directory_id ] : false;
373
-
374
- if( ! empty( $directory ) ) {
375
- $url = $directory[ 'url' ] . 'layout/' . urlencode( $_REQUEST[ 'lid' ] ) . '/?action=download';
376
- if( ! empty( $directory[ 'args' ] ) && is_array( $directory[ 'args' ] ) ) {
377
- $url = add_query_arg( $directory[ 'args' ], $url );
378
- }
379
-
380
- $response = wp_remote_get( $url );
381
- if ( $response['response']['code'] == 200 ) {
382
- // For now, we'll just pretend to load this
383
- $panels_data = json_decode( $response['body'], true );
384
- } else {
385
- wp_send_json_error( array(
386
- 'error' => true,
387
- 'message' => __( 'There was a problem fetching the layout. Please try again later.', 'siteorigin-panels' ),
388
- ) );
389
- }
390
- }
391
- $raw_panels_data = true;
392
-
393
- } elseif ( current_user_can( 'edit_post', $_REQUEST['lid'] ) ) {
394
- $panels_data = get_post_meta( $_REQUEST['lid'], 'panels_data', true );
395
-
396
- // Clear id and timestamp for SO widgets to prevent 'newer content version' notification in widget forms.
397
- foreach ( $panels_data['widgets'] as &$widget ) {
398
- unset( $widget['_sow_form_id'] );
399
- unset( $widget['_sow_form_timestamp'] );
400
- }
401
- }
402
-
403
- if( $raw_panels_data ) {
404
- // This panels_data is flagged as raw, so it needs to be processed.
405
- $panels_data = apply_filters( 'siteorigin_panels_data', $panels_data, false );
406
- $panels_data['widgets'] = SiteOrigin_Panels_Admin::single()->process_raw_widgets( $panels_data['widgets'], array(), true, true );
407
- }
408
-
409
- wp_send_json_success( $panels_data );
410
- }
411
-
412
- /**
413
- * Ajax handler to import a layout
414
- */
415
- function action_import_layout() {
416
- if ( empty( $_REQUEST['_panelsnonce'] ) || ! wp_verify_nonce( $_REQUEST['_panelsnonce'], 'panels_action' ) ) {
417
- wp_die();
418
- }
419
-
420
- if ( ! empty( $_FILES['panels_import_data']['tmp_name'] ) ) {
421
- header( 'content-type:application/json' );
422
- $json = file_get_contents( $_FILES['panels_import_data']['tmp_name'] );
423
- $panels_data = json_decode( $json, true );
424
- $panels_data = apply_filters( 'siteorigin_panels_data', $panels_data, false );
425
- $panels_data['widgets'] = SiteOrigin_Panels_Admin::single()->process_raw_widgets( $panels_data['widgets'], array(), true, true );
426
- $json = json_encode( $panels_data );
427
- @unlink( $_FILES['panels_import_data']['tmp_name'] );
428
- echo $json;
429
- }
430
- wp_die();
431
- }
432
-
433
- /**
434
- * Export a given layout as a JSON file.
435
- */
436
- function action_export_layout() {
437
- if ( empty( $_REQUEST['_panelsnonce'] ) || ! wp_verify_nonce( $_REQUEST['_panelsnonce'], 'panels_action' ) ) {
438
- wp_die();
439
- }
440
-
441
- $export_data = wp_unslash( $_POST['panels_export_data'] );
442
-
443
- $decoded_export_data = json_decode( $export_data, true );
444
-
445
- if ( ! empty( $decoded_export_data['name'] ) ) {
446
- $decoded_export_data['id'] = sanitize_title_with_dashes( $decoded_export_data['name'] );
447
- $filename = $decoded_export_data['id'];
448
- } else {
449
- $filename = 'layout-' . date( 'dmY' );
450
- }
451
-
452
-
453
- header( 'content-type: application/json' );
454
- header( "Content-Disposition: attachment; filename=$filename.json" );
455
-
456
- echo $export_data;
457
-
458
- wp_die();
459
- }
460
-
461
- /**
462
- * Enable the directory.
463
- */
464
- function action_directory_enable() {
465
- if ( empty( $_REQUEST['_panelsnonce'] ) || ! wp_verify_nonce( $_REQUEST['_panelsnonce'], 'panels_action' ) ) {
466
- wp_die();
467
- }
468
- $user = get_current_user_id();
469
- update_user_meta( $user, 'so_panels_directory_enabled', true );
470
- wp_die();
471
- }
472
-
473
- /**
474
- * Load a layout from a json file
475
- *
476
- * @param $id
477
- * @param $name
478
- * @param $json_file
479
- * @param bool $screenshot
480
- *
481
- * @return array The data for the layout
482
- */
483
- public static function load_layout($id, $name, $json_file, $screenshot = false) {
484
- $layout_data = json_decode(file_get_contents($json_file), true);
485
- $layout_data = apply_filters('siteorigin_panels_load_layout_' . $id, $layout_data);
486
-
487
- $layout_data = array_merge(array(
488
- 'name' => $name,
489
- 'screenshot' => $screenshot,
490
- ), $layout_data);
491
-
492
- return $layout_data;
493
- }
494
- }
1
+ <?php
2
+
3
+ /**
4
+ * Class SiteOrigin_Panels_Admin
5
+ *
6
+ * Handles all the admin and database interactions.
7
+ */
8
+ class SiteOrigin_Panels_Admin_Layouts {
9
+
10
+ const LAYOUT_URL = 'https://layouts.siteorigin.com/';
11
+
12
+ function __construct() {
13
+ // Filter all the available external layout directories.
14
+ add_filter( 'siteorigin_panels_external_layout_directories', array( $this, 'filter_directories' ), 8 );
15
+ // Filter all the available local layout folders.
16
+ add_filter( 'siteorigin_panels_prebuilt_layouts', array( $this, 'get_local_layouts' ), 8 );
17
+
18
+ add_action( 'wp_ajax_so_panels_layouts_query', array( $this, 'action_get_prebuilt_layouts' ) );
19
+ add_action( 'wp_ajax_so_panels_get_layout', array( $this, 'action_get_prebuilt_layout' ) );
20
+ add_action( 'wp_ajax_so_panels_import_layout', array( $this, 'action_import_layout' ) );
21
+ add_action( 'wp_ajax_so_panels_export_layout', array( $this, 'action_export_layout' ) );
22
+ add_action( 'wp_ajax_so_panels_directory_enable', array( $this, 'action_directory_enable' ) );
23
+ }
24
+
25
+ /**
26
+ * @return SiteOrigin_Panels_Admin_Layouts
27
+ */
28
+ public static function single() {
29
+ static $single;
30
+ return empty( $single ) ? $single = new self() : $single;
31
+ }
32
+
33
+ /**
34
+ * Add the main SiteOrigin layout directory
35
+ */
36
+ public function filter_directories( $directories ){
37
+ if ( apply_filters( 'siteorigin_panels_layouts_directory_enabled', true ) ) {
38
+ $directories['siteorigin'] = array(
39
+ // The title of the layouts directory in the sidebar.
40
+ 'title' => __( 'Layouts Directory', 'siteorigin-panels' ),
41
+ // The URL of the directory.
42
+ 'url' => self::LAYOUT_URL,
43
+ // Any additional arguments to pass to the layouts server
44
+ 'args' => array()
45
+ );
46
+ }
47
+
48
+ return $directories;
49
+ }
50
+
51
+ /**
52
+ * Get all the layout directories.
53
+ *
54
+ * @return array
55
+ */
56
+ public function get_directories(){
57
+ $directories = apply_filters( 'siteorigin_panels_external_layout_directories', array() );
58
+ if( empty( $directories ) || ! is_array( $directories ) ) {
59
+ $directories = array();
60
+ }
61
+
62
+ return $directories;
63
+ }
64
+
65
+
66
+ /**
67
+ * Looks through local folders in the active theme and any others filtered in by theme and plugins, to find JSON
68
+ * prebuilt layouts.
69
+ *
70
+ */
71
+ public function get_local_layouts() {
72
+
73
+ // By default we'll look for layouts in a directory in the active theme
74
+ $layout_folders = array( get_template_directory() . '/siteorigin-page-builder-layouts' );
75
+
76
+ // And the child theme if there is one.
77
+ if ( is_child_theme() ) {
78
+ $layout_folders[] = get_stylesheet_directory() . '/siteorigin-page-builder-layouts';
79
+ }
80
+
81
+ // This allows themes and plugins to customize where we look for layouts.
82
+ $layout_folders = apply_filters( 'siteorigin_panels_local_layouts_directories', $layout_folders );
83
+
84
+ $layouts = array();
85
+ foreach ( $layout_folders as $folder ) {
86
+ $folder = realpath($folder);
87
+ if ( file_exists( $folder ) && is_dir( $folder ) ) {
88
+ $files = list_files( $folder, 1 );
89
+ if ( empty( $files ) ) {
90
+ continue;
91
+ }
92
+
93
+ foreach ( $files as $file ) {
94
+
95
+ if ( function_exists( 'mime_content_type' ) ) {
96
+ // get file mime type
97
+ $mime_type = mime_content_type( $file );
98
+
99
+ // Valid if text files.
100
+ $valid_file_type = strpos( $mime_type, 'text/' ) === 0;
101
+ } else {
102
+ // If `mime_content_type` isn't available, just check file extension.
103
+ $ext = pathinfo( $file, PATHINFO_EXTENSION );
104
+
105
+ // skip files which don't have a `.json` extension.
106
+ $valid_file_type = ! empty( $ext ) && $ext === 'json';
107
+ }
108
+
109
+ if ( ! $valid_file_type ) {
110
+ continue;
111
+ }
112
+
113
+ // get file contents
114
+ $file_contents = file_get_contents( $file );
115
+
116
+ // skip if file_get_contents fails
117
+ if ( $file_contents === false ) {
118
+ continue;
119
+ }
120
+
121
+ // json decode
122
+ $panels_data = json_decode( $file_contents, true );
123
+
124
+ if ( ! empty( $panels_data ) ) {
125
+ // get file name by stripping out folder path and .json extension
126
+ $file_name = str_replace( array( $folder . '/', '.json' ), '', $file );
127
+
128
+ // get name: check for id or name else use filename
129
+ $panels_data['id'] = sanitize_title_with_dashes( $this->get_layout_id( $panels_data, $file_name ) );
130
+
131
+ if ( empty( $panels_data['name'] ) ) {
132
+ $panels_data['name'] = $file_name;
133
+ }
134
+
135
+ $panels_data['name'] = sanitize_text_field( $panels_data['name'] );
136
+
137
+ // get screenshot: check for screenshot prop else try use image file with same filename.
138
+ $panels_data['screenshot'] = $this->get_layout_file_screenshot( $panels_data, $folder, $file_name );
139
+
140
+ // set item on layouts array
141
+ $layouts[ $panels_data['id'] ] = $panels_data;
142
+ }
143
+ }
144
+ }
145
+ }
146
+
147
+ return $layouts;
148
+ }
149
+
150
+ private function get_layout_id( $layout_data, $fallback ) {
151
+ if ( ! empty( $layout_data['id'] ) ) {
152
+ return $layout_data['id'];
153
+ } else if ( ! empty( $layout_data['name'] ) ) {
154
+ return $layout_data['name'];
155
+ } else {
156
+ return $fallback;
157
+ }
158
+ }
159
+
160
+ private function get_layout_file_screenshot( $panels_data, $folder_path, $file_name ) {
161
+ if ( ! empty( $panels_data['screenshot'] ) ) {
162
+ return $panels_data['screenshot'];
163
+ } else {
164
+ $paths = glob( $folder_path . "/$file_name.{jpg,jpeg,gif,png}", GLOB_BRACE );
165
+ // Highlander Condition. There can be only one.
166
+ $screenshot_path = empty( $paths ) ? '' : wp_normalize_path( $paths[0] );
167
+ $wp_content_dir = wp_normalize_path( WP_CONTENT_DIR );
168
+ $screenshot_url = '';
169
+ if ( file_exists( $screenshot_path ) &&
170
+ strrpos( $screenshot_path, $wp_content_dir ) === 0 ) {
171
+
172
+ $screenshot_url = str_replace( $wp_content_dir, content_url(), $screenshot_path );
173
+ }
174
+
175
+ return $screenshot_url;
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Gets all the prebuilt layouts.
181
+ */
182
+ function action_get_prebuilt_layouts() {
183
+ if ( empty( $_REQUEST['_panelsnonce'] ) || ! wp_verify_nonce( $_REQUEST['_panelsnonce'], 'panels_action' ) ) {
184
+ wp_die( __( 'Invalid request.', 'siteorigin-panels' ), 403 );
185
+ }
186
+
187
+ // Get any layouts that the current user could edit.
188
+ header( 'content-type: application/json' );
189
+
190
+ $type = ! empty( $_REQUEST['type'] ) ? $_REQUEST['type'] : 'directory-siteorigin';
191
+ $search = ! empty( $_REQUEST['search'] ) ? trim( strtolower( $_REQUEST['search'] ) ) : '';
192
+ $page_num = ! empty( $_REQUEST['page'] ) ? intval( $_REQUEST['page'] ) : 1;
193
+
194
+ $return = array(
195
+ 'title' => '',
196
+ 'items' => array()
197
+ );
198
+ if ( $type == 'prebuilt' ) {
199
+ $return['title'] = __( 'Theme Defined Layouts', 'siteorigin-panels' );
200
+
201
+ // This is for theme bundled prebuilt directories
202
+ $layouts = apply_filters( 'siteorigin_panels_prebuilt_layouts', array() );
203
+
204
+ foreach ( $layouts as $id => $vals ) {
205
+ if ( ! empty( $search ) && strpos( strtolower( $vals['name'] ), $search ) === false ) {
206
+ continue;
207
+ }
208
+
209
+ $return['items'][] = array(
210
+ 'title' => $vals['name'],
211
+ 'id' => $id,
212
+ 'type' => 'prebuilt',
213
+ 'description' => isset( $vals['description'] ) ? $vals['description'] : '',
214
+ 'screenshot' => ! empty( $vals['screenshot'] ) ? $vals['screenshot'] : ''
215
+ );
216
+ }
217
+
218
+ $return['max_num_pages'] = 1;
219
+ } elseif ( substr( $type, 0, 10 ) == 'directory-' ) {
220
+ $return['title'] = __( 'Layouts Directory', 'siteorigin-panels' );
221
+
222
+ // This is a query of the prebuilt layout directory
223
+ $query = array();
224
+ if ( ! empty( $search ) ) {
225
+ $query['search'] = $search;
226
+ }
227
+ $query['page'] = $page_num;
228
+
229
+ $directory_id = str_replace( 'directory-', '', $type );
230
+ $directories = $this->get_directories();
231
+ $directory = ! empty( $directories[ $directory_id ] ) ? $directories[ $directory_id ] : false;
232
+
233
+ if( empty( $directory ) ) {
234
+ return false;
235
+ }
236
+
237
+ $url = add_query_arg( $query, $directory[ 'url' ] . 'wp-admin/admin-ajax.php?action=query_layouts' );
238
+ if( ! empty( $directory[ 'args' ] ) && is_array( $directory[ 'args' ] ) ) {
239
+ $url = add_query_arg( $directory[ 'args' ], $url );
240
+ }
241
+
242
+ $url = apply_filters( 'siteorigin_panels_layouts_directory_url', $url );
243
+ $response = wp_remote_get( $url );
244
+
245
+ if ( is_array( $response ) && $response['response']['code'] == 200 ) {
246
+ $results = json_decode( $response['body'], true );
247
+ if ( ! empty( $results ) && ! empty( $results['items'] ) ) {
248
+ foreach ( $results['items'] as $item ) {
249
+ $item['id'] = $item['slug'];
250
+ $item['type'] = $type;
251
+
252
+ if( empty( $item['screenshot'] ) && ! empty( $item['preview'] ) ) {
253
+ $preview_url = add_query_arg( 'screenshot', 'true', $item[ 'preview' ] );
254
+ $item['screenshot'] = 'https://s.wordpress.com/mshots/v1/' . urlencode( $preview_url ) . '?w=700';
255
+ }
256
+
257
+ $return['items'][] = $item;
258
+ }
259
+ }
260
+
261
+ $return['max_num_pages'] = $results['max_num_pages'];
262
+ }
263
+ } elseif ( strpos( $type, 'clone_' ) !== false ) {
264
+ // Check that the user can view the given page types
265
+ $post_type = get_post_type_object( str_replace( 'clone_', '', $type ) );
266
+ if( empty( $post_type ) ) {
267
+ return;
268
+ }
269
+
270
+ $return['title'] = sprintf( __( 'Clone %s', 'siteorigin-panels' ), esc_html( $post_type->labels->singular_name ) );
271
+
272
+ global $wpdb;
273
+ $user_can_read_private = ( $post_type->name == 'post' && current_user_can( 'read_private_posts' ) || ( $post_type->name == 'page' && current_user_can( 'read_private_pages' ) ) );
274
+ $include_private = $user_can_read_private ? "OR posts.post_status = 'private' " : "";
275
+
276
+ // Select only the posts with the given post type that also have panels_data
277
+ $results = $wpdb->get_results( "
278
+ SELECT SQL_CALC_FOUND_ROWS DISTINCT ID, post_title, meta.meta_value
279
+ FROM {$wpdb->posts} AS posts
280
+ JOIN {$wpdb->postmeta} AS meta ON posts.ID = meta.post_id
281
+ WHERE
282
+ posts.post_type = '" . esc_sql( $post_type->name ) . "'
283
+ AND meta.meta_key = 'panels_data'
284
+ " . ( ! empty( $search ) ? 'AND posts.post_title LIKE "%' . esc_sql( $search ) . '%"' : '' ) . "
285
+ AND ( posts.post_status = 'publish' OR posts.post_status = 'draft' " . $include_private . ")
286
+ ORDER BY post_date DESC
287
+ LIMIT 16 OFFSET " . intval( ( $page_num - 1 ) * 16 ) );
288
+ $total_posts = $wpdb->get_var( "SELECT FOUND_ROWS();" );
289
+
290
+ foreach ( $results as $result ) {
291
+ $thumbnail = get_the_post_thumbnail_url( $result->ID, array( 400, 300 ) );
292
+ $return['items'][] = array(
293
+ 'id' => $result->ID,
294
+ 'title' => $result->post_title,
295
+ 'type' => $type,
296
+ 'screenshot' => ! empty( $thumbnail ) ? $thumbnail : ''
297
+ );
298
+ }
299
+
300
+ $return['max_num_pages'] = ceil( $total_posts / 16 );
301
+
302
+ } else {
303
+ // An invalid type. Display an error message.
304
+ }
305
+
306
+ // Add the search part to the title
307
+ if ( ! empty( $search ) ) {
308
+ $return['title'] .= __( ' - Results For:', 'siteorigin-panels' ) . ' <em>' . esc_html( $search ) . '</em>';
309
+ }
310
+
311
+ echo json_encode( $return );
312
+
313
+ wp_die();
314
+ }
315
+
316
+ /**
317
+ * Ajax handler to get an individual prebuilt layout
318
+ */
319
+ function action_get_prebuilt_layout() {
320
+ if ( empty( $_REQUEST['type'] ) ) {
321
+ wp_die();
322
+ }
323
+ if ( empty( $_REQUEST['lid'] ) ) {
324
+ wp_die();
325
+ }
326
+ if ( empty( $_REQUEST['_panelsnonce'] ) || ! wp_verify_nonce( $_REQUEST['_panelsnonce'], 'panels_action' ) ) {
327
+ wp_die();
328
+ }
329
+
330
+ header( 'content-type: application/json' );
331
+ $panels_data = array();
332
+ $raw_panels_data = false;
333
+
334
+ if ( $_REQUEST['type'] == 'prebuilt' ) {
335
+ $layouts = apply_filters( 'siteorigin_panels_prebuilt_layouts', array() );
336
+ $lid = ! empty( $_REQUEST['lid'] ) ? $_REQUEST['lid'] : false;
337
+
338
+ if ( empty( $lid ) || empty( $layouts[ $lid ] ) ) {
339
+ wp_send_json_error( array(
340
+ 'error' => true,
341
+ 'message' => __( 'Missing layout ID or no such layout exists', 'siteorigin-panels' ),
342
+ ) );
343
+ }
344
+
345
+ $layout = $layouts[ $_REQUEST['lid'] ];
346
+
347
+ // Fix the format of this layout
348
+ if( !empty( $layout[ 'filename' ] ) ) {
349
+ $filename = $layout[ 'filename' ];
350
+ // Only accept filenames that end with .json
351
+ if( substr( $filename, -5, 5 ) === '.json' && file_exists( $filename ) ) {
352
+ $panels_data = json_decode( file_get_contents( $filename ), true );
353
+ $layout[ 'widgets' ] = ! empty( $panels_data[ 'widgets' ] ) ? $panels_data[ 'widgets' ] : array();
354
+ $layout[ 'grids' ] = ! empty( $panels_data[ 'grids' ] ) ? $panels_data[ 'grids' ] : array();
355
+ $layout[ 'grid_cells' ] = ! empty( $panels_data[ 'grid_cells' ] ) ? $panels_data[ 'grid_cells' ] : array();
356
+ }
357
+ }
358
+
359
+ // A theme or plugin could use this to change the data in the layout
360
+ $panels_data = apply_filters( 'siteorigin_panels_prebuilt_layout', $layout, $lid );
361
+
362
+ // Remove all the layout specific attributes
363
+ if ( isset( $panels_data['name'] ) ) unset( $panels_data['name'] );
364
+ if ( isset( $panels_data['screenshot'] ) ) unset( $panels_data['screenshot'] );
365
+ if ( isset( $panels_data['filename'] ) ) unset( $panels_data['filename'] );
366
+
367
+ $raw_panels_data = true;
368
+
369
+ } elseif ( substr( $_REQUEST['type'], 0, 10 ) == 'directory-' ) {
370
+ $directory_id = str_replace( 'directory-', '', $_REQUEST['type'] );
371
+ $directories = $this->get_directories();
372
+ $directory = ! empty( $directories[ $directory_id ] ) ? $directories[ $directory_id ] : false;
373
+
374
+ if( ! empty( $directory ) ) {
375
+ $url = $directory[ 'url' ] . 'layout/' . urlencode( $_REQUEST[ 'lid' ] ) . '/?action=download';
376
+ if( ! empty( $directory[ 'args' ] ) && is_array( $directory[ 'args' ] ) ) {
377
+ $url = add_query_arg( $directory[ 'args' ], $url );
378
+ }
379
+
380
+ $response = wp_remote_get( $url );
381
+ if ( $response['response']['code'] == 200 ) {
382
+ // For now, we'll just pretend to load this
383
+ $panels_data = json_decode( $response['body'], true );
384
+ } else {
385
+ wp_send_json_error( array(
386
+ 'error' => true,
387
+ 'message' => __( 'There was a problem fetching the layout. Please try again later.', 'siteorigin-panels' ),
388
+ ) );
389
+ }
390
+ }
391
+ $raw_panels_data = true;
392
+
393
+ } elseif ( current_user_can( 'edit_post', $_REQUEST['lid'] ) ) {
394
+ $panels_data = get_post_meta( $_REQUEST['lid'], 'panels_data', true );
395
+
396
+ // Clear id and timestamp for SO widgets to prevent 'newer content version' notification in widget forms.
397
+ foreach ( $panels_data['widgets'] as &$widget ) {
398
+ unset( $widget['_sow_form_id'] );
399
+ unset( $widget['_sow_form_timestamp'] );
400
+ }
401
+ }
402
+
403
+ if( $raw_panels_data ) {
404
+ // This panels_data is flagged as raw, so it needs to be processed.
405
+ $panels_data = apply_filters( 'siteorigin_panels_data', $panels_data, false );
406
+ $panels_data['widgets'] = SiteOrigin_Panels_Admin::single()->process_raw_widgets( $panels_data['widgets'], array(), true, true );
407
+ }
408
+
409
+ wp_send_json_success( $panels_data );
410
+ }
411
+
412
+ /**
413
+ * Ajax handler to import a layout
414
+ */
415
+ function action_import_layout() {
416
+ if ( empty( $_REQUEST['_panelsnonce'] ) || ! wp_verify_nonce( $_REQUEST['_panelsnonce'], 'panels_action' ) ) {
417
+ wp_die();
418
+ }
419
+
420
+ if ( ! empty( $_FILES['panels_import_data']['tmp_name'] ) ) {
421
+ header( 'content-type:application/json' );
422
+ $json = file_get_contents( $_FILES['panels_import_data']['tmp_name'] );
423
+ $panels_data = json_decode( $json, true );
424
+ $panels_data = apply_filters( 'siteorigin_panels_data', $panels_data, false );
425
+ $panels_data['widgets'] = SiteOrigin_Panels_Admin::single()->process_raw_widgets( $panels_data['widgets'], array(), true, true );
426
+ $json = json_encode( $panels_data );
427
+ @unlink( $_FILES['panels_import_data']['tmp_name'] );
428
+ echo $json;
429
+ }
430
+ wp_die();
431
+ }
432
+
433
+ /**
434
+ * Export a given layout as a JSON file.
435
+ */
436
+ function action_export_layout() {
437
+ if ( empty( $_REQUEST['_panelsnonce'] ) || ! wp_verify_nonce( $_REQUEST['_panelsnonce'], 'panels_action' ) ) {
438
+ wp_die();
439
+ }
440
+
441
+ $export_data = wp_unslash( $_POST['panels_export_data'] );
442
+
443
+ $decoded_export_data = json_decode( $export_data, true );
444
+
445
+ if ( ! empty( $decoded_export_data['name'] ) ) {
446
+ $decoded_export_data['id'] = sanitize_title_with_dashes( $decoded_export_data['name'] );
447
+ $filename = $decoded_export_data['id'];
448
+ } else {
449
+ $filename = 'layout-' . date( 'dmY' );
450
+ }
451
+
452
+
453
+ header( 'content-type: application/json' );
454
+ header( "Content-Disposition: attachment; filename=$filename.json" );
455
+
456
+ echo $export_data;
457
+
458
+ wp_die();
459
+ }
460
+
461
+ /**
462
+ * Enable the directory.
463
+ */
464
+ function action_directory_enable() {
465
+ if ( empty( $_REQUEST['_panelsnonce'] ) || ! wp_verify_nonce( $_REQUEST['_panelsnonce'], 'panels_action' ) ) {
466
+ wp_die();
467
+ }
468
+ $user = get_current_user_id();
469
+ update_user_meta( $user, 'so_panels_directory_enabled', true );
470
+ wp_die();
471
+ }
472
+
473
+ /**
474
+ * Load a layout from a json file
475
+ *
476
+ * @param $id
477
+ * @param $name
478
+ * @param $json_file
479
+ * @param bool $screenshot
480
+ *
481
+ * @return array The data for the layout
482
+ */
483
+ public static function load_layout($id, $name, $json_file, $screenshot = false) {
484
+ $layout_data = json_decode(file_get_contents($json_file), true);
485
+ $layout_data = apply_filters('siteorigin_panels_load_layout_' . $id, $layout_data);
486
+
487
+ $layout_data = array_merge(array(
488
+ 'name' => $name,
489
+ 'screenshot' => $screenshot,
490
+ ), $layout_data);
491
+
492
+ return $layout_data;
493
+ }
494
+ }
inc/admin-tutorials.php DELETED
@@ -1,50 +0,0 @@
1
- <?php
2
-
3
- class SiteOrigin_Panels_Admin_Tutorials {
4
-
5
- function __construct() {
6
- add_action( 'wp_ajax_so_panels_get_tutorials', array( $this, 'action_get_tutorials' ) );
7
- }
8
-
9
- /**
10
- * @return SiteOrigin_Panels_Admin_Tutorials
11
- */
12
- public static function single() {
13
- static $single;
14
- return empty( $single ) ? $single = new self() : $single;
15
- }
16
-
17
- /**
18
- * Get the latest tutorials from SiteOrigin
19
- */
20
- public function action_get_tutorials(){
21
- if ( empty( $_REQUEST['_panelsnonce'] ) || ! wp_verify_nonce( $_REQUEST['_panelsnonce'], 'panels_action' ) ) {
22
- wp_die();
23
- }
24
-
25
- $user = get_current_user_id();
26
- update_user_meta( $user, 'so_panels_tutorials_enabled', true );
27
-
28
- header( 'content-type:application/json' );
29
-
30
- $tutorials = get_transient( 'siteorigin_panels_tutorials' );
31
-
32
- if( empty( $tutorials ) ) {
33
- $response = wp_remote_get('https://siteorigin.com/wp-json/siteorigin/v1/tutorials/page-builder/');
34
- if ( is_array( $response ) && $response['response']['code'] == 200 ) {
35
- $tutorials = json_decode( $response['body'] );
36
- set_transient( 'siteorigin_panels_tutorials', $tutorials, 86400 );
37
- }
38
- else {
39
- $tutorials = array(
40
- 'error' => __( 'Error loading latest tutorials. Please try again after a few minutes.', 'siteorigin-panels' ),
41
- );
42
- }
43
- }
44
-
45
- echo json_encode( $tutorials );
46
-
47
- wp_die();
48
- }
49
-
50
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
inc/admin-widget-dialog.php CHANGED
@@ -1,193 +1,193 @@
1
- <?php
2
-
3
- class SiteOrigin_Panels_Admin_Widget_Dialog {
4
-
5
- function __construct() {
6
- add_filter( 'siteorigin_panels_widgets', array( $this, 'add_recommended_widgets' ) );
7
- add_filter( 'siteorigin_panels_widget_dialog_tabs', array( $this, 'add_widgets_dialog_tabs' ), 20 );
8
- }
9
-
10
- /**
11
- * @return SiteOrigin_Panels_Admin_Widget_Dialog
12
- */
13
- public static function single() {
14
- static $single;
15
- return empty( $single ) ? $single = new self() : $single;
16
- }
17
-
18
- /**
19
- * Add some default recommended widgets.
20
- *
21
- * @param $widgets
22
- *
23
- * @return array
24
- */
25
- function add_recommended_widgets( $widgets ) {
26
-
27
- if ( ! empty( $widgets['WP_Widget_Black_Studio_TinyMCE'] ) ) {
28
- $widgets['WP_Widget_Black_Studio_TinyMCE']['groups'] = array( 'recommended' );
29
- $widgets['WP_Widget_Black_Studio_TinyMCE']['icon'] = 'dashicons dashicons-edit';
30
- }
31
-
32
- if ( siteorigin_panels_setting( 'recommended-widgets' ) ) {
33
- // Add in all the widgets bundle widgets
34
- $widgets = wp_parse_args(
35
- $widgets,
36
- include plugin_dir_path( __FILE__ ) . 'data/widgets-bundle.php'
37
- );
38
- }
39
-
40
- foreach ( $widgets as $class => $data ) {
41
- if ( strpos( $class, 'SiteOrigin_Panels_Widgets_' ) === 0 || strpos( $class, 'SiteOrigin_Panels_Widget_' ) === 0 ) {
42
- $widgets[ $class ]['groups'] = array( 'panels' );
43
- }
44
- }
45
-
46
- if ( ! empty( $widgets['SiteOrigin_Panels_Widgets_Layout'] ) ) {
47
- $widgets['SiteOrigin_Panels_Widgets_Layout']['icon'] = 'dashicons dashicons-analytics';
48
- }
49
-
50
- $wordpress_widgets = array(
51
- 'WP_Widget_Pages',
52
- 'WP_Widget_Links',
53
- 'WP_Widget_Search',
54
- 'WP_Widget_Archives',
55
- 'WP_Widget_Meta',
56
- 'WP_Widget_Calendar',
57
- 'WP_Widget_Text',
58
- 'WP_Widget_Categories',
59
- 'WP_Widget_Recent_Posts',
60
- 'WP_Widget_Recent_Comments',
61
- 'WP_Widget_RSS',
62
- 'WP_Widget_Tag_Cloud',
63
- 'WP_Nav_Menu_Widget',
64
- );
65
-
66
- foreach ( $wordpress_widgets as $wordpress_widget ) {
67
- if ( isset( $widgets[ $wordpress_widget ] ) ) {
68
- $widgets[ $wordpress_widget ]['groups'] = array( 'wordpress' );
69
- $widgets[ $wordpress_widget ]['icon'] = 'dashicons dashicons-wordpress';
70
- }
71
- }
72
-
73
- // Third-party plugins dettection.
74
- foreach ( $widgets as $widget_id => &$widget ) {
75
- if ( strpos( $widget_id, 'WC_' ) === 0 || strpos( $widget_id, 'WooCommerce' ) !== false ) {
76
- $widget['groups'][] = 'woocommerce';
77
- }
78
- if ( strpos( $widget_id, 'BBP_' ) === 0 || strpos( $widget_id, 'BBPress' ) !== false ) {
79
- $widget['groups'][] = 'bbpress';
80
- }
81
- if ( strpos( $widget_id, 'Jetpack' ) !== false || strpos( $widget['title'], 'Jetpack' ) !== false ) {
82
- $widget['groups'][] = 'jetpack';
83
- }
84
- }
85
-
86
- return $widgets;
87
- }
88
-
89
- /**
90
- * Add tabs to the widget dialog
91
- *
92
- * @param $tabs
93
- *
94
- * @return array
95
- */
96
- function add_widgets_dialog_tabs( $tabs ) {
97
-
98
- $tabs['widgets_bundle'] = array(
99
- 'title' => __( 'Widgets Bundle', 'siteorigin-panels' ),
100
- 'filter' => array(
101
- 'groups' => array( 'so-widgets-bundle' )
102
- )
103
- );
104
-
105
- if ( class_exists( 'SiteOrigin_Widgets_Bundle' ) ) {
106
- // Add a message about enabling more widgets
107
- $tabs['widgets_bundle']['message'] = preg_replace(
108
- array(
109
- '/1\{ *(.*?) *\}/'
110
- ),
111
- array(
112
- '<a href="' . admin_url( 'plugins.php?page=so-widgets-plugins' ) . '">$1</a>'
113
- ),
114
- __( 'Enable more widgets in the 1{Widgets Bundle settings}.', 'siteorigin-panels' )
115
- );
116
- } else {
117
- // Add a message about installing the widgets bundle
118
- $tabs['widgets_bundle']['message'] = preg_replace(
119
- '/1\{ *(.*?) *\}/',
120
- '<a href="' . siteorigin_panels_plugin_activation_install_url( 'so-widgets-bundle', __( 'SiteOrigin Widgets Bundle', 'siteorigin-panels' ) ) . '">$1</a>',
121
- __( 'Install the 1{Widgets Bundle} to get extra widgets.', 'siteorigin-panels' )
122
- );
123
- }
124
-
125
- // Add the Widgets Bundle message to the main widgets tab
126
- $tabs[0]['message'] = $tabs['widgets_bundle']['message'];
127
-
128
- $tabs['page_builder'] = array(
129
- 'title' => __( 'Page Builder Widgets', 'siteorigin-panels' ),
130
- 'message' => preg_replace(
131
- array(
132
- '/1\{ *(.*?) *\}/'
133
- ),
134
- array(
135
- '<a href="' . admin_url( 'options-general.php?page=siteorigin_panels' ) . '">$1</a>'
136
- ),
137
- __( 'You can enable the legacy (PB) widgets in the 1{Page Builder settings}.', 'siteorigin-panels' )
138
- ),
139
- 'filter' => array(
140
- 'groups' => array( 'panels' )
141
- )
142
- );
143
-
144
- $tabs['wordpress'] = array(
145
- 'title' => __( 'WordPress Widgets', 'siteorigin-panels' ),
146
- 'filter' => array(
147
- 'groups' => array( 'wordpress' )
148
- )
149
- );
150
-
151
- // Check for woocommerce plugin.
152
- if ( defined( 'WOOCOMMERCE_VERSION' ) ) {
153
- $tabs['woocommerce'] = array(
154
- // TRANSLATORS: The name of WordPress plugin
155
- 'title' => __( 'WooCommerce', 'woocommerce' ),
156
- 'filter' => array(
157
- 'groups' => array( 'woocommerce' )
158
- )
159
- );
160
- }
161
-
162
- // Check for jetpack plugin.
163
- if ( defined( 'JETPACK__VERSION' ) ) {
164
- $tabs['jetpack'] = array(
165
- // TRANSLATORS: The name of WordPress plugin
166
- 'title' => __( 'Jetpack', 'jetpack' ),
167
- 'filter' => array(
168
- 'groups' => array( 'jetpack' )
169
- ),
170
- );
171
- }
172
-
173
- // Check for bbpress plugin.
174
- if ( function_exists( 'bbpress' ) ) {
175
- $tabs['bbpress'] = array(
176
- // TRANSLATORS: The name of WordPress plugin
177
- 'title' => __( 'BBPress', 'bbpress' ),
178
- 'filter' => array(
179
- 'groups' => array( 'bbpress' )
180
- ),
181
- );
182
- }
183
-
184
- $tabs['recommended'] = array(
185
- 'title' => __( 'Recommended Widgets', 'siteorigin-panels' ),
186
- 'filter' => array(
187
- 'groups' => array( 'recommended' )
188
- )
189
- );
190
-
191
- return $tabs;
192
- }
193
- }
1
+ <?php
2
+
3
+ class SiteOrigin_Panels_Admin_Widget_Dialog {
4
+
5
+ function __construct() {
6
+ add_filter( 'siteorigin_panels_widgets', array( $this, 'add_recommended_widgets' ) );
7
+ add_filter( 'siteorigin_panels_widget_dialog_tabs', array( $this, 'add_widgets_dialog_tabs' ), 20 );
8
+ }
9
+
10
+ /**
11
+ * @return SiteOrigin_Panels_Admin_Widget_Dialog
12
+ */
13
+ public static function single() {
14
+ static $single;
15
+ return empty( $single ) ? $single = new self() : $single;
16
+ }
17
+
18
+ /**
19
+ * Add some default recommended widgets.
20
+ *
21
+ * @param $widgets
22
+ *
23
+ * @return array
24
+ */
25
+ function add_recommended_widgets( $widgets ) {
26
+
27
+ if ( ! empty( $widgets['WP_Widget_Black_Studio_TinyMCE'] ) ) {
28
+ $widgets['WP_Widget_Black_Studio_TinyMCE']['groups'] = array( 'recommended' );
29
+ $widgets['WP_Widget_Black_Studio_TinyMCE']['icon'] = 'dashicons dashicons-edit';
30
+ }
31
+
32
+ if ( siteorigin_panels_setting( 'recommended-widgets' ) ) {
33
+ // Add in all the widgets bundle widgets
34
+ $widgets = wp_parse_args(
35
+ $widgets,
36
+ include plugin_dir_path( __FILE__ ) . 'data/widgets-bundle.php'
37
+ );
38
+ }
39
+
40
+ foreach ( $widgets as $class => $data ) {
41
+ if ( strpos( $class, 'SiteOrigin_Panels_Widgets_' ) === 0 || strpos( $class, 'SiteOrigin_Panels_Widget_' ) === 0 ) {
42
+ $widgets[ $class ]['groups'] = array( 'panels' );
43
+ }
44
+ }
45
+
46
+ if ( ! empty( $widgets['SiteOrigin_Panels_Widgets_Layout'] ) ) {
47
+ $widgets['SiteOrigin_Panels_Widgets_Layout']['icon'] = 'dashicons dashicons-analytics';
48
+ }
49
+
50
+ $wordpress_widgets = array(
51
+ 'WP_Widget_Pages',
52
+ 'WP_Widget_Links',
53
+ 'WP_Widget_Search',
54
+ 'WP_Widget_Archives',
55
+ 'WP_Widget_Meta',
56
+ 'WP_Widget_Calendar',
57
+ 'WP_Widget_Text',
58
+ 'WP_Widget_Categories',
59
+ 'WP_Widget_Recent_Posts',
60
+ 'WP_Widget_Recent_Comments',
61
+ 'WP_Widget_RSS',
62
+ 'WP_Widget_Tag_Cloud',
63
+ 'WP_Nav_Menu_Widget',
64
+ );
65
+
66
+ foreach ( $wordpress_widgets as $wordpress_widget ) {
67
+ if ( isset( $widgets[ $wordpress_widget ] ) ) {
68
+ $widgets[ $wordpress_widget ]['groups'] = array( 'wordpress' );
69
+ $widgets[ $wordpress_widget ]['icon'] = 'dashicons dashicons-wordpress';
70
+ }
71
+ }
72
+
73
+ // Third-party plugins dettection.
74
+ foreach ( $widgets as $widget_id => &$widget ) {
75
+ if ( strpos( $widget_id, 'WC_' ) === 0 || strpos( $widget_id, 'WooCommerce' ) !== false ) {
76
+ $widget['groups'][] = 'woocommerce';
77
+ }
78
+ if ( strpos( $widget_id, 'BBP_' ) === 0 || strpos( $widget_id, 'BBPress' ) !== false ) {
79
+ $widget['groups'][] = 'bbpress';
80
+ }
81
+ if ( strpos( $widget_id, 'Jetpack' ) !== false || strpos( $widget['title'], 'Jetpack' ) !== false ) {
82
+ $widget['groups'][] = 'jetpack';
83
+ }
84
+ }
85
+
86
+ return $widgets;
87
+ }
88
+
89
+ /**
90
+ * Add tabs to the widget dialog
91
+ *
92
+ * @param $tabs
93
+ *
94
+ * @return array
95
+ */
96
+ function add_widgets_dialog_tabs( $tabs ) {
97
+
98
+ $tabs['widgets_bundle'] = array(
99
+ 'title' => __( 'Widgets Bundle', 'siteorigin-panels' ),
100
+ 'filter' => array(
101
+ 'groups' => array( 'so-widgets-bundle' )
102
+ )
103
+ );
104
+
105
+ if ( class_exists( 'SiteOrigin_Widgets_Bundle' ) ) {
106
+ // Add a message about enabling more widgets
107
+ $tabs['widgets_bundle']['message'] = preg_replace(
108
+ array(
109
+ '/1\{ *(.*?) *\}/'
110
+ ),
111
+ array(
112
+ '<a href="' . admin_url( 'plugins.php?page=so-widgets-plugins' ) . '">$1</a>'
113
+ ),
114
+ __( 'Enable more widgets in the 1{Widgets Bundle settings}.', 'siteorigin-panels' )
115
+ );
116
+ } else {
117
+ // Add a message about installing the widgets bundle
118
+ $tabs['widgets_bundle']['message'] = preg_replace(
119
+ '/1\{ *(.*?) *\}/',
120
+ '<a href="' . siteorigin_panels_plugin_activation_install_url( 'so-widgets-bundle', __( 'SiteOrigin Widgets Bundle', 'siteorigin-panels' ) ) . '">$1</a>',
121
+ __( 'Install the 1{Widgets Bundle} to get extra widgets.', 'siteorigin-panels' )
122
+ );
123
+ }
124
+
125
+ // Add the Widgets Bundle message to the main widgets tab
126
+ $tabs[0]['message'] = $tabs['widgets_bundle']['message'];
127
+
128
+ $tabs['page_builder'] = array(
129
+ 'title' => __( 'Page Builder Widgets', 'siteorigin-panels' ),
130
+ 'message' => preg_replace(
131
+ array(
132
+ '/1\{ *(.*?) *\}/'
133
+ ),
134
+ array(
135
+ '<a href="' . admin_url( 'options-general.php?page=siteorigin_panels' ) . '">$1</a>'
136
+ ),
137
+ __( 'You can enable the legacy (PB) widgets in the 1{Page Builder settings}.', 'siteorigin-panels' )
138
+ ),
139
+ 'filter' => array(
140
+ 'groups' => array( 'panels' )
141
+ )
142
+ );
143
+
144
+ $tabs['wordpress'] = array(
145
+ 'title' => __( 'WordPress Widgets', 'siteorigin-panels' ),
146
+ 'filter' => array(
147
+ 'groups' => array( 'wordpress' )
148
+ )
149
+ );
150
+
151
+ // Check for woocommerce plugin.
152
+ if ( defined( 'WOOCOMMERCE_VERSION' ) ) {
153
+ $tabs['woocommerce'] = array(
154
+ // TRANSLATORS: The name of WordPress plugin
155
+ 'title' => __( 'WooCommerce', 'woocommerce' ),
156
+ 'filter' => array(
157
+ 'groups' => array( 'woocommerce' )
158
+ )
159
+ );
160
+ }
161
+
162
+ // Check for jetpack plugin.
163
+ if ( defined( 'JETPACK__VERSION' ) ) {
164
+ $tabs['jetpack'] = array(
165
+ // TRANSLATORS: The name of WordPress plugin
166
+ 'title' => __( 'Jetpack', 'jetpack' ),
167
+ 'filter' => array(
168
+ 'groups' => array( 'jetpack' )
169
+ ),
170
+ );
171
+ }
172
+
173
+ // Check for bbpress plugin.
174
+ if ( function_exists( 'bbpress' ) ) {
175
+ $tabs['bbpress'] = array(
176
+ // TRANSLATORS: The name of WordPress plugin
177
+ 'title' => __( 'BBPress', 'bbpress' ),
178
+ 'filter' => array(
179
+ 'groups' => array( 'bbpress' )
180
+ ),
181
+ );
182
+ }
183
+
184
+ $tabs['recommended'] = array(
185
+ 'title' => __( 'Recommended Widgets', 'siteorigin-panels' ),
186
+ 'filter' => array(
187
+ 'groups' => array( 'recommended' )
188
+ )
189
+ );
190
+
191
+ return $tabs;
192
+ }
193
+ }
inc/admin.php CHANGED
@@ -1,1485 +1,1485 @@
1
- <?php
2
-
3
- /**
4
- * Class SiteOrigin_Panels_Admin
5
- *
6
- * Handles all the admin and database interactions.
7
- */
8
- class SiteOrigin_Panels_Admin {
9
-
10
- /**
11
- * @var bool Store that we're in the save post action, to prevent infinite loops
12
- */
13
- private $in_save_post;
14
-
15
- function __construct() {
16
-
17
- add_action( 'plugin_action_links_siteorigin-panels/siteorigin-panels.php', array(
18
- $this,
19
- 'plugin_action_links'
20
- ) );
21
-
22
- add_action( 'plugins_loaded', array( $this, 'admin_init_widget_count' ) );
23
-
24
- add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ) );
25
- add_action( 'admin_init', array( $this, 'save_home_page' ) );
26
- add_action( 'save_post', array( $this, 'save_post' ) );
27
-
28
- add_action( 'after_switch_theme', array( $this, 'update_home_on_theme_change' ) );
29
-
30
- // Enqueuing admin scripts
31
- add_action( 'admin_print_scripts-post-new.php', array( $this, 'enqueue_admin_scripts' ) );
32
- add_action( 'admin_print_scripts-post.php', array( $this, 'enqueue_admin_scripts' ) );
33
- add_action( 'admin_print_scripts-appearance_page_so_panels_home_page', array(
34
- $this,
35
- 'enqueue_admin_scripts'
36
- ) );
37
- add_action( 'admin_print_scripts-widgets.php', array( $this, 'enqueue_admin_scripts' ) );
38
- add_action( 'admin_print_scripts-edit.php', array( $this, 'footer_column_css' ) );
39
-
40
- // Enqueue the admin styles
41
- add_action( 'admin_print_styles-post-new.php', array( $this, 'enqueue_admin_styles' ) );
42
- add_action( 'admin_print_styles-post.php', array( $this, 'enqueue_admin_styles' ) );
43
- add_action( 'admin_print_styles-appearance_page_so_panels_home_page', array( $this, 'enqueue_admin_styles' ) );
44
- add_action( 'admin_print_styles-widgets.php', array( $this, 'enqueue_admin_styles' ) );
45
-
46
- // The help tab
47
- add_action( 'load-page.php', array( $this, 'add_help_tab' ), 12 );
48
- add_action( 'load-post-new.php', array( $this, 'add_help_tab' ), 12 );
49
- add_action( 'load-appearance_page_so_panels_home_page', array( $this, 'add_help_tab' ), 12 );
50
-
51
- add_action( 'customize_controls_print_footer_scripts', array( $this, 'js_templates' ) );
52
-
53
- // Register all the admin actions
54
- add_action( 'wp_ajax_so_panels_builder_content', array( $this, 'action_builder_content' ) );
55
- add_action( 'wp_ajax_so_panels_widget_form', array( $this, 'action_widget_form' ) );
56
- add_action( 'wp_ajax_so_panels_live_editor_preview', array( $this, 'action_live_editor_preview' ) );
57
- add_action( 'wp_ajax_so_panels_layout_block_sanitize', array( $this, 'layout_block_sanitize' ) );
58
- add_action( 'wp_ajax_so_panels_layout_block_preview', array( $this, 'layout_block_preview' ) );
59
-
60
- // Initialize the additional admin classes.
61
- SiteOrigin_Panels_Admin_Widget_Dialog::single();
62
- SiteOrigin_Panels_Admin_Widgets_Bundle::single();
63
- SiteOrigin_Panels_Admin_Layouts::single();
64
-
65
- // Check to make sure we have all the correct markup
66
- SiteOrigin_Panels_Admin_Dashboard::single();
67
-
68
- $this->in_save_post = false;
69
-
70
-
71
- // Enqueue Yoast compatibility
72
- add_action( 'admin_print_scripts-post-new.php', array( $this, 'enqueue_yoast_compat' ), 100 );
73
- add_action( 'admin_print_scripts-post.php', array( $this, 'enqueue_yoast_compat' ), 100 );
74
-
75
- // Block editor specific actions
76
- if ( function_exists( 'register_block_type' ) ) {
77
- add_action( 'admin_notices', array( $this, 'admin_notices' ) );
78
- add_filter( 'gutenberg_can_edit_post_type', array( $this, 'show_classic_editor_for_panels' ), 10, 2 );
79
- add_filter( 'use_block_editor_for_post_type', array( $this, 'show_classic_editor_for_panels' ), 10, 2 );
80
- add_action( 'admin_print_scripts-edit.php', array( $this, 'add_panels_add_new_button' ) );
81
- if( siteorigin_panels_setting( 'admin-post-state' ) ) {
82
- add_filter( 'display_post_states', array( $this, 'add_panels_post_state' ), 10, 2 );
83
- }
84
- }
85
- }
86
-
87
- /**
88
- * @return SiteOrigin_Panels_Admin
89
- */
90
- public static function single() {
91
- static $single;
92
- return empty( $single ) ? $single = new self() : $single;
93
- }
94
-
95
- /**
96
- * Do some general admin initialization
97
- */
98
- public function admin_init_widget_count(){
99
- if( siteorigin_panels_setting( 'admin-widget-count' ) ) {
100
-
101
- // Add the custom columns
102
- $post_types = siteorigin_panels_setting( 'post-types' );
103
- if( ! empty( $post_types ) ) {
104
- foreach( $post_types as $post_type ) {
105
- add_filter( 'manage_' . $post_type . 's_columns' , array( $this, 'add_custom_column' ) );
106
- add_action( 'manage_' . $post_type . 's_custom_column' , array( $this, 'display_custom_column' ), 10, 2 );
107
- }
108
- }
109
- }
110
- }
111
-
112
- /**
113
- * Check if this is an admin page.
114
- *
115
- * @return mixed|void
116
- */
117
- static function is_admin() {
118
- $screen = get_current_screen();
119
- $is_panels_page = ( $screen->base == 'post' && in_array( $screen->id, siteorigin_panels_setting( 'post-types' ) ) ) ||
120
- in_array( $screen->base, array( 'appearance_page_so_panels_home_page', 'widgets', 'customize' ) ) ||
121
- self::is_block_editor();
122
-
123
- return apply_filters( 'siteorigin_panels_is_admin_page', $is_panels_page );
124
- }
125
-
126
- /**
127
- * Check if the current page is Gutenberg or the Block Ediotr
128
- *
129
- * @return bool
130
- */
131
- static function is_block_editor() {
132
- // This is for the Gutenberg plugin.
133
- $is_gutenberg_page = function_exists( 'is_gutenberg_page' ) && is_gutenberg_page();
134
- // This is for WP 5 with the integrated block editor.
135
- $is_block_editor = false;
136
-
137
- if ( function_exists( 'get_current_screen' ) ) {
138
- $current_screen = get_current_screen();
139
- if ( $current_screen && method_exists( $current_screen, 'is_block_editor' ) ) {
140
- $is_block_editor = $current_screen->is_block_editor();
141
- }
142
- }
143
-
144
- return $is_gutenberg_page || $is_block_editor;
145
- }
146
-
147
-
148
- /**
149
- * Add action links to the plugin list for Page Builder.
150
- *
151
- * @param $links
152
- *
153
- * @return array
154
- */
155
- function plugin_action_links( $links ) {
156
- if( ! is_array( $links ) ) {
157
- return $links;
158
- }
159
-
160
- unset( $links['edit'] );
161
- $links[] = '<a href="http://siteorigin.com/threads/plugin-page-builder/">' . __( 'Support Forum', 'siteorigin-panels' ) . '</a>';
162
-
163
- if( SiteOrigin_Panels::display_premium_teaser() ) {
164
- $links[] = '<a href="' . esc_url( SiteOrigin_Panels::premium_url() ) . '" style="color: #3db634" target="_blank" rel="noopener noreferrer">' . __('Addons', 'siteorigin-panels') . '</a>';
165
- }
166
-
167
- return $links;
168
- }
169
-
170
- /**
171
- * Callback to register the Page Builder Metaboxes
172
- */
173
- function add_meta_boxes() {
174
-
175
- foreach ( siteorigin_panels_setting( 'post-types' ) as $type ) {
176
- add_meta_box(
177
- 'so-panels-panels',
178
- __( 'Page Builder', 'siteorigin-panels' ),
179
- array( $this, 'render_meta_boxes' ),
180
- ( string ) $type,
181
- 'advanced',
182
- 'high',
183
- array(
184
- // Ideally when we have panels data for a page we would set this to false and it would cause the
185
- // editor to fall back to classic editor, but that's not the case so we just declare it as a `__back_compat_meta_box`.
186
- '__back_compat_meta_box' => true,
187
- '__block_editor_compatible_meta_box' => false,
188
- )
189
- );
190
- }
191
- }
192
-
193
- /**
194
- * Render a panel metabox.
195
- *
196
- * @param $post
197
- */
198
- function render_meta_boxes( $post ) {
199
- $panels_data = $this->get_current_admin_panels_data();
200
- include plugin_dir_path( __FILE__ ) . '../tpl/metabox-panels.php';
201
- }
202
-
203
- /**
204
- * Save the panels data
205
- *
206
- * @param $post_id
207
- *
208
- * @action save_post
209
- */
210
- function save_post( $post_id ) {
211
- // Check that everything is valid with this save.
212
- if(
213
- $this->in_save_post ||
214
- ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) ||
215
- empty( $_POST['_sopanels_nonce'] ) ||
216
- ! wp_verify_nonce( $_POST['_sopanels_nonce'], 'save' ) ||
217
- ! current_user_can( 'edit_post', $post_id ) ||
218
- ! isset( $_POST['panels_data'] )
219
- ) {
220
- return;
221
- }
222
- $this->in_save_post = true;
223
- // Get post from db as it might have been changed and saved by other plugins.
224
- $post = get_post( $post_id );
225
- $old_panels_data = get_post_meta( $post_id, 'panels_data', true );
226
- $panels_data = json_decode( wp_unslash( $_POST['panels_data'] ), true );
227
-
228
- $panels_data['widgets'] = $this->process_raw_widgets(
229
- $panels_data['widgets'],
230
- ! empty( $old_panels_data['widgets'] ) ? $old_panels_data['widgets'] : false,
231
- false
232
- );
233
-
234
- if ( siteorigin_panels_setting( 'sidebars-emulator' ) ) {
235
- $sidebars_emulator = SiteOrigin_Panels_Sidebars_Emulator::single();
236
- $panels_data['widgets'] = $sidebars_emulator->generate_sidebar_widget_ids( $panels_data['widgets'], $post_id );
237
- }
238
-
239
- $panels_data = SiteOrigin_Panels_Styles_Admin::single()->sanitize_all( $panels_data );
240
- $panels_data = apply_filters( 'siteorigin_panels_data_pre_save', $panels_data, $post, $post_id );
241
-
242
- if ( ! empty( $panels_data['widgets'] ) || ! empty( $panels_data['grids'] ) ) {
243
- // Use `update_metadata` instead of `update_post_meta` to prevent saving to parent post when it's a revision, e.g. preview.
244
- update_metadata( 'post', $post_id, 'panels_data', map_deep( $panels_data, array( 'SiteOrigin_Panels_Admin', 'double_slash_string' ) ) );
245
-
246
- if( siteorigin_panels_setting( 'copy-content' ) ) {
247
- // Store a version of the HTML in post_content
248
- $post_parent_id = wp_is_post_revision( $post_id );
249
- $layout_id = ( ! empty( $post_parent_id ) ) ? $post_parent_id : $post_id;
250
-
251
- SiteOrigin_Panels_Post_Content_Filters::add_filters();
252
- $GLOBALS[ 'SITEORIGIN_PANELS_POST_CONTENT_RENDER' ] = true;
253
- $post_content = SiteOrigin_Panels::renderer()->render( $layout_id, false, $panels_data );
254
- $post_css = SiteOrigin_Panels::renderer()->generate_css( $layout_id, $panels_data );
255
- SiteOrigin_Panels_Post_Content_Filters::remove_filters();
256
- unset( $GLOBALS[ 'SITEORIGIN_PANELS_POST_CONTENT_RENDER' ] );
257
-
258
- // Update the post_content
259
- $post->post_content = $post_content;
260
- if( siteorigin_panels_setting( 'copy-styles' ) ) {
261
- $post->post_content .= "\n\n";
262
- $post->post_content .= '<style type="text/css" class="panels-style" data-panels-style-for-post="' . intval( $layout_id ) . '">';
263
- $post->post_content .= '@import url(' . SiteOrigin_Panels::front_css_url() . '); ';
264
- $post->post_content .= $post_css;
265
- $post->post_content .= '</style>';
266
- }
267
- wp_update_post( $post );
268
- }
269
-
270
- } else {
271
- // There are no widgets or rows, so delete the panels data
272
- delete_post_meta( $post_id, 'panels_data' );
273
- }
274
-
275
- $this->in_save_post = false;
276
- }
277
-
278
- /**
279
- * Enqueue the panels admin scripts
280
- *
281
- * @param string $prefix
282
- * @param bool $force Should we force the enqueues
283
- *
284
- * @action admin_print_scripts-post-new.php
285
- * @action admin_print_scripts-post.php
286
- * @action admin_print_scripts-appearance_page_so_panels_home_page
287
- */
288
- function enqueue_admin_scripts( $prefix = '', $force = false ) {
289
- $screen = get_current_screen();
290
- if ( $force || self::is_admin() ) {
291
- // Media is required for row styles
292
- wp_enqueue_media();
293
- wp_enqueue_script(
294
- 'so-panels-admin',
295
- siteorigin_panels_url( 'js/siteorigin-panels' . SITEORIGIN_PANELS_VERSION_SUFFIX . SITEORIGIN_PANELS_JS_SUFFIX . '.js' ),
296
- array(
297
- 'jquery',
298
- 'jquery-ui-resizable',
299
- 'jquery-ui-sortable',
300
- 'jquery-ui-draggable',
301
- 'underscore',
302
- 'backbone',
303
- 'plupload',
304
- 'plupload-all'
305
- ),
306
- SITEORIGIN_PANELS_VERSION,
307
- true
308
- );
309
- add_action( 'admin_footer', array( $this, 'js_templates' ) );
310
-
311
- $widgets = $this->get_widgets();
312
- $directory_enabled = get_user_meta( get_current_user_id(), 'so_panels_directory_enabled', true );
313
-
314
- // This is the widget we'll use for default text
315
- if( ! empty( $widgets[ 'SiteOrigin_Widget_Editor_Widget' ] ) ) $text_widget = 'SiteOrigin_Widget_Editor_Widget';
316
- else if( ! empty( $widgets[ 'WP_Widget_Text' ] ) ) $text_widget = 'WP_Widget_Text';
317
- else $text_widget = false;
318
- $text_widget = apply_filters( 'siteorigin_panels_text_widget_class', $text_widget );
319
-
320
- $user = wp_get_current_user();
321
-
322
- $load_on_attach = siteorigin_panels_setting( 'load-on-attach' ) || isset( $_GET['siteorigin-page-builder'] );
323
- wp_localize_script( 'so-panels-admin', 'panelsOptions', array(
324
- 'user' => ! empty( $user ) ? $user->ID : 0,
325
- 'ajaxurl' => wp_nonce_url( admin_url( 'admin-ajax.php' ), 'panels_action', '_panelsnonce' ),
326
- 'widgets' => $widgets,
327
- 'text_widget' => $text_widget,
328
- 'widget_dialog_tabs' => apply_filters( 'siteorigin_panels_widget_dialog_tabs', array(
329
- 0 => array(
330
- 'title' => __( 'All Widgets', 'siteorigin-panels' ),
331
- 'filter' => array(
332
- 'installed' => true,
333
- 'groups' => ''
334
- )
335
- )
336
- ) ),
337
- 'row_layouts' => apply_filters( 'siteorigin_panels_row_layouts', array() ),
338
- 'directory_enabled' => ! empty( $directory_enabled ),
339
- 'copy_content' => siteorigin_panels_setting( 'copy-content' ),
340
- 'cache' => array(),
341
- 'instant_open' => siteorigin_panels_setting( 'instant-open-widgets' ),
342
-
343
- // Settings for the contextual menu
344
- 'contextual' => array(
345
- // Developers can change which widgets are displayed by default using this filter
346
- 'default_widgets' => apply_filters( 'siteorigin_panels_contextual_default_widgets', array(
347
- 'SiteOrigin_Widget_Editor_Widget',
348
- 'SiteOrigin_Widget_Button_Widget',
349
- 'SiteOrigin_Widget_Image_Widget',
350
- 'SiteOrigin_Panels_Widgets_Layout',
351
- ) )
352
- ),
353
-
354
- // General localization messages
355
- 'loc' => array(
356
- 'missing_widget' => array(
357
- 'title' => __( 'Missing Widget', 'siteorigin-panels' ),
358
- 'description' => __( "Page Builder doesn't know about this widget.", 'siteorigin-panels' ),
359
- ),
360
- 'time' => array(
361
- // TRANSLATORS: Number of seconds since
362
- 'seconds' => __( '%d seconds', 'siteorigin-panels' ),
363
- // TRANSLATORS: Number of minutes since
364
- 'minutes' => __( '%d minutes', 'siteorigin-panels' ),
365
- // TRANSLATORS: Number of hours since
366
- 'hours' => __( '%d hours', 'siteorigin-panels' ),
367
-
368
- // TRANSLATORS: A single second since
369
- 'second' => __( '%d second', 'siteorigin-panels' ),
370
- // TRANSLATORS: A single minute since
371
- 'minute' => __( '%d minute', 'siteorigin-panels' ),
372
- // TRANSLATORS: A single hour since
373
- 'hour' => __( '%d hour', 'siteorigin-panels' ),
374
-
375
- // TRANSLATORS: Time ago - eg. "1 minute before".
376
- 'ago' => __( '%s before', 'siteorigin-panels' ),
377
- 'now' => __( 'Now', 'siteorigin-panels' ),
378
- ),
379
- 'history' => array(
380
- // History messages
381
- 'current' => __( 'Current', 'siteorigin-panels' ),
382
- 'revert' => __( 'Original', 'siteorigin-panels' ),
383
- 'restore' => __( 'Version restored', 'siteorigin-panels' ),
384
- 'back_to_editor' => __( 'Converted to editor', 'siteorigin-panels' ),
385
-
386
- // Widgets
387
- // TRANSLATORS: Message displayed in the history when a widget is deleted
388
- 'widget_deleted' => __( 'Widget deleted', 'siteorigin-panels' ),
389
- // TRANSLATORS: Message displayed in the history when a widget is added
390
- 'widget_added' => __( 'Widget added', 'siteorigin-panels' ),
391
- // TRANSLATORS: Message displayed in the history when a widget is edited
392
- 'widget_edited' => __( 'Widget edited', 'siteorigin-panels' ),
393
- // TRANSLATORS: Message displayed in the history when a widget is duplicated
394
- 'widget_duplicated' => __( 'Widget duplicated', 'siteorigin-panels' ),
395
- // TRANSLATORS: Message displayed in the history when a widget position is changed
396
- 'widget_moved' => __( 'Widget moved', 'siteorigin-panels' ),
397
-
398
- // Rows
399
- // TRANSLATORS: Message displayed in the history when a row is deleted
400
- 'row_deleted' => __( 'Row deleted', 'siteorigin-panels' ),
401
- // TRANSLATORS: Message displayed in the history when a row is added
402
- 'row_added' => __( 'Row added', 'siteorigin-panels' ),
403
- // TRANSLATORS: Message displayed in the history when a row is edited
404
- 'row_edited' => __( 'Row edited', 'siteorigin-panels' ),
405
- // TRANSLATORS: Message displayed in the history when a row position is changed
406
- 'row_moved' => __( 'Row moved', 'siteorigin-panels' ),
407
- // TRANSLATORS: Message displayed in the history when a row is duplicated
408
- 'row_duplicated' => __( 'Row duplicated', 'siteorigin-panels' ),
409
- // TRANSLATORS: Message displayed in the history when a row is pasted
410
- 'row_pasted' => __( 'Row pasted', 'siteorigin-panels' ),
411
-
412
- // Cells
413
- 'cell_resized' => __( 'Cell resized', 'siteorigin-panels' ),
414
-
415
- // Prebuilt
416
- 'prebuilt_loaded' => __( 'Prebuilt layout loaded', 'siteorigin-panels' ),
417
- ),
418
-
419
- // general localization
420
- 'prebuilt_loading' => __( 'Loading prebuilt layout', 'siteorigin-panels' ),
421
- 'confirm_use_builder' => __( "Would you like to copy this editor's existing content to Page Builder?", 'siteorigin-panels' ),
422
- 'confirm_stop_builder' => __( "Would you like to clear your Page Builder content and revert to using the standard visual editor?", 'siteorigin-panels' ),
423
- // TRANSLATORS: This is the title for a widget called "Layout Builder"
424
- 'layout_widget' => __( 'Layout Builder Widget', 'siteorigin-panels' ),
425
- // TRANSLATORS: A standard confirmation message
426
- 'dropdown_confirm' => __( 'Are you sure?', 'siteorigin-panels' ),
427
- // TRANSLATORS: When a layout file is ready to be inserted. %s is the filename.
428
- 'ready_to_insert' => __( '%s is ready to insert.', 'siteorigin-panels' ),
429
-
430
- // Everything for the contextual menu
431
- 'contextual' => array(
432
- 'add_widget_below' => __( 'Add Widget Below', 'siteorigin-panels' ),
433
- 'add_widget_cell' => __( 'Add Widget to Cell', 'siteorigin-panels' ),
434
- 'search_widgets' => __( 'Search Widgets', 'siteorigin-panels' ),
435
-
436
- 'add_row' => __( 'Add Row', 'siteorigin-panels' ),
437
- 'column' => __( 'Column', 'siteorigin-panels' ),
438
-
439
- 'cell_actions' => __( 'Cell Actions', 'siteorigin-panels' ),
440
- 'cell_paste_widget' => __( 'Paste Widget', 'siteorigin-panels' ),
441
-
442
- 'widget_actions' => __( 'Widget Actions', 'siteorigin-panels' ),
443
- 'widget_edit' => __( 'Edit Widget', 'siteorigin-panels' ),
444
- 'widget_duplicate' => __( 'Duplicate Widget', 'siteorigin-panels' ),
445
- 'widget_delete' => __( 'Delete Widget', 'siteorigin-panels' ),
446
- 'widget_copy' => __( 'Copy Widget', 'siteorigin-panels' ),
447
- 'widget_paste' => __( 'Paste Widget Below', 'siteorigin-panels' ),
448
-
449
- 'row_actions' => __( 'Row Actions', 'siteorigin-panels' ),
450
- 'row_edit' => __( 'Edit Row', 'siteorigin-panels' ),
451
- 'row_duplicate' => __( 'Duplicate Row', 'siteorigin-panels' ),
452
- 'row_delete' => __( 'Delete Row', 'siteorigin-panels' ),
453
- 'row_copy' => __( 'Copy Row', 'siteorigin-panels' ),
454
- 'row_paste' => __( 'Paste Row', 'siteorigin-panels' ),
455
- ),
456
- 'draft' => __( 'Draft', 'siteorigin-panels' ),
457
- 'untitled' => __( 'Untitled', 'siteorigin-panels' ),
458
- 'row' => array(
459
- 'add' => __( 'New Row', 'siteorigin-panels' ),
460
- 'edit' => __( 'Row', 'siteorigin-panels' ),
461
- ),
462
- 'welcomeMessage' => array(
463
- 'addingDisabled' => __( 'Hmmm... Adding layout elements is not enabled. Please check if Page Builder has been configured to allow adding elements.', 'siteorigin-panels' ),
464
- 'oneEnabled' => __( 'Add a {{%= items[0] %}} to get started.', 'siteorigin-panels' ),
465
- 'twoEnabled' => __( 'Add a {{%= items[0] %}} or {{%= items[1] %}} to get started.', 'siteorigin-panels' ),
466
- 'threeEnabled' => __( 'Add a {{%= items[0] %}}, {{%= items[1] %}} or {{%= items[2] %}} to get started.', 'siteorigin-panels' ),
467
- 'addWidgetButton' => "<a href='#' class='so-tool-button so-widget-add'>" . __( 'Widget', 'siteorigin-panels' ) . "</a>",
468
- 'addRowButton' => "<a href='#' class='so-tool-button so-row-add'>" . __( 'Row', 'siteorigin-panels' ) . "</a>",
469
- 'addPrebuiltButton' => "<a href='#' class='so-tool-button so-prebuilt-add'>" . __( 'Prebuilt Layout', 'siteorigin-panels' ) . "</a>",
470
- 'docsMessage' => sprintf(
471
- __( 'Read our %s if you need help.', 'siteorigin-panels' ),
472
- "<a href='https://siteorigin.com/page-builder/documentation/' target='_blank' rel='noopener noreferrer'>" . __( 'documentation', 'siteorigin-panels' ) . "</a>"
473
- ),
474
- ),
475
- ),
476
- 'plupload' => array(
477
- 'max_file_size' => wp_max_upload_size() . 'b',
478
- 'url' => wp_nonce_url( admin_url( 'admin-ajax.php' ), 'panels_action', '_panelsnonce' ),
479
- 'flash_swf_url' => includes_url( 'js/plupload/plupload.flash.swf' ),
480
- 'silverlight_xap_url' => includes_url( 'js/plupload/plupload.silverlight.xap' ),
481
- 'filter_title' => __( 'Page Builder layouts', 'siteorigin-panels' ),
482
- 'error_message' => __( 'Error uploading or importing file.', 'siteorigin-panels' ),
483
- ),
484
- 'wpColorPickerOptions' => apply_filters( 'siteorigin_panels_wpcolorpicker_options', array() ),
485
- 'prebuiltDefaultScreenshot' => siteorigin_panels_url( 'css/images/prebuilt-default.png' ),
486
- 'loadOnAttach' => $load_on_attach ,
487
- 'siteoriginWidgetRegex' => str_replace( '*+', '*', get_shortcode_regex( array( 'siteorigin_widget' ) ) ),
488
- 'forms' => array(
489
- 'loadingFailed' => __( 'Unknown error. Failed to load the form. Please check your internet connection, contact your web site administrator, or try again later.', 'siteorigin-panels' ),
490
- )
491
- ) );
492
-
493
- $js_widgets = array();
494
- if ( $screen->base != 'widgets' ) {
495
- // Render all the widget forms. A lot of widgets use this as a chance to enqueue their scripts
496
- $original_post = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : null; // Make sure widgets don't change the global post.
497
- global $wp_widget_factory;
498
- foreach ( $wp_widget_factory->widgets as $widget_obj ) {
499
- ob_start();
500
- $return = $widget_obj->form( array() );
501
- // These are the new widgets in WP 4.8 which are largely JS based. They only enqueue their own
502
- // scripts on the 'widgets' screen.
503
- if ( $this->is_core_js_widget( $widget_obj ) && method_exists( $widget_obj, 'enqueue_admin_scripts' ) ) {
504
- $widget_obj->enqueue_admin_scripts();
505
- }
506
- do_action_ref_array( 'in_widget_form', array( &$widget_obj, &$return, array() ) );
507
- ob_end_clean();
508
-
509
- // Need to render templates for new WP 4.8 widgets when not on the 'widgets' screen or in the customizer.
510
- if ( $this->is_core_js_widget( $widget_obj ) ) {
511
- $js_widgets[] = $widget_obj;
512
- }
513
- }
514
- $GLOBALS['post'] = $original_post;
515
- }
516
-
517
- // This gives panels a chance to enqueue scripts too, without having to check the screen ID.
518
- if ( $screen->base != 'widgets' && $screen->base != 'customize' ) {
519
- foreach ( $js_widgets as $js_widget ) {
520
- $js_widget->render_control_template_scripts();
521
- }
522
- do_action( 'siteorigin_panel_enqueue_admin_scripts' );
523
- do_action( 'sidebar_admin_setup' );
524
- }
525
- }
526
- }
527
-
528
- public function enqueue_yoast_compat(){
529
- if( self::is_admin() && defined( 'WPSEO_FILE' ) && wp_script_is( 'yoast-seo-metabox' ) ) {
530
- wp_enqueue_script(
531
- 'so-panels-yoast-compat',
532
- siteorigin_panels_url( 'js/yoast-compat' . SITEORIGIN_PANELS_JS_SUFFIX . '.js' ),
533
- array('jquery', 'yoast-seo-metabox' ),
534
- SITEORIGIN_PANELS_VERSION,
535
- true
536
- );
537
- }
538
- }
539
-
540
- /**
541
- * Enqueue the admin panel styles
542
- *
543
- * @param string $prefix
544
- * @param bool $force Should we force the enqueue
545
- *
546
- * @action admin_print_styles-post-new.php
547
- * @action admin_print_styles-post.php
548
- */
549
- function enqueue_admin_styles( $prefix = '', $force = false ) {
550
- if ( $force || self::is_admin() ) {
551
- wp_enqueue_style(
552
- 'so-panels-admin',
553
- siteorigin_panels_url( 'css/admin' . SITEORIGIN_PANELS_CSS_SUFFIX . '.css' ),
554
- array( 'wp-color-picker' ),
555
- SITEORIGIN_PANELS_VERSION
556
- );
557
- do_action( 'siteorigin_panel_enqueue_admin_styles' );
558
- }
559
- }
560
-
561
- /**
562
- * Add a help tab to pages that include a Page Builder interface.
563
- *
564
- * @param $prefix
565
- */
566
- function add_help_tab( $prefix ) {
567
- $screen = get_current_screen();
568
- if (
569
- ( $screen->base == 'post' && ( in_array( $screen->id, siteorigin_panels_setting( 'post-types' ) ) || $screen->id == '' ) )
570
- || ( $screen->id == 'appearance_page_so_panels_home_page' )
571
- ) {
572
- $screen->add_help_tab( array(
573
- 'id' => 'panels-help-tab', //unique id for the tab
574
- 'title' => __( 'Page Builder', 'siteorigin-panels' ), //unique visible title for the tab
575
- 'callback' => array( $this, 'help_tab_content' )
576
- ) );
577
- }
578
- }
579
-
580
- /**
581
- * Display the content for the help tab.
582
- */
583
- function help_tab_content() {
584
- include plugin_dir_path( __FILE__ ) . '../tpl/help.php';
585
- }
586
-
587
- /**
588
- * Get the Page Builder data for the current admin page.
589
- *
590
- * @return array
591
- */
592
- function get_current_admin_panels_data() {
593
- $screen = get_current_screen();
594
-
595
- // Localize the panels with the panels data
596
- if ( $screen->base == 'appearance_page_so_panels_home_page' ) {
597
- $home_page_id = get_option( 'page_on_front' );
598
- if ( empty( $home_page_id ) ) {
599
- $home_page_id = get_option( 'siteorigin_panels_home_page_id' );
600
- }
601
-
602
- $panels_data = ! empty( $home_page_id ) ? get_post_meta( $home_page_id, 'panels_data', true ) : null;
603
-
604
- if ( is_null( $panels_data ) ) {
605
- // Load the default layout
606
- $layouts = apply_filters( 'siteorigin_panels_prebuilt_layouts', array() );
607
-
608
- $home_name = siteorigin_panels_setting( 'home-page-default' ) ? siteorigin_panels_setting( 'home-page-default' ) : 'home';
609
- $panels_data = ! empty( $layouts[ $home_name ] ) ? $layouts[ $home_name ] : current( $layouts );
610
- } elseif ( empty( $panels_data ) ) {
611
- // The current page_on_front isn't using page builder
612
- return false;
613
- }
614
-
615
- $panels_data = apply_filters( 'siteorigin_panels_data', $panels_data, 'home' );
616
- } else {
617
- global $post;
618
- if ( ! empty( $post ) ) {
619
- $panels_data = get_post_meta( $post->ID, 'panels_data', true );
620
- $panels_data = apply_filters( 'siteorigin_panels_data', $panels_data, $post->ID );
621
- }
622
- }
623
-
624
- if ( empty( $panels_data ) ) {
625
- $panels_data = array();
626
- }
627
-
628
- return $panels_data;
629
- }
630
-
631
- /**
632
- * Save home page
633
- */
634
- function save_home_page() {
635
- if ( ! isset( $_POST['_sopanels_home_nonce'] ) || ! wp_verify_nonce( $_POST['_sopanels_home_nonce'], 'save' ) ) {
636
- return;
637
- }
638
- if ( ! current_user_can( 'edit_theme_options' ) ) {
639
- return;
640
- }
641
- if ( ! isset( $_POST['panels_data'] ) ) {
642
- return;
643
- }
644
-
645
- // Check that the home page ID is set and the home page exists
646
- $page_id = get_option( 'page_on_front' );
647
- if ( empty( $page_id ) ) {
648
- $page_id = get_option( 'siteorigin_panels_home_page_id' );
649
- }
650
-
651
- $post_content = wp_unslash( $_POST['post_content'] );
652
-
653
- if ( ! $page_id || get_post_meta( $page_id, 'panels_data', true ) == '' ) {
654
- // Lets create a new page
655
- $page_id = wp_insert_post( array(
656
- // TRANSLATORS: This is the default name given to a user's home page
657
- 'post_title' => __( 'Home Page', 'siteorigin-panels' ),
658
- 'post_status' => ! empty( $_POST['siteorigin_panels_home_enabled'] ) ? 'publish' : 'draft',
659
- 'post_type' => 'page',
660
- 'post_content' => $post_content,
661
- 'comment_status' => 'closed',
662
- ) );
663
- update_option( 'page_on_front', $page_id );
664
- update_option( 'siteorigin_panels_home_page_id', $page_id );
665
-
666
- // Action triggered when creating a new home page through the custom home page interface
667
- do_action( 'siteorigin_panels_create_home_page', $page_id );
668
- } else {
669
- // `wp_insert_post` does it's own sanitization, but it seems `wp_update_post` doesn't.
670
- $post_content = sanitize_post_field( 'post_content', $post_content, $page_id, 'db' );
671
-
672
- // Update the post with changed content to save revision if necessary.
673
- wp_update_post( array( 'ID' => $page_id, 'post_content' => $post_content ) );
674
- }
675
-
676
- $page = get_post( $page_id );
677
-
678
- // Save the updated page data
679
- $old_panels_data = get_post_meta( $page_id, 'panels_data', true );
680
- $panels_data = json_decode( wp_unslash( $_POST['panels_data'] ), true );
681
- $panels_data['widgets'] = $this->process_raw_widgets(
682
- $panels_data['widgets'],
683
- ! empty( $old_panels_data['widgets'] ) ? $old_panels_data['widgets'] : false,
684
- false
685
- );
686
-
687
- if ( siteorigin_panels_setting( 'sidebars-emulator' ) ) {
688
- $sidebars_emulator = SiteOrigin_Panels_Sidebars_Emulator::single();
689
- $panels_data['widgets'] = $sidebars_emulator->generate_sidebar_widget_ids( $panels_data['widgets'], $page_id );
690
- }
691
-
692
- $panels_data = SiteOrigin_Panels_Styles_Admin::single()->sanitize_all( $panels_data );
693
- $panels_data = apply_filters( 'siteorigin_panels_data_pre_save', $panels_data, $page, $page_id );
694
-
695
- update_post_meta( $page_id, 'panels_data', map_deep( $panels_data, array( 'SiteOrigin_Panels_Admin', 'double_slash_string' ) ) );
696
-
697
- $template = get_post_meta( $page_id, '_wp_page_template', true );
698
- $home_template = siteorigin_panels_setting( 'home-template' );
699
- if ( ( $template == '' || $template == 'default' ) && ! empty( $home_template ) ) {
700
- // Set the home page template
701
- update_post_meta( $page_id, '_wp_page_template', $home_template );
702
- }
703
-
704
- if ( ! empty( $_POST['siteorigin_panels_home_enabled'] ) ) {
705
- update_option( 'show_on_front', 'page' );
706
- update_option( 'page_on_front', $page_id );
707
- update_option( 'siteorigin_panels_home_page_id', $page_id );
708
- wp_publish_post( $page_id );
709
- } else {
710
- // We're disabling this home page
711
- update_option( 'show_on_front', 'posts' );
712
-
713
- // Change the post status to draft
714
- $post = get_post( $page_id );
715
- if ( $post->post_status != 'draft' ) {
716
- global $wpdb;
717
-
718
- $wpdb->update( $wpdb->posts, array( 'post_status' => 'draft' ), array( 'ID' => $post->ID ) );
719
- clean_post_cache( $post->ID );
720
-
721
- $old_status = $post->post_status;
722
- $post->post_status = 'draft';
723
- wp_transition_post_status( 'draft', $old_status, $post );
724
-
725
- do_action( 'edit_post', $post->ID, $post );
726
- do_action( "save_post_{$post->post_type}", $post->ID, $post, true );
727
- do_action( 'save_post', $post->ID, $post, true );
728
- do_action( 'wp_insert_post', $post->ID, $post, true );
729
- }
730
- }
731
- }
732
-
733
- /**
734
- * After the theme is switched, change the template on the home page if the theme supports home page functionality.
735
- */
736
- function update_home_on_theme_change() {
737
- $page_id = get_option( 'page_on_front' );
738
- if ( empty( $page_id ) ) {
739
- $page_id = get_option( 'siteorigin_panels_home_page_id' );
740
- }
741
-
742
- if ( siteorigin_panels_setting( 'home-page' ) && siteorigin_panels_setting( 'home-template' ) && $page_id && get_post_meta( $page_id, 'panels_data', true ) !== '' ) {
743
- // Lets update the home page to use the home template that this theme supports
744
- update_post_meta( $page_id, '_wp_page_template', siteorigin_panels_setting( 'home-template' ) );
745
- }
746
- }
747
-
748
- /**
749
- * @return array|mixed|void
750
- */
751
- function get_widgets() {
752
- global $wp_widget_factory;
753
- $widgets = array();
754
- foreach ( $wp_widget_factory->widgets as $widget_obj ) {
755
- $class = get_class( $widget_obj );
756
- $widgets[ $class ] = array(
757
- 'class' => $class,
758
- 'title' => ! empty( $widget_obj->name ) ? $widget_obj->name : __( 'Untitled Widget', 'siteorigin-panels' ),
759
- 'description' => ! empty( $widget_obj->widget_options['description'] ) ? $widget_obj->widget_options['description'] : '',
760
- 'installed' => true,
761
- 'groups' => array(),
762
- );
763
-
764
- // Get Page Builder specific widget options
765
- if ( isset( $widget_obj->widget_options['panels_title'] ) ) {
766
- $widgets[ $class ]['panels_title'] = $widget_obj->widget_options['panels_title'];
767
- }
768
- if ( isset( $widget_obj->widget_options['panels_groups'] ) ) {
769
- $widgets[ $class ]['groups'] = $widget_obj->widget_options['panels_groups'];
770
- }
771
- if ( isset( $widget_obj->widget_options['panels_icon'] ) ) {
772
- $widgets[ $class ]['icon'] = $widget_obj->widget_options['panels_icon'];
773
- }
774
-
775
- }
776
-
777
- // Other plugins can manipulate the list of widgets. Possibly to add recommended widgets
778
- $widgets = apply_filters( 'siteorigin_panels_widgets', $widgets );
779
-
780
- // Exclude these temporarily, as they won't work until we have a reliable way to enqueue their admin form scripts.
781
- $to_exclude = array(
782
- 'Jetpack_Gallery_Widget',
783
- 'WPCOM_Widget_GooglePlus_Badge',
784
- 'Jetpack_Widget_Social_Icons',
785
- 'Jetpack_Twitter_Timeline_Widget'
786
- );
787
-
788
- foreach ( $to_exclude as $widget_class ) {
789
- if ( in_array( $widget_class, $widgets ) ) {
790
- unset( $widgets[ $widget_class ] );
791
- }
792
- }
793
-
794
- // Sort the widgets alphabetically
795
- uasort( $widgets, array( $this, 'widgets_sorter' ) );
796
-
797
- return $widgets;
798
- }
799
-
800
- /**
801
- * Sorts widgets for get_widgets function by title
802
- *
803
- * @param $a
804
- * @param $b
805
- *
806
- * @return int
807
- */
808
- function widgets_sorter( $a, $b ) {
809
- if ( empty( $a['title'] ) ) {
810
- return - 1;
811
- }
812
- if ( empty( $b['title'] ) ) {
813
- return 1;
814
- }
815
-
816
- return $a['title'] > $b['title'] ? 1 : - 1;
817
- }
818
-
819
- /**
820
- * Process raw widgets that have come from the Page Builder front end.
821
- *
822
- * @param array $widgets An array of widgets from panels_data.
823
- * @param array $old_widgets
824
- * @param bool $escape_classes Should the class names be escaped.
825
- * @param bool $force
826
- *
827
- * @return array
828
- */
829
- function process_raw_widgets( $widgets, $old_widgets = array(), $escape_classes = false, $force = false ) {
830
- if ( empty( $widgets ) || ! is_array( $widgets ) ) {
831
- return array();
832
- }
833
-
834
- $old_widgets_by_id = array();
835
- if( ! empty( $old_widgets ) ) {
836
- foreach( $old_widgets as $widget ) {
837
- if( ! empty( $widget[ 'panels_info' ][ 'widget_id' ] ) ) {
838
- $old_widgets_by_id[ $widget[ 'panels_info' ][ 'widget_id' ] ] = $widget;
839
- unset( $old_widgets_by_id[ $widget[ 'panels_info' ][ 'widget_id' ] ][ 'panels_info' ] );
840
- }
841
- }
842
- }
843
-
844
- foreach( $widgets as $i => & $widget ) {
845
- if ( ! is_array( $widget ) ) {
846
- continue;
847
- }
848
-
849
- if ( is_array( $widget ) ) {
850
- $info = (array) ( is_array( $widget['panels_info'] ) ? $widget['panels_info'] : $widget['info'] );
851
- } else {
852
- $info = array();
853
- }
854
- unset( $widget['info'] );
855
-
856
- $info[ 'class' ] = apply_filters( 'siteorigin_panels_widget_class', $info[ 'class' ] );
857
-
858
- if ( ! empty( $info['raw'] ) || $force ) {
859
- $the_widget = SiteOrigin_Panels::get_widget_instance( $info['class'] );
860
- if ( ! empty( $the_widget ) &&
861
- method_exists( $the_widget, 'update' ) ) {
862
-
863
- if(
864
- ! empty( $old_widgets_by_id ) &&
865
- ! empty( $widget[ 'panels_info' ][ 'widget_id' ] ) &&
866
- ! empty( $old_widgets_by_id[ $widget[ 'panels_info' ][ 'widget_id' ] ] )
867
- ){
868
- $old_widget = $old_widgets_by_id[ $widget[ 'panels_info' ][ 'widget_id' ] ];
869
- }
870
- else {
871
- $old_widget = $widget;
872
- }
873
-
874
- /** @var WP_Widget $the_widget */
875
- $the_widget = SiteOrigin_Panels::get_widget_instance( $info['class'] );
876
- $instance = $the_widget->update( $widget, $old_widget );
877
- $instance = apply_filters( 'widget_update_callback', $instance, $widget, $old_widget, $the_widget );
878
-
879
- $widget = $instance;
880
-
881
- unset( $info['raw'] );
882
- }
883
- }
884
-
885
- if( $escape_classes ) {
886
- // Escaping for namespaced widgets
887
- $info[ 'class' ] = preg_replace( '/\\\\+/', '\\\\\\\\', $info['class'] );
888
- }
889
-
890
- $widget['panels_info'] = $info;
891
- }
892
-
893
- return $widgets;
894
- }
895
-
896
- /**
897
- * Add all the footer JS templates.
898
- */
899
- function js_templates() {
900
- include plugin_dir_path( __FILE__ ) . '../tpl/js-templates.php';
901
- }
902
-
903
- /**
904
- * Render a widget form with all the Page Builder specific fields
905
- *
906
- * @param string $widget_class The class of the widget
907
- * @param array $instance Widget values
908
- * @param bool $raw
909
- * @param string $widget_number
910
- *
911
- * @return mixed|string The form
912
- */
913
- function render_form( $widget_class, $instance = array(), $raw = false, $widget_number = '{$id}' ) {
914
-
915
- $the_widget = SiteOrigin_Panels::get_widget_instance( $widget_class );
916
- // This is a chance for plugins to replace missing widgets
917
- $the_widget = apply_filters( 'siteorigin_panels_widget_object', $the_widget, $widget_class );
918
-
919
- if ( empty( $the_widget ) || ! is_a( $the_widget, 'WP_Widget' ) ) {
920
- $widgets = $this->get_widgets();
921
-
922
- if ( ! empty( $widgets[ $widget_class ] ) && ! empty( $widgets[ $widget_class ]['plugin'] ) ) {
923
- // We know about this widget, show a form about installing it.
924
- $install_url = siteorigin_panels_plugin_activation_install_url( $widgets[ $widget_class ]['plugin']['slug'], $widgets[ $widget_class ]['plugin']['name'] );
925
- $form =
926
- '<div class="panels-missing-widget-form">' .
927
- '<p>' .
928
- preg_replace(
929
- array(
930
- '/1\{ *(.*?) *\}/',
931
- '/2\{ *(.*?) *\}/',
932
- ),
933
- array(
934
- '<a href="' . $install_url . '" target="_blank" rel="noopener noreferrer">$1</a>',
935
- '<strong>$1</strong>'
936
- ),
937
- sprintf(
938
- __( 'You need to install 1{%1$s} to use the widget 2{%2$s}.', 'siteorigin-panels' ),
939
- $widgets[ $widget_class ]['plugin']['name'],
940
- $widget_class
941
- )
942
- ) .
943
- '</p>' .
944
- '<p>' . __( "Save and reload this page to start using the widget after you've installed it.", 'siteorigin-panels' ) . '</p>' .
945
- '</div>';
946
- } else {
947
- // This widget is missing, so show a missing widgets form.
948
- $form =
949
- '<div class="panels-missing-widget-form"><p>' .
950
- preg_replace(
951
- array(
952
- '/1\{ *(.*?) *\}/',
953
- '/2\{ *(.*?) *\}/',
954
- ),
955
- array(
956
- '<strong>$1</strong>',
957
- '<a href="https://siteorigin.com/thread/" target="_blank" rel="noopener noreferrer">$1</a>'
958
- ),
959
- sprintf(
960
- __( 'The widget 1{%1$s} is not available. Please try locate and install the missing plugin. Post on the 2{support forums} if you need help.', 'siteorigin-panels' ),
961
- esc_html( $widget_class )
962
- )
963
- ) .
964
- '</p></div>';
965
- }
966
-
967
- // Allow other themes and plugins to change the missing widget form
968
- return apply_filters( 'siteorigin_panels_missing_widget_form', $form, $widget_class, $instance );
969
- }
970
-
971
- if ( $raw ) {
972
- $instance = $the_widget->update( $instance, $instance );
973
- }
974
-
975
- $the_widget->id = 'temp';
976
- $the_widget->number = $widget_number;
977
-
978
- ob_start();
979
- if ( $this->is_core_js_widget( $the_widget ) ) {
980
- ?><div class="widget-content"><?php
981
- }
982
- $return = $the_widget->form( $instance );
983
- do_action_ref_array( 'in_widget_form', array( &$the_widget, &$return, $instance ) );
984
- if ( $this->is_core_js_widget( $the_widget ) ) {
985
- ?>
986
- </div>
987
- <input type="hidden" name="id_base" class="id_base" value="<?php echo esc_attr( $the_widget->id_base ); ?>" />
988
- <?php
989
- }
990
- $form = ob_get_clean();
991
-
992
- // Convert the widget field naming into ones that Page Builder uses
993
- $exp = preg_quote( $the_widget->get_field_name( '____' ) );
994
- $exp = str_replace( '____', '(.*?)', $exp );
995
- $form = preg_replace( '/' . $exp . '/', 'widgets[' . preg_replace( '/\$(\d)/', '\\\$$1', $widget_number ) . '][$1]', $form );
996
-
997
- $form = apply_filters( 'siteorigin_panels_widget_form', $form, $widget_class, $instance );
998
-
999
- // Add all the information fields
1000
- return $form;
1001
- }
1002
-
1003
- /**
1004
- * Checks whether a widget is considered to be a JS widget. I.e. it needs to have scripts and/or styles enqueued for
1005
- * it's admin form to work.
1006
- *
1007
- * Can remove the whitelist of core widgets when all widgets are following a similar pattern.
1008
- *
1009
- * @param $widget The widget to be tested.
1010
- *
1011
- * @return bool Whether or not the widget is considered a JS widget.
1012
- */
1013
- function is_core_js_widget( $widget ) {
1014
- $js_widgets = array(
1015
- 'WP_Widget_Custom_HTML',
1016
- 'WP_Widget_Media_Audio',
1017
- 'WP_Widget_Media_Gallery',
1018
- 'WP_Widget_Media_Image',
1019
- 'WP_Widget_Media_Video',
1020
- 'WP_Widget_Text',
1021
- );
1022
-
1023
- $is_js_widget = in_array( get_class( $widget ), $js_widgets ) &&
1024
- // Need to check this for `WP_Widget_Text` which was not a JS widget before 4.8
1025
- method_exists( $widget, 'render_control_template_scripts' );
1026
-
1027
- return $is_js_widget;
1028
- }
1029
-
1030
- ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1031
- // ADMIN AJAX ACTIONS
1032
- ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1033
-
1034
- /**
1035
- * Get builder content based on the submitted panels_data.
1036
- */
1037
- function action_builder_content() {
1038
- header( 'content-type: text/html' );
1039
-
1040
- if ( ! current_user_can( 'edit_post', $_POST['post_id'] ) ) {
1041
- wp_die();
1042
- }
1043
-
1044
- if ( empty( $_POST['post_id'] ) || empty( $_POST['panels_data'] ) ) {
1045
- echo '';
1046
- wp_die();
1047
- }
1048
-
1049
- // echo the content
1050
- $old_panels_data = get_post_meta( $_POST['post_id'], 'panels_data', true );
1051
- $panels_data = json_decode( wp_unslash( $_POST['panels_data'] ), true );
1052
- $panels_data['widgets'] = $this->process_raw_widgets(
1053
- $panels_data['widgets'],
1054
- ! empty( $old_panels_data['widgets'] ) ? $old_panels_data['widgets'] : false,
1055
- false
1056
- );
1057
- $panels_data = SiteOrigin_Panels_Styles_Admin::single()->sanitize_all( $panels_data );
1058
-
1059
- // Create a version of the builder data for post content
1060
- SiteOrigin_Panels_Post_Content_Filters::add_filters();
1061
- $GLOBALS[ 'SITEORIGIN_PANELS_POST_CONTENT_RENDER' ] = true;
1062
- echo SiteOrigin_Panels::renderer()->render( intval( $_POST['post_id'] ), false, $panels_data );
1063
- SiteOrigin_Panels_Post_Content_Filters::remove_filters();
1064
- unset( $GLOBALS[ 'SITEORIGIN_PANELS_POST_CONTENT_RENDER' ] );
1065
-
1066
- wp_die();
1067
- }
1068
-
1069
- /**
1070
- * Display a widget form with the provided data
1071
- */
1072
- function action_widget_form() {
1073
- if ( empty( $_REQUEST['_panelsnonce'] ) || ! wp_verify_nonce( $_REQUEST['_panelsnonce'], 'panels_action' ) ) {
1074
- wp_die(
1075
- __( 'The supplied nonce is invalid.', 'siteorigin-panels' ),
1076
- __( 'Invalid nonce.', 'siteorigin-panels' ),
1077
- 403
1078
- );
1079
- }
1080
- if ( empty( $_REQUEST['widget'] ) ) {
1081
- wp_die(
1082
- __( 'Please specify the type of widget form to be rendered.', 'siteorigin-panels' ),
1083
- __( 'Missing widget type.', 'siteorigin-panels' ),
1084
- 400
1085
- );
1086
- }
1087
-
1088
- $request = array_map( 'stripslashes_deep', $_REQUEST );
1089
-
1090
- $widget_class = $request['widget'];
1091
- $widget_class = apply_filters( 'siteorigin_panels_widget_class', $widget_class );
1092
- $instance = ! empty( $request['instance'] ) ? json_decode( $request['instance'], true ) : array();
1093
-
1094
- $form = $this->render_form( $widget_class, $instance, $_REQUEST['raw'] == 'true' );
1095
- $form = apply_filters( 'siteorigin_panels_ajax_widget_form', $form, $widget_class, $instance );
1096
-
1097
- echo $form;
1098
- wp_die();
1099
- }
1100
-
1101
- /**
1102
- * Preview in the live editor when there is no public view of the item
1103
- */
1104
- function action_live_editor_preview() {
1105
- if ( empty( $_REQUEST['_panelsnonce'] ) || ! wp_verify_nonce( $_REQUEST['_panelsnonce'], 'live-editor-preview' ) ) {
1106
- wp_die();
1107
- }
1108
-
1109
- include plugin_dir_path( __FILE__ ) . '../tpl/live-editor-preview.php';
1110
-
1111
- exit();
1112
- }
1113
-
1114
- /**
1115
- * Preview in the block editor.
1116
- */
1117
- public function layout_block_preview() {
1118
-
1119
- if ( empty( $_REQUEST['_panelsnonce'] ) || ! wp_verify_nonce( $_REQUEST['_panelsnonce'], 'layout-block-preview' ) ) {
1120
- wp_die();
1121
- }
1122
-
1123
- $panels_data = json_decode( wp_unslash( $_POST['panelsData'] ), true );
1124
- $builder_id = 'gbp' . uniqid();
1125
- $panels_data['widgets'] = SiteOrigin_Panels_Admin::single()->process_raw_widgets( $panels_data['widgets'], false, true, true );
1126
- $panels_data = SiteOrigin_Panels_Styles_Admin::single()->sanitize_all( $panels_data );
1127
- $sowb_active = class_exists( 'SiteOrigin_Widgets_Bundle' );
1128
- if ( $sowb_active ) {
1129
- // We need this to get our widgets bundle to add it's styles inline for previews.
1130
- add_filter( 'siteorigin_widgets_is_preview', '__return_true' );
1131
- }
1132
- $rendered_layout = SiteOrigin_Panels::renderer()->render( $builder_id, true, $panels_data, $layout_data, true );
1133
-
1134
- // Need to explicitly call `siteorigin_widget_print_styles` because Gutenberg previews don't render a full version of the front end,
1135
- // so neither the `wp_head` nor the `wp_footer` actions are called, which usually trigger `siteorigin_widget_print_styles`.
1136
- if ( $sowb_active ) {
1137
- ob_start();
1138
- siteorigin_widget_print_styles();
1139
- $rendered_layout .= ob_get_clean();
1140
- }
1141
-
1142
- echo $rendered_layout;
1143
- wp_die();
1144
- }
1145
-
1146
- public function layout_block_sanitize() {
1147
-
1148
- if ( empty( $_REQUEST['_panelsnonce'] ) || ! wp_verify_nonce( $_REQUEST['_panelsnonce'], 'layout-block-sanitize' ) ) {
1149
- wp_die();
1150
- }
1151
-
1152
- $panels_data = json_decode( wp_unslash( $_POST['panelsData'] ), true );
1153
- $panels_data['widgets'] = SiteOrigin_Panels_Admin::single()->process_raw_widgets( $panels_data['widgets'], false, true, true );
1154
- $panels_data = SiteOrigin_Panels_Styles_Admin::single()->sanitize_all( $panels_data );
1155
-
1156
- wp_send_json( $panels_data );
1157
- }
1158
-
1159
- /**
1160
- * Add a column that indicates if a column is powered by Page Builder
1161
- *
1162
- * @param $columns
1163
- *
1164
- * @return array
1165
- */
1166
- function add_custom_column( $columns ){
1167
- $index = array_search( 'comments', array_keys( $columns ) );
1168
-
1169
- if( empty( $index ) ) {
1170
- $columns = array_merge(
1171
- $columns,
1172
- array( 'panels' => __( 'Page Builder', 'siteorigin-panels' ) )
1173
- );
1174
- }
1175
- else {
1176
- $columns = array_slice( $columns, 0, $index, true ) +
1177
- array( 'panels' => __( 'Page Builder', 'siteorigin-panels' ) ) +
1178
- array_slice( $columns, $index, count( $columns ) - 1, true );
1179
- }
1180
-
1181
- return $columns;
1182
- }
1183
-
1184
- function display_custom_column( $column, $post_id ){
1185
- if( $column != 'panels' ) return;
1186
-
1187
- $panels_data = get_post_meta( $post_id, 'panels_data', true );
1188
- if( ! empty( $panels_data['widgets'] ) ) {
1189
- $widgets_count = count( $panels_data['widgets'] );
1190
- printf( _n( '%s Widget', '%s Widgets', $widgets_count, 'siteorigin-panels' ), $widgets_count );
1191
- }
1192
- else {
1193
- echo '—';
1194
- }
1195
- }
1196
-
1197
- public function footer_column_css(){
1198
- if( siteorigin_panels_setting( 'admin-widget-count' ) ) {
1199
- $screen = get_current_screen();
1200
- $post_types = siteorigin_panels_setting( 'post-types' );
1201
-
1202
- if(
1203
- $screen->base == 'edit' &&
1204
- is_array( $post_types ) &&
1205
- in_array( $screen->post_type, $post_types )
1206
- ){
1207
- ?><style type="text/css">.column-panels{ width: 10% }</style><?php
1208
- }
1209
- }
1210
- }
1211
-
1212
- /**
1213
- * Add double slashes to strings
1214
- *
1215
- * @param $value
1216
- *
1217
- * @return string
1218
- */
1219
- public static function double_slash_string( $value ){
1220
- return is_string( $value ) ? addcslashes( $value, '\\' ) : $value;
1221
- }
1222
-
1223
- public function get_layout_directories(){
1224
-
1225
- }
1226
-
1227
- /**
1228
- * Display links for various SiteOrigin addons
1229
- */
1230
- public static function display_footer_premium_link(){
1231
- $links = array(
1232
- array(
1233
- 'text' => __('Get a lightbox addon for SiteOrigin widgets', 'siteorigin-panels'),
1234
- 'url' => SiteOrigin_Panels::premium_url('plugin/lightbox')
1235
- ),
1236
- array(
1237
- 'text' => __('Get the row, cell and widget animations addon', 'siteorigin-panels'),
1238
- 'url' => SiteOrigin_Panels::premium_url('plugin/lightbox')
1239
- ),
1240
- array(
1241
- 'text' => __('Get premium email support for SiteOrigin Page Builder', 'siteorigin-panels'),
1242
- 'url' => SiteOrigin_Panels::premium_url()
1243
- ),
1244
- );
1245
- $link = $links[array_rand($links)];
1246
-
1247
- ?>
1248
- <a href="<?php echo esc_url( $link['url'] ) ?>" target="_blank" rel='noopener noreferrer'>
1249
- <?php echo esc_html( $link['text'] ) ?>.
1250
- </a>
1251
- <?php
1252
- }
1253
-
1254
- public function admin_notices() {
1255
- global $typenow, $pagenow;
1256
- $is_new = $pagenow == 'post-new.php';
1257
- $post_types = siteorigin_panels_setting( 'post-types' );
1258
- $is_panels_type = in_array( $typenow, $post_types );
1259
- $use_classic = siteorigin_panels_setting( 'use-classic' );
1260
- $show_classic_admin_notice = $is_new && $is_panels_type && $use_classic;
1261
- $show_classic_admin_notice = apply_filters( 'so_panels_show_classic_admin_notice', $show_classic_admin_notice );
1262
- if ( $show_classic_admin_notice ) {
1263
- $settings_url = self_admin_url( 'options-general.php?page=siteorigin_panels' );
1264
- $notice = sprintf(
1265
- __( 'This post type is set to use the Classic Editor by default for new posts. If you’d like to change this to the block editor, please go to <a href="%s" class="components-notice__action is-link">Page Builder Settings</a> and uncheck <strong>Use Classic Editor for new posts</strong>' ),
1266
- $settings_url
1267
- );
1268
- ?>
1269
- <div id="siteorigin-panels-use-classic-notice" class="notice notice-info"><p id="use-classic-notice"><?php echo $notice ?></p></div>
1270
- <?php
1271
- }
1272
- }
1273
-
1274
- /**
1275
- * Show Classic Editor for existing PB posts.
1276
- *
1277
- * @param $use_block_editor
1278
- * @param $post_type
1279
- *
1280
- * @return bool
1281
- */
1282
- public function show_classic_editor_for_panels( $use_block_editor, $post_type ) {
1283
-
1284
- // For new pages.
1285
- if ( isset( $_GET['block-editor'] ) ) {
1286
- return $use_block_editor;
1287
- } else if ( isset( $_GET['siteorigin-page-builder'] ) ) {
1288
- return false;
1289
- }
1290
-
1291
- $post_types = siteorigin_panels_setting( 'post-types' );
1292
- global $pagenow;
1293
- // If the `$post_type` is set to be used by Page Builder for new posts.
1294
- $is_new_panels_type = $pagenow == 'post-new.php' && in_array( $post_type, $post_types );
1295
- $use_classic = siteorigin_panels_setting( 'use-classic' );
1296
- // For existing posts.
1297
- global $post;
1298
- if ( ! empty( $post ) ) {
1299
- // If the post has blocks just allow `$use_block_editor` to decide.
1300
- if ( ! has_blocks( $post ) ) {
1301
- $panels_data = get_post_meta( $post->ID, 'panels_data', true );
1302
- if ( ! empty( $panels_data ) || ( $use_classic && $is_new_panels_type ) ) {
1303
- $use_block_editor = false;
1304
- }
1305
- }
1306
- } else if ( $is_new_panels_type ) {
1307
- $use_block_editor = false;
1308
- }
1309
-
1310
- return $use_block_editor;
1311
- }
1312
-
1313
- /**
1314
- * This was copied from Gutenberg and slightly modified as a quick way to allow users to create new Page Builder pages
1315
- * in the classic editor without requiring the classic editor plugin be installed.
1316
- */
1317
- function add_panels_add_new_button() {
1318
- global $typenow;
1319
-
1320
- if ( 'wp_block' === $typenow ) {
1321
- ?>
1322
- <style type="text/css">
1323
- .page-title-action {
1324
- display: none;
1325
- }
1326
- </style>
1327
- <?php
1328
- }
1329
-
1330
- if ( ! $this->show_add_new_dropdown_for_type( $typenow ) ) {
1331
- return;
1332
- }
1333
-
1334
- ?>
1335
- <style type="text/css">
1336
- .split-page-title-action {
1337
- display: inline-block;
1338
- }
1339
-
1340
- .split-page-title-action a,
1341
- .split-page-title-action a:active,
1342
- .split-page-title-action .expander:after {
1343
- padding: 6px 10px;
1344
- position: relative;
1345
- top: -3px;
1346
- text-decoration: none;
1347
- border: 1px solid #ccc;
1348
- border-radius: 2px 0px 0px 2px;
1349
- background: #f7f7f7;
1350
- text-shadow: none;
1351
- font-weight: 600;
1352
- font-size: 13px;
1353
- line-height: normal; /* IE8-IE11 need this for buttons */
1354
- color: #0073aa; /* some of these controls are button elements and don't inherit from links */
1355
- cursor: pointer;
1356
- outline: 0;
1357
- }
1358
-
1359
- .split-page-title-action a:hover,
1360
- .split-page-title-action .expander:hover:after {
1361
- border-color: #008EC2;
1362
- background: #00a0d2;
1363
- color: #fff;
1364
- }
1365
-
1366
- .split-page-title-action a:focus,
1367
- .split-page-title-action .expander:focus:after {
1368
- border-color: #5b9dd9;
1369
- box-shadow: 0 0 2px rgba( 30, 140, 190, 0.8 );
1370
- }
1371
-
1372
- .split-page-title-action .expander:after {
1373
- content: "\f140";
1374
- font: 400 20px/.5 dashicons;
1375
- speak: none;
1376
- top: 0px;
1377
- <?php if ( is_rtl() ) : ?>
1378
- right: -1px;
1379
- <?php else : ?>
1380
- left: -1px;
1381
- <?php endif; ?>
1382
- position: relative;
1383
- vertical-align: top;
1384
- text-decoration: none !important;
1385
- padding: 4px 5px 5px 4px;
1386
- border-radius: 0px 2px 2px 0px;
1387
- }
1388
-
1389
- .split-page-title-action .dropdown {
1390
- display: none;
1391
- }
1392
-
1393
- .split-page-title-action .dropdown.visible {
1394
- display: block;
1395
- position: absolute;
1396
- margin-top: 3px;
1397
- z-index: 1;
1398
- }
1399
-
1400
- .split-page-title-action .dropdown.visible a {
1401
- display: block;
1402
- top: 0;
1403
- margin: -1px 0;
1404
- <?php if ( is_rtl() ) : ?>
1405
- padding-left: 9px;
1406
- <?php else : ?>
1407
- padding-right: 9px;
1408
- <?php endif; ?>
1409
- }
1410
-
1411
- .split-page-title-action .expander {
1412
- outline: none;
1413
- }
1414
-
1415
- </style>
1416
- <script type="text/javascript">
1417
- document.addEventListener( 'DOMContentLoaded', function() {
1418
- var buttons = document.getElementsByClassName( 'page-title-action' ),
1419
- button = buttons.item( 0 );
1420
-
1421
- if ( ! button ) {
1422
- return;
1423
- }
1424
-
1425
- var url = button.href;
1426
- var urlHasParams = ( -1 !== url.indexOf( '?' ) );
1427
- var panelsUrl = url + ( urlHasParams ? '&' : '?' ) + 'siteorigin-page-builder';
1428
- var blockEditorUrl = url + ( urlHasParams ? '&' : '?' ) + 'block-editor';
1429
-
1430
- var newbutton = '<span id="split-page-title-action" class="split-page-title-action">';
1431
- newbutton += '<a href="' + url + '">' + button.innerText + '</a>';
1432
- newbutton += '<span class="expander" tabindex="0" role="button" aria-haspopup="true" aria-label="<?php echo esc_attr( __( 'Toggle editor selection menu', 'siteorigin-panels' ) ); ?>"></span>';
1433
- newbutton += '<span class="dropdown"><a href="' + panelsUrl + '"><?php echo esc_html( __( 'SiteOrigin Page Builder', 'siteorigin-panels' ) ); ?></a>';
1434
- newbutton += '<a href="' + blockEditorUrl + '"><?php echo esc_html( __( 'Block Editor', 'siteorigin-panels' ) ); ?></a></span></span><span class="page-title-action" style="display:none;"></span>';
1435
-
1436
- button.insertAdjacentHTML( 'afterend', newbutton );
1437
- button.parentNode.removeChild( button );
1438
-
1439
- var expander = document.getElementById( 'split-page-title-action' ).getElementsByClassName( 'expander' ).item( 0 );
1440
- var dropdown = expander.parentNode.querySelector( '.dropdown' );
1441
- function toggleDropdown() {
1442
- dropdown.classList.toggle( 'visible' );
1443
- }
1444
- expander.addEventListener( 'click', function( e ) {
1445
- e.preventDefault();
1446
- toggleDropdown();
1447
- } );
1448
- expander.addEventListener( 'keydown', function( e ) {
1449
- if ( 13 === e.which || 32 === e.which ) {
1450
- e.preventDefault();
1451
- toggleDropdown();
1452
- }
1453
- } );
1454
- } );
1455
- </script>
1456
- <?php
1457
- }
1458
-
1459
- private function show_add_new_dropdown_for_type( $post_type ) {
1460
-
1461
- $show = in_array( $post_type, siteorigin_panels_setting( 'post-types' ) );
1462
-
1463
- // WooCommerce product type doesn't support block editor...
1464
- $show = $show && ! ( class_exists( 'WooCommerce' ) && $post_type == 'product' );
1465
-
1466
- if ( class_exists( 'SiteOrigin_Premium_Plugin_Cpt_Builder' ) ) {
1467
- $show = $show && $post_type != SiteOrigin_Premium_Plugin_Cpt_Builder::POST_TYPE;
1468
- $cpt_builder = SiteOrigin_Premium_Plugin_Cpt_Builder::single();
1469
- $so_custom_types = $cpt_builder->get_post_types();
1470
- $show = $show && ! isset( $so_custom_types[ $post_type ] );
1471
- }
1472
-
1473
- return apply_filters( 'so_panels_show_add_new_dropdown_for_type', $show, $post_type );
1474
- }
1475
-
1476
- public function add_panels_post_state( $post_states, $post ) {
1477
- $panels_data = get_post_meta( $post->ID, 'panels_data', true );
1478
-
1479
- if ( ! empty( $panels_data ) ) {
1480
- $post_states[] = __( 'SiteOrigin Page Builder', 'siteorigin-panels' );
1481
- }
1482
-
1483
- return $post_states;
1484
- }
1485
- }
1
+ <?php
2
+
3
+ /**
4
+ * Class SiteOrigin_Panels_Admin
5
+ *
6
+ * Handles all the admin and database interactions.
7
+ */
8
+ class SiteOrigin_Panels_Admin {
9
+
10
+ /**
11
+ * @var bool Store that we're in the save post action, to prevent infinite loops
12
+ */
13
+ private $in_save_post;
14
+
15
+ function __construct() {
16
+
17
+ add_action( 'plugin_action_links_siteorigin-panels/siteorigin-panels.php', array(
18
+ $this,
19
+ 'plugin_action_links'
20
+ ) );
21
+
22
+ add_action( 'plugins_loaded', array( $this, 'admin_init_widget_count' ) );
23
+
24
+ add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ) );
25
+ add_action( 'admin_init', array( $this, 'save_home_page' ) );
26
+ add_action( 'save_post', array( $this, 'save_post' ) );
27
+
28
+ add_action( 'after_switch_theme', array( $this, 'update_home_on_theme_change' ) );
29
+
30
+ // Enqueuing admin scripts
31
+ add_action( 'admin_print_scripts-post-new.php', array( $this, 'enqueue_admin_scripts' ) );
32
+ add_action( 'admin_print_scripts-post.php', array( $this, 'enqueue_admin_scripts' ) );
33
+ add_action( 'admin_print_scripts-appearance_page_so_panels_home_page', array(
34
+ $this,
35
+ 'enqueue_admin_scripts'
36
+ ) );
37
+ add_action( 'admin_print_scripts-widgets.php', array( $this, 'enqueue_admin_scripts' ) );
38
+ add_action( 'admin_print_scripts-edit.php', array( $this, 'footer_column_css' ) );
39
+
40
+ // Enqueue the admin styles
41
+ add_action( 'admin_print_styles-post-new.php', array( $this, 'enqueue_admin_styles' ) );
42
+ add_action( 'admin_print_styles-post.php', array( $this, 'enqueue_admin_styles' ) );
43
+ add_action( 'admin_print_styles-appearance_page_so_panels_home_page', array( $this, 'enqueue_admin_styles' ) );
44
+ add_action( 'admin_print_styles-widgets.php', array( $this, 'enqueue_admin_styles' ) );
45
+
46
+ // The help tab
47
+ add_action( 'load-page.php', array( $this, 'add_help_tab' ), 12 );
48
+ add_action( 'load-post-new.php', array( $this, 'add_help_tab' ), 12 );
49
+ add_action( 'load-appearance_page_so_panels_home_page', array( $this, 'add_help_tab' ), 12 );
50
+
51
+ add_action( 'customize_controls_print_footer_scripts', array( $this, 'js_templates' ) );
52
+
53
+ // Register all the admin actions
54
+ add_action( 'wp_ajax_so_panels_builder_content', array( $this, 'action_builder_content' ) );
55
+ add_action( 'wp_ajax_so_panels_widget_form', array( $this, 'action_widget_form' ) );
56
+ add_action( 'wp_ajax_so_panels_live_editor_preview', array( $this, 'action_live_editor_preview' ) );
57
+ add_action( 'wp_ajax_so_panels_layout_block_sanitize', array( $this, 'layout_block_sanitize' ) );
58
+ add_action( 'wp_ajax_so_panels_layout_block_preview', array( $this, 'layout_block_preview' ) );
59
+
60
+ // Initialize the additional admin classes.
61
+ SiteOrigin_Panels_Admin_Widget_Dialog::single();
62
+ SiteOrigin_Panels_Admin_Widgets_Bundle::single();
63
+ SiteOrigin_Panels_Admin_Layouts::single();
64
+
65
+ // Check to make sure we have all the correct markup
66
+ SiteOrigin_Panels_Admin_Dashboard::single();
67
+
68
+ $this->in_save_post = false;
69
+
70
+
71
+ // Enqueue Yoast compatibility
72
+ add_action( 'admin_print_scripts-post-new.php', array( $this, 'enqueue_yoast_compat' ), 100 );
73
+ add_action( 'admin_print_scripts-post.php', array( $this, 'enqueue_yoast_compat' ), 100 );
74
+
75
+ // Block editor specific actions
76
+ if ( function_exists( 'register_block_type' ) ) {
77
+ add_action( 'admin_notices', array( $this, 'admin_notices' ) );
78
+ add_filter( 'gutenberg_can_edit_post_type', array( $this, 'show_classic_editor_for_panels' ), 10, 2 );
79
+ add_filter( 'use_block_editor_for_post_type', array( $this, 'show_classic_editor_for_panels' ), 10, 2 );
80
+ add_action( 'admin_print_scripts-edit.php', array( $this, 'add_panels_add_new_button' ) );
81
+ if( siteorigin_panels_setting( 'admin-post-state' ) ) {
82
+ add_filter( 'display_post_states', array( $this, 'add_panels_post_state' ), 10, 2 );
83
+ }
84
+ }
85
+ }
86
+
87
+ /**
88
+ * @return SiteOrigin_Panels_Admin
89
+ */
90
+ public static function single() {
91
+ static $single;
92
+ return empty( $single ) ? $single = new self() : $single;
93
+ }
94
+
95
+ /**
96
+ * Do some general admin initialization
97
+ */
98
+ public function admin_init_widget_count(){
99
+ if( siteorigin_panels_setting( 'admin-widget-count' ) ) {
100
+
101
+ // Add the custom columns
102
+ $post_types = siteorigin_panels_setting( 'post-types' );
103
+ if( ! empty( $post_types ) ) {
104
+ foreach( $post_types as $post_type ) {
105
+ add_filter( 'manage_' . $post_type . 's_columns' , array( $this, 'add_custom_column' ) );
106
+ add_action( 'manage_' . $post_type . 's_custom_column' , array( $this, 'display_custom_column' ), 10, 2 );
107
+ }
108
+ }
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Check if this is an admin page.
114
+ *
115
+ * @return mixed|void
116
+ */
117
+ static function is_admin() {
118
+ $screen = get_current_screen();
119
+ $is_panels_page = ( $screen->base == 'post' && in_array( $screen->id, siteorigin_panels_setting( 'post-types' ) ) ) ||
120
+ in_array( $screen->base, array( 'appearance_page_so_panels_home_page', 'widgets', 'customize' ) ) ||
121
+ self::is_block_editor();
122
+
123
+ return apply_filters( 'siteorigin_panels_is_admin_page', $is_panels_page );
124
+ }
125
+
126
+ /**
127
+ * Check if the current page is Gutenberg or the Block Ediotr
128
+ *
129
+ * @return bool
130
+ */
131
+ static function is_block_editor() {
132
+ // This is for the Gutenberg plugin.
133
+ $is_gutenberg_page = function_exists( 'is_gutenberg_page' ) && is_gutenberg_page();
134
+ // This is for WP 5 with the integrated block editor.
135
+ $is_block_editor = false;
136
+
137
+ if ( function_exists( 'get_current_screen' ) ) {
138
+ $current_screen = get_current_screen();
139
+ if ( $current_screen && method_exists( $current_screen, 'is_block_editor' ) ) {
140
+ $is_block_editor = $current_screen->is_block_editor();
141
+ }
142
+ }
143
+
144
+ return $is_gutenberg_page || $is_block_editor;
145
+ }
146
+
147
+
148
+ /**
149
+ * Add action links to the plugin list for Page Builder.
150
+ *
151
+ * @param $links
152
+ *
153
+ * @return array
154
+ */
155
+ function plugin_action_links( $links ) {
156
+ if( ! is_array( $links ) ) {
157
+ return $links;
158
+ }
159
+
160
+ unset( $links['edit'] );
161
+ $links[] = '<a href="http://siteorigin.com/threads/plugin-page-builder/">' . __( 'Support Forum', 'siteorigin-panels' ) . '</a>';
162
+
163
+ if( SiteOrigin_Panels::display_premium_teaser() ) {
164
+ $links[] = '<a href="' . esc_url( SiteOrigin_Panels::premium_url() ) . '" style="color: #3db634" target="_blank" rel="noopener noreferrer">' . __('Addons', 'siteorigin-panels') . '</a>';
165
+ }
166
+
167
+ return $links;
168
+ }
169
+
170
+ /**
171
+ * Callback to register the Page Builder Metaboxes
172
+ */
173
+ function add_meta_boxes() {
174
+
175
+ foreach ( siteorigin_panels_setting( 'post-types' ) as $type ) {
176
+ add_meta_box(
177
+ 'so-panels-panels',
178
+ __( 'Page Builder', 'siteorigin-panels' ),
179
+ array( $this, 'render_meta_boxes' ),
180
+ ( string ) $type,
181
+ 'advanced',
182
+ 'high',
183
+ array(
184
+ // Ideally when we have panels data for a page we would set this to false and it would cause the
185
+ // editor to fall back to classic editor, but that's not the case so we just declare it as a `__back_compat_meta_box`.
186
+ '__back_compat_meta_box' => true,
187
+ '__block_editor_compatible_meta_box' => false,
188
+ )
189
+ );
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Render a panel metabox.
195
+ *
196
+ * @param $post
197
+ */
198
+ function render_meta_boxes( $post ) {
199
+ $panels_data = $this->get_current_admin_panels_data();
200
+ include plugin_dir_path( __FILE__ ) . '../tpl/metabox-panels.php';
201
+ }
202
+
203
+ /**
204
+ * Save the panels data
205
+ *
206
+ * @param $post_id
207
+ *
208
+ * @action save_post
209
+ */
210
+ function save_post( $post_id ) {
211
+ // Check that everything is valid with this save.
212
+ if(
213
+ $this->in_save_post ||
214
+ ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) ||
215
+ empty( $_POST['_sopanels_nonce'] ) ||
216
+ ! wp_verify_nonce( $_POST['_sopanels_nonce'], 'save' ) ||
217
+ ! current_user_can( 'edit_post', $post_id ) ||
218
+ ! isset( $_POST['panels_data'] )
219
+ ) {
220
+ return;
221
+ }
222
+ $this->in_save_post = true;
223
+ // Get post from db as it might have been changed and saved by other plugins.
224
+ $post = get_post( $post_id );
225
+ $old_panels_data = get_post_meta( $post_id, 'panels_data', true );
226
+ $panels_data = json_decode( wp_unslash( $_POST['panels_data'] ), true );
227
+
228
+ $panels_data['widgets'] = $this->process_raw_widgets(
229
+ $panels_data['widgets'],
230
+ ! empty( $old_panels_data['widgets'] ) ? $old_panels_data['widgets'] : false,
231
+ false
232
+ );
233
+
234
+ if ( siteorigin_panels_setting( 'sidebars-emulator' ) ) {
235
+ $sidebars_emulator = SiteOrigin_Panels_Sidebars_Emulator::single();
236
+ $panels_data['widgets'] = $sidebars_emulator->generate_sidebar_widget_ids( $panels_data['widgets'], $post_id );
237
+ }
238
+
239
+ $panels_data = SiteOrigin_Panels_Styles_Admin::single()->sanitize_all( $panels_data );
240
+ $panels_data = apply_filters( 'siteorigin_panels_data_pre_save', $panels_data, $post, $post_id );
241
+
242
+ if ( ! empty( $panels_data['widgets'] ) || ! empty( $panels_data['grids'] ) ) {
243
+ // Use `update_metadata` instead of `update_post_meta` to prevent saving to parent post when it's a revision, e.g. preview.
244
+ update_metadata( 'post', $post_id, 'panels_data', map_deep( $panels_data, array( 'SiteOrigin_Panels_Admin', 'double_slash_string' ) ) );
245
+
246
+ if( siteorigin_panels_setting( 'copy-content' ) ) {
247
+ // Store a version of the HTML in post_content
248
+ $post_parent_id = wp_is_post_revision( $post_id );
249
+ $layout_id = ( ! empty( $post_parent_id ) ) ? $post_parent_id : $post_id;
250
+
251
+ SiteOrigin_Panels_Post_Content_Filters::add_filters();
252
+ $GLOBALS[ 'SITEORIGIN_PANELS_POST_CONTENT_RENDER' ] = true;
253
+ $post_content = SiteOrigin_Panels::renderer()->render( $layout_id, false, $panels_data );
254
+ $post_css = SiteOrigin_Panels::renderer()->generate_css( $layout_id, $panels_data );
255
+ SiteOrigin_Panels_Post_Content_Filters::remove_filters();
256
+ unset( $GLOBALS[ 'SITEORIGIN_PANELS_POST_CONTENT_RENDER' ] );
257
+
258
+ // Update the post_content
259
+ $post->post_content = $post_content;
260
+ if( siteorigin_panels_setting( 'copy-styles' ) ) {
261
+ $post->post_content .= "\n\n";
262
+ $post->post_content .= '<style type="text/css" class="panels-style" data-panels-style-for-post="' . intval( $layout_id ) . '">';
263
+ $post->post_content .= '@import url(' . SiteOrigin_Panels::front_css_url() . '); ';
264
+ $post->post_content .= $post_css;
265
+ $post->post_content .= '</style>';
266
+ }
267
+ wp_update_post( $post );
268
+ }
269
+
270
+ } else {
271
+ // There are no widgets or rows, so delete the panels data
272
+ delete_post_meta( $post_id, 'panels_data' );
273
+ }
274
+
275
+ $this->in_save_post = false;
276
+ }
277
+
278
+ /**
279
+ * Enqueue the panels admin scripts
280
+ *
281
+ * @param string $prefix
282
+ * @param bool $force Should we force the enqueues
283
+ *
284
+ * @action admin_print_scripts-post-new.php
285
+ * @action admin_print_scripts-post.php
286
+ * @action admin_print_scripts-appearance_page_so_panels_home_page
287
+ */
288
+ function enqueue_admin_scripts( $prefix = '', $force = false ) {
289
+ $screen = get_current_screen();
290
+ if ( $force || self::is_admin() ) {
291
+ // Media is required for row styles
292
+ wp_enqueue_media();
293
+ wp_enqueue_script(
294
+ 'so-panels-admin',
295
+ siteorigin_panels_url( 'js/siteorigin-panels' . SITEORIGIN_PANELS_VERSION_SUFFIX . SITEORIGIN_PANELS_JS_SUFFIX . '.js' ),
296
+ array(
297
+ 'jquery',
298
+ 'jquery-ui-resizable',
299
+ 'jquery-ui-sortable',
300
+ 'jquery-ui-draggable',
301
+ 'underscore',
302
+ 'backbone',
303
+ 'plupload',
304
+ 'plupload-all'
305
+ ),
306
+ SITEORIGIN_PANELS_VERSION,
307
+ true
308
+ );
309
+ add_action( 'admin_footer', array( $this, 'js_templates' ) );
310
+
311
+ $widgets = $this->get_widgets();
312
+ $directory_enabled = get_user_meta( get_current_user_id(), 'so_panels_directory_enabled', true );
313
+
314
+ // This is the widget we'll use for default text
315
+ if( ! empty( $widgets[ 'SiteOrigin_Widget_Editor_Widget' ] ) ) $text_widget = 'SiteOrigin_Widget_Editor_Widget';
316
+ else if( ! empty( $widgets[ 'WP_Widget_Text' ] ) ) $text_widget = 'WP_Widget_Text';
317
+ else $text_widget = false;
318
+ $text_widget = apply_filters( 'siteorigin_panels_text_widget_class', $text_widget );
319
+
320
+ $user = wp_get_current_user();
321
+
322
+ $load_on_attach = siteorigin_panels_setting( 'load-on-attach' ) || isset( $_GET['siteorigin-page-builder'] );
323
+ wp_localize_script( 'so-panels-admin', 'panelsOptions', array(
324
+ 'user' => ! empty( $user ) ? $user->ID : 0,
325
+ 'ajaxurl' => wp_nonce_url( admin_url( 'admin-ajax.php' ), 'panels_action', '_panelsnonce' ),
326
+ 'widgets' => $widgets,
327
+ 'text_widget' => $text_widget,
328
+ 'widget_dialog_tabs' => apply_filters( 'siteorigin_panels_widget_dialog_tabs', array(
329
+ 0 => array(
330
+ 'title' => __( 'All Widgets', 'siteorigin-panels' ),
331
+ 'filter' => array(
332
+ 'installed' => true,
333
+ 'groups' => ''
334
+ )
335
+ )
336
+ ) ),
337
+ 'row_layouts' => apply_filters( 'siteorigin_panels_row_layouts', array() ),
338
+ 'directory_enabled' => ! empty( $directory_enabled ),
339
+ 'copy_content' => siteorigin_panels_setting( 'copy-content' ),
340
+ 'cache' => array(),
341
+ 'instant_open' => siteorigin_panels_setting( 'instant-open-widgets' ),
342
+
343
+ // Settings for the contextual menu
344
+ 'contextual' => array(
345
+ // Developers can change which widgets are displayed by default using this filter
346
+ 'default_widgets' => apply_filters( 'siteorigin_panels_contextual_default_widgets', array(
347
+ 'SiteOrigin_Widget_Editor_Widget',
348
+ 'SiteOrigin_Widget_Button_Widget',
349
+ 'SiteOrigin_Widget_Image_Widget',
350
+ 'SiteOrigin_Panels_Widgets_Layout',
351
+ ) )
352
+ ),
353
+
354
+ // General localization messages
355
+ 'loc' => array(
356
+ 'missing_widget' => array(
357
+ 'title' => __( 'Missing Widget', 'siteorigin-panels' ),
358
+ 'description' => __( "Page Builder doesn't know about this widget.", 'siteorigin-panels' ),
359
+ ),
360
+ 'time' => array(
361
+ // TRANSLATORS: Number of seconds since
362
+ 'seconds' => __( '%d seconds', 'siteorigin-panels' ),
363
+ // TRANSLATORS: Number of minutes since
364
+ 'minutes' => __( '%d minutes', 'siteorigin-panels' ),
365
+ // TRANSLATORS: Number of hours since
366
+ 'hours' => __( '%d hours', 'siteorigin-panels' ),
367
+
368
+ // TRANSLATORS: A single second since
369
+ 'second' => __( '%d second', 'siteorigin-panels' ),
370
+ // TRANSLATORS: A single minute since
371
+ 'minute' => __( '%d minute', 'siteorigin-panels' ),
372
+ // TRANSLATORS: A single hour since
373
+ 'hour' => __( '%d hour', 'siteorigin-panels' ),
374
+
375
+ // TRANSLATORS: Time ago - eg. "1 minute before".
376
+ 'ago' => __( '%s before', 'siteorigin-panels' ),
377
+ 'now' => __( 'Now', 'siteorigin-panels' ),
378
+ ),
379
+ 'history' => array(
380
+ // History messages
381
+ 'current' => __( 'Current', 'siteorigin-panels' ),
382
+ 'revert' => __( 'Original', 'siteorigin-panels' ),
383
+ 'restore' => __( 'Version restored', 'siteorigin-panels' ),
384
+ 'back_to_editor' => __( 'Converted to editor', 'siteorigin-panels' ),
385
+
386
+ // Widgets
387
+ // TRANSLATORS: Message displayed in the history when a widget is deleted
388
+ 'widget_deleted' => __( 'Widget deleted', 'siteorigin-panels' ),
389
+ // TRANSLATORS: Message displayed in the history when a widget is added
390
+ 'widget_added' => __( 'Widget added', 'siteorigin-panels' ),
391
+ // TRANSLATORS: Message displayed in the history when a widget is edited
392
+ 'widget_edited' => __( 'Widget edited', 'siteorigin-panels' ),
393
+ // TRANSLATORS: Message displayed in the history when a widget is duplicated
394
+ 'widget_duplicated' => __( 'Widget duplicated', 'siteorigin-panels' ),
395
+ // TRANSLATORS: Message displayed in the history when a widget position is changed
396
+ 'widget_moved' => __( 'Widget moved', 'siteorigin-panels' ),
397
+
398
+ // Rows
399
+ // TRANSLATORS: Message displayed in the history when a row is deleted
400
+ 'row_deleted' => __( 'Row deleted', 'siteorigin-panels' ),
401
+ // TRANSLATORS: Message displayed in the history when a row is added
402
+ 'row_added' => __( 'Row added', 'siteorigin-panels' ),
403
+ // TRANSLATORS: Message displayed in the history when a row is edited
404
+ 'row_edited' => __( 'Row edited', 'siteorigin-panels' ),
405
+ // TRANSLATORS: Message displayed in the history when a row position is changed
406
+ 'row_moved' => __( 'Row moved', 'siteorigin-panels' ),
407
+ // TRANSLATORS: Message displayed in the history when a row is duplicated
408
+ 'row_duplicated' => __( 'Row duplicated', 'siteorigin-panels' ),
409
+ // TRANSLATORS: Message displayed in the history when a row is pasted
410
+ 'row_pasted' => __( 'Row pasted', 'siteorigin-panels' ),
411
+
412
+ // Cells
413
+ 'cell_resized' => __( 'Cell resized', 'siteorigin-panels' ),
414
+
415
+ // Prebuilt
416
+ 'prebuilt_loaded' => __( 'Prebuilt layout loaded', 'siteorigin-panels' ),
417
+ ),
418
+
419
+ // general localization
420
+ 'prebuilt_loading' => __( 'Loading prebuilt layout', 'siteorigin-panels' ),
421
+ 'confirm_use_builder' => __( "Would you like to copy this editor's existing content to Page Builder?", 'siteorigin-panels' ),
422
+ 'confirm_stop_builder' => __( "Would you like to clear your Page Builder content and revert to using the standard visual editor?", 'siteorigin-panels' ),
423
+ // TRANSLATORS: This is the title for a widget called "Layout Builder"
424
+ 'layout_widget' => __( 'Layout Builder Widget', 'siteorigin-panels' ),
425
+ // TRANSLATORS: A standard confirmation message
426
+ 'dropdown_confirm' => __( 'Are you sure?', 'siteorigin-panels' ),
427
+ // TRANSLATORS: When a layout file is ready to be inserted. %s is the filename.
428
+ 'ready_to_insert' => __( '%s is ready to insert.', 'siteorigin-panels' ),
429
+
430
+ // Everything for the contextual menu
431
+ 'contextual' => array(
432
+ 'add_widget_below' => __( 'Add Widget Below', 'siteorigin-panels' ),
433
+ 'add_widget_cell' => __( 'Add Widget to Cell', 'siteorigin-panels' ),
434
+ 'search_widgets' => __( 'Search Widgets', 'siteorigin-panels' ),
435
+
436
+ 'add_row' => __( 'Add Row', 'siteorigin-panels' ),
437
+ 'column' => __( 'Column', 'siteorigin-panels' ),
438
+
439
+ 'cell_actions' => __( 'Cell Actions', 'siteorigin-panels' ),
440
+ 'cell_paste_widget' => __( 'Paste Widget', 'siteorigin-panels' ),
441
+
442
+ 'widget_actions' => __( 'Widget Actions', 'siteorigin-panels' ),
443
+ 'widget_edit' => __( 'Edit Widget', 'siteorigin-panels' ),
444
+ 'widget_duplicate' => __( 'Duplicate Widget', 'siteorigin-panels' ),
445
+ 'widget_delete' => __( 'Delete Widget', 'siteorigin-panels' ),
446
+ 'widget_copy' => __( 'Copy Widget', 'siteorigin-panels' ),
447
+ 'widget_paste' => __( 'Paste Widget Below', 'siteorigin-panels' ),
448
+
449
+ 'row_actions' => __( 'Row Actions', 'siteorigin-panels' ),
450
+ 'row_edit' => __( 'Edit Row', 'siteorigin-panels' ),
451
+ 'row_duplicate' => __( 'Duplicate Row', 'siteorigin-panels' ),
452
+ 'row_delete' => __( 'Delete Row', 'siteorigin-panels' ),
453
+ 'row_copy' => __( 'Copy Row', 'siteorigin-panels' ),
454
+ 'row_paste' => __( 'Paste Row', 'siteorigin-panels' ),
455
+ ),
456
+ 'draft' => __( 'Draft', 'siteorigin-panels' ),
457
+ 'untitled' => __( 'Untitled', 'siteorigin-panels' ),
458
+ 'row' => array(
459
+ 'add' => __( 'New Row', 'siteorigin-panels' ),
460
+ 'edit' => __( 'Row', 'siteorigin-panels' ),
461
+ ),
462
+ 'welcomeMessage' => array(
463
+ 'addingDisabled' => __( 'Hmmm... Adding layout elements is not enabled. Please check if Page Builder has been configured to allow adding elements.', 'siteorigin-panels' ),
464
+ 'oneEnabled' => __( 'Add a {{%= items[0] %}} to get started.', 'siteorigin-panels' ),
465
+ 'twoEnabled' => __( 'Add a {{%= items[0] %}} or {{%= items[1] %}} to get started.', 'siteorigin-panels' ),
466
+ 'threeEnabled' => __( 'Add a {{%= items[0] %}}, {{%= items[1] %}} or {{%= items[2] %}} to get started.', 'siteorigin-panels' ),
467
+ 'addWidgetButton' => "<a href='#' class='so-tool-button so-widget-add'>" . __( 'Widget', 'siteorigin-panels' ) . "</a>",
468
+ 'addRowButton' => "<a href='#' class='so-tool-button so-row-add'>" . __( 'Row', 'siteorigin-panels' ) . "</a>",
469
+ 'addPrebuiltButton' => "<a href='#' class='so-tool-button so-prebuilt-add'>" . __( 'Prebuilt Layout', 'siteorigin-panels' ) . "</a>",
470
+ 'docsMessage' => sprintf(
471
+ __( 'Read our %s if you need help.', 'siteorigin-panels' ),
472
+ "<a href='https://siteorigin.com/page-builder/documentation/' target='_blank' rel='noopener noreferrer'>" . __( 'documentation', 'siteorigin-panels' ) . "</a>"
473
+ ),
474
+ ),
475
+ ),
476
+ 'plupload' => array(
477
+ 'max_file_size' => wp_max_upload_size() . 'b',
478
+ 'url' => wp_nonce_url( admin_url( 'admin-ajax.php' ), 'panels_action', '_panelsnonce' ),
479
+ 'flash_swf_url' => includes_url( 'js/plupload/plupload.flash.swf' ),
480
+ 'silverlight_xap_url' => includes_url( 'js/plupload/plupload.silverlight.xap' ),
481
+ 'filter_title' => __( 'Page Builder layouts', 'siteorigin-panels' ),
482
+ 'error_message' => __( 'Error uploading or importing file.', 'siteorigin-panels' ),
483
+ ),
484
+ 'wpColorPickerOptions' => apply_filters( 'siteorigin_panels_wpcolorpicker_options', array() ),
485
+ 'prebuiltDefaultScreenshot' => siteorigin_panels_url( 'css/images/prebuilt-default.png' ),
486
+ 'loadOnAttach' => $load_on_attach ,
487
+ 'siteoriginWidgetRegex' => str_replace( '*+', '*', get_shortcode_regex( array( 'siteorigin_widget' ) ) ),
488
+ 'forms' => array(
489
+ 'loadingFailed' => __( 'Unknown error. Failed to load the form. Please check your internet connection, contact your web site administrator, or try again later.', 'siteorigin-panels' ),
490
+ )
491
+ ) );
492
+
493
+ $js_widgets = array();
494
+ if ( $screen->base != 'widgets' ) {
495
+ // Render all the widget forms. A lot of widgets use this as a chance to enqueue their scripts
496
+ $original_post = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : null; // Make sure widgets don't change the global post.
497
+ global $wp_widget_factory;
498
+ foreach ( $wp_widget_factory->widgets as $widget_obj ) {
499
+ ob_start();
500
+ $return = $widget_obj->form( array() );
501
+ // These are the new widgets in WP 4.8 which are largely JS based. They only enqueue their own
502
+ // scripts on the 'widgets' screen.
503
+ if ( $this->is_core_js_widget( $widget_obj ) && method_exists( $widget_obj, 'enqueue_admin_scripts' ) ) {
504
+ $widget_obj->enqueue_admin_scripts();
505
+ }
506
+ do_action_ref_array( 'in_widget_form', array( &$widget_obj, &$return, array() ) );
507
+ ob_end_clean();
508
+
509
+ // Need to render templates for new WP 4.8 widgets when not on the 'widgets' screen or in the customizer.
510
+ if ( $this->is_core_js_widget( $widget_obj ) ) {
511
+ $js_widgets[] = $widget_obj;
512
+ }
513
+ }
514
+ $GLOBALS['post'] = $original_post;
515
+ }
516
+
517
+ // This gives panels a chance to enqueue scripts too, without having to check the screen ID.
518
+ if ( $screen->base != 'widgets' && $screen->base != 'customize' ) {
519
+ foreach ( $js_widgets as $js_widget ) {
520
+ $js_widget->render_control_template_scripts();
521
+ }
522
+ do_action( 'siteorigin_panel_enqueue_admin_scripts' );
523
+ do_action( 'sidebar_admin_setup' );
524
+ }
525
+ }
526
+ }
527
+
528
+ public function enqueue_yoast_compat(){
529
+ if( self::is_admin() && defined( 'WPSEO_FILE' ) && wp_script_is( 'yoast-seo-metabox' ) ) {
530
+ wp_enqueue_script(
531
+ 'so-panels-yoast-compat',
532
+ siteorigin_panels_url( 'js/yoast-compat' . SITEORIGIN_PANELS_JS_SUFFIX . '.js' ),
533
+ array('jquery', 'yoast-seo-metabox' ),
534
+ SITEORIGIN_PANELS_VERSION,
535
+ true
536
+ );
537
+ }
538
+ }
539
+
540
+ /**
541
+ * Enqueue the admin panel styles
542
+ *
543
+ * @param string $prefix
544
+ * @param bool $force Should we force the enqueue
545
+ *
546
+ * @action admin_print_styles-post-new.php
547
+ * @action admin_print_styles-post.php
548
+ */
549
+ function enqueue_admin_styles( $prefix = '', $force = false ) {
550
+ if ( $force || self::is_admin() ) {
551
+ wp_enqueue_style(
552
+ 'so-panels-admin',
553
+ siteorigin_panels_url( 'css/admin' . SITEORIGIN_PANELS_CSS_SUFFIX . '.css' ),
554
+ array( 'wp-color-picker' ),
555
+ SITEORIGIN_PANELS_VERSION
556
+ );
557
+ do_action( 'siteorigin_panel_enqueue_admin_styles' );
558
+ }
559
+ }
560
+
561
+ /**
562
+ * Add a help tab to pages that include a Page Builder interface.
563
+ *
564
+ * @param $prefix
565
+ */
566
+ function add_help_tab( $prefix ) {
567
+ $screen = get_current_screen();
568
+ if (
569
+ ( $screen->base == 'post' && ( in_array( $screen->id, siteorigin_panels_setting( 'post-types' ) ) || $screen->id == '' ) )
570
+ || ( $screen->id == 'appearance_page_so_panels_home_page' )
571
+ ) {
572
+ $screen->add_help_tab( array(
573
+ 'id' => 'panels-help-tab', //unique id for the tab
574
+ 'title' => __( 'Page Builder', 'siteorigin-panels' ), //unique visible title for the tab
575
+ 'callback' => array( $this, 'help_tab_content' )
576
+ ) );
577
+ }
578
+ }
579
+
580
+ /**
581
+ * Display the content for the help tab.
582
+ */
583
+ function help_tab_content() {
584
+ include plugin_dir_path( __FILE__ ) . '../tpl/help.php';
585
+ }
586
+
587
+ /**
588
+ * Get the Page Builder data for the current admin page.
589
+ *
590
+ * @return array
591
+ */
592
+ function get_current_admin_panels_data() {
593
+ $screen = get_current_screen();
594
+
595
+ // Localize the panels with the panels data
596
+ if ( $screen->base == 'appearance_page_so_panels_home_page' ) {
597
+ $home_page_id = get_option( 'page_on_front' );
598
+ if ( empty( $home_page_id ) ) {
599
+ $home_page_id = get_option( 'siteorigin_panels_home_page_id' );
600
+ }
601
+
602
+ $panels_data = ! empty( $home_page_id ) ? get_post_meta( $home_page_id, 'panels_data', true ) : null;
603
+
604
+ if ( is_null( $panels_data ) ) {
605
+ // Load the default layout
606
+ $layouts = apply_filters( 'siteorigin_panels_prebuilt_layouts', array() );
607
+
608
+ $home_name = siteorigin_panels_setting( 'home-page-default' ) ? siteorigin_panels_setting( 'home-page-default' ) : 'home';
609
+ $panels_data = ! empty( $layouts[ $home_name ] ) ? $layouts[ $home_name ] : current( $layouts );
610
+ } elseif ( empty( $panels_data ) ) {
611
+ // The current page_on_front isn't using page builder
612
+ return false;
613
+ }
614
+
615
+ $panels_data = apply_filters( 'siteorigin_panels_data', $panels_data, 'home' );
616
+ } else {
617
+ global $post;
618
+ if ( ! empty( $post ) ) {
619
+ $panels_data = get_post_meta( $post->ID, 'panels_data', true );
620
+ $panels_data = apply_filters( 'siteorigin_panels_data', $panels_data, $post->ID );
621
+ }
622
+ }
623
+
624
+ if ( empty( $panels_data ) ) {
625
+ $panels_data = array();
626
+ }
627
+
628
+ return $panels_data;
629
+ }
630
+
631
+ /**
632
+ * Save home page
633
+ */
634
+ function save_home_page() {
635
+ if ( ! isset( $_POST['_sopanels_home_nonce'] ) || ! wp_verify_nonce( $_POST['_sopanels_home_nonce'], 'save' ) ) {
636
+ return;
637
+ }
638
+ if ( ! current_user_can( 'edit_theme_options' ) ) {
639
+ return;
640
+ }
641
+ if ( ! isset( $_POST['panels_data'] ) ) {
642
+ return;
643
+ }
644
+
645
+ // Check that the home page ID is set and the home page exists
646
+ $page_id = get_option( 'page_on_front' );
647
+ if ( empty( $page_id ) ) {
648
+ $page_id = get_option( 'siteorigin_panels_home_page_id' );
649
+ }
650
+
651
+ $post_content = wp_unslash( $_POST['post_content'] );
652
+
653
+ if ( ! $page_id || get_post_meta( $page_id, 'panels_data', true ) == '' ) {
654
+ // Lets create a new page
655
+ $page_id = wp_insert_post( array(
656
+ // TRANSLATORS: This is the default name given to a user's home page
657
+ 'post_title' => __( 'Home Page', 'siteorigin-panels' ),
658
+ 'post_status' => ! empty( $_POST['siteorigin_panels_home_enabled'] ) ? 'publish' : 'draft',
659
+ 'post_type' => 'page',
660
+ 'post_content' => $post_content,
661
+ 'comment_status' => 'closed',
662
+ ) );
663
+ update_option( 'page_on_front', $page_id );
664
+ update_option( 'siteorigin_panels_home_page_id', $page_id );
665
+
666
+ // Action triggered when creating a new home page through the custom home page interface
667
+ do_action( 'siteorigin_panels_create_home_page', $page_id );
668
+ } else {
669
+ // `wp_insert_post` does it's own sanitization, but it seems `wp_update_post` doesn't.
670
+ $post_content = sanitize_post_field( 'post_content', $post_content, $page_id, 'db' );
671
+
672
+ // Update the post with changed content to save revision if necessary.
673
+ wp_update_post( array( 'ID' => $page_id, 'post_content' => $post_content ) );
674
+ }
675
+
676
+ $page = get_post( $page_id );
677
+
678
+ // Save the updated page data
679
+ $old_panels_data = get_post_meta( $page_id, 'panels_data', true );
680
+ $panels_data = json_decode( wp_unslash( $_POST['panels_data'] ), true );
681
+ $panels_data['widgets'] = $this->process_raw_widgets(
682
+ $panels_data['widgets'],
683
+ ! empty( $old_panels_data['widgets'] ) ? $old_panels_data['widgets'] : false,
684
+ false
685
+ );
686
+
687
+ if ( siteorigin_panels_setting( 'sidebars-emulator' ) ) {
688
+ $sidebars_emulator = SiteOrigin_Panels_Sidebars_Emulator::single();
689
+ $panels_data['widgets'] = $sidebars_emulator->generate_sidebar_widget_ids( $panels_data['widgets'], $page_id );
690
+ }
691
+
692
+ $panels_data = SiteOrigin_Panels_Styles_Admin::single()->sanitize_all( $panels_data );
693
+ $panels_data = apply_filters( 'siteorigin_panels_data_pre_save', $panels_data, $page, $page_id );
694
+
695
+ update_post_meta( $page_id, 'panels_data', map_deep( $panels_data, array( 'SiteOrigin_Panels_Admin', 'double_slash_string' ) ) );
696
+
697
+ $template = get_post_meta( $page_id, '_wp_page_template', true );
698
+ $home_template = siteorigin_panels_setting( 'home-template' );
699
+ if ( ( $template == '' || $template == 'default' ) && ! empty( $home_template ) ) {
700
+ // Set the home page template
701
+ update_post_meta( $page_id, '_wp_page_template', $home_template );
702
+ }
703
+
704
+ if ( ! empty( $_POST['siteorigin_panels_home_enabled'] ) ) {
705
+ update_option( 'show_on_front', 'page' );
706
+ update_option( 'page_on_front', $page_id );
707
+ update_option( 'siteorigin_panels_home_page_id', $page_id );
708
+ wp_publish_post( $page_id );
709
+ } else {
710
+ // We're disabling this home page
711
+ update_option( 'show_on_front', 'posts' );
712
+
713
+ // Change the post status to draft
714
+ $post = get_post( $page_id );
715
+ if ( $post->post_status != 'draft' ) {
716
+ global $wpdb;
717
+
718
+ $wpdb->update( $wpdb->posts, array( 'post_status' => 'draft' ), array( 'ID' => $post->ID ) );
719
+ clean_post_cache( $post->ID );
720
+
721
+ $old_status = $post->post_status;
722
+ $post->post_status = 'draft';
723
+ wp_transition_post_status( 'draft', $old_status, $post );
724
+
725
+ do_action( 'edit_post', $post->ID, $post );
726
+ do_action( "save_post_{$post->post_type}", $post->ID, $post, true );
727
+ do_action( 'save_post', $post->ID, $post, true );
728
+ do_action( 'wp_insert_post', $post->ID, $post, true );
729
+ }
730
+ }
731
+ }
732
+
733
+ /**
734
+ * After the theme is switched, change the template on the home page if the theme supports home page functionality.
735
+ */
736
+ function update_home_on_theme_change() {
737
+ $page_id = get_option( 'page_on_front' );
738
+ if ( empty( $page_id ) ) {
739
+ $page_id = get_option( 'siteorigin_panels_home_page_id' );
740
+ }
741
+
742
+ if ( siteorigin_panels_setting( 'home-page' ) && siteorigin_panels_setting( 'home-template' ) && $page_id && get_post_meta( $page_id, 'panels_data', true ) !== '' ) {
743
+ // Lets update the home page to use the home template that this theme supports
744
+ update_post_meta( $page_id, '_wp_page_template', siteorigin_panels_setting( 'home-template' ) );
745
+ }
746
+ }
747
+
748
+ /**
749
+ * @return array|mixed|void
750
+ */
751
+ function get_widgets() {
752
+ global $wp_widget_factory;
753
+ $widgets = array();
754
+ foreach ( $wp_widget_factory->widgets as $widget_obj ) {
755
+ $class = get_class( $widget_obj );
756
+ $widgets[ $class ] = array(
757
+ 'class' => $class,
758
+ 'title' => ! empty( $widget_obj->name ) ? $widget_obj->name : __( 'Untitled Widget', 'siteorigin-panels' ),
759
+ 'description' => ! empty( $widget_obj->widget_options['description'] ) ? $widget_obj->widget_options['description'] : '',
760
+ 'installed' => true,
761
+ 'groups' => array(),
762
+ );
763
+
764
+ // Get Page Builder specific widget options
765
+ if ( isset( $widget_obj->widget_options['panels_title'] ) ) {
766
+ $widgets[ $class ]['panels_title'] = $widget_obj->widget_options['panels_title'];
767
+ }
768
+ if ( isset( $widget_obj->widget_options['panels_groups'] ) ) {
769
+ $widgets[ $class ]['groups'] = $widget_obj->widget_options['panels_groups'];
770
+ }
771
+ if ( isset( $widget_obj->widget_options['panels_icon'] ) ) {
772
+ $widgets[ $class ]['icon'] = $widget_obj->widget_options['panels_icon'];
773
+ }
774
+
775
+ }
776
+
777
+ // Other plugins can manipulate the list of widgets. Possibly to add recommended widgets
778
+ $widgets = apply_filters( 'siteorigin_panels_widgets', $widgets );
779
+
780
+ // Exclude these temporarily, as they won't work until we have a reliable way to enqueue their admin form scripts.
781
+ $to_exclude = array(
782
+ 'Jetpack_Gallery_Widget',
783
+ 'WPCOM_Widget_GooglePlus_Badge',
784
+ 'Jetpack_Widget_Social_Icons',
785
+ 'Jetpack_Twitter_Timeline_Widget'
786
+ );
787
+
788
+ foreach ( $to_exclude as $widget_class ) {
789
+ if ( in_array( $widget_class, $widgets ) ) {
790
+ unset( $widgets[ $widget_class ] );
791
+ }
792
+ }
793
+
794
+ // Sort the widgets alphabetically
795
+ uasort( $widgets, array( $this, 'widgets_sorter' ) );
796
+
797
+ return $widgets;
798
+ }
799
+
800
+ /**
801
+ * Sorts widgets for get_widgets function by title
802
+ *
803
+ * @param $a
804
+ * @param $b
805
+ *
806
+ * @return int
807
+ */
808
+ function widgets_sorter( $a, $b ) {
809
+ if ( empty( $a['title'] ) ) {
810
+ return - 1;
811
+ }
812
+ if ( empty( $b['title'] ) ) {
813
+ return 1;
814
+ }
815
+
816
+ return $a['title'] > $b['title'] ? 1 : - 1;
817
+ }
818
+
819
+ /**
820
+ * Process raw widgets that have come from the Page Builder front end.
821
+ *
822
+ * @param array $widgets An array of widgets from panels_data.
823
+ * @param array $old_widgets
824
+ * @param bool $escape_classes Should the class names be escaped.
825
+ * @param bool $force
826
+ *
827
+ * @return array
828
+ */
829
+ function process_raw_widgets( $widgets, $old_widgets = array(), $escape_classes = false, $force = false ) {
830
+ if ( empty( $widgets ) || ! is_array( $widgets ) ) {
831
+ return array();
832
+ }
833
+
834
+ $old_widgets_by_id = array();
835
+ if( ! empty( $old_widgets ) ) {
836
+ foreach( $old_widgets as $widget ) {
837
+ if( ! empty( $widget[ 'panels_info' ][ 'widget_id' ] ) ) {
838
+ $old_widgets_by_id[ $widget[ 'panels_info' ][ 'widget_id' ] ] = $widget;
839
+ unset( $old_widgets_by_id[ $widget[ 'panels_info' ][ 'widget_id' ] ][ 'panels_info' ] );
840
+ }
841
+ }
842
+ }
843
+
844
+ foreach( $widgets as $i => & $widget ) {
845
+ if ( ! is_array( $widget ) ) {
846
+ continue;
847
+ }
848
+
849
+ if ( is_array( $widget ) ) {
850
+ $info = (array) ( is_array( $widget['panels_info'] ) ? $widget['panels_info'] : $widget['info'] );
851
+ } else {
852
+ $info = array();
853
+ }
854
+ unset( $widget['info'] );
855
+
856
+ $info[ 'class' ] = apply_filters( 'siteorigin_panels_widget_class', $info[ 'class' ] );
857
+
858
+ if ( ! empty( $info['raw'] ) || $force ) {
859
+ $the_widget = SiteOrigin_Panels::get_widget_instance( $info['class'] );
860
+ if ( ! empty( $the_widget ) &&
861
+ method_exists( $the_widget, 'update' ) ) {
862
+
863
+ if(
864
+ ! empty( $old_widgets_by_id ) &&
865
+ ! empty( $widget[ 'panels_info' ][ 'widget_id' ] ) &&
866
+ ! empty( $old_widgets_by_id[ $widget[ 'panels_info' ][ 'widget_id' ] ] )
867
+ ){
868
+ $old_widget = $old_widgets_by_id[ $widget[ 'panels_info' ][ 'widget_id' ] ];
869
+ }
870
+ else {
871
+ $old_widget = $widget;
872
+ }
873
+
874
+ /** @var WP_Widget $the_widget */
875
+ $the_widget = SiteOrigin_Panels::get_widget_instance( $info['class'] );
876
+ $instance = $the_widget->update( $widget, $old_widget );
877
+ $instance = apply_filters( 'widget_update_callback', $instance, $widget, $old_widget, $the_widget );
878
+
879
+ $widget = $instance;
880
+
881
+ unset( $info['raw'] );
882
+ }
883
+ }
884
+
885
+ if( $escape_classes ) {
886
+ // Escaping for namespaced widgets
887
+ $info[ 'class' ] = preg_replace( '/\\\\+/', '\\\\\\\\', $info['class'] );
888
+ }
889
+
890
+ $widget['panels_info'] = $info;
891
+ }
892
+
893
+ return $widgets;
894
+ }
895
+
896
+ /**
897
+ * Add all the footer JS templates.
898
+ */
899
+ function js_templates() {
900
+ include plugin_dir_path( __FILE__ ) . '../tpl/js-templates.php';
901
+ }
902
+
903
+ /**
904
+ * Render a widget form with all the Page Builder specific fields
905
+ *
906
+ * @param string $widget_class The class of the widget
907
+ * @param array $instance Widget values
908
+ * @param bool $raw
909
+ * @param string $widget_number
910
+ *
911
+ * @return mixed|string The form
912
+ */
913
+ function render_form( $widget_class, $instance = array(), $raw = false, $widget_number = '{$id}' ) {
914
+
915
+ $the_widget = SiteOrigin_Panels::get_widget_instance( $widget_class );
916
+ // This is a chance for plugins to replace missing widgets
917
+ $the_widget = apply_filters( 'siteorigin_panels_widget_object', $the_widget, $widget_class );
918
+
919
+ if ( empty( $the_widget ) || ! is_a( $the_widget, 'WP_Widget' ) ) {
920
+ $widgets = $this->get_widgets();
921
+
922
+ if ( ! empty( $widgets[ $widget_class ] ) && ! empty( $widgets[ $widget_class ]['plugin'] ) ) {
923
+ // We know about this widget, show a form about installing it.
924
+ $install_url = siteorigin_panels_plugin_activation_install_url( $widgets[ $widget_class ]['plugin']['slug'], $widgets[ $widget_class ]['plugin']['name'] );
925
+ $form =
926
+ '<div class="panels-missing-widget-form">' .
927
+ '<p>' .
928
+ preg_replace(
929
+ array(
930
+ '/1\{ *(.*?) *\}/',
931
+ '/2\{ *(.*?) *\}/',
932
+ ),
933
+ array(
934
+ '<a href="' . $install_url . '" target="_blank" rel="noopener noreferrer">$1</a>',
935
+ '<strong>$1</strong>'
936
+ ),
937
+ sprintf(
938
+ __( 'You need to install 1{%1$s} to use the widget 2{%2$s}.', 'siteorigin-panels' ),
939
+ $widgets[ $widget_class ]['plugin']['name'],
940
+ $widget_class
941
+ )
942
+ ) .
943
+ '</p>' .
944
+ '<p>' . __( "Save and reload this page to start using the widget after you've installed it.", 'siteorigin-panels' ) . '</p>' .
945
+ '</div>';
946
+ } else {
947
+ // This widget is missing, so show a missing widgets form.
948
+ $form =
949
+ '<div class="panels-missing-widget-form"><p>' .
950
+ preg_replace(
951
+ array(
952
+ '/1\{ *(.*?) *\}/',
953
+ '/2\{ *(.*?) *\}/',
954
+ ),
955
+ array(
956
+ '<strong>$1</strong>',
957
+ '<a href="https://siteorigin.com/thread/" target="_blank" rel="noopener noreferrer">$1</a>'
958
+ ),
959
+ sprintf(
960
+ __( 'The widget 1{%1$s} is not available. Please try locate and install the missing plugin. Post on the 2{support forums} if you need help.', 'siteorigin-panels' ),
961
+ esc_html( $widget_class )
962
+ )
963
+ ) .
964
+ '</p></div>';
965
+ }
966
+
967
+ // Allow other themes and plugins to change the missing widget form
968
+ return apply_filters( 'siteorigin_panels_missing_widget_form', $form, $widget_class, $instance );
969
+ }
970
+
971
+ if ( $raw ) {
972
+ $instance = $the_widget->update( $instance, $instance );
973
+ }
974
+
975
+ $the_widget->id = 'temp';
976
+ $the_widget->number = $widget_number;
977
+
978
+ ob_start();
979
+ if ( $this->is_core_js_widget( $the_widget ) ) {
980
+ ?><div class="widget-content"><?php
981
+ }
982
+ $return = $the_widget->form( $instance );
983
+ do_action_ref_array( 'in_widget_form', array( &$the_widget, &$return, $instance ) );
984
+ if ( $this->is_core_js_widget( $the_widget ) ) {
985
+ ?>
986
+ </div>
987
+ <input type="hidden" name="id_base" class="id_base" value="<?php echo esc_attr( $the_widget->id_base ); ?>" />
988
+ <?php
989
+ }
990
+ $form = ob_get_clean();
991
+
992
+ // Convert the widget field naming into ones that Page Builder uses
993
+ $exp = preg_quote( $the_widget->get_field_name( '____' ) );
994
+ $exp = str_replace( '____', '(.*?)', $exp );
995
+ $form = preg_replace( '/' . $exp . '/', 'widgets[' . preg_replace( '/\$(\d)/', '\\\$$1', $widget_number ) . '][$1]', $form );
996
+
997
+ $form = apply_filters( 'siteorigin_panels_widget_form', $form, $widget_class, $instance );
998
+
999
+ // Add all the information fields
1000
+ return $form;
1001
+ }
1002
+
1003
+ /**
1004
+ * Checks whether a widget is considered to be a JS widget. I.e. it needs to have scripts and/or styles enqueued for
1005
+ * it's admin form to work.
1006
+ *
1007
+ * Can remove the whitelist of core widgets when all widgets are following a similar pattern.
1008
+ *
1009
+ * @param $widget The widget to be tested.
1010
+ *
1011
+ * @return bool Whether or not the widget is considered a JS widget.
1012
+ */
1013
+ function is_core_js_widget( $widget ) {
1014
+ $js_widgets = array(
1015
+ 'WP_Widget_Custom_HTML',
1016
+ 'WP_Widget_Media_Audio',
1017
+ 'WP_Widget_Media_Gallery',
1018
+ 'WP_Widget_Media_Image',
1019
+ 'WP_Widget_Media_Video',
1020
+ 'WP_Widget_Text',
1021
+ );
1022
+
1023
+ $is_js_widget = in_array( get_class( $widget ), $js_widgets ) &&
1024
+ // Need to check this for `WP_Widget_Text` which was not a JS widget before 4.8
1025
+ method_exists( $widget, 'render_control_template_scripts' );
1026
+
1027
+ return $is_js_widget;
1028
+ }
1029
+
1030
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1031
+ // ADMIN AJAX ACTIONS
1032
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1033
+
1034
+ /**
1035
+ * Get builder content based on the submitted panels_data.
1036
+ */
1037
+ function action_builder_content() {
1038
+ header( 'content-type: text/html' );
1039
+
1040
+ if ( ! current_user_can( 'edit_post', $_POST['post_id'] ) ) {
1041
+ wp_die();
1042
+ }
1043
+
1044
+ if ( empty( $_POST['post_id'] ) || empty( $_POST['panels_data'] ) ) {
1045
+ echo '';
1046
+ wp_die();
1047
+ }
1048
+
1049
+ // echo the content
1050
+ $old_panels_data = get_post_meta( $_POST['post_id'], 'panels_data', true );
1051
+ $panels_data = json_decode( wp_unslash( $_POST['panels_data'] ), true );
1052
+ $panels_data['widgets'] = $this->process_raw_widgets(
1053
+ $panels_data['widgets'],
1054
+ ! empty( $old_panels_data['widgets'] ) ? $old_panels_data['widgets'] : false,
1055
+ false
1056
+ );
1057
+ $panels_data = SiteOrigin_Panels_Styles_Admin::single()->sanitize_all( $panels_data );
1058
+
1059
+ // Create a version of the builder data for post content
1060
+ SiteOrigin_Panels_Post_Content_Filters::add_filters();
1061
+ $GLOBALS[ 'SITEORIGIN_PANELS_POST_CONTENT_RENDER' ] = true;
1062
+ echo SiteOrigin_Panels::renderer()->render( intval( $_POST['post_id'] ), false, $panels_data );
1063
+ SiteOrigin_Panels_Post_Content_Filters::remove_filters();
1064
+ unset( $GLOBALS[ 'SITEORIGIN_PANELS_POST_CONTENT_RENDER' ] );
1065
+
1066
+ wp_die();
1067
+ }
1068
+
1069
+ /**
1070
+ * Display a widget form with the provided data
1071
+ */
1072
+ function action_widget_form() {
1073
+ if ( empty( $_REQUEST['_panelsnonce'] ) || ! wp_verify_nonce( $_REQUEST['_panelsnonce'], 'panels_action' ) ) {
1074
+ wp_die(
1075
+ __( 'The supplied nonce is invalid.', 'siteorigin-panels' ),
1076
+ __( 'Invalid nonce.', 'siteorigin-panels' ),
1077
+ 403
1078
+ );
1079
+ }
1080
+ if ( empty( $_REQUEST['widget'] ) ) {
1081
+ wp_die(
1082
+ __( 'Please specify the type of widget form to be rendered.', 'siteorigin-panels' ),
1083
+ __( 'Missing widget type.', 'siteorigin-panels' ),
1084
+ 400
1085
+ );
1086
+ }
1087
+
1088
+ $request = array_map( 'stripslashes_deep', $_REQUEST );
1089
+
1090
+ $widget_class = $request['widget'];
1091
+ $widget_class = apply_filters( 'siteorigin_panels_widget_class', $widget_class );
1092
+ $instance = ! empty( $request['instance'] ) ? json_decode( $request['instance'], true ) : array();
1093
+
1094
+ $form = $this->render_form( $widget_class, $instance, $_REQUEST['raw'] == 'true' );
1095
+ $form = apply_filters( 'siteorigin_panels_ajax_widget_form', $form, $widget_class, $instance );
1096
+
1097
+ echo $form;
1098
+ wp_die();
1099
+ }
1100
+
1101
+ /**
1102
+ * Preview in the live editor when there is no public view of the item
1103
+ */
1104
+ function action_live_editor_preview() {
1105
+ if ( empty( $_REQUEST['_panelsnonce'] ) || ! wp_verify_nonce( $_REQUEST['_panelsnonce'], 'live-editor-preview' ) ) {
1106
+ wp_die();
1107
+ }
1108
+
1109
+ include plugin_dir_path( __FILE__ ) . '../tpl/live-editor-preview.php';
1110
+
1111
+ exit();
1112
+ }
1113
+
1114
+ /**
1115
+ * Preview in the block editor.
1116
+ */
1117
+ public function layout_block_preview() {
1118
+
1119
+ if ( empty( $_REQUEST['_panelsnonce'] ) || ! wp_verify_nonce( $_REQUEST['_panelsnonce'], 'layout-block-preview' ) ) {
1120
+ wp_die();
1121
+ }
1122
+
1123
+ $panels_data = json_decode( wp_unslash( $_POST['panelsData'] ), true );
1124
+ $builder_id = 'gbp' . uniqid();
1125
+ $panels_data['widgets'] = SiteOrigin_Panels_Admin::single()->process_raw_widgets( $panels_data['widgets'], false, true, true );
1126
+ $panels_data = SiteOrigin_Panels_Styles_Admin::single()->sanitize_all( $panels_data );
1127
+ $sowb_active = class_exists( 'SiteOrigin_Widgets_Bundle' );
1128
+ if ( $sowb_active ) {
1129
+ // We need this to get our widgets bundle to add it's styles inline for previews.
1130
+ add_filter( 'siteorigin_widgets_is_preview', '__return_true' );
1131
+ }
1132
+ $rendered_layout = SiteOrigin_Panels::renderer()->render( $builder_id, true, $panels_data, $layout_data, true );
1133
+
1134
+ // Need to explicitly call `siteorigin_widget_print_styles` because Gutenberg previews don't render a full version of the front end,
1135
+ // so neither the `wp_head` nor the `wp_footer` actions are called, which usually trigger `siteorigin_widget_print_styles`.
1136
+ if ( $sowb_active ) {
1137
+ ob_start();
1138
+ siteorigin_widget_print_styles();
1139
+ $rendered_layout .= ob_get_clean();
1140
+ }
1141
+
1142
+ echo $rendered_layout;
1143
+ wp_die();
1144
+ }
1145
+
1146
+ public function layout_block_sanitize() {
1147
+
1148
+ if ( empty( $_REQUEST['_panelsnonce'] ) || ! wp_verify_nonce( $_REQUEST['_panelsnonce'], 'layout-block-sanitize' ) ) {
1149
+ wp_die();
1150
+ }
1151
+
1152
+ $panels_data = json_decode( wp_unslash( $_POST['panelsData'] ), true );
1153
+ $panels_data['widgets'] = SiteOrigin_Panels_Admin::single()->process_raw_widgets( $panels_data['widgets'], false, true, true );
1154
+ $panels_data = SiteOrigin_Panels_Styles_Admin::single()->sanitize_all( $panels_data );
1155
+
1156
+ wp_send_json( $panels_data );
1157
+ }
1158
+
1159
+ /**
1160
+ * Add a column that indicates if a column is powered by Page Builder
1161
+ *
1162
+ * @param $columns
1163
+ *
1164
+ * @return array
1165
+ */
1166
+ function add_custom_column( $columns ){
1167
+ $index = array_search( 'comments', array_keys( $columns ) );
1168
+
1169
+ if( empty( $index ) ) {
1170
+ $columns = array_merge(
1171
+ $columns,
1172
+ array( 'panels' => __( 'Page Builder', 'siteorigin-panels' ) )
1173
+ );
1174
+ }
1175
+ else {
1176
+ $columns = array_slice( $columns, 0, $index, true ) +
1177
+ array( 'panels' => __( 'Page Builder', 'siteorigin-panels' ) ) +
1178
+ array_slice( $columns, $index, count( $columns ) - 1, true );
1179
+ }
1180
+
1181
+ return $columns;
1182
+ }
1183
+
1184
+ function display_custom_column( $column, $post_id ){
1185
+ if( $column != 'panels' ) return;
1186
+
1187
+ $panels_data = get_post_meta( $post_id, 'panels_data', true );
1188
+ if( ! empty( $panels_data['widgets'] ) ) {
1189
+ $widgets_count = count( $panels_data['widgets'] );
1190
+ printf( _n( '%s Widget', '%s Widgets', $widgets_count, 'siteorigin-panels' ), $widgets_count );
1191
+ }
1192
+ else {
1193
+ echo '—';
1194
+ }
1195
+ }
1196
+
1197
+ public function footer_column_css(){
1198
+ if( siteorigin_panels_setting( 'admin-widget-count' ) ) {
1199
+ $screen = get_current_screen();
1200
+ $post_types = siteorigin_panels_setting( 'post-types' );
1201
+
1202
+ if(
1203
+ $screen->base == 'edit' &&
1204
+ is_array( $post_types ) &&
1205
+ in_array( $screen->post_type, $post_types )
1206
+ ){
1207
+ ?><style type="text/css">.column-panels{ width: 10% }</style><?php
1208
+ }
1209
+ }
1210
+ }
1211
+
1212
+ /**
1213
+ * Add double slashes to strings
1214
+ *
1215
+ * @param $value
1216
+ *
1217
+ * @return string
1218
+ */
1219
+ public static function double_slash_string( $value ){
1220
+ return is_string( $value ) ? addcslashes( $value, '\\' ) : $value;
1221
+ }
1222
+
1223
+ public function get_layout_directories(){
1224
+
1225
+ }
1226
+
1227
+ /**
1228
+ * Display links for various SiteOrigin addons
1229
+ */
1230
+ public static function display_footer_premium_link(){
1231
+ $links = array(
1232
+ array(
1233
+ 'text' => __('Get a lightbox addon for SiteOrigin widgets', 'siteorigin-panels'),
1234
+ 'url' => SiteOrigin_Panels::premium_url('plugin/lightbox')
1235
+ ),
1236
+ array(
1237
+ 'text' => __('Get the row, cell and widget animations addon', 'siteorigin-panels'),
1238
+ 'url' => SiteOrigin_Panels::premium_url('plugin/lightbox')
1239
+ ),
1240
+ array(
1241
+ 'text' => __('Get premium email support for SiteOrigin Page Builder', 'siteorigin-panels'),
1242
+ 'url' => SiteOrigin_Panels::premium_url()
1243
+ ),
1244
+ );
1245
+ $link = $links[array_rand($links)];
1246
+
1247
+ ?>
1248
+ <a href="<?php echo esc_url( $link['url'] ) ?>" target="_blank" rel='noopener noreferrer'>
1249
+ <?php echo esc_html( $link['text'] ) ?>.
1250
+ </a>
1251
+ <?php
1252
+ }
1253
+
1254
+ public function admin_notices() {
1255
+ global $typenow, $pagenow;
1256
+ $is_new = $pagenow == 'post-new.php';
1257
+ $post_types = siteorigin_panels_setting( 'post-types' );
1258
+ $is_panels_type = in_array( $typenow, $post_types );
1259
+ $use_classic = siteorigin_panels_setting( 'use-classic' );
1260
+ $show_classic_admin_notice = $is_new && $is_panels_type && $use_classic;
1261
+ $show_classic_admin_notice = apply_filters( 'so_panels_show_classic_admin_notice', $show_classic_admin_notice );
1262
+ if ( $show_classic_admin_notice ) {
1263
+ $settings_url = self_admin_url( 'options-general.php?page=siteorigin_panels' );
1264
+ $notice = sprintf(
1265
+ __( 'This post type is set to use the Classic Editor by default for new posts. If you’d like to change this to the block editor, please go to <a href="%s" class="components-notice__action is-link">Page Builder Settings</a> and uncheck <strong>Use Classic Editor for new posts</strong>' ),
1266
+ $settings_url
1267
+ );
1268
+ ?>
1269
+ <div id="siteorigin-panels-use-classic-notice" class="notice notice-info"><p id="use-classic-notice"><?php echo $notice ?></p></div>
1270
+ <?php
1271
+ }
1272
+ }
1273
+
1274
+ /**
1275
+ * Show Classic Editor for existing PB posts.
1276
+ *
1277
+ * @param $use_block_editor
1278
+ * @param $post_type
1279
+ *
1280
+ * @return bool
1281
+ */
1282
+ public function show_classic_editor_for_panels( $use_block_editor, $post_type ) {
1283
+
1284
+ // For new pages.
1285
+ if ( isset( $_GET['block-editor'] ) ) {
1286
+ return $use_block_editor;
1287
+ } else if ( isset( $_GET['siteorigin-page-builder'] ) ) {
1288
+ return false;
1289
+ }
1290
+
1291
+ $post_types = siteorigin_panels_setting( 'post-types' );
1292
+ global $pagenow;
1293
+ // If the `$post_type` is set to be used by Page Builder for new posts.
1294
+ $is_new_panels_type = $pagenow == 'post-new.php' && in_array( $post_type, $post_types );
1295
+ $use_classic = siteorigin_panels_setting( 'use-classic' );
1296
+ // For existing posts.
1297
+ global $post;
1298
+ if ( ! empty( $post ) ) {
1299
+ // If the post has blocks just allow `$use_block_editor` to decide.
1300
+ if ( ! has_blocks( $post ) ) {
1301
+ $panels_data = get_post_meta( $post->ID, 'panels_data', true );
1302
+ if ( ! empty( $panels_data ) || ( $use_classic && $is_new_panels_type ) ) {
1303
+ $use_block_editor = false;
1304
+ }
1305
+ }
1306
+ } else if ( $is_new_panels_type ) {
1307
+ $use_block_editor = false;
1308
+ }
1309
+
1310
+ return $use_block_editor;
1311
+ }
1312
+
1313
+ /**
1314
+ * This was copied from Gutenberg and slightly modified as a quick way to allow users to create new Page Builder pages
1315
+ * in the classic editor without requiring the classic editor plugin be installed.
1316
+ */
1317
+ function add_panels_add_new_button() {
1318
+ global $typenow;
1319
+
1320
+ if ( 'wp_block' === $typenow ) {
1321
+ ?>
1322
+ <style type="text/css">
1323
+ .page-title-action {
1324
+ display: none;
1325
+ }
1326
+ </style>
1327
+ <?php
1328
+ }
1329
+
1330
+ if ( ! $this->show_add_new_dropdown_for_type( $typenow ) ) {
1331
+ return;
1332
+ }
1333
+
1334
+ ?>
1335
+ <style type="text/css">
1336
+ .split-page-title-action {
1337
+ display: inline-block;
1338
+ }
1339
+
1340
+ .split-page-title-action a,
1341
+ .split-page-title-action a:active,
1342
+ .split-page-title-action .expander:after {
1343
+ padding: 6px 10px;
1344
+ position: relative;
1345
+ top: -3px;
1346
+ text-decoration: none;
1347
+ border: 1px solid #ccc;
1348
+ border-radius: 2px 0px 0px 2px;
1349
+ background: #f7f7f7;
1350
+ text-shadow: none;
1351
+ font-weight: 600;
1352
+ font-size: 13px;
1353
+ line-height: normal; /* IE8-IE11 need this for buttons */
1354
+ color: #0073aa; /* some of these controls are button elements and don't inherit from links */
1355
+ cursor: pointer;
1356
+ outline: 0;
1357
+ }
1358
+
1359
+ .split-page-title-action a:hover,
1360
+ .split-page-title-action .expander:hover:after {
1361
+ border-color: #008EC2;
1362
+ background: #00a0d2;
1363
+ color: #fff;
1364
+ }
1365
+
1366
+ .split-page-title-action a:focus,
1367
+ .split-page-title-action .expander:focus:after {
1368
+ border-color: #5b9dd9;
1369
+ box-shadow: 0 0 2px rgba( 30, 140, 190, 0.8 );
1370
+ }
1371
+
1372
+ .split-page-title-action .expander:after {
1373
+ content: "\f140";
1374
+ font: 400 20px/.5 dashicons;
1375
+ speak: none;
1376
+ top: 0px;
1377
+ <?php if ( is_rtl() ) : ?>
1378
+ right: -1px;
1379
+ <?php else : ?>
1380
+ left: -1px;
1381
+ <?php endif; ?>
1382
+ position: relative;
1383
+ vertical-align: top;
1384
+ text-decoration: none !important;
1385
+ padding: 4px 5px 5px 4px;
1386
+ border-radius: 0px 2px 2px 0px;
1387
+ }
1388
+
1389
+ .split-page-title-action .dropdown {
1390
+ display: none;
1391
+ }
1392
+
1393
+ .split-page-title-action .dropdown.visible {
1394
+ display: block;
1395
+ position: absolute;
1396
+ margin-top: 3px;
1397
+ z-index: 1;
1398
+ }
1399
+
1400
+ .split-page-title-action .dropdown.visible a {
1401
+ display: block;
1402
+ top: 0;
1403
+ margin: -1px 0;
1404
+ <?php if ( is_rtl() ) : ?>
1405
+ padding-left: 9px;
1406
+ <?php else : ?>
1407
+ padding-right: 9px;
1408
+ <?php endif; ?>
1409
+ }
1410
+
1411
+ .split-page-title-action .expander {
1412
+ outline: none;
1413
+ }
1414
+
1415
+ </style>
1416
+ <script type="text/javascript">
1417
+ document.addEventListener( 'DOMContentLoaded', function() {
1418
+ var buttons = document.getElementsByClassName( 'page-title-action' ),
1419
+ button = buttons.item( 0 );
1420
+
1421
+ if ( ! button ) {
1422
+ return;
1423
+ }
1424
+
1425
+ var url = button.href;
1426
+ var urlHasParams = ( -1 !== url.indexOf( '?' ) );
1427
+ var panelsUrl = url + ( urlHasParams ? '&' : '?' ) + 'siteorigin-page-builder';
1428
+ var blockEditorUrl = url + ( urlHasParams ? '&' : '?' ) + 'block-editor';
1429
+
1430
+ var newbutton = '<span id="split-page-title-action" class="split-page-title-action">';
1431
+ newbutton += '<a href="' + url + '">' + button.innerText + '</a>';
1432
+ newbutton += '<span class="expander" tabindex="0" role="button" aria-haspopup="true" aria-label="<?php echo esc_attr( __( 'Toggle editor selection menu', 'siteorigin-panels' ) ); ?>"></span>';
1433
+ newbutton += '<span class="dropdown"><a href="' + panelsUrl + '"><?php echo esc_html( __( 'SiteOrigin Page Builder', 'siteorigin-panels' ) ); ?></a>';
1434
+ newbutton += '<a href="' + blockEditorUrl + '"><?php echo esc_html( __( 'Block Editor', 'siteorigin-panels' ) ); ?></a></span></span><span class="page-title-action" style="display:none;"></span>';
1435
+
1436
+ button.insertAdjacentHTML( 'afterend', newbutton );
1437
+ button.parentNode.removeChild( button );
1438
+
1439
+ var expander = document.getElementById( 'split-page-title-action' ).getElementsByClassName( 'expander' ).item( 0 );
1440
+ var dropdown = expander.parentNode.querySelector( '.dropdown' );
1441
+ function toggleDropdown() {
1442
+ dropdown.classList.toggle( 'visible' );
1443
+ }
1444
+ expander.addEventListener( 'click', function( e ) {
1445
+ e.preventDefault();
1446
+ toggleDropdown();
1447
+ } );
1448
+ expander.addEventListener( 'keydown', function( e ) {
1449
+ if ( 13 === e.which || 32 === e.which ) {
1450
+ e.preventDefault();
1451
+ toggleDropdown();
1452
+ }
1453
+ } );
1454
+ } );
1455
+ </script>
1456
+ <?php
1457
+ }
1458
+
1459
+ private function show_add_new_dropdown_for_type( $post_type ) {
1460
+
1461
+ $show = in_array( $post_type, siteorigin_panels_setting( 'post-types' ) );
1462
+
1463
+ // WooCommerce product type doesn't support block editor...
1464
+ $show = $show && ! ( class_exists( 'WooCommerce' ) && $post_type == 'product' );
1465
+
1466
+ if ( class_exists( 'SiteOrigin_Premium_Plugin_Cpt_Builder' ) ) {
1467
+ $show = $show && $post_type != SiteOrigin_Premium_Plugin_Cpt_Builder::POST_TYPE;
1468
+ $cpt_builder = SiteOrigin_Premium_Plugin_Cpt_Builder::single();
1469
+ $so_custom_types = $cpt_builder->get_post_types();
1470
+ $show = $show && ! isset( $so_custom_types[ $post_type ] );
1471
+ }
1472
+
1473
+ return apply_filters( 'so_panels_show_add_new_dropdown_for_type', $show, $post_type );
1474
+ }
1475
+
1476
+ public function add_panels_post_state( $post_states, $post ) {
1477
+ $panels_data = get_post_meta( $post->ID, 'panels_data', true );
1478
+
1479
+ if ( ! empty( $panels_data ) ) {
1480
+ $post_states[] = __( 'SiteOrigin Page Builder', 'siteorigin-panels' );
1481
+ }
1482
+
1483
+ return $post_states;
1484
+ }
1485
+ }
inc/cache-renderer.php CHANGED
@@ -1,37 +1,37 @@
1
- <?php
2
-
3
- class SiteOrigin_Panels_Cache_Renderer {
4
-
5
- function __construct() {
6
- // Clear cache when the Page Builder version changes
7
- add_action( 'siteorigin_panels_version_changed', array( $this, 'clear_cache' ), 10, 0 );
8
-
9
- // When we activate/deactivate a plugin or switch themes that might change rendering
10
- add_action( 'activated_plugin', array( $this, 'clear_cache' ), 10, 0 );
11
- add_action( 'deactivated_plugin', array( $this, 'clear_cache' ), 10, 0 );
12
- add_action( 'switch_theme', array( $this, 'clear_cache' ), 10, 0 );
13
-
14
- // When settings are saved, this is also a good way to force a cache refresh
15
- add_action( 'siteorigin_panels_save_settings', array( $this, 'clear_cache' ), 10, 0 );
16
-
17
- // When a single post is saved
18
- add_action( 'save_post', array( $this, 'clear_cache' ), 10, 2 );
19
- }
20
-
21
- /**
22
- * @return SiteOrigin_Panels_Cache_Renderer
23
- */
24
- static function single() {
25
- static $single;
26
- return empty( $single ) ? $single = new self() : $single;
27
- }
28
-
29
- /**
30
- * Clear post meta cache.
31
- *
32
- * Keep this around for a bit in attempt to delete any existing caches.
33
- */
34
- public function clear_cache(){
35
- delete_post_meta_by_key( 'siteorigin_panels_cache' );
36
- }
37
- }
1
+ <?php
2
+
3
+ class SiteOrigin_Panels_Cache_Renderer {
4
+
5
+ function __construct() {
6
+ // Clear cache when the Page Builder version changes
7
+ add_action( 'siteorigin_panels_version_changed', array( $this, 'clear_cache' ), 10, 0 );
8
+
9
+ // When we activate/deactivate a plugin or switch themes that might change rendering
10
+ add_action( 'activated_plugin', array( $this, 'clear_cache' ), 10, 0 );
11
+ add_action( 'deactivated_plugin', array( $this, 'clear_cache' ), 10, 0 );
12
+ add_action( 'switch_theme', array( $this, 'clear_cache' ), 10, 0 );
13
+
14
+ // When settings are saved, this is also a good way to force a cache refresh
15
+ add_action( 'siteorigin_panels_save_settings', array( $this, 'clear_cache' ), 10, 0 );
16
+
17
+ // When a single post is saved
18
+ add_action( 'save_post', array( $this, 'clear_cache' ), 10, 2 );
19
+ }
20
+
21
+ /**
22
+ * @return SiteOrigin_Panels_Cache_Renderer
23
+ */
24
+ static function single() {
25
+ static $single;
26
+ return empty( $single ) ? $single = new self() : $single;
27
+ }
28
+
29
+ /**
30
+ * Clear post meta cache.
31
+ *
32
+ * Keep this around for a bit in attempt to delete any existing caches.
33
+ */
34
+ public function clear_cache(){
35
+ delete_post_meta_by_key( 'siteorigin_panels_cache' );
36
+ }
37
+ }
inc/css-builder.php CHANGED
@@ -1,256 +1,256 @@
1
- <?php
2
-
3
-
4
- /**
5
- * Class SiteOrigin_Panels_Css_Builder
6
- *
7
- * Use for building CSS for a page.
8
- */
9
- class SiteOrigin_Panels_Css_Builder {
10
-
11
- private $css;
12
-
13
- function __construct() {
14
- $this->css = array();
15
- }
16
-
17
- /**
18
- * Add some general CSS.
19
- *
20
- * @param string $selector
21
- * @param array $attributes
22
- * @param int $resolution The pixel resolution that this applies to
23
- */
24
- public function add_css( $selector, $attributes, $resolution = 1920 ) {
25
- $attribute_string = array();
26
- foreach ( $attributes as $k => $v ) {
27
-
28
- if( is_array( $v ) ) {
29
- for( $i = 0; $i < count( $v ); $i++ ) {
30
- if ( ! strlen( (string) $v[ $i ] ) ) continue;
31
- $attribute_string[] = esc_html( $k ) . ':' . esc_html( $v[ $i ] );
32
- }
33
- }
34
- else {
35
- if ( ! strlen( (string) $v ) ) continue;
36
- $attribute_string[] = esc_html( $k ) . ':' . esc_html( $v );
37
- }
38
- }
39
- $attribute_string = implode( ';', $attribute_string );
40
-
41
- // Add everything we need to the CSS selector
42
- if ( empty( $this->css[ $resolution ] ) ) {
43
- $this->css[ $resolution ] = array();
44
- }
45
- if ( empty( $this->css[ $resolution ][ $attribute_string ] ) ) {
46
- $this->css[ $resolution ][ $attribute_string ] = array();
47
- }
48
- $this->css[ $resolution ][ $attribute_string ][] = $selector;
49
- }
50
-
51
- /**
52
- * Add CSS that applies to a row or group of rows.
53
- *
54
- * @param int $li The layout ID. If false, then the CSS applies to all layouts.
55
- * @param int|bool|string $ri The row index. If false, then the CSS applies to all rows.
56
- * @param string $sub_selector A sub selector if we need one.
57
- * @param array $attributes An array of attributes.
58
- * @param int $resolution The pixel resolution that this applies to
59
- * @param bool $specify_layout Sometimes for CSS specificity, we need to include the layout ID.
60
- */
61
- public function add_row_css( $li, $ri = false, $sub_selector = '', $attributes = array(), $resolution = 1920, $specify_layout = false ) {
62
- $selector = array();
63
-
64
- // Special case of `> .panel-row-style` sub_selector
65
- if ( $ri === false ) {
66
- // This applies to all rows
67
- $selector[] = '#pl-' . $li;
68
- $selector[] = '.panel-grid';
69
- } else {
70
- // This applies to a specific row
71
- if ( $specify_layout ) {
72
- $selector[] = '#pl-' . $li;
73
- }
74
- if ( is_string( $ri ) ) {
75
- $selector[] = '#' . $ri;
76
- } else {
77
- $selector[] = '#pg-' . $li . '-' . $ri;
78
- }
79
- }
80
-
81
- $selector = implode( ' ', $selector );
82
- $selector = $this->add_sub_selector( $selector, $sub_selector );
83
-
84
- // Add this to the CSS array
85
- $this->add_css( $selector, $attributes, $resolution );
86
- }
87
-
88
- /**
89
- * Add cell specific CSS
90
- *
91
- * @param int $li The layout ID. If false, then the CSS applies to all layouts.
92
- * @param int|bool $ri The row index. If false, then the CSS applies to all rows.
93
- * @param int|bool $ci The cell index. If false, then the CSS applies to all rows.
94
- * @param string $sub_selector A sub selector if we need one.
95
- * @param array $attributes An array of attributes.
96
- * @param int $resolution The pixel resolution that this applies to
97
- * @param bool $specify_layout Sometimes for CSS specificity, we need to include the layout ID.
98
- */
99
- public function add_cell_css( $li, $ri = false, $ci = false, $sub_selector = '', $attributes = array(), $resolution = 1920, $specify_layout = false ) {
100
- $selector_parts = array();
101
-
102
- if ( $ri === false && $ci === false ) {
103
- // This applies to all cells in the layout
104
- $selector_parts[] = '#pl-' . $li;
105
- $selector_parts[] = '.panel-grid-cell';
106
- } elseif ( $ri !== false && $ci === false ) {
107
- // This applies to all cells in a row
108
- $sel = '';
109
-
110
- if ( $specify_layout ) {
111
- $sel = '#pl-' . $li . ' ';
112
- }
113
- $sel .= is_string( $ri ) ? ( '#' . $ri ) : '#pg-' . $li . '-' . $ri;
114
-
115
- // If row styles are set, there's a row style wrapper between the row and the cell, so we need to include
116
- // the selector for both. This is a somewhat hacky fix, but trying to prevent further breakage in existing
117
- // layouts.
118
- $sel_with_style = ', ' . $sel . ' > .panel-row-style';
119
-
120
- $sel .= ' > .panel-grid-cell';
121
- $sel_with_style .= ' > .panel-grid-cell';
122
-
123
- $selector_parts[] = $sel;
124
- $selector_parts[] = $sel_with_style;
125
- } elseif ( $ri !== false && $ci !== false ) {
126
- // This applies to a specific cell
127
- if ( $specify_layout ) {
128
- $selector_parts[] = '#pl-' . $li;
129
- }
130
- $selector_parts[] = '#pgc-' . $li . '-' . $ri . '-' . $ci;
131
- }
132
-
133
- $selector = implode( ' ', $selector_parts );
134
- if ( ! empty( $sub_selector ) ) {
135
- $selector = $this->add_sub_selector( $selector, $sub_selector );
136
- }
137
-
138
- // Add this to the CSS array
139
- $this->add_css( $selector, $attributes, $resolution );
140
- }
141
-
142
- /**
143
- * Add widget specific CSS
144
- *
145
- * @param int $li The layout ID. If false, then the CSS applies to all layouts.
146
- * @param int|bool $ri The row index. If false, then the CSS applies to all rows.
147
- * @param int|bool $ci The cell index. If false, then the CSS applies to all rows.
148
- * @param int|bool $wi The widget index. If false, then CSS applies to all widgets.
149
- * @param string $sub_selector A sub selector if we need one.
150
- * @param array $attributes An array of attributes.
151
- * @param int $resolution The pixel resolution that this applies to
152
- * @param bool $specify_layout Sometimes for CSS specificity, we need to include the layout ID.
153
- */
154
- public function add_widget_css( $li, $ri = false, $ci = false, $wi = false, $sub_selector, $attributes = array(), $resolution = 1920, $specify_layout = false ) {
155
- $selector = array();
156
-
157
- if ( $ri === false && $ci === false && $wi === false ) {
158
- // This applies to all widgets in the layout
159
- $selector[] = '#pl-' . $li;
160
- $selector[] = '.so-panel';
161
- } else if ( $ri !== false && $ci === false && $wi === false ) {
162
- // This applies to all widgets in a row
163
- if ( $specify_layout ) {
164
- $selector[] = '#pl-' . $li;
165
- }
166
- $selector[] = is_string( $ri ) ? ( '#' . $ri ) : '#pg-' . $li . '-' . $ri;
167
- $selector[] = '.so-panel';
168
- } else if ( $ri !== false && $ci !== false && $wi === false ) {
169
- if ( $specify_layout ) {
170
- $selector[] = '#pl-' . $li;
171
- }
172
- $selector[] = '#pgc-' . $li . '-' . $ri . '-' . $ci;
173
- $selector[] = '.so-panel';
174
- } else {
175
- // This applies to a specific widget
176
- if ( $specify_layout ) {
177
- $selector[] = '#pl-' . $li;
178
- }
179
- $selector[] = '#panel-' . $li . '-' . $ri . '-' . $ci . '-' . $wi;
180
- }
181
-
182
- $selector = implode( ' ', $selector );
183
- $selector = $this->add_sub_selector( $selector, $sub_selector );
184
-
185
- // Add this to the CSS array
186
- $this->add_css( $selector, $attributes, $resolution );
187
- }
188
-
189
- /**
190
- * Add a sub selector to the main selector
191
- *
192
- * @param string $selector
193
- * @param string|array $sub_selector
194
- *
195
- * @return string
196
- */
197
- private function add_sub_selector( $selector, $sub_selector ){
198
- $return = array();
199
-
200
- if( ! empty( $sub_selector ) ) {
201
- if( ! is_array( $sub_selector ) ) $sub_selector = array( $sub_selector );
202
-
203
- foreach( $sub_selector as $sub ) {
204
- $return[] = $selector . $sub;
205
- }
206
- }
207
- else {
208
- $return = array( $selector );
209
- }
210
-
211
- return implode( ', ', $return );
212
- }
213
-
214
- /**
215
- * Gets the CSS for this particular layout.
216
- */
217
- public function get_css() {
218
- // Build actual CSS from the array
219
- $css_text = '';
220
- krsort( $this->css );
221
- foreach ( $this->css as $res => $def ) {
222
- if( strpos( $res, ':' ) !== false ) {
223
- list( $max_res, $min_res ) = explode( ':', $res, 2 );
224
- }
225
- else {
226
- $min_res = false;
227
- $max_res = $res;
228
- }
229
-
230
- if ( empty( $def ) ) {
231
- continue;
232
- }
233
-
234
- if ( $max_res === '' && $min_res > 0 ) {
235
- $css_text .= '@media (min-width:' . intval( $min_res ) . 'px) {';
236
- } elseif ( $max_res < 1920 ) {
237
- $css_text .= '@media (max-width:' . intval( $max_res ) . 'px)';
238
- if ( ! empty( $min_res ) ) {
239
- $css_text .= ' and (min-width:' . intval( $min_res ) . 'px) ';
240
- }
241
- $css_text .= '{ ';
242
- }
243
-
244
- foreach ( $def as $property => $selector ) {
245
- $selector = array_unique( $selector );
246
- $css_text .= implode( ' , ', $selector ) . ' { ' . $property . ' } ';
247
- }
248
-
249
- if ( ( $max_res === '' && $min_res > 0 ) || $max_res < 1920 ) {
250
- $css_text .= ' } ';
251
- }
252
- }
253
-
254
- return $css_text;
255
- }
256
- }
1
+ <?php
2
+
3
+
4
+ /**
5
+ * Class SiteOrigin_Panels_Css_Builder
6
+ *
7
+ * Use for building CSS for a page.
8
+ */
9
+ class SiteOrigin_Panels_Css_Builder {
10
+
11
+ private $css;
12
+
13
+ function __construct() {
14
+ $this->css = array();
15
+ }
16
+
17
+ /**
18
+ * Add some general CSS.
19
+ *
20
+ * @param string $selector
21
+ * @param array $attributes
22
+ * @param int $resolution The pixel resolution that this applies to
23
+ */
24
+ public function add_css( $selector, $attributes, $resolution = 1920 ) {
25
+ $attribute_string = array();
26
+ foreach ( $attributes as $k => $v ) {
27
+
28
+ if( is_array( $v ) ) {
29
+ for( $i = 0; $i < count( $v ); $i++ ) {
30
+ if ( ! strlen( (string) $v[ $i ] ) ) continue;
31
+ $attribute_string[] = esc_html( $k ) . ':' . esc_html( $v[ $i ] );
32
+ }
33
+ }
34
+ else {
35
+ if ( ! strlen( (string) $v ) ) continue;
36
+ $attribute_string[] = esc_html( $k ) . ':' . esc_html( $v );
37
+ }
38
+ }
39
+ $attribute_string = implode( ';', $attribute_string );
40
+
41
+ // Add everything we need to the CSS selector
42
+ if ( empty( $this->css[ $resolution ] ) ) {
43
+ $this->css[ $resolution ] = array();
44
+ }
45
+ if ( empty( $this->css[ $resolution ][ $attribute_string ] ) ) {
46
+ $this->css[ $resolution ][ $attribute_string ] = array();
47
+ }
48
+ $this->css[ $resolution ][ $attribute_string ][] = $selector;
49
+ }
50
+
51
+ /**
52
+ * Add CSS that applies to a row or group of rows.
53
+ *
54
+ * @param int $li The layout ID. If false, then the CSS applies to all layouts.
55
+ * @param int|bool|string $ri The row index. If false, then the CSS applies to all rows.
56
+ * @param string $sub_selector A sub selector if we need one.
57
+ * @param array $attributes An array of attributes.
58
+ * @param int $resolution The pixel resolution that this applies to
59
+ * @param bool $specify_layout Sometimes for CSS specificity, we need to include the layout ID.
60
+ */
61
+ public function add_row_css( $li, $ri = false, $sub_selector = '', $attributes = array(), $resolution = 1920, $specify_layout = false ) {
62
+ $selector = array();
63
+
64
+ // Special case of `> .panel-row-style` sub_selector
65
+ if ( $ri === false ) {
66
+ // This applies to all rows
67
+ $selector[] = '#pl-' . $li;
68
+ $selector[] = '.panel-grid';
69
+ } else {
70
+ // This applies to a specific row
71
+ if ( $specify_layout ) {
72
+ $selector[] = '#pl-' . $li;
73
+ }
74
+ if ( is_string( $ri ) ) {
75
+ $selector[] = '#' . $ri;
76
+ } else {
77
+ $selector[] = '#pg-' . $li . '-' . $ri;
78
+ }
79
+ }
80
+
81
+ $selector = implode( ' ', $selector );
82
+ $selector = $this->add_sub_selector( $selector, $sub_selector );
83
+
84
+ // Add this to the CSS array
85
+ $this->add_css( $selector, $attributes, $resolution );
86
+ }
87
+
88
+ /**
89
+ * Add cell specific CSS
90
+ *
91
+ * @param int $li The layout ID. If false, then the CSS applies to all layouts.
92
+ * @param int|bool $ri The row index. If false, then the CSS applies to all rows.
93
+ * @param int|bool $ci The cell index. If false, then the CSS applies to all rows.
94
+ * @param string $sub_selector A sub selector if we need one.
95
+ * @param array $attributes An array of attributes.
96
+ * @param int $resolution The pixel resolution that this applies to
97
+ * @param bool $specify_layout Sometimes for CSS specificity, we need to include the layout ID.
98
+ */
99
+ public function add_cell_css( $li, $ri = false, $ci = false, $sub_selector = '', $attributes = array(), $resolution = 1920, $specify_layout = false ) {
100
+ $selector_parts = array();
101
+
102
+ if ( $ri === false && $ci === false ) {
103
+ // This applies to all cells in the layout
104
+ $selector_parts[] = '#pl-' . $li;
105
+ $selector_parts[] = '.panel-grid-cell';
106
+ } elseif ( $ri !== false && $ci === false ) {
107
+ // This applies to all cells in a row
108
+ $sel = '';
109
+
110
+ if ( $specify_layout ) {
111
+ $sel = '#pl-' . $li . ' ';
112
+ }
113
+ $sel .= is_string( $ri ) ? ( '#' . $ri ) : '#pg-' . $li . '-' . $ri;
114
+
115
+ // If row styles are set, there's a row style wrapper between the row and the cell, so we need to include
116
+ // the selector for both. This is a somewhat hacky fix, but trying to prevent further breakage in existing
117
+ // layouts.
118
+ $sel_with_style = ', ' . $sel . ' > .panel-row-style';
119
+
120
+ $sel .= ' > .panel-grid-cell';
121
+ $sel_with_style .= ' > .panel-grid-cell';
122
+
123
+ $selector_parts[] = $sel;
124
+ $selector_parts[] = $sel_with_style;
125
+ } elseif ( $ri !== false && $ci !== false ) {
126
+ // This applies to a specific cell
127
+ if ( $specify_layout ) {
128
+ $selector_parts[] = '#pl-' . $li;
129
+ }
130
+ $selector_parts[] = '#pgc-' . $li . '-' . $ri . '-' . $ci;
131
+ }
132
+
133
+ $selector = implode( ' ', $selector_parts );
134
+ if ( ! empty( $sub_selector ) ) {
135
+ $selector = $this->add_sub_selector( $selector, $sub_selector );
136
+ }
137
+
138
+ // Add this to the CSS array
139
+ $this->add_css( $selector, $attributes, $resolution );
140
+ }
141
+
142
+ /**
143
+ * Add widget specific CSS
144
+ *
145
+ * @param int $li The layout ID. If false, then the CSS applies to all layouts.
146
+ * @param int|bool $ri The row index. If false, then the CSS applies to all rows.
147
+ * @param int|bool $ci The cell index. If false, then the CSS applies to all rows.
148
+ * @param int|bool $wi The widget index. If false, then CSS applies to all widgets.
149
+ * @param string $sub_selector A sub selector if we need one.
150
+ * @param array $attributes An array of attributes.
151
+ * @param int $resolution The pixel resolution that this applies to
152
+ * @param bool $specify_layout Sometimes for CSS specificity, we need to include the layout ID.
153
+ */
154
+ public function add_widget_css( $li, $ri = false, $ci = false, $wi = false, $sub_selector, $attributes = array(), $resolution = 1920, $specify_layout = false ) {
155
+ $selector = array();
156
+
157
+ if ( $ri === false && $ci === false && $wi === false ) {
158
+ // This applies to all widgets in the layout
159
+ $selector[] = '#pl-' . $li;
160
+ $selector[] = '.so-panel';
161
+ } else if ( $ri !== false && $ci === false && $wi === false ) {
162
+ // This applies to all widgets in a row
163
+ if ( $specify_layout ) {
164
+ $selector[] = '#pl-' . $li;
165
+ }
166
+ $selector[] = is_string( $ri ) ? ( '#' . $ri ) : '#pg-' . $li . '-' . $ri;
167
+ $selector[] = '.so-panel';
168
+ } else if ( $ri !== false && $ci !== false && $wi === false ) {
169
+ if ( $specify_layout ) {
170
+ $selector[] = '#pl-' . $li;
171
+ }
172
+ $selector[] = '#pgc-' . $li . '-' . $ri . '-' . $ci;
173
+ $selector[] = '.so-panel';
174
+ } else {
175
+ // This applies to a specific widget
176
+ if ( $specify_layout ) {
177
+ $selector[] = '#pl-' . $li;
178
+ }
179
+ $selector[] = '#panel-' . $li . '-' . $ri . '-' . $ci . '-' . $wi;
180
+ }
181
+
182
+ $selector = implode( ' ', $selector );
183
+ $selector = $this->add_sub_selector( $selector, $sub_selector );
184
+
185
+ // Add this to the CSS array
186
+ $this->add_css( $selector, $attributes, $resolution );
187
+ }
188
+
189
+ /**
190
+ * Add a sub selector to the main selector
191
+ *
192
+ * @param string $selector
193
+ * @param string|array $sub_selector
194
+ *
195
+ * @return string
196
+ */
197
+ private function add_sub_selector( $selector, $sub_selector ){
198
+ $return = array();
199
+
200
+ if( ! empty( $sub_selector ) ) {
201
+ if( ! is_array( $sub_selector ) ) $sub_selector = array( $sub_selector );
202
+
203
+ foreach( $sub_selector as $sub ) {
204
+ $return[] = $selector . $sub;
205
+ }
206
+ }
207
+ else {
208
+ $return = array( $selector );
209
+ }
210
+
211
+ return implode( ', ', $return );
212
+ }
213
+
214
+ /**
215
+ * Gets the CSS for this particular layout.
216
+ */
217
+ public function get_css() {
218
+ // Build actual CSS from the array
219
+ $css_text = '';
220
+ krsort( $this->css );
221
+ foreach ( $this->css as $res => $def ) {
222
+ if( strpos( $res, ':' ) !== false ) {
223
+ list( $max_res, $min_res ) = explode( ':', $res, 2 );
224
+ }
225
+ else {
226
+ $min_res = false;
227
+ $max_res = $res;
228
+ }
229
+
230
+ if ( empty( $def ) ) {
231
+ continue;
232
+ }
233
+
234
+ if ( $max_res === '' && $min_res > 0 ) {
235
+ $css_text .= '@media (min-width:' . intval( $min_res ) . 'px) {';
236
+ } elseif ( $max_res < 1920 ) {
237
+ $css_text .= '@media (max-width:' . intval( $max_res ) . 'px)';
238
+ if ( ! empty( $min_res ) ) {
239
+ $css_text .= ' and (min-width:' . intval( $min_res ) . 'px) ';
240
+ }
241
+ $css_text .= '{ ';
242
+ }
243
+
244
+ foreach ( $def as $property => $selector ) {
245
+ $selector = array_unique( $selector );
246
+ $css_text .= implode( ' , ', $selector ) . ' { ' . $property . ' } ';
247
+ }
248
+
249
+ if ( ( $max_res === '' && $min_res > 0 ) || $max_res < 1920 ) {
250
+ $css_text .= ' } ';
251
+ }
252
+ }
253
+
254
+ return $css_text;
255
+ }
256
+ }
inc/functions.php CHANGED
@@ -1,106 +1,106 @@
1
- <?php
2
- /**
3
- * Contains several legacy and shorthand functions
4
- *
5
- * @since 3.0
6
- */
7
-
8
- /**
9
- * @return mixed|void Are we currently viewing the home page
10
- */
11
- function siteorigin_panels_is_home() {
12
- return SiteOrigin_Panels::is_home();
13
- }
14
-
15
- /**
16
- * Check if we're currently viewing a page builder page.
17
- *
18
- * @param bool $can_edit Also check if the user can edit this page
19
- *
20
- * @return bool
21
- */
22
- function siteorigin_panels_is_panel( $can_edit = false ) {
23
- return SiteOrigin_Panels::is_panel( $can_edit );
24
- }
25
-
26
-
27
- function siteorigin_panels_get_home_page_data() {
28
- return SiteOrigin_Panels::single()->get_home_page_data();
29
- }
30
-
31
- /**
32
- * Render Page Builder content
33
- *
34
- * @param bool $post_id
35
- * @param bool $enqueue_css
36
- * @param bool $panels_data
37
- *
38
- * @return string The HTML content.
39
- */
40
- function siteorigin_panels_render( $post_id = false, $enqueue_css = true, $panels_data = false ) {
41
- return SiteOrigin_Panels::renderer()->render( $post_id, $enqueue_css, $panels_data );
42
- }
43
-
44
- /**
45
- * Generate the CSS for the page layout.
46
- *
47
- * @param $post_id
48
- * @param $panels_data
49
- * @return string
50
- */
51
- function siteorigin_panels_generate_css($post_id, $panels_data = false){
52
- return SiteOrigin_Panels::renderer()->generate_css( $post_id, $panels_data );
53
- }
54
-
55
- /**
56
- * Legacy function to process raw widgets.
57
- *
58
- * @param $widgets
59
- * @param $old_widgets
60
- * @param $escape_classes
61
- *
62
- * @return array
63
- */
64
- function siteorigin_panels_process_raw_widgets( $widgets, $old_widgets = false, $escape_classes = false ) {
65
- return SiteOrigin_Panels_Admin::single()->process_raw_widgets( $widgets, $old_widgets, $escape_classes );
66
- }
67
-
68
- function siteorigin_panels_the_widget( $widget_info, $instance, $grid, $cell, $panel, $is_first, $is_last, $post_id = false, $style_wrapper = '' ) {
69
- SiteOrigin_Panels::renderer()->the_widget( $widget_info, $instance, $grid, $cell, $panel, $is_first, $is_last, $post_id, $style_wrapper );
70
- }
71
-
72
- /**
73
- * Get a setting with the given key.
74
- *
75
- * @param string $key
76
- *
77
- * @return array|bool|mixed|null
78
- */
79
- function siteorigin_panels_setting( $key = '' ) {
80
- return SiteOrigin_Panels_Settings::single()->get( $key );
81
- }
82
-
83
- function siteorigin_panels_plugin_activation_install_url( $plugin, $plugin_name, $source = false ) {
84
- return SiteOrigin_Panels_Admin_Widgets_Bundle::install_url( $plugin, $plugin_name, $source );
85
- }
86
-
87
- /**
88
- * A null function for compatibility with aTheme themes.
89
- *
90
- * @return bool
91
- */
92
- function siteorigin_panels_activate(){
93
- return false;
94
- }
95
-
96
-
97
- /**
98
- * Returns the base URL of our widget with `$path` appended.
99
- *
100
- * @param string $path Extra path to append to the end of the URL.
101
- *
102
- * @return string Base URL of the widget, with $path appended.
103
- */
104
- function siteorigin_panels_url( $path = '' ) {
105
- return plugins_url( 'siteorigin-panels/' . $path );
106
  }
1
+ <?php
2
+ /**
3
+ * Contains several legacy and shorthand functions
4
+ *
5
+ * @since 3.0
6
+ */
7
+
8
+ /**
9
+ * @return mixed|void Are we currently viewing the home page
10
+ */
11
+ function siteorigin_panels_is_home() {
12
+ return SiteOrigin_Panels::is_home();
13
+ }
14
+
15
+ /**
16
+ * Check if we're currently viewing a page builder page.
17
+ *
18
+ * @param bool $can_edit Also check if the user can edit this page
19
+ *
20
+ * @return bool
21
+ */
22
+ function siteorigin_panels_is_panel( $can_edit = false ) {
23
+ return SiteOrigin_Panels::is_panel( $can_edit );
24
+ }
25
+
26
+
27
+ function siteorigin_panels_get_home_page_data() {
28
+ return SiteOrigin_Panels::single()->get_home_page_data();
29
+ }
30
+
31
+ /**
32
+ * Render Page Builder content
33
+ *
34
+ * @param bool $post_id
35
+ * @param bool $enqueue_css
36
+ * @param bool $panels_data
37
+ *
38
+ * @return string The HTML content.
39
+ */
40
+ function siteorigin_panels_render( $post_id = false, $enqueue_css = true, $panels_data = false ) {
41
+ return SiteOrigin_Panels::renderer()->render( $post_id, $enqueue_css, $panels_data );
42
+ }
43
+
44
+ /**
45
+ * Generate the CSS for the page layout.
46
+ *
47
+ * @param $post_id
48
+ * @param $panels_data
49
+ * @return string
50
+ */
51
+ function siteorigin_panels_generate_css($post_id, $panels_data = false){
52
+ return SiteOrigin_Panels::renderer()->generate_css( $post_id, $panels_data );
53
+ }
54
+
55
+ /**
56
+ * Legacy function to process raw widgets.
57
+ *
58
+ * @param $widgets
59
+ * @param $old_widgets
60
+ * @param $escape_classes
61
+ *
62
+ * @return array
63
+ */
64
+ function siteorigin_panels_process_raw_widgets( $widgets, $old_widgets = false, $escape_classes = false ) {
65
+ return SiteOrigin_Panels_Admin::single()->process_raw_widgets( $widgets, $old_widgets, $escape_classes );
66
+ }
67
+
68
+ function siteorigin_panels_the_widget( $widget_info, $instance, $grid, $cell, $panel, $is_first, $is_last, $post_id = false, $style_wrapper = '' ) {
69
+ SiteOrigin_Panels::renderer()->the_widget( $widget_info, $instance, $grid, $cell, $panel, $is_first, $is_last, $post_id, $style_wrapper );
70
+ }
71
+
72
+ /**
73
+ * Get a setting with the given key.
74
+ *
75
+ * @param string $key
76
+ *
77
+ * @return array|bool|mixed|null
78
+ */
79
+ function siteorigin_panels_setting( $key = '' ) {
80
+ return SiteOrigin_Panels_Settings::single()->get( $key );
81
+ }
82
+
83
+ function siteorigin_panels_plugin_activation_install_url( $plugin, $plugin_name, $source = false ) {
84
+ return SiteOrigin_Panels_Admin_Widgets_Bundle::install_url( $plugin, $plugin_name, $source );
85
+ }
86
+
87
+ /**
88
+ * A null function for compatibility with aTheme themes.
89
+ *
90
+ * @return bool
91
+ */
92
+ function siteorigin_panels_activate(){
93
+ return false;
94
+ }
95
+
96
+
97
+ /**
98
+ * Returns the base URL of our widget with `$path` appended.
99
+ *
100
+ * @param string $path Extra path to append to the end of the URL.
101
+ *
102
+ * @return string Base URL of the widget, with $path appended.
103
+ */
104
+ function siteorigin_panels_url( $path = '' ) {
105
+ return plugins_url( 'siteorigin-panels/' . $path );
106
  }
inc/live-editor.php CHANGED
@@ -1,84 +1,84 @@
1
- <?php
2
-
3
- /**
4
- * The live editor class. Only loaded when in live editor mode.
5
- *
6
- * Class SiteOrigin_Panels_Live_Editor
7
- */
8
- class SiteOrigin_Panels_Live_Editor {
9
-
10
- function __construct() {
11
- add_action( 'template_redirect', array( $this, 'xss_headers' ) );
12
- add_action( 'get_post_metadata', array( $this, 'post_metadata' ), 10, 3 );
13
- add_action( 'wp_enqueue_scripts', array( $this, 'frontend_scripts' ) );
14
-
15
- // Don't display the admin bar when in live editor mode
16
- add_filter( 'show_admin_bar', '__return_false' );
17
- }
18
-
19
- public static function single() {
20
- static $single;
21
- return empty( $single ) ? $single = new self() : $single;
22
- }
23
-
24
- public function xss_headers(){
25
- global $post;
26
- if(
27
- ! empty( $_POST['live_editor_panels_data'] ) &&
28
- ! empty( $post->ID ) &&
29
- current_user_can( 'edit_post', $post->ID )
30
- ) {
31
- // Disable XSS protection when in the Live Editor
32
- header( 'X-XSS-Protection: 0' );
33
- }
34
- }
35
-
36
-
37
- /**
38
- * Edit the page builder data when we're viewing the live editor version. This is necessary to ensure updated styles
39
- * are rendered in the preview.
40
- *
41
- * @param $value
42
- * @param $post_id
43
- * @param $meta_key
44
- *
45
- * @return array
46
- */
47
- function post_metadata( $value, $post_id, $meta_key ) {
48
- if (
49
- $meta_key == 'panels_data' &&
50
- current_user_can( 'edit_post', $post_id ) &&
51
- ! empty( $_POST['live_editor_panels_data'] ) &&
52
- $_POST['live_editor_post_ID'] == $post_id
53
- ) {
54
- $value = array( json_decode( wp_unslash( $_POST['live_editor_panels_data'] ), true ) );
55
- }
56
- return $value;
57
- }
58
-
59
- /**
60
- * Load the frontend scripts for the live editor
61
- */
62
- function frontend_scripts() {
63
- wp_enqueue_script(
64
- 'live-editor-front',
65
- siteorigin_panels_url( 'js/live-editor/live-editor-front' . SITEORIGIN_PANELS_JS_SUFFIX . '.js' ),
66
- array( 'jquery' ),
67
- SITEORIGIN_PANELS_VERSION
68
- );
69
-
70
- wp_enqueue_script(
71
- 'live-editor-scrollto',
72
- siteorigin_panels_url( 'js/live-editor/jquery.scrollTo' . SITEORIGIN_PANELS_JS_SUFFIX . '.js' ),
73
- array( 'jquery' ),
74
- SITEORIGIN_PANELS_VERSION
75
- );
76
-
77
- wp_enqueue_style(
78
- 'live-editor-front',
79
- siteorigin_panels_url( 'css/live-editor-front' . SITEORIGIN_PANELS_CSS_SUFFIX . '.css' ),
80
- array(),
81
- SITEORIGIN_PANELS_VERSION
82
- );
83
- }
84
- }
1
+ <?php
2
+
3
+ /**
4
+ * The live editor class. Only loaded when in live editor mode.
5
+ *
6
+ * Class SiteOrigin_Panels_Live_Editor
7
+ */
8
+ class SiteOrigin_Panels_Live_Editor {
9
+
10
+ function __construct() {
11
+ add_action( 'template_redirect', array( $this, 'xss_headers' ) );
12
+ add_action( 'get_post_metadata', array( $this, 'post_metadata' ), 10, 3 );
13
+ add_action( 'wp_enqueue_scripts', array( $this, 'frontend_scripts' ) );
14
+
15
+ // Don't display the admin bar when in live editor mode
16
+ add_filter( 'show_admin_bar', '__return_false' );
17
+ }
18
+
19
+ public static function single() {
20
+ static $single;
21
+ return empty( $single ) ? $single = new self() : $single;
22
+ }
23
+
24
+ public function xss_headers(){
25
+ global $post;
26
+ if(
27
+ ! empty( $_POST['live_editor_panels_data'] ) &&
28
+ ! empty( $post->ID ) &&
29
+ current_user_can( 'edit_post', $post->ID )
30
+ ) {
31
+ // Disable XSS protection when in the Live Editor
32
+ header( 'X-XSS-Protection: 0' );
33
+ }
34
+ }
35
+
36
+
37
+ /**
38
+ * Edit the page builder data when we're viewing the live editor version. This is necessary to ensure updated styles
39
+ * are rendered in the preview.
40
+ *
41
+ * @param $value
42
+ * @param $post_id
43
+ * @param $meta_key
44
+ *
45
+ * @return array
46
+ */
47
+ function post_metadata( $value, $post_id, $meta_key ) {
48
+ if (
49
+ $meta_key == 'panels_data' &&
50
+ current_user_can( 'edit_post', $post_id ) &&
51
+ ! empty( $_POST['live_editor_panels_data'] ) &&
52
+ $_POST['live_editor_post_ID'] == $post_id
53
+ ) {
54
+ $value = array( json_decode( wp_unslash( $_POST['live_editor_panels_data'] ), true ) );
55
+ }
56
+ return $value;
57
+ }
58
+
59
+ /**
60
+ * Load the frontend scripts for the live editor
61
+ */
62
+ function frontend_scripts() {
63
+ wp_enqueue_script(
64
+ 'live-editor-front',
65
+ siteorigin_panels_url( 'js/live-editor/live-editor-front' . SITEORIGIN_PANELS_JS_SUFFIX . '.js' ),
66
+ array( 'jquery' ),
67
+ SITEORIGIN_PANELS_VERSION
68
+ );
69
+
70
+ wp_enqueue_script(
71
+ 'live-editor-scrollto',
72
+ siteorigin_panels_url( 'js/live-editor/jquery.scrollTo' . SITEORIGIN_PANELS_JS_SUFFIX . '.js' ),
73
+ array( 'jquery' ),
74
+ SITEORIGIN_PANELS_VERSION
75
+ );
76
+
77
+ wp_enqueue_style(
78
+ 'live-editor-front',
79
+ siteorigin_panels_url( 'css/live-editor-front' . SITEORIGIN_PANELS_CSS_SUFFIX . '.css' ),
80
+ array(),
81
+ SITEORIGIN_PANELS_VERSION
82
+ );
83
+ }
84
+ }
inc/renderer-legacy.php CHANGED
@@ -1,168 +1,168 @@
1
- <?php
2
-
3
- class SiteOrigin_Panels_Renderer_Legacy extends SiteOrigin_Panels_Renderer {
4
-
5
- public static function single() {
6
- static $single;
7
- return empty( $single ) ? $single = new self() : $single;
8
- }
9
-
10
- /**
11
- * Generate the CSS for the page layout.
12
- *
13
- * @param $post_id
14
- * @param $panels_data
15
- * @param $layout_data
16
- *
17
- * @return string
18
- */
19
- public function generate_css( $post_id, $panels_data = false, $layout_data = false) {
20
- // Exit if we don't have panels data
21
- if ( empty( $panels_data ) ) {
22
- $panels_data = get_post_meta( $post_id, 'panels_data', true );
23
- if( empty( $panels_data ) ) {
24
- return '';
25
- }
26
- }
27
- if ( empty( $layout_data ) ) {
28
- $layout_data = $this->get_panels_layout_data( $panels_data );
29
- $layout_data = apply_filters( 'siteorigin_panels_layout_data', $layout_data, $post_id );
30
- }
31
-
32
- // Get some of the default settings
33
- $settings = siteorigin_panels_setting();
34
- $panels_tablet_width = $settings['tablet-width'];
35
- $panels_mobile_width = $settings['mobile-width'];
36
- $panels_margin_bottom = $settings['margin-bottom'];
37
- $panels_margin_bottom_last_row = $settings['margin-bottom-last-row'];
38
-
39
- $css = new SiteOrigin_Panels_Css_Builder();
40
-
41
- $ci = 0;
42
- foreach ( $layout_data as $ri => $row ) {
43
- if( empty( $row['cells'] ) ) continue;
44
-
45
- // Let other themes and plugins change the gutter.
46
- $gutter = apply_filters( 'siteorigin_panels_css_row_gutter', $settings['margin-sides'] . 'px', $row, $ri, $panels_data );
47
- preg_match( '/([0-9\.,]+)(.*)/', $gutter, $gutter_parts );
48
-
49
- $cell_count = count( $row['cells'] );
50
-
51
- // Add the cell sizing
52
- foreach( $row['cells'] as $ci => $cell ) {
53
- $weight = apply_filters( 'siteorigin_panels_css_cell_weight', $cell['weight'], $row, $ri, $cell, $ci - 1, $panels_data, $post_id );
54
-
55
- // Add the width and ensure we have correct formatting for CSS.
56
- $css->add_cell_css( $post_id, $ri, $ci, '', array(
57
- 'width' => round( $weight * 100, 4 ) . '%',
58
- ) );
59
- }
60
-
61
- if( ! empty( $row['style']['collapse_order'] ) && $row['style']['collapse_order'] == 'right-top') {
62
- $css->add_cell_css( $post_id, $ri, false, '', array(
63
- 'float' => 'right'
64
- ) );
65
- }
66
-
67
- if(
68
- $ri != count( $layout_data ) - 1 ||
69
- ! empty( $row[ 'style' ][ 'bottom_margin' ] ) ||
70
- ! empty( $panels_margin_bottom_last_row )
71
- ) {
72
- // Filter the bottom margin for this row with the arguments
73
- $css->add_row_css( $post_id, $ri, '', array(
74
- 'margin-bottom' => apply_filters( 'siteorigin_panels_css_row_margin_bottom', $panels_margin_bottom . 'px', $row, $ri, $panels_data, $post_id )
75
- ) );
76
- }
77
-
78
- $margin_half = ( floatval( $gutter_parts[1] ) / 2 ) . $gutter_parts[2];
79
- $css->add_row_css($post_id, $ri, '', array(
80
- 'margin-left' => '-' . $margin_half,
81
- 'margin-right' => '-' . $margin_half,
82
- ) );
83
- $css->add_cell_css($post_id, $ri, false, '', array(
84
- 'padding-left' => $margin_half,
85
- 'padding-right' => $margin_half,
86
- ) );
87
- }
88
-
89
- // Add the bottom margins
90
- $css->add_widget_css( $post_id, false, false, false, '', array(
91
- 'margin-bottom' => apply_filters( 'siteorigin_panels_css_cell_margin_bottom', $panels_margin_bottom . 'px', false, false, $panels_data, $post_id )
92
- ) );
93
- $css->add_widget_css( $post_id, false, false, false, ':last-child', array(
94
- 'margin-bottom' => apply_filters( 'siteorigin_panels_css_cell_last_margin_bottom', '0px', false, false, $panels_data, $post_id )
95
- ) );
96
-
97
- if ( $settings['responsive'] ) {
98
-
99
- $css->add_cell_css($post_id, false, false, '', array(
100
- 'float' => 'none',
101
- 'width' => 'auto'
102
- ), $panels_mobile_width);
103
-
104
- $css->add_row_css($post_id, false, '', array(
105
- 'margin-left' => 0,
106
- 'margin-right' => 0,
107
- ), $panels_mobile_width);
108
-
109
- $css->add_cell_css( $post_id, false, false, '', array(
110
- 'padding' => 0,
111
- ), $panels_mobile_width );
112
-
113
- // Hide empty cells on mobile
114
- $css->add_row_css( $post_id, false, ' .panel-grid-cell-empty', array(
115
- 'display' => 'none',
116
- ), $panels_mobile_width );
117
-
118
- // Hide empty cells on mobile
119
- $css->add_row_css( $post_id, false, ' .panel-grid-cell-mobile-last', array(
120
- 'margin-bottom' => '0px',
121
- ), $panels_mobile_width );
122
-
123
- foreach ( $layout_data as $ri => $row ) {
124
- $css->add_cell_css( $post_id, $ri, false, '', array(
125
- 'margin-bottom' => $panels_margin_bottom . 'px',
126
- ), $panels_mobile_width );
127
-
128
- $css->add_cell_css( $post_id, $ri, false, ':last-child', array(
129
- 'margin-bottom' => '0px',
130
- ), $panels_mobile_width );
131
- }
132
- }
133
-
134
- foreach ( $panels_data['widgets'] as $widget_id => $widget ) {
135
- if ( ! empty( $widget['panels_info']['style']['link_color'] ) ) {
136
- $css->add_widget_css( $post_id, $widget['panels_info']['grid'], $widget['panels_info']['cell'], $widget['panels_info']['cell_index'], ' a', array(
137
- 'color' => $widget['panels_info']['style']['link_color']
138
- ) );
139
- }
140
- }
141
-
142
- // Let other plugins and components filter the CSS object.
143
- $css = apply_filters( 'siteorigin_panels_css_object', $css, $panels_data, $post_id, $layout_data );
144
-
145
- return $css->get_css();
146
- }
147
-
148
- /**
149
- * This overwrites the parent function to get the cells in reverse order when using right_on_top collapse mode.
150
- *
151
- * @param $cells The cells to modify
152
- * @param $row The row the cells belong to
153
- *
154
- * @return mixed
155
- */
156
- protected function modify_row_cells( $cells, $row ){
157
- if( ! empty( $row['style']['collapse_order'] ) && $row['style']['collapse_order'] == 'right-top') {
158
- $cells = array_reverse( $cells, true );
159
- }
160
-
161
- return $cells;
162
-
163
- }
164
-
165
- public function front_css_url(){
166
- return siteorigin_panels_url( 'css/front' . ( siteorigin_panels_setting( 'legacy-layout' ) ? '-legacy' : '' ) . '.css' );
167
- }
168
- }
1
+ <?php
2
+
3
+ class SiteOrigin_Panels_Renderer_Legacy extends SiteOrigin_Panels_Renderer {
4
+
5
+ public static function single() {
6
+ static $single;
7
+ return empty( $single ) ? $single = new self() : $single;
8
+ }
9
+
10
+ /**
11
+ * Generate the CSS for the page layout.
12
+ *
13
+ * @param $post_id
14
+ * @param $panels_data
15
+ * @param $layout_data
16
+ *
17
+ * @return string
18
+ */
19
+ public function generate_css( $post_id, $panels_data = false, $layout_data = false) {
20
+ // Exit if we don't have panels data
21
+ if ( empty( $panels_data ) ) {
22
+ $panels_data = get_post_meta( $post_id, 'panels_data', true );
23
+ if( empty( $panels_data ) ) {
24
+ return '';
25
+ }
26
+ }
27
+ if ( empty( $layout_data ) ) {
28
+ $layout_data = $this->get_panels_layout_data( $panels_data );
29
+ $layout_data = apply_filters( 'siteorigin_panels_layout_data', $layout_data, $post_id );
30
+ }
31
+
32
+ // Get some of the default settings
33
+ $settings = siteorigin_panels_setting();
34
+ $panels_tablet_width = $settings['tablet-width'];
35
+ $panels_mobile_width = $settings['mobile-width'];
36
+ $panels_margin_bottom = $settings['margin-bottom'];
37
+ $panels_margin_bottom_last_row = $settings['margin-bottom-last-row'];
38
+
39
+ $css = new SiteOrigin_Panels_Css_Builder();
40
+
41
+ $ci = 0;
42
+ foreach ( $layout_data as $ri => $row ) {
43
+ if( empty( $row['cells'] ) ) continue;
44
+
45
+ // Let other themes and plugins change the gutter.
46
+ $gutter = apply_filters( 'siteorigin_panels_css_row_gutter', $settings['margin-sides'] . 'px', $row, $ri, $panels_data );
47
+ preg_match( '/([0-9\.,]+)(.*)/', $gutter, $gutter_parts );
48
+
49
+ $cell_count = count( $row['cells'] );
50
+
51
+ // Add the cell sizing
52
+ foreach( $row['cells'] as $ci => $cell ) {
53
+ $weight = apply_filters( 'siteorigin_panels_css_cell_weight', $cell['weight'], $row, $ri, $cell, $ci - 1, $panels_data, $post_id );
54
+
55
+ // Add the width and ensure we have correct formatting for CSS.
56
+ $css->add_cell_css( $post_id, $ri, $ci, '', array(
57
+ 'width' => round( $weight * 100, 4 ) . '%',
58
+ ) );
59
+ }
60
+
61
+ if( ! empty( $row['style']['collapse_order'] ) && $row['style']['collapse_order'] == 'right-top') {
62
+ $css->add_cell_css( $post_id, $ri, false, '', array(
63
+ 'float' => 'right'
64
+ ) );
65
+ }
66
+
67
+ if(
68
+ $ri != count( $layout_data ) - 1 ||
69
+ ! empty( $row[ 'style' ][ 'bottom_margin' ] ) ||
70
+ ! empty( $panels_margin_bottom_last_row )
71
+ ) {
72
+ // Filter the bottom margin for this row with the arguments
73
+ $css->add_row_css( $post_id, $ri, '', array(
74
+ 'margin-bottom' => apply_filters( 'siteorigin_panels_css_row_margin_bottom', $panels_margin_bottom . 'px', $row, $ri, $panels_data, $post_id )
75
+ ) );
76
+ }
77
+
78
+ $margin_half = ( floatval( $gutter_parts[1] ) / 2 ) . $gutter_parts[2];
79
+ $css->add_row_css($post_id, $ri, '', array(
80
+ 'margin-left' => '-' . $margin_half,
81
+ 'margin-right' => '-' . $margin_half,
82
+ ) );
83
+ $css->add_cell_css($post_id, $ri, false, '', array(
84
+ 'padding-left' => $margin_half,
85
+ 'padding-right' => $margin_half,
86
+ ) );
87
+ }
88
+
89
+ // Add the bottom margins
90
+ $css->add_widget_css( $post_id, false, false, false, '', array(
91
+ 'margin-bottom' => apply_filters( 'siteorigin_panels_css_cell_margin_bottom', $panels_margin_bottom . 'px', false, false, $panels_data, $post_id )
92
+ ) );
93
+ $css->add_widget_css( $post_id, false, false, false, ':last-child', array(
94
+ 'margin-bottom' => apply_filters( 'siteorigin_panels_css_cell_last_margin_bottom', '0px', false, false, $panels_data, $post_id )
95
+ ) );
96
+
97
+ if ( $settings['responsive'] ) {
98
+
99
+ $css->add_cell_css($post_id, false, false, '', array(
100
+ 'float' => 'none',
101
+ 'width' => 'auto'
102
+ ), $panels_mobile_width);
103
+
104
+ $css->add_row_css($post_id, false, '', array(
105
+ 'margin-left' => 0,
106
+ 'margin-right' => 0,
107
+ ), $panels_mobile_width);
108
+
109
+ $css->add_cell_css( $post_id, false, false, '', array(
110
+ 'padding' => 0,
111
+ ), $panels_mobile_width );
112
+
113
+ // Hide empty cells on mobile
114
+ $css->add_row_css( $post_id, false, ' .panel-grid-cell-empty', array(
115
+ 'display' => 'none',
116
+ ), $panels_mobile_width );
117
+
118
+ // Hide empty cells on mobile
119
+ $css->add_row_css( $post_id, false, ' .panel-grid-cell-mobile-last', array(
120
+ 'margin-bottom' => '0px',
121
+ ), $panels_mobile_width );
122
+
123
+ foreach ( $layout_data as $ri => $row ) {
124
+ $css->add_cell_css( $post_id, $ri, false, '', array(
125
+ 'margin-bottom' => $panels_margin_bottom . 'px',
126
+ ), $panels_mobile_width );
127
+
128
+ $css->add_cell_css( $post_id, $ri, false, ':last-child', array(
129
+ 'margin-bottom' => '0px',
130
+ ), $panels_mobile_width );
131
+ }
132
+ }
133
+
134
+ foreach ( $panels_data['widgets'] as $widget_id => $widget ) {
135
+ if ( ! empty( $widget['panels_info']['style']['link_color'] ) ) {
136
+ $css->add_widget_css( $post_id, $widget['panels_info']['grid'], $widget['panels_info']['cell'], $widget['panels_info']['cell_index'], ' a', array(
137
+ 'color' => $widget['panels_info']['style']['link_color']
138
+ ) );
139
+ }
140
+ }
141
+
142
+ // Let other plugins and components filter the CSS object.
143
+ $css = apply_filters( 'siteorigin_panels_css_object', $css, $panels_data, $post_id, $layout_data );
144
+
145
+ return $css->get_css();
146
+ }
147
+
148
+ /**
149
+ * This overwrites the parent function to get the cells in reverse order when using right_on_top collapse mode.
150
+ *
151
+ * @param $cells The cells to modify
152
+ * @param $row The row the cells belong to
153
+ *
154
+ * @return mixed
155
+ */
156
+ protected function modify_row_cells( $cells, $row ){
157
+ if( ! empty( $row['style']['collapse_order'] ) && $row['style']['collapse_order'] == 'right-top') {
158
+ $cells = array_reverse( $cells, true );
159
+ }
160
+
161
+ return $cells;
162
+
163
+ }
164
+
165
+ public function front_css_url(){
166
+ return siteorigin_panels_url( 'css/front' . ( siteorigin_panels_setting( 'legacy-layout' ) ? '-legacy' : '' ) . '.css' );
167
+ }
168
+ }
inc/renderer.php CHANGED
@@ -1,864 +1,872 @@
1
- <?php
2
-
3
- class SiteOrigin_Panels_Renderer {
4
-
5
- private $inline_css;
6
-
7
- function __construct() {
8
- add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_styles' ), 1 );
9
- $this->inline_css = null;
10
- }
11
-
12
- public static function single() {
13
- static $single;
14
-
15
- return empty( $single ) ? $single = new self() : $single;
16
- }
17
-
18
- /**
19
- * Add CSS that needs to go inline.
20
- *
21
- * @param $post_id
22
- * @param $css
23
- */
24
- public function add_inline_css( $post_id, $css ) {
25
- if ( is_null( $this->inline_css ) ) {
26
- // Initialize the inline CSS array and add actions to handle printing.
27
- $this->inline_css = array();
28
- add_action( 'wp_head', array( $this, 'print_inline_css' ), 12 );
29
- add_action( 'wp_footer', array( $this, 'print_inline_css' ) );
30
- }
31
-
32
- $this->inline_css[ $post_id ] = $css;
33
-
34
- // Enqueue the front styles, if they haven't already been enqueued
35
- if ( ! wp_style_is( 'siteorigin-panels-front', 'enqueued' ) ) {
36
- wp_enqueue_style( 'siteorigin-panels-front' );
37
- }
38
- }
39
-
40
- /**
41
- * Generate the CSS for the page layout.
42
- *
43
- * @param $post_id
44
- * @param $panels_data
45
- * @param $layout_data
46
- *
47
- * @return string
48
- */
49
- public function generate_css( $post_id, $panels_data = false, $layout_data = false ) {
50
- // Exit if we don't have panels data
51
- if ( empty( $panels_data ) ) {
52
- $panels_data = get_post_meta( $post_id, 'panels_data', true );
53
- $panels_data = apply_filters( 'siteorigin_panels_data', $panels_data, $post_id );
54
- if ( empty( $panels_data ) ) {
55
- return '';
56
- }
57
- }
58
- if ( empty( $layout_data ) ) {
59
- $layout_data = $this->get_panels_layout_data( $panels_data );
60
- $layout_data = apply_filters( 'siteorigin_panels_layout_data', $layout_data, $post_id );
61
- }
62
-
63
- // Get some of the default settings
64
- $settings = siteorigin_panels_setting();
65
- $panels_tablet_width = $settings['tablet-width'];
66
- $panels_mobile_width = $settings['mobile-width'];
67
- $panels_margin_bottom = $settings['margin-bottom'];
68
- $panels_margin_bottom_last_row = $settings['margin-bottom-last-row'];
69
-
70
- $css = new SiteOrigin_Panels_Css_Builder();
71
-
72
- $ci = 0;
73
- foreach ( $layout_data as $ri => $row ) {
74
- if ( empty( $row['cells'] ) ) {
75
- continue;
76
- }
77
-
78
- // Let other themes and plugins change the gutter.
79
- $gutter = apply_filters( 'siteorigin_panels_css_row_gutter', $settings['margin-sides'] . 'px', $row, $ri, $panels_data );
80
- preg_match( '/([0-9\.,]+)(.*)/', $gutter, $gutter_parts );
81
-
82
- $cell_count = count( $row['cells'] );
83
-
84
- // Add the cell sizing
85
- foreach ( $row['cells'] as $ci => $cell ) {
86
- $weight = apply_filters( 'siteorigin_panels_css_cell_weight', $cell['weight'], $row, $ri, $cell, $ci - 1, $panels_data, $post_id );
87
- $rounded_width = round( $weight * 100, 4 ) . '%';
88
- $calc_width = 'calc(' . $rounded_width . ' - ( ' . ( 1 - $weight ) . ' * ' . $gutter . ' ) )';
89
-
90
- // Add the width and ensure we have correct formatting for CSS.
91
- $css->add_cell_css( $post_id, $ri, $ci, '', array(
92
- 'width' => array(
93
- // For some locales PHP uses ',' for decimal separation.
94
- // This seems to happen when a plugin calls `setlocale(LC_ALL, 'de_DE');` or `setlocale(LC_NUMERIC, 'de_DE');`
95
- // This should prevent issues with column sizes in these cases.
96
- str_replace( ',', '.', $rounded_width ),
97
- str_replace( ',', '.', intval($gutter) ? $calc_width : '' ), // Exclude if there's a zero gutter
98
- )
99
- ) );
100
-
101
- // Add in any widget specific CSS
102
- foreach ( $cell['widgets'] as $wi => $widget ) {
103
- $widget_style_data = ! empty( $widget['panels_info']['style'] ) ? $widget['panels_info']['style'] : array();
104
- $widget_css = apply_filters(
105
- 'siteorigin_panels_css_widget_css',
106
- array(),
107
- $widget_style_data,
108
- $row,
109
- $ri,
110
- $cell,
111
- $ci - 1,
112
- $widget,
113
- $wi,
114
- $panels_data,
115
- $post_id
116
- );
117
-
118
- $css->add_widget_css(
119
- $post_id,
120
- $ri,
121
- $ci,
122
- $wi,
123
- '',
124
- $widget_css,
125
- 1920,
126
- true
127
- );
128
- }
129
- }
130
-
131
- if (
132
- $ri != count( $layout_data ) - 1 ||
133
- ! empty( $row['style']['bottom_margin'] ) ||
134
- ! empty( $panels_margin_bottom_last_row )
135
- ) {
136
- // Filter the bottom margin for this row with the arguments
137
- $css->add_row_css( $post_id, $ri, '', array(
138
- 'margin-bottom' => apply_filters( 'siteorigin_panels_css_row_margin_bottom', $panels_margin_bottom . 'px', $row, $ri, $panels_data, $post_id )
139
- ) );
140
- }
141
-
142
- $collapse_order = ! empty( $row['style']['collapse_order'] ) ? $row['style']['collapse_order'] : ( ! is_rtl() ? 'left-top' : 'right-top' );
143
-
144
- if ( $settings['responsive'] ) {
145
-
146
- // The default collapse behaviour
147
- if ( empty( $row['style']['collapse_behaviour'] ) ) {
148
-
149
- if (
150
- $settings['tablet-layout'] &&
151
- $cell_count >= 3 &&
152
- $panels_tablet_width > $panels_mobile_width
153
- ) {
154
- // Tablet responsive css for the row
155
-
156
- $css->add_row_css( $post_id, $ri, array(
157
- '.panel-no-style',
158
- '.panel-has-style > .panel-row-style'
159
- ), array(
160
- '-ms-flex-wrap' => $collapse_order == 'left-top' ? 'wrap' : 'wrap-reverse',
161
- '-webkit-flex-wrap' => $collapse_order == 'left-top' ? 'wrap' : 'wrap-reverse',
162
- 'flex-wrap' => $collapse_order == 'left-top' ? 'wrap' : 'wrap-reverse',
163
- ), $panels_tablet_width . ':' . ( $panels_mobile_width + 1 ) );
164
-
165
- $css->add_cell_css( $post_id, $ri, false, '', array(
166
- '-ms-flex' => '0 1 50%',
167
- '-webkit-flex' => '0 1 50%',
168
- 'flex' => '0 1 50%',
169
- 'margin-right' => '0',
170
- 'margin-bottom' => $panels_margin_bottom . 'px',
171
- ), $panels_tablet_width . ':' . ( $panels_mobile_width + 1 ) );
172
-
173
-
174
- $remove_bottom_margin = ':nth-';
175
- if ( $collapse_order == 'left-top' ) {
176
- $remove_bottom_margin .= 'last-child(' . ( count( $row['cells'] ) % 2 == 0 ? '-n+2' : '1' ) . ')';
177
- } else {
178
- $remove_bottom_margin .= 'child(-n+2)';
179
- }
180
-
181
- $css->add_cell_css( $post_id, $ri, false, $remove_bottom_margin, array(
182
- 'margin-bottom' => 0,
183
- ), $panels_tablet_width . ':' . ( $panels_mobile_width + 1 )
184
- );
185
-
186
- if ( ! empty( $gutter_parts[1] ) ) {
187
- // Tablet responsive css for cells
188
-
189
- $css->add_cell_css( $post_id, $ri, false, ':nth-child(even)', array(
190
- 'padding-left' => ( floatval( $gutter_parts[1] / 2 ) . $gutter_parts[2] ),
191
- ), $panels_tablet_width . ':' . ( $panels_mobile_width + 1 ) );
192
-
193
- $css->add_cell_css( $post_id, $ri, false, ':nth-child(odd)', array(
194
- 'padding-right' => ( floatval( $gutter_parts[1] / 2 ) . $gutter_parts[2] ),
195
- ), $panels_tablet_width . ':' . ( $panels_mobile_width + 1 ) );
196
- }
197
-
198
- }
199
-
200
- // Mobile Responsive
201
- $css->add_row_css( $post_id, $ri, array(
202
- '.panel-no-style',
203
- '.panel-has-style > .panel-row-style'
204
- ), array(
205
- '-webkit-flex-direction' => $collapse_order == 'left-top' ? 'column' : 'column-reverse',
206
- '-ms-flex-direction' => $collapse_order == 'left-top' ? 'column' : 'column-reverse',
207
- 'flex-direction' => $collapse_order == 'left-top' ? 'column' : 'column-reverse',
208
- ), $panels_mobile_width );
209
-
210
- $css->add_cell_css( $post_id, $ri, false, '', array(
211
- 'width' => '100%',
212
- 'margin-right' => 0,
213
- ), $panels_mobile_width );
214
- }
215
-
216
-
217
- foreach ( $row['cells'] as $ci => $cell ) {
218
- if ( ( $collapse_order == 'left-top' && $ci != $cell_count - 1 ) || ( $collapse_order == 'right-top' && $ci !== 0 ) ) {
219
- $css->add_cell_css( $post_id, $ri, $ci, '', array(
220
- 'margin-bottom' => $panels_margin_bottom . 'px',
221
- ), $panels_mobile_width );
222
- }
223
- }
224
- }
225
-
226
- }
227
-
228
- // Add the bottom margins
229
- $css->add_widget_css( $post_id, false, false, false, '', array(
230
- 'margin-bottom' => apply_filters( 'siteorigin_panels_css_cell_margin_bottom', $panels_margin_bottom . 'px', false, false, $panels_data, $post_id )
231
- ) );
232
- $css->add_widget_css( $post_id, false, false, false, ':last-child', array(
233
- 'margin-bottom' => apply_filters( 'siteorigin_panels_css_cell_last_margin_bottom', '0px', false, false, $panels_data, $post_id )
234
- ) );
235
-
236
- if ( $settings['responsive'] ) {
237
- $css->add_cell_css( $post_id, false, false, '', array(
238
- 'padding' => 0,
239
- ), $panels_mobile_width );
240
-
241
- // Hide empty cells on mobile
242
- $css->add_row_css( $post_id, false, ' .panel-grid-cell-empty', array(
243
- 'display' => 'none',
244
- ), $panels_mobile_width );
245
-
246
- // Hide empty cells on mobile
247
- $css->add_row_css( $post_id, false, ' .panel-grid-cell-mobile-last', array(
248
- 'margin-bottom' => '0px',
249
- ), $panels_mobile_width );
250
- }
251
-
252
- // Let other plugins and components filter the CSS object.
253
- $css = apply_filters( 'siteorigin_panels_css_object', $css, $panels_data, $post_id, $layout_data );
254
-
255
- return $css->get_css();
256
- }
257
-
258
- /**
259
- * Render the panels.
260
- *
261
- * @param int|string|bool $post_id The Post ID or 'home'.
262
- * @param bool $enqueue_css Should we also enqueue the layout CSS.
263
- * @param array|bool $panels_data Existing panels data. By default load from settings or post meta.
264
- * @param array $layout_data Reformatted panels_data that includes data about the render.
265
- *
266
- * @return string
267
- */
268
- function render( $post_id = false, $enqueue_css = true, $panels_data = false, & $layout_data = array(), $is_preview = false ) {
269
-
270
- if ( empty( $post_id ) ) {
271
- $post_id = get_the_ID();
272
-
273
- if ( class_exists( 'WooCommerce' ) && is_shop() ) {
274
- $post_id = wc_get_page_id( 'shop' );
275
- }
276
- }
277
-
278
- global $siteorigin_panels_current_post;
279
- $old_current_post = $siteorigin_panels_current_post;
280
- $siteorigin_panels_current_post = $post_id;
281
-
282
- // Try get the cached panel from in memory cache.
283
- global $siteorigin_panels_cache;
284
- if ( ! empty( $siteorigin_panels_cache ) && ! empty( $siteorigin_panels_cache[ $post_id ] ) ) {
285
- return $siteorigin_panels_cache[ $post_id ];
286
- }
287
-
288
- if ( empty( $panels_data ) ) {
289
- $panels_data = $this->get_panels_data_for_post( $post_id );
290
- if ( $panels_data === false ) {
291
- return false;
292
- }
293
- }
294
-
295
- $panels_data = apply_filters( 'siteorigin_panels_data', $panels_data, $post_id );
296
- if ( empty( $panels_data ) || empty( $panels_data['grids'] ) ) {
297
- return '';
298
- }
299
-
300
- if ( $is_preview ) {
301
- $GLOBALS[ 'SITEORIGIN_PANELS_PREVIEW_RENDER' ] = true;
302
- }
303
-
304
- $layout_data = $this->get_panels_layout_data( $panels_data );
305
- $layout_data = apply_filters( 'siteorigin_panels_layout_data', $layout_data, $post_id );
306
-
307
- ob_start();
308
-
309
- // Add the panel layout wrapper
310
- $layout_classes = apply_filters( 'siteorigin_panels_layout_classes', array( 'panel-layout' ), $post_id, $panels_data );
311
- if ( is_rtl() ) {
312
- $layout_classes[] = 'panel-is-rtl';
313
- }
314
- $layout_attributes = apply_filters( 'siteorigin_panels_layout_attributes', array(
315
- 'id' => 'pl-' . $post_id,
316
- 'class' => implode( ' ', $layout_classes ),
317
- ), $post_id, $panels_data );
318
-
319
- $this->render_element( 'div', $layout_attributes );
320
-
321
- echo apply_filters( 'siteorigin_panels_before_content', '', $panels_data, $post_id );
322
-
323
- foreach ( $layout_data as $ri => & $row ) {
324
- $this->render_row( $post_id, $ri, $row, $panels_data );
325
- }
326
-
327
- echo apply_filters( 'siteorigin_panels_after_content', '', $panels_data, $post_id );
328
-
329
- echo '</div>';
330
-
331
- do_action( 'siteorigin_panels_after_render', $panels_data, $post_id );
332
-
333
- $html = ob_get_clean();
334
-
335
- if ( $enqueue_css && ! isset( $this->inline_css[ $post_id ] ) ) {
336
- wp_enqueue_style( 'siteorigin-panels-front' );
337
- $this->add_inline_css( $post_id, $this->generate_css( $post_id, $panels_data, $layout_data ) );
338
- }
339
-
340
- // Reset the current post
341
- $siteorigin_panels_current_post = $old_current_post;
342
-
343
- $rendered_layout = apply_filters( 'siteorigin_panels_render', $html, $post_id, ! empty( $post ) ? $post : null );
344
-
345
- if ( $is_preview ) {
346
- $widget_css = '@import url(' . SiteOrigin_Panels::front_css_url() . '); ';
347
- $widget_css .= SiteOrigin_Panels::renderer()->generate_css( $post_id, $panels_data, $layout_data );
348
- $widget_css = preg_replace( '/\s+/', ' ', $widget_css );
349
- $rendered_layout .= "\n\n" .
350
- '<style type="text/css" class="panels-style" data-panels-style-for-post="' . esc_attr( $post_id ) . '">' .
351
- $widget_css .
352
- '</style>';
353
- }
354
-
355
- unset( $GLOBALS[ 'SITEORIGIN_PANELS_PREVIEW_RENDER' ] );
356
-
357
- return $rendered_layout;
358
- }
359
-
360
- /**
361
- * Echo the style wrapper and return if there was a wrapper
362
- *
363
- * @param string $name The name of the style wrapper
364
- * @param array $style The style wrapper args. Used as an argument for siteorigin_panels_{$name}_style_attributes
365
- * @param string|bool $for An identifier of what this style wrapper is for
366
- *
367
- * @return bool Is there a style wrapper
368
- */
369
- private function start_style_wrapper( $name, $style = array(), $for = false ) {
370
- $attributes = array();
371
-
372
- if ( empty( $attributes['class'] ) ) {
373
- $attributes['class'] = array();
374
- }
375
- if ( empty( $attributes['style'] ) ) {
376
- $attributes['style'] = '';
377
- }
378
-
379
- // Get everything related to the style wrapper
380
- $attributes = apply_filters( 'siteorigin_panels_' . $name . '_style_attributes', $attributes, $style );
381
- $attributes = apply_filters( 'siteorigin_panels_general_style_attributes', $attributes, $style );
382
-
383
- $standard_css = array();
384
- $standard_css = apply_filters( 'siteorigin_panels_' . $name . '_style_css', $standard_css, $style );
385
- $standard_css = apply_filters( 'siteorigin_panels_general_style_css', $standard_css, $style );
386
-
387
- $mobile_css = array();
388
- $mobile_css = apply_filters( 'siteorigin_panels_' . $name . '_style_mobile_css', $mobile_css, $style );
389
- $mobile_css = apply_filters( 'siteorigin_panels_general_style_mobile_css', $mobile_css, $style );
390
-
391
- // Remove anything we didn't actually use
392
- if ( empty( $attributes['class'] ) ) {
393
- unset( $attributes['class'] );
394
- }
395
- if ( empty( $attributes['style'] ) ) {
396
- unset( $attributes['style'] );
397
- }
398
-
399
- $style_wrapper = '';
400
- if ( ! empty( $attributes ) || ! empty( $standard_css ) || ! empty( $mobile_css ) ) {
401
- if ( empty( $attributes['class'] ) ) {
402
- $attributes['class'] = array();
403
- }
404
- $attributes['class'][] = 'panel-' . $name . '-style';
405
- if ( ! empty( $for ) ) {
406
- $attributes['class'][] = 'panel-' . $name . '-style-for-' . sanitize_html_class( $for );
407
- }
408
- $attributes['class'] = array_unique( $attributes['class'] );
409
-
410
- // Filter and sanitize the classes
411
- $attributes['class'] = apply_filters( 'siteorigin_panels_' . $name . '_style_classes', $attributes['class'], $attributes, $style );
412
- $attributes['class'] = array_map( 'sanitize_html_class', $attributes['class'] );
413
-
414
- $style_wrapper = '<div ';
415
- foreach ( $attributes as $name => $value ) {
416
- // Attributes start with _ are used for internal communication between filters, so are not added to the HTML
417
- // We don't make use of this in our styling, so its left as a mechanism for other plugins.
418
- if ( substr( $name, 0, 1 ) === '_' ) {
419
- continue;
420
- }
421
-
422
- if ( is_array( $value ) ) {
423
- $style_wrapper .= $name . '="' . esc_attr( implode( " ", array_unique( $value ) ) ) . '" ';
424
- } else {
425
- $style_wrapper .= $name . '="' . esc_attr( $value ) . '" ';
426
- }
427
- }
428
- $style_wrapper .= '>';
429
-
430
- return $style_wrapper;
431
- }
432
-
433
- return $style_wrapper;
434
- }
435
-
436
- /**
437
- * Render the widget.
438
- *
439
- * @param array $widget_info The widget info.
440
- * @param array $instance The widget instance
441
- * @param int $grid_index The grid index.
442
- * @param int $cell_index The cell index.
443
- * @param int $widget_index The index of this widget.
444
- * @param bool $is_first Is this the first widget in the cell.
445
- * @param bool $is_last Is this the last widget in the cell.
446
- * @param bool $post_id
447
- * @param string $style_wrapper The start of the style wrapper
448
- */
449
- function the_widget( $widget_info, $instance, $grid_index, $cell_index, $widget_index, $is_first, $is_last, $post_id = false, $style_wrapper = '' ) {
450
-
451
- // Set widget class to $widget
452
- $widget_class = $widget_info['class'];
453
- $widget_class = apply_filters( 'siteorigin_panels_widget_class', $widget_class );
454
-
455
- // Load the widget from the widget factory and give themes and plugins a chance to provide their own
456
- $the_widget = SiteOrigin_Panels::get_widget_instance( $widget_class );
457
- $the_widget = apply_filters( 'siteorigin_panels_widget_object', $the_widget, $widget_class, $instance );
458
-
459
- if ( empty( $post_id ) ) {
460
- $post_id = get_the_ID();
461
-
462
- if ( class_exists( 'WooCommerce' ) && is_shop() ) {
463
- $post_id = wc_get_page_id( 'shop' );
464
- }
465
- }
466
-
467
- $classes = array( 'so-panel' );
468
- if ( siteorigin_panels_setting( 'add-widget-class' ) ) {
469
- $classes[] = 'widget';
470
- }
471
- if ( ! empty( $the_widget ) && ! empty( $the_widget->id_base ) ) {
472
- $classes[] = 'widget_' . $the_widget->id_base;
473
- }
474
- if ( ! empty( $the_widget ) && is_array( $the_widget->widget_options ) && ! empty( $the_widget->widget_options['classname'] ) ) {
475
- $classes[] = $the_widget->widget_options['classname'];
476
- }
477
- if ( $is_first ) {
478
- $classes[] = 'panel-first-child';
479
- }
480
- if ( $is_last ) {
481
- $classes[] = 'panel-last-child';
482
- }
483
- $id = 'panel-' . $post_id . '-' . $grid_index . '-' . $cell_index . '-' . $widget_index;
484
-
485
- // Filter and sanitize the classes
486
- $classes = apply_filters( 'siteorigin_panels_widget_classes', $classes, $widget_class, $instance, $widget_info );
487
- $classes = explode( ' ', implode( ' ', $classes ) );
488
- $classes = array_filter( $classes );
489
- $classes = array_unique( $classes );
490
- $classes = array_map( 'sanitize_html_class', $classes );
491
-
492
- $title_html = siteorigin_panels_setting( 'title-html' );
493
- if ( strpos( $title_html, '{{title}}' ) !== false ) {
494
- list( $before_title, $after_title ) = explode( '{{title}}', $title_html, 2 );
495
- } else {
496
- $before_title = '<h3 class="widget-title">';
497
- $after_title = '</h3>';
498
- }
499
-
500
- // Attributes of the widget wrapper
501
- $attributes = apply_filters( 'siteorigin_panels_widget_attributes', array(
502
- 'id' => $id,
503
- 'class' => implode( ' ', $classes ),
504
- 'data-index' => $widget_info['widget_index'],
505
- ), $widget_info );
506
-
507
- $before_widget = '<div ';
508
- foreach ( $attributes as $k => $v ) {
509
- $before_widget .= esc_attr( $k ) . '="' . esc_attr( $v ) . '" ';
510
- }
511
- $before_widget .= '>';
512
-
513
- $args = array(
514
- 'before_widget' => $before_widget,
515
- 'after_widget' => '</div>',
516
- 'before_title' => $before_title,
517
- 'after_title' => $after_title,
518
- 'widget_id' => 'widget-' . $grid_index . '-' . $cell_index . '-' . $widget_index
519
- );
520
-
521
- // Let other themes and plugins change the arguments that go to the widget class.
522
- $args = apply_filters( 'siteorigin_panels_widget_args', $args );
523
-
524
- // If there is a style wrapper, add it.
525
- if ( ! empty( $style_wrapper ) ) {
526
- $args['before_widget'] = $args['before_widget'] . $style_wrapper;
527
- $args['after_widget'] = '</div>' . $args['after_widget'];
528
- }
529
-
530
- // This gives other plugins the chance to take over rendering of widgets
531
- $widget_html = apply_filters( 'siteorigin_panels_the_widget_html', '', $the_widget, $args, $instance );
532
-
533
- if ( ! empty( $widget_html ) ) {
534
- echo $args['before_widget'];
535
- echo $widget_html;
536
- echo $args['after_widget'];
537
- } else if ( ! empty( $the_widget ) && is_a( $the_widget, 'WP_Widget' ) ) {
538
- $the_widget->widget( $args, $instance );
539
- } else {
540
- // This gives themes a chance to display some sort of placeholder for missing widgets
541
- echo apply_filters( 'siteorigin_panels_missing_widget', $args['before_widget'] . $args['after_widget'], $widget_class, $args, $instance );
542
- }
543
- }
544
-
545
- /**
546
- * Print inline CSS in the header and footer.
547
- */
548
- function print_inline_css() {
549
- if ( ! empty( $this->inline_css ) ) {
550
- $the_css = '';
551
- foreach ( $this->inline_css as $post_id => $css ) {
552
- if ( empty( $css ) ) {
553
- continue;
554
- }
555
- $the_css .= '/* Layout ' . esc_attr( $post_id ) . ' */ ';
556
- $the_css .= $css;
557
- }
558
-
559
- // Reset the inline CSS
560
- $this->inline_css = null;
561
-
562
- switch ( current_filter() ) {
563
- case 'wp_head' :
564
- $css_id = 'head';
565
- break;
566
-
567
- case 'wp_footer' :
568
- $css_id = 'footer';
569
- break;
570
-
571
- default :
572
- $css_id = sanitize_html_class( current_filter() );
573
- break;
574
- }
575
-
576
- // Allow third party developers to change the inline styles or remove them completely.
577
- $the_css = apply_filters( 'siteorigin_panels_inline_styles', $the_css );
578
-
579
- if ( ! empty( $the_css ) ) {
580
- ?>
581
- <style type="text/css" media="all"
582
- id="siteorigin-panels-layouts-<?php echo esc_attr( $css_id ) ?>"><?php echo $the_css ?></style><?php
583
- }
584
- }
585
- }
586
-
587
- /**
588
- * Enqueue the required styles
589
- */
590
- function enqueue_styles() {
591
- // Register the style to support possible lazy loading
592
- wp_register_style( 'siteorigin-panels-front', SiteOrigin_Panels::front_css_url(), array(), SITEORIGIN_PANELS_VERSION );
593
- }
594
-
595
- /**
596
- * Retrieve panels data for a post or a prebuilt layout or the home page layout.
597
- *
598
- * @param string $post_id
599
- *
600
- * @return array
601
- */
602
- private function get_panels_data_for_post( $post_id ) {
603
- if ( SiteOrigin_Panels::is_live_editor() ) {
604
- if (
605
- current_user_can( 'edit_post', $post_id ) &&
606
- ! empty( $_POST['live_editor_panels_data'] ) &&
607
- $_POST['live_editor_post_ID'] == $post_id
608
- ) {
609
- $panels_data = json_decode( wp_unslash( $_POST['live_editor_panels_data'] ), true );
610
-
611
- if ( ! empty( $panels_data['widgets'] ) ) {
612
- $panels_data['widgets'] = SiteOrigin_Panels_Admin::single()->process_raw_widgets( $panels_data['widgets'] );
613
- }
614
- }
615
- } else if ( strpos( $post_id, 'prebuilt:' ) === 0 ) {
616
- list( $null, $prebuilt_id ) = explode( ':', $post_id, 2 );
617
- $layouts = apply_filters( 'siteorigin_panels_prebuilt_layouts', array() );
618
- $panels_data = ! empty( $layouts[ $prebuilt_id ] ) ? $layouts[ $prebuilt_id ] : array();
619
- } else if ( $post_id == 'home' ) {
620
- $page_id = get_option( 'page_on_front' );
621
- if ( empty( $page_id ) ) {
622
- $page_id = get_option( 'siteorigin_panels_home_page_id' );
623
- }
624
-
625
- $panels_data = ! empty( $page_id ) ? get_post_meta( $page_id, 'panels_data', true ) : null;
626
-
627
- if ( is_null( $panels_data ) ) {
628
- // Load the default layout
629
- $layouts = apply_filters( 'siteorigin_panels_prebuilt_layouts', array() );
630
- $prebuilt_id = siteorigin_panels_setting( 'home-page-default' ) ? siteorigin_panels_setting( 'home-page-default' ) : 'home';
631
-
632
- $panels_data = ! empty( $layouts[ $prebuilt_id ] ) ? $layouts[ $prebuilt_id ] : current( $layouts );
633
- }
634
- } else {
635
- if ( post_password_required( $post_id ) ) {
636
- return false;
637
- }
638
- $panels_data = get_post_meta( $post_id, 'panels_data', true );
639
- }
640
-
641
- return $panels_data;
642
- }
643
-
644
- /**
645
- * Transform flat panels data into a hierarchical structure.
646
- *
647
- * @param array $panels_data Flat panels data containing `grids`, `grid_cells`, and `widgets`.
648
- *
649
- * @return array Hierarchical structure of rows => cells => widgets.
650
- */
651
- public function get_panels_layout_data( $panels_data ) {
652
- $layout_data = array();
653
-
654
- foreach ( $panels_data['grids'] as $grid ) {
655
- $layout_data[] = array(
656
- 'style' => ! empty( $grid['style'] ) ? $grid['style'] : array(),
657
- 'ratio' => ! empty( $grid['ratio'] ) ? $grid['ratio'] : '',
658
- 'ratio_direction' => ! empty( $grid['ratio_direction'] ) ? $grid['ratio_direction'] : '',
659
- 'color_label' => ! empty( $grid['color_label'] ) ? $grid['color_label'] : '',
660
- 'label' => ! empty( $grid['label'] ) ? $grid['label'] : '',
661
- 'cells' => array()
662
- );
663
- }
664
-
665
- foreach ( $panels_data['grid_cells'] as $cell ) {
666
- $layout_data[ $cell['grid'] ]['cells'][] = array(
667
- 'widgets' => array(),
668
- 'style' => ! empty( $cell['style'] ) ? $cell['style'] : array(),
669
- 'weight' => floatval( $cell['weight'] ),
670
- );
671
- }
672
-
673
- foreach ( $panels_data['widgets'] as $i => $widget ) {
674
- $widget['panels_info']['widget_index'] = $i;
675
- $row_index = intval( $widget['panels_info']['grid'] );
676
- $cell_index = intval( $widget['panels_info']['cell'] );
677
- $layout_data[ $row_index ]['cells'][ $cell_index ]['widgets'][] = $widget;
678
- }
679
-
680
- return $layout_data;
681
- }
682
-
683
- /**
684
- * Outputs the given HTML tag with the given attributes.
685
- *
686
- * @param string $tag The HTML element to render.
687
- * @param array $attributes The attributes for the HTML element.
688
- *
689
- */
690
- private function render_element( $tag, $attributes ) {
691
-
692
- echo '<' . $tag;
693
- foreach ( $attributes as $name => $value ) {
694
- if ( $value ) {
695
- echo ' ' . $name . '="' . esc_attr( $value ) . '" ';
696
- }
697
- }
698
- echo '>';
699
-
700
- }
701
-
702
- /**
703
- * Render everything for the given row, including:
704
- * - filters before and after row,
705
- * - row style wrapper,
706
- * - row element wrapper with attributes,
707
- * - child cells
708
- *
709
- * @param string $post_id The ID of the post containing this layout.
710
- * @param int $ri The index of this row.
711
- * @param array $row The model containing this row's data and child cells.
712
- * @param array $panels_data A copy of panels_data for filters.
713
- *
714
- */
715
- private function render_row( $post_id, $ri, & $row, & $panels_data ) {
716
- $row_style_wrapper = $this->start_style_wrapper( 'row', ! empty( $row['style'] ) ? $row['style'] : array(), $post_id . '-' . $ri );
717
-
718
- $row_classes = array( 'panel-grid' );
719
- $row_classes[] = ! empty( $row_style_wrapper ) ? 'panel-has-style' : 'panel-no-style';
720
- $row_classes = apply_filters( 'siteorigin_panels_row_classes', $row_classes, $row );
721
-
722
- $row_attributes = apply_filters( 'siteorigin_panels_row_attributes', array(
723
- 'id' => 'pg-' . $post_id . '-' . $ri,
724
- 'class' => implode( ' ', $row_classes ),
725
- ), $row );
726
-
727
- // This allows other themes and plugins to add html before the row
728
- echo apply_filters( 'siteorigin_panels_before_row', '', $row, $row_attributes );
729
-
730
- $this->render_element( 'div', $row_attributes );
731
-
732
- if ( ! empty( $row_style_wrapper ) ) {
733
- echo $row_style_wrapper;
734
- }
735
-
736
- if( method_exists( $this, 'modify_row_cells' ) ) {
737
- // This gives other renderers a chance to change the cell order
738
- $row['cells'] = $cells = $this->modify_row_cells( $row['cells'], $row );
739
- }
740
-
741
- foreach ( $row['cells'] as $ci => & $cell ) {
742
- $this->render_cell( $post_id, $ri, $ci, $cell, $row['cells'], $panels_data );
743
- }
744
-
745
- // Close the style wrapper
746
- if ( ! empty( $row_style_wrapper ) ) {
747
- echo '</div>';
748
- }
749
-
750
- echo '</div>';
751
-
752
- // This allows other themes and plugins to add html after the row
753
- echo apply_filters( 'siteorigin_panels_after_row', '', $row, $row_attributes );
754
-
755
- }
756
-
757
- /**
758
- *
759
- * Render everything for the given cell, including:
760
- * - filters before and after cell,
761
- * - cell element wrapper with attributes,
762
- * - style wrapper,
763
- * - child widgets
764
- *
765
- * @param string $post_id The ID of the post containing this layout.
766
- * @param int $ri The index of this cell's parent row.
767
- * @param int $ci The index of this cell.
768
- * @param array $cell The model containing this cell's data and child widgets.
769
- * @param array $cells The array of cells containing this cell.
770
- * @param array $panels_data A copy of panels_data for filters
771
- */
772
- private function render_cell( $post_id, $ri, $ci, & $cell, $cells, & $panels_data ) {
773
-
774
- $cell_classes = array( 'panel-grid-cell' );
775
-
776
- if ( empty( $cell['widgets'] ) ) {
777
- $cell_classes[] = 'panel-grid-cell-empty';
778
- }
779
-
780
- if ( $ci == count( $cells ) - 2 && count( $cells[ $ci + 1 ]['widgets'] ) == 0 ) {
781
- $cell_classes[] = 'panel-grid-cell-mobile-last';
782
- }
783
-
784
- // Themes can add their own styles to cells
785
- $cell_classes = apply_filters( 'siteorigin_panels_cell_classes', $cell_classes, $cell );
786
-
787
- // Legacy filter, use `siteorigin_panels_cell_classes` instead
788
- $cell_classes = apply_filters( 'siteorigin_panels_row_cell_classes', $cell_classes, $panels_data, $cell );
789
-
790
- $cell_attributes = apply_filters( 'siteorigin_panels_cell_attributes', array(
791
- 'id' => 'pgc-' . $post_id . '-' . $ri . '-' . $ci,
792
- 'class' => implode( ' ', $cell_classes ),
793
- ), $cell );
794
-
795
- // Legacy filter, use `siteorigin_panels_cell_attributes` instead
796
- $cell_attributes = apply_filters( 'siteorigin_panels_row_cell_attributes', $cell_attributes, $panels_data, $cell );
797
-
798
- echo apply_filters( 'siteorigin_panels_before_cell', '', $cell, $cell_attributes );
799
-
800
- $this->render_element( 'div', $cell_attributes );
801
-
802
- $grid = $panels_data['grids'][ $ri ];
803
-
804
- if ( empty( $cell['style']['class'] ) && ! empty( $grid['style']['cell_class'] ) ) {
805
- $cell['style']['class'] = $grid['style']['cell_class'];
806
- }
807
-
808
- $cell_style = ! empty( $cell['style'] ) ? $cell['style'] : array();
809
- $cell_style_wrapper = $this->start_style_wrapper( 'cell', $cell_style, $post_id . '-' . $ri . '-' . $ci );
810
- if ( ! empty( $cell_style_wrapper ) ) {
811
- echo $cell_style_wrapper;
812
- }
813
-
814
- foreach ( $cell['widgets'] as $wi => & $widget ) {
815
- $is_last = ( $wi == count( $cell['widgets'] ) - 1 );
816
- $this->render_widget( $post_id, $ri, $ci, $wi, $widget, $is_last );
817
- }
818
-
819
- if ( ! empty( $cell_style_wrapper ) ) {
820
- echo '</div>';
821
- }
822
- echo '</div>';
823
-
824
- echo apply_filters( 'siteorigin_panels_after_cell', '', $cell, $cell_attributes );
825
- }
826
-
827
- /**
828
- *
829
- * Gets the style wrapper for this widget and passes it through to `the_widget` along with other required parameters.
830
- *
831
- * @param string $post_id The ID of the post containing this layout.
832
- * @param int $ri The index of this widget's ancestor row.
833
- * @param int $ci The index of this widget's parent cell.
834
- * @param int $wi The index of this widget.
835
- * @param array $widget The model containing this widget's data.
836
- * @param bool $is_last Whether this is the last widget in the parent cell.
837
- *
838
- */
839
- private function render_widget( $post_id, $ri, $ci, $wi, & $widget, $is_last ) {
840
-
841
- $widget_style_wrapper = $this->start_style_wrapper(
842
- 'widget',
843
- ! empty( $widget['panels_info']['style'] ) ? $widget['panels_info']['style'] : array(),
844
- $post_id . '-' . $ri . '-' . $ci . '-' . $wi
845
- );
846
-
847
- $this->the_widget(
848
- $widget['panels_info'],
849
- $widget,
850
- $ri,
851
- $ci,
852
- $wi,
853
- $wi == 0,
854
- $is_last,
855
- $post_id,
856
- $widget_style_wrapper
857
- );
858
-
859
- }
860
-
861
- public function front_css_url() {
862
- return siteorigin_panels_url( 'css/front-flex' . SITEORIGIN_PANELS_CSS_SUFFIX . '.css' );
863
- }
864
- }
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class SiteOrigin_Panels_Renderer {
4
+
5
+ private $inline_css;
6
+
7
+ function __construct() {
8
+ add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_styles' ), 1 );
9
+ $this->inline_css = null;
10
+ }
11
+
12
+ public static function single() {
13
+ static $single;
14
+
15
+ return empty( $single ) ? $single = new self() : $single;
16
+ }
17
+
18
+ /**
19
+ * Add CSS that needs to go inline.
20
+ *
21
+ * @param $post_id
22
+ * @param $css
23
+ */
24
+ public function add_inline_css( $post_id, $css ) {
25
+ if ( is_null( $this->inline_css ) ) {
26
+ // Initialize the inline CSS array and add actions to handle printing.
27
+ $this->inline_css = array();
28
+ add_action( 'wp_head', array( $this, 'print_inline_css' ), 12 );
29
+ add_action( 'wp_footer', array( $this, 'print_inline_css' ) );
30
+ }
31
+
32
+ $this->inline_css[ $post_id ] = $css;
33
+
34
+ // Enqueue the front styles, if they haven't already been enqueued
35
+ if ( ! wp_style_is( 'siteorigin-panels-front', 'enqueued' ) ) {
36
+ wp_enqueue_style( 'siteorigin-panels-front' );
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Generate the CSS for the page layout.
42
+ *
43
+ * @param $post_id
44
+ * @param $panels_data
45
+ * @param $layout_data
46
+ *
47
+ * @return string
48
+ */
49
+ public function generate_css( $post_id, $panels_data = false, $layout_data = false ) {
50
+ // Exit if we don't have panels data
51
+ if ( empty( $panels_data ) ) {
52
+ $panels_data = get_post_meta( $post_id, 'panels_data', true );
53
+ $panels_data = apply_filters( 'siteorigin_panels_data', $panels_data, $post_id );
54
+ if ( empty( $panels_data ) ) {
55
+ return '';
56
+ }
57
+ }
58
+ if ( empty( $layout_data ) ) {
59
+ $layout_data = $this->get_panels_layout_data( $panels_data );
60
+ $layout_data = apply_filters( 'siteorigin_panels_layout_data', $layout_data, $post_id );
61
+ }
62
+
63
+ // Get some of the default settings
64
+ $settings = siteorigin_panels_setting();
65
+ $panels_tablet_width = $settings['tablet-width'];
66
+ $panels_mobile_width = $settings['mobile-width'];
67
+ $panels_margin_bottom_last_row = $settings['margin-bottom-last-row'];
68
+
69
+ $css = new SiteOrigin_Panels_Css_Builder();
70
+
71
+ $ci = 0;
72
+ foreach ( $layout_data as $ri => $row ) {
73
+
74
+ // Filter the bottom margin for this row with the arguments
75
+ $panels_margin_bottom = apply_filters( 'siteorigin_panels_css_row_margin_bottom', $settings['margin-bottom'] . 'px', $row, $ri, $panels_data, $post_id );
76
+ $panels_mobile_margin_bottom = apply_filters( 'siteorigin_panels_css_row_mobile_margin_bottom', $settings['margin-bottom'] . 'px', $row, $ri, $panels_data, $post_id );
77
+
78
+ if ( empty( $row['cells'] ) ) {
79
+ continue;
80
+ }
81
+
82
+ // Let other themes and plugins change the gutter.
83
+ $gutter = apply_filters( 'siteorigin_panels_css_row_gutter', $settings['margin-sides'] . 'px', $row, $ri, $panels_data );
84
+ preg_match( '/([0-9\.,]+)(.*)/', $gutter, $gutter_parts );
85
+
86
+ $cell_count = count( $row['cells'] );
87
+
88
+ // Add the cell sizing
89
+ foreach ( $row['cells'] as $ci => $cell ) {
90
+ $weight = apply_filters( 'siteorigin_panels_css_cell_weight', $cell['weight'], $row, $ri, $cell, $ci - 1, $panels_data, $post_id );
91
+ $rounded_width = round( $weight * 100, 4 ) . '%';
92
+ $calc_width = 'calc(' . $rounded_width . ' - ( ' . ( 1 - $weight ) . ' * ' . $gutter . ' ) )';
93
+
94
+ // Add the width and ensure we have correct formatting for CSS.
95
+ $css->add_cell_css( $post_id, $ri, $ci, '', array(
96
+ 'width' => array(
97
+ // For some locales PHP uses ',' for decimal separation.
98
+ // This seems to happen when a plugin calls `setlocale(LC_ALL, 'de_DE');` or `setlocale(LC_NUMERIC, 'de_DE');`
99
+ // This should prevent issues with column sizes in these cases.
100
+ str_replace( ',', '.', $rounded_width ),
101
+ str_replace( ',', '.', intval($gutter) ? $calc_width : '' ), // Exclude if there's a zero gutter
102
+ )
103
+ ) );
104
+
105
+ // Add in any widget specific CSS
106
+ foreach ( $cell['widgets'] as $wi => $widget ) {
107
+ $widget_style_data = ! empty( $widget['panels_info']['style'] ) ? $widget['panels_info']['style'] : array();
108
+ $widget_css = apply_filters(
109
+ 'siteorigin_panels_css_widget_css',
110
+ array(),
111
+ $widget_style_data,
112
+ $row,
113
+ $ri,
114
+ $cell,
115
+ $ci - 1,
116
+ $widget,
117
+ $wi,
118
+ $panels_data,
119
+ $post_id
120
+ );
121
+
122
+ $css->add_widget_css(
123
+ $post_id,
124
+ $ri,
125
+ $ci,
126
+ $wi,
127
+ '',
128
+ $widget_css,
129
+ 1920,
130
+ true
131
+ );
132
+ }
133
+ }
134
+
135
+ if (
136
+ $ri != count( $layout_data ) - 1 ||
137
+ ! empty( $row['style']['bottom_margin'] ) ||
138
+ ! empty( $panels_margin_bottom_last_row )
139
+ ) {
140
+ $css->add_row_css( $post_id, $ri, '', array(
141
+ 'margin-bottom' => $panels_margin_bottom
142
+ ) );
143
+ }
144
+
145
+ $collapse_order = ! empty( $row['style']['collapse_order'] ) ? $row['style']['collapse_order'] : ( ! is_rtl() ? 'left-top' : 'right-top' );
146
+
147
+ if ( $settings['responsive'] && empty( $row['style']['collapse_behaviour'] ) ) {
148
+ // The default collapse behaviour
149
+ if (
150
+ $settings['tablet-layout'] &&
151
+ $cell_count >= 3 &&
152
+ $panels_tablet_width > $panels_mobile_width
153
+ ) {
154
+ // Tablet responsive css for the row
155
+
156
+ $css->add_row_css( $post_id, $ri, array(
157
+ '.panel-no-style',
158
+ '.panel-has-style > .panel-row-style'
159
+ ), array(
160
+ '-ms-flex-wrap' => $collapse_order == 'left-top' ? 'wrap' : 'wrap-reverse',
161
+ '-webkit-flex-wrap' => $collapse_order == 'left-top' ? 'wrap' : 'wrap-reverse',
162
+ 'flex-wrap' => $collapse_order == 'left-top' ? 'wrap' : 'wrap-reverse',
163
+ ), $panels_tablet_width . ':' . ( $panels_mobile_width + 1 ) );
164
+
165
+ $css->add_cell_css( $post_id, $ri, false, '', array(
166
+ '-ms-flex' => '0 1 50%',
167
+ '-webkit-flex' => '0 1 50%',
168
+ 'flex' => '0 1 50%',
169
+ 'margin-right' => '0',
170
+ 'margin-bottom' => $panels_margin_bottom,
171
+ ), $panels_tablet_width . ':' . ( $panels_mobile_width + 1 ) );
172
+
173
+
174
+ $remove_bottom_margin = ':nth-';
175
+ if ( $collapse_order == 'left-top' ) {
176
+ $remove_bottom_margin .= 'last-child(' . ( count( $row['cells'] ) % 2 == 0 ? '-n+2' : '1' ) . ')';
177
+ } else {
178
+ $remove_bottom_margin .= 'child(-n+2)';
179
+ }
180
+
181
+ $css->add_cell_css( $post_id, $ri, false, $remove_bottom_margin, array(
182
+ 'margin-bottom' => 0,
183
+ ), $panels_tablet_width . ':' . ( $panels_mobile_width + 1 )
184
+ );
185
+
186
+ if ( ! empty( $gutter_parts[1] ) ) {
187
+ // Tablet responsive css for cells
188
+
189
+ $css->add_cell_css( $post_id, $ri, false, ':nth-child(even)', array(
190
+ 'padding-left' => ( floatval( $gutter_parts[1] / 2 ) . $gutter_parts[2] ),
191
+ ), $panels_tablet_width . ':' . ( $panels_mobile_width + 1 ) );
192
+
193
+ $css->add_cell_css( $post_id, $ri, false, ':nth-child(odd)', array(
194
+ 'padding-right' => ( floatval( $gutter_parts[1] / 2 ) . $gutter_parts[2] ),
195
+ ), $panels_tablet_width . ':' . ( $panels_mobile_width + 1 ) );
196
+ }
197
+
198
+ }
199
+
200
+ // Mobile Responsive
201
+ $css->add_row_css( $post_id, $ri, array(
202
+ '.panel-no-style',
203
+ '.panel-has-style > .panel-row-style'
204
+ ), array(
205
+ '-webkit-flex-direction' => $collapse_order == 'left-top' ? 'column' : 'column-reverse',
206
+ '-ms-flex-direction' => $collapse_order == 'left-top' ? 'column' : 'column-reverse',
207
+ 'flex-direction' => $collapse_order == 'left-top' ? 'column' : 'column-reverse',
208
+ ), $panels_mobile_width );
209
+
210
+ $css->add_cell_css( $post_id, $ri, false, '', array(
211
+ 'width' => '100%',
212
+ 'margin-right' => 0,
213
+ ), $panels_mobile_width );
214
+
215
+
216
+ foreach ( $row['cells'] as $ci => $cell ) {
217
+ if ( ( $collapse_order == 'left-top' && $ci != $cell_count - 1 ) || ( $collapse_order == 'right-top' && $ci !== 0 ) ) {
218
+ $css->add_cell_css( $post_id, $ri, $ci, '', array(
219
+ 'margin-bottom' => $panels_mobile_margin_bottom
220
+ ), $panels_mobile_width );
221
+ }
222
+ }
223
+
224
+ if( $panels_mobile_margin_bottom != $panels_margin_bottom ) {
225
+ // If we need a different bottom margin for
226
+ $css->add_row_css( $post_id, $ri, '', array(
227
+ 'margin-bottom' => $panels_mobile_margin_bottom
228
+ ), $panels_mobile_width );
229
+ }
230
+
231
+
232
+ } // End of responsive code
233
+
234
+ }
235
+
236
+ // Add the bottom margins
237
+ $css->add_widget_css( $post_id, false, false, false, '', array(
238
+ 'margin-bottom' => apply_filters( 'siteorigin_panels_css_cell_margin_bottom', $settings['margin-bottom'] . 'px', false, false, $panels_data, $post_id )
239
+ ) );
240
+ $css->add_widget_css( $post_id, false, false, false, ':last-child', array(
241
+ 'margin-bottom' => apply_filters( 'siteorigin_panels_css_cell_last_margin_bottom', '0px', false, false, $panels_data, $post_id )
242
+ ) );
243
+
244
+ if ( $settings['responsive'] ) {
245
+ $css->add_cell_css( $post_id, false, false, '', array(
246
+ 'padding' => 0,
247
+ ), $panels_mobile_width );
248
+
249
+ // Hide empty cells on mobile
250
+ $css->add_row_css( $post_id, false, ' .panel-grid-cell-empty', array(
251
+ 'display' => 'none',
252
+ ), $panels_mobile_width );
253
+
254
+ // Hide empty cells on mobile
255
+ $css->add_row_css( $post_id, false, ' .panel-grid-cell-mobile-last', array(
256
+ 'margin-bottom' => '0px',
257
+ ), $panels_mobile_width );
258
+ }
259
+
260
+ // Let other plugins and components filter the CSS object.
261
+ $css = apply_filters( 'siteorigin_panels_css_object', $css, $panels_data, $post_id, $layout_data );
262
+
263
+ return $css->get_css();
264
+ }
265
+
266
+ /**
267
+ * Render the panels.
268
+ *
269
+ * @param int|string|bool $post_id The Post ID or 'home'.
270
+ * @param bool $enqueue_css Should we also enqueue the layout CSS.
271
+ * @param array|bool $panels_data Existing panels data. By default load from settings or post meta.
272
+ * @param array $layout_data Reformatted panels_data that includes data about the render.
273
+ *
274
+ * @return string
275
+ */
276
+ function render( $post_id = false, $enqueue_css = true, $panels_data = false, & $layout_data = array(), $is_preview = false ) {
277
+
278
+ if ( empty( $post_id ) ) {
279
+ $post_id = get_the_ID();
280
+
281
+ if ( class_exists( 'WooCommerce' ) && is_shop() ) {
282
+ $post_id = wc_get_page_id( 'shop' );
283
+ }
284
+ }
285
+
286
+ global $siteorigin_panels_current_post;
287
+ $old_current_post = $siteorigin_panels_current_post;
288
+ $siteorigin_panels_current_post = $post_id;
289
+
290
+ // Try get the cached panel from in memory cache.
291
+ global $siteorigin_panels_cache;
292
+ if ( ! empty( $siteorigin_panels_cache ) && ! empty( $siteorigin_panels_cache[ $post_id ] ) ) {
293
+ return $siteorigin_panels_cache[ $post_id ];
294
+ }
295
+
296
+ if ( empty( $panels_data ) ) {
297
+ $panels_data = $this->get_panels_data_for_post( $post_id );
298
+ if ( $panels_data === false ) {
299
+ return false;
300
+ }
301
+ }
302
+
303
+ $panels_data = apply_filters( 'siteorigin_panels_data', $panels_data, $post_id );
304
+ if ( empty( $panels_data ) || empty( $panels_data['grids'] ) ) {
305
+ return '';
306
+ }
307
+
308
+ if ( $is_preview ) {
309
+ $GLOBALS[ 'SITEORIGIN_PANELS_PREVIEW_RENDER' ] = true;
310
+ }
311
+
312
+ $layout_data = $this->get_panels_layout_data( $panels_data );
313
+ $layout_data = apply_filters( 'siteorigin_panels_layout_data', $layout_data, $post_id );
314
+
315
+ ob_start();
316
+
317
+ // Add the panel layout wrapper
318
+ $layout_classes = apply_filters( 'siteorigin_panels_layout_classes', array( 'panel-layout' ), $post_id, $panels_data );
319
+ if ( is_rtl() ) {
320
+ $layout_classes[] = 'panel-is-rtl';
321
+ }
322
+ $layout_attributes = apply_filters( 'siteorigin_panels_layout_attributes', array(
323
+ 'id' => 'pl-' . $post_id,
324
+ 'class' => implode( ' ', $layout_classes ),
325
+ ), $post_id, $panels_data );
326
+
327
+ $this->render_element( 'div', $layout_attributes );
328
+
329
+ echo apply_filters( 'siteorigin_panels_before_content', '', $panels_data, $post_id );
330
+
331
+ foreach ( $layout_data as $ri => & $row ) {
332
+ $this->render_row( $post_id, $ri, $row, $panels_data );
333
+ }
334
+
335
+ echo apply_filters( 'siteorigin_panels_after_content', '', $panels_data, $post_id );
336
+
337
+ echo '</div>';
338
+
339
+ do_action( 'siteorigin_panels_after_render', $panels_data, $post_id );
340
+
341
+ $html = ob_get_clean();
342
+
343
+ if ( $enqueue_css && ! isset( $this->inline_css[ $post_id ] ) ) {
344
+ wp_enqueue_style( 'siteorigin-panels-front' );
345
+ $this->add_inline_css( $post_id, $this->generate_css( $post_id, $panels_data, $layout_data ) );
346
+ }
347
+
348
+ // Reset the current post
349
+ $siteorigin_panels_current_post = $old_current_post;
350
+
351
+ $rendered_layout = apply_filters( 'siteorigin_panels_render', $html, $post_id, ! empty( $post ) ? $post : null );
352
+
353
+ if ( $is_preview ) {
354
+ $widget_css = '@import url(' . SiteOrigin_Panels::front_css_url() . '); ';
355
+ $widget_css .= SiteOrigin_Panels::renderer()->generate_css( $post_id, $panels_data, $layout_data );
356
+ $widget_css = preg_replace( '/\s+/', ' ', $widget_css );
357
+ $rendered_layout .= "\n\n" .
358
+ '<style type="text/css" class="panels-style" data-panels-style-for-post="' . esc_attr( $post_id ) . '">' .
359
+ $widget_css .
360
+ '</style>';
361
+ }
362
+
363
+ unset( $GLOBALS[ 'SITEORIGIN_PANELS_PREVIEW_RENDER' ] );
364
+
365
+ return $rendered_layout;
366
+ }
367
+
368
+ /**
369
+ * Echo the style wrapper and return if there was a wrapper
370
+ *
371
+ * @param string $name The name of the style wrapper
372
+ * @param array $style The style wrapper args. Used as an argument for siteorigin_panels_{$name}_style_attributes
373
+ * @param string|bool $for An identifier of what this style wrapper is for
374
+ *
375
+ * @return bool Is there a style wrapper
376
+ */
377
+ private function start_style_wrapper( $name, $style = array(), $for = false ) {
378
+ $attributes = array();
379
+
380
+ if ( empty( $attributes['class'] ) ) {
381
+ $attributes['class'] = array();
382
+ }
383
+ if ( empty( $attributes['style'] ) ) {
384
+ $attributes['style'] = '';
385
+ }
386
+
387
+ // Get everything related to the style wrapper
388
+ $attributes = apply_filters( 'siteorigin_panels_' . $name . '_style_attributes', $attributes, $style );
389
+ $attributes = apply_filters( 'siteorigin_panels_general_style_attributes', $attributes, $style );
390
+
391
+ $standard_css = array();
392
+ $standard_css = apply_filters( 'siteorigin_panels_' . $name . '_style_css', $standard_css, $style );
393
+ $standard_css = apply_filters( 'siteorigin_panels_general_style_css', $standard_css, $style );
394
+
395
+ $mobile_css = array();
396
+ $mobile_css = apply_filters( 'siteorigin_panels_' . $name . '_style_mobile_css', $mobile_css, $style );
397
+ $mobile_css = apply_filters( 'siteorigin_panels_general_style_mobile_css', $mobile_css, $style );
398
+
399
+ // Remove anything we didn't actually use
400
+ if ( empty( $attributes['class'] ) ) {
401
+ unset( $attributes['class'] );
402
+ }
403
+ if ( empty( $attributes['style'] ) ) {
404
+ unset( $attributes['style'] );
405
+ }
406
+
407
+ $style_wrapper = '';
408
+ if ( ! empty( $attributes ) || ! empty( $standard_css ) || ! empty( $mobile_css ) ) {
409
+ if ( empty( $attributes['class'] ) ) {
410
+ $attributes['class'] = array();
411
+ }
412
+ $attributes['class'][] = 'panel-' . $name . '-style';
413
+ if ( ! empty( $for ) ) {
414
+ $attributes['class'][] = 'panel-' . $name . '-style-for-' . sanitize_html_class( $for );
415
+ }
416
+ $attributes['class'] = array_unique( $attributes['class'] );
417
+
418
+ // Filter and sanitize the classes
419
+ $attributes['class'] = apply_filters( 'siteorigin_panels_' . $name . '_style_classes', $attributes['class'], $attributes, $style );
420
+ $attributes['class'] = array_map( 'sanitize_html_class', $attributes['class'] );
421
+
422
+ $style_wrapper = '<div ';
423
+ foreach ( $attributes as $name => $value ) {
424
+ // Attributes start with _ are used for internal communication between filters, so are not added to the HTML
425
+ // We don't make use of this in our styling, so its left as a mechanism for other plugins.
426
+ if ( substr( $name, 0, 1 ) === '_' ) {
427
+ continue;
428
+ }
429
+
430
+ if ( is_array( $value ) ) {
431
+ $style_wrapper .= $name . '="' . esc_attr( implode( " ", array_unique( $value ) ) ) . '" ';
432
+ } else {
433
+ $style_wrapper .= $name . '="' . esc_attr( $value ) . '" ';
434
+ }
435
+ }
436
+ $style_wrapper .= '>';
437
+
438
+ return $style_wrapper;
439
+ }
440
+
441
+ return $style_wrapper;
442
+ }
443
+
444
+ /**
445
+ * Render the widget.
446
+ *
447
+ * @param array $widget_info The widget info.
448
+ * @param array $instance The widget instance
449
+ * @param int $grid_index The grid index.
450
+ * @param int $cell_index The cell index.
451
+ * @param int $widget_index The index of this widget.
452
+ * @param bool $is_first Is this the first widget in the cell.
453
+ * @param bool $is_last Is this the last widget in the cell.
454
+ * @param bool $post_id
455
+ * @param string $style_wrapper The start of the style wrapper
456
+ */
457
+ function the_widget( $widget_info, $instance, $grid_index, $cell_index, $widget_index, $is_first, $is_last, $post_id = false, $style_wrapper = '' ) {
458
+
459
+ // Set widget class to $widget
460
+ $widget_class = $widget_info['class'];
461
+ $widget_class = apply_filters( 'siteorigin_panels_widget_class', $widget_class );
462
+
463
+ // Load the widget from the widget factory and give themes and plugins a chance to provide their own
464
+ $the_widget = SiteOrigin_Panels::get_widget_instance( $widget_class );
465
+ $the_widget = apply_filters( 'siteorigin_panels_widget_object', $the_widget, $widget_class, $instance );
466
+
467
+ if ( empty( $post_id ) ) {
468
+ $post_id = get_the_ID();
469
+
470
+ if ( class_exists( 'WooCommerce' ) && is_shop() ) {
471
+ $post_id = wc_get_page_id( 'shop' );
472
+ }
473
+ }
474
+
475
+ $classes = array( 'so-panel' );
476
+ if ( siteorigin_panels_setting( 'add-widget-class' ) ) {
477
+ $classes[] = 'widget';
478
+ }
479
+ if ( ! empty( $the_widget ) && ! empty( $the_widget->id_base ) ) {
480
+ $classes[] = 'widget_' . $the_widget->id_base;
481
+ }
482
+ if ( ! empty( $the_widget ) && is_array( $the_widget->widget_options ) && ! empty( $the_widget->widget_options['classname'] ) ) {
483
+ $classes[] = $the_widget->widget_options['classname'];
484
+ }
485
+ if ( $is_first ) {
486
+ $classes[] = 'panel-first-child';
487
+ }
488
+ if ( $is_last ) {
489
+ $classes[] = 'panel-last-child';
490
+ }
491
+ $id = 'panel-' . $post_id . '-' . $grid_index . '-' . $cell_index . '-' . $widget_index;
492
+
493
+ // Filter and sanitize the classes
494
+ $classes = apply_filters( 'siteorigin_panels_widget_classes', $classes, $widget_class, $instance, $widget_info );
495
+ $classes = explode( ' ', implode( ' ', $classes ) );
496
+ $classes = array_filter( $classes );
497
+ $classes = array_unique( $classes );
498
+ $classes = array_map( 'sanitize_html_class', $classes );
499
+
500
+ $title_html = siteorigin_panels_setting( 'title-html' );
501
+ if ( strpos( $title_html, '{{title}}' ) !== false ) {
502
+ list( $before_title, $after_title ) = explode( '{{title}}', $title_html, 2 );
503
+ } else {
504
+ $before_title = '<h3 class="widget-title">';
505
+ $after_title = '</h3>';
506
+ }
507
+
508
+ // Attributes of the widget wrapper
509
+ $attributes = apply_filters( 'siteorigin_panels_widget_attributes', array(
510
+ 'id' => $id,
511
+ 'class' => implode( ' ', $classes ),
512
+ 'data-index' => $widget_info['widget_index'],
513
+ ), $widget_info );
514
+
515
+ $before_widget = '<div ';
516
+ foreach ( $attributes as $k => $v ) {
517
+ $before_widget .= esc_attr( $k ) . '="' . esc_attr( $v ) . '" ';
518
+ }
519
+ $before_widget .= '>';
520
+
521
+ $args = array(
522
+ 'before_widget' => $before_widget,
523
+ 'after_widget' => '</div>',
524
+ 'before_title' => $before_title,
525
+ 'after_title' => $after_title,
526
+ 'widget_id' => 'widget-' . $grid_index . '-' . $cell_index . '-' . $widget_index
527
+ );
528
+
529
+ // Let other themes and plugins change the arguments that go to the widget class.
530
+ $args = apply_filters( 'siteorigin_panels_widget_args', $args );
531
+
532
+ // If there is a style wrapper, add it.
533
+ if ( ! empty( $style_wrapper ) ) {
534
+ $args['before_widget'] = $args['before_widget'] . $style_wrapper;
535
+ $args['after_widget'] = '</div>' . $args['after_widget'];
536
+ }
537
+
538
+ // This gives other plugins the chance to take over rendering of widgets
539
+ $widget_html = apply_filters( 'siteorigin_panels_the_widget_html', '', $the_widget, $args, $instance );
540
+
541
+ if ( ! empty( $widget_html ) ) {
542
+ echo $args['before_widget'];
543
+ echo $widget_html;
544
+ echo $args['after_widget'];
545
+ } else if ( ! empty( $the_widget ) && is_a( $the_widget, 'WP_Widget' ) ) {
546
+ $the_widget->widget( $args, $instance );
547
+ } else {
548
+ // This gives themes a chance to display some sort of placeholder for missing widgets
549
+ echo apply_filters( 'siteorigin_panels_missing_widget', $args['before_widget'] . $args['after_widget'], $widget_class, $args, $instance );
550
+ }
551
+ }
552
+
553
+ /**
554
+ * Print inline CSS in the header and footer.
555
+ */
556
+ function print_inline_css() {
557
+ if ( ! empty( $this->inline_css ) ) {
558
+ $the_css = '';
559
+ foreach ( $this->inline_css as $post_id => $css ) {
560
+ if ( empty( $css ) ) {
561
+ continue;
562
+ }
563
+ $the_css .= '/* Layout ' . esc_attr( $post_id ) . ' */ ';
564
+ $the_css .= $css;
565
+ }
566
+
567
+ // Reset the inline CSS
568
+ $this->inline_css = null;
569
+
570
+ switch ( current_filter() ) {
571
+ case 'wp_head' :
572
+ $css_id = 'head';
573
+ break;
574
+
575
+ case 'wp_footer' :
576
+ $css_id = 'footer';
577
+ break;
578
+
579
+ default :
580
+ $css_id = sanitize_html_class( current_filter() );
581
+ break;
582
+ }
583
+
584
+ // Allow third party developers to change the inline styles or remove them completely.
585
+ $the_css = apply_filters( 'siteorigin_panels_inline_styles', $the_css );
586
+
587
+ if ( ! empty( $the_css ) ) {
588
+ ?>
589
+ <style type="text/css" media="all"
590
+ id="siteorigin-panels-layouts-<?php echo esc_attr( $css_id ) ?>"><?php echo $the_css ?></style><?php
591
+ }
592
+ }
593
+ }
594
+
595
+ /**
596
+ * Enqueue the required styles
597
+ */
598
+ function enqueue_styles() {
599
+ // Register the style to support possible lazy loading
600
+ wp_register_style( 'siteorigin-panels-front', SiteOrigin_Panels::front_css_url(), array(), SITEORIGIN_PANELS_VERSION );
601
+ }
602
+
603
+ /**
604
+ * Retrieve panels data for a post or a prebuilt layout or the home page layout.
605
+ *
606
+ * @param string $post_id
607
+ *
608
+ * @return array
609
+ */
610
+ private function get_panels_data_for_post( $post_id ) {
611
+ if ( SiteOrigin_Panels::is_live_editor() ) {
612
+ if (
613
+ current_user_can( 'edit_post', $post_id ) &&
614
+ ! empty( $_POST['live_editor_panels_data'] ) &&
615
+ $_POST['live_editor_post_ID'] == $post_id
616
+ ) {
617
+ $panels_data = json_decode( wp_unslash( $_POST['live_editor_panels_data'] ), true );
618
+
619
+ if ( ! empty( $panels_data['widgets'] ) ) {
620
+ $panels_data['widgets'] = SiteOrigin_Panels_Admin::single()->process_raw_widgets( $panels_data['widgets'] );
621
+ }
622
+ }
623
+ } else if ( strpos( $post_id, 'prebuilt:' ) === 0 ) {
624
+ list( $null, $prebuilt_id ) = explode( ':', $post_id, 2 );
625
+ $layouts = apply_filters( 'siteorigin_panels_prebuilt_layouts', array() );
626
+ $panels_data = ! empty( $layouts[ $prebuilt_id ] ) ? $layouts[ $prebuilt_id ] : array();
627
+ } else if ( $post_id == 'home' ) {
628
+ $page_id = get_option( 'page_on_front' );
629
+ if ( empty( $page_id ) ) {
630
+ $page_id = get_option( 'siteorigin_panels_home_page_id' );
631
+ }
632
+
633
+ $panels_data = ! empty( $page_id ) ? get_post_meta( $page_id, 'panels_data', true ) : null;
634
+
635
+ if ( is_null( $panels_data ) ) {
636
+ // Load the default layout
637
+ $layouts = apply_filters( 'siteorigin_panels_prebuilt_layouts', array() );
638
+ $prebuilt_id = siteorigin_panels_setting( 'home-page-default' ) ? siteorigin_panels_setting( 'home-page-default' ) : 'home';
639
+
640
+ $panels_data = ! empty( $layouts[ $prebuilt_id ] ) ? $layouts[ $prebuilt_id ] : current( $layouts );
641
+ }
642
+ } else {
643
+ if ( post_password_required( $post_id ) ) {
644
+ return false;
645
+ }
646
+ $panels_data = get_post_meta( $post_id, 'panels_data', true );
647
+ }
648
+
649
+ return $panels_data;
650
+ }
651
+
652
+ /**
653
+ * Transform flat panels data into a hierarchical structure.
654
+ *
655
+ * @param array $panels_data Flat panels data containing `grids`, `grid_cells`, and `widgets`.
656
+ *
657
+ * @return array Hierarchical structure of rows => cells => widgets.
658
+ */
659
+ public function get_panels_layout_data( $panels_data ) {
660
+ $layout_data = array();
661
+
662
+ foreach ( $panels_data['grids'] as $grid ) {
663
+ $layout_data[] = array(
664
+ 'style' => ! empty( $grid['style'] ) ? $grid['style'] : array(),
665
+ 'ratio' => ! empty( $grid['ratio'] ) ? $grid['ratio'] : '',
666
+ 'ratio_direction' => ! empty( $grid['ratio_direction'] ) ? $grid['ratio_direction'] : '',
667
+ 'color_label' => ! empty( $grid['color_label'] ) ? $grid['color_label'] : '',
668
+ 'label' => ! empty( $grid['label'] ) ? $grid['label'] : '',
669
+ 'cells' => array()
670
+ );
671
+ }
672
+
673
+ foreach ( $panels_data['grid_cells'] as $cell ) {
674
+ $layout_data[ $cell['grid'] ]['cells'][] = array(
675
+ 'widgets' => array(),
676
+ 'style' => ! empty( $cell['style'] ) ? $cell['style'] : array(),
677
+ 'weight' => floatval( $cell['weight'] ),
678
+ );
679
+ }
680
+
681
+ foreach ( $panels_data['widgets'] as $i => $widget ) {
682
+ $widget['panels_info']['widget_index'] = $i;
683
+ $row_index = intval( $widget['panels_info']['grid'] );
684
+ $cell_index = intval( $widget['panels_info']['cell'] );
685
+ $layout_data[ $row_index ]['cells'][ $cell_index ]['widgets'][] = $widget;
686
+ }
687
+
688
+ return $layout_data;
689
+ }
690
+
691
+ /**
692
+ * Outputs the given HTML tag with the given attributes.
693
+ *
694
+ * @param string $tag The HTML element to render.
695
+ * @param array $attributes The attributes for the HTML element.
696
+ *
697
+ */
698
+ private function render_element( $tag, $attributes ) {
699
+
700
+ echo '<' . $tag;
701
+ foreach ( $attributes as $name => $value ) {
702
+ if ( $value ) {
703
+ echo ' ' . $name . '="' . esc_attr( $value ) . '" ';
704
+ }
705
+ }
706
+ echo '>';
707
+
708
+ }
709
+
710
+ /**
711
+ * Render everything for the given row, including:
712
+ * - filters before and after row,
713
+ * - row style wrapper,
714
+ * - row element wrapper with attributes,
715
+ * - child cells
716
+ *
717
+ * @param string $post_id The ID of the post containing this layout.
718
+ * @param int $ri The index of this row.
719
+ * @param array $row The model containing this row's data and child cells.
720
+ * @param array $panels_data A copy of panels_data for filters.
721
+ *
722
+ */
723
+ private function render_row( $post_id, $ri, & $row, & $panels_data ) {
724
+ $row_style_wrapper = $this->start_style_wrapper( 'row', ! empty( $row['style'] ) ? $row['style'] : array(), $post_id . '-' . $ri );
725
+
726
+ $row_classes = array( 'panel-grid' );
727
+ $row_classes[] = ! empty( $row_style_wrapper ) ? 'panel-has-style' : 'panel-no-style';
728
+ $row_classes = apply_filters( 'siteorigin_panels_row_classes', $row_classes, $row );
729
+
730
+ $row_attributes = apply_filters( 'siteorigin_panels_row_attributes', array(
731
+ 'id' => 'pg-' . $post_id . '-' . $ri,
732
+ 'class' => implode( ' ', $row_classes ),
733
+ ), $row );
734
+
735
+ // This allows other themes and plugins to add html before the row
736
+ echo apply_filters( 'siteorigin_panels_before_row', '', $row, $row_attributes );
737
+
738
+ $this->render_element( 'div', $row_attributes );
739
+
740
+ if ( ! empty( $row_style_wrapper ) ) {
741
+ echo $row_style_wrapper;
742
+ }
743
+
744
+ if( method_exists( $this, 'modify_row_cells' ) ) {
745
+ // This gives other renderers a chance to change the cell order
746
+ $row['cells'] = $cells = $this->modify_row_cells( $row['cells'], $row );
747
+ }
748
+
749
+ foreach ( $row['cells'] as $ci => & $cell ) {
750
+ $this->render_cell( $post_id, $ri, $ci, $cell, $row['cells'], $panels_data );
751
+ }
752
+
753
+ // Close the style wrapper
754
+ if ( ! empty( $row_style_wrapper ) ) {
755
+ echo '</div>';
756
+ }
757
+
758
+ echo '</div>';
759
+
760
+ // This allows other themes and plugins to add html after the row
761
+ echo apply_filters( 'siteorigin_panels_after_row', '', $row, $row_attributes );
762
+
763
+ }
764
+
765
+ /**
766
+ *
767
+ * Render everything for the given cell, including:
768
+ * - filters before and after cell,
769
+ * - cell element wrapper with attributes,
770
+ * - style wrapper,
771
+ * - child widgets
772
+ *
773
+ * @param string $post_id The ID of the post containing this layout.
774
+ * @param int $ri The index of this cell's parent row.
775
+ * @param int $ci The index of this cell.
776
+ * @param array $cell The model containing this cell's data and child widgets.
777
+ * @param array $cells The array of cells containing this cell.
778
+ * @param array $panels_data A copy of panels_data for filters
779
+ */
780
+ private function render_cell( $post_id, $ri, $ci, & $cell, $cells, & $panels_data ) {
781
+
782
+ $cell_classes = array( 'panel-grid-cell' );
783
+
784
+ if ( empty( $cell['widgets'] ) ) {
785
+ $cell_classes[] = 'panel-grid-cell-empty';
786
+ }
787
+
788
+ if ( $ci == count( $cells ) - 2 && count( $cells[ $ci + 1 ]['widgets'] ) == 0 ) {
789
+ $cell_classes[] = 'panel-grid-cell-mobile-last';
790
+ }
791
+
792
+ // Themes can add their own styles to cells
793
+ $cell_classes = apply_filters( 'siteorigin_panels_cell_classes', $cell_classes, $cell );
794
+
795
+ // Legacy filter, use `siteorigin_panels_cell_classes` instead
796
+ $cell_classes = apply_filters( 'siteorigin_panels_row_cell_classes', $cell_classes, $panels_data, $cell );
797
+
798
+ $cell_attributes = apply_filters( 'siteorigin_panels_cell_attributes', array(
799
+ 'id' => 'pgc-' . $post_id . '-' . $ri . '-' . $ci,
800
+ 'class' => implode( ' ', $cell_classes ),
801
+ ), $cell );
802
+
803
+ // Legacy filter, use `siteorigin_panels_cell_attributes` instead
804
+ $cell_attributes = apply_filters( 'siteorigin_panels_row_cell_attributes', $cell_attributes, $panels_data, $cell );
805
+
806
+ echo apply_filters( 'siteorigin_panels_before_cell', '', $cell, $cell_attributes );
807
+
808
+ $this->render_element( 'div', $cell_attributes );
809
+
810
+ $grid = $panels_data['grids'][ $ri ];
811
+
812
+ if ( empty( $cell['style']['class'] ) && ! empty( $grid['style']['cell_class'] ) ) {
813
+ $cell['style']['class'] = $grid['style']['cell_class'];
814
+ }
815
+
816
+ $cell_style = ! empty( $cell['style'] ) ? $cell['style'] : array();
817
+ $cell_style_wrapper = $this->start_style_wrapper( 'cell', $cell_style, $post_id . '-' . $ri . '-' . $ci );
818
+ if ( ! empty( $cell_style_wrapper ) ) {
819
+ echo $cell_style_wrapper;
820
+ }
821
+
822
+ foreach ( $cell['widgets'] as $wi => & $widget ) {
823
+ $is_last = ( $wi == count( $cell['widgets'] ) - 1 );
824
+ $this->render_widget( $post_id, $ri, $ci, $wi, $widget, $is_last );
825
+ }
826
+
827
+ if ( ! empty( $cell_style_wrapper ) ) {
828
+ echo '</div>';
829
+ }
830
+ echo '</div>';
831
+
832
+ echo apply_filters( 'siteorigin_panels_after_cell', '', $cell, $cell_attributes );
833
+ }
834
+
835
+ /**
836
+ *
837
+ * Gets the style wrapper for this widget and passes it through to `the_widget` along with other required parameters.
838
+ *
839
+ * @param string $post_id The ID of the post containing this layout.
840
+ * @param int $ri The index of this widget's ancestor row.
841
+ * @param int $ci The index of this widget's parent cell.
842
+ * @param int $wi The index of this widget.
843
+ * @param array $widget The model containing this widget's data.
844
+ * @param bool $is_last Whether this is the last widget in the parent cell.
845
+ *
846
+ */
847
+ private function render_widget( $post_id, $ri, $ci, $wi, & $widget, $is_last ) {
848
+
849
+ $widget_style_wrapper = $this->start_style_wrapper(
850
+ 'widget',
851
+ ! empty( $widget['panels_info']['style'] ) ? $widget['panels_info']['style'] : array(),
852
+ $post_id . '-' . $ri . '-' . $ci . '-' . $wi
853
+ );
854
+
855
+ $this->the_widget(
856
+ $widget['panels_info'],
857
+ $widget,
858
+ $ri,
859
+ $ci,
860
+ $wi,
861
+ $wi == 0,
862
+ $is_last,
863
+ $post_id,
864
+ $widget_style_wrapper
865
+ );
866
+
867
+ }
868
+
869
+ public function front_css_url() {
870
+ return siteorigin_panels_url( 'css/front-flex' . SITEORIGIN_PANELS_CSS_SUFFIX . '.css' );
871
+ }
872
+ }
inc/revisions.php CHANGED
@@ -1,107 +1,107 @@
1
- <?php
2
-
3
- /**
4
- * Class SiteOrigin_Panels_Revisions
5
- *
6
- * Handles Page Builder revisions.
7
- */
8
- class SiteOrigin_Panels_Revisions {
9
-
10
- function __construct() {
11
- add_action( 'save_post', array( $this, 'save_post' ), 11, 2 );
12
- add_action( 'wp_restore_post_revision', array( $this, 'revisions_restore' ), 10, 2 );
13
-
14
- add_filter( '_wp_post_revision_fields', array( $this, 'revisions_fields' ) );
15
- add_filter( '_wp_post_revision_field_panels_data_field', array( $this, 'revisions_field' ), 10, 3 );
16
- }
17
-
18
- /**
19
- * @return SiteOrigin_Panels_Admin
20
- */
21
- public static function single() {
22
- static $single;
23
- return empty( $single ) ? $single = new self() : $single;
24
- }
25
-
26
- /**
27
- * Store the Page Builder meta in the revision.
28
- *
29
- * @param $post_id
30
- * @param $post
31
- */
32
- function save_post( $post_id, $post ) {
33
- if( is_preview() ) return;
34
-
35
- $parent_id = wp_is_post_revision( $post_id );
36
- if ( $parent_id ) {
37
- // Check whether the panels data needs to be copied to the revision.
38
- $panels_data = get_metadata( 'post', $post_id, 'panels_data', true );
39
- if ( empty( $panels_data ) ) {
40
- // If the panels data meta exists for the post parent, copy it into the revision.
41
- $panels_data = get_post_meta( $parent_id, 'panels_data', true );
42
- if ( ! empty( $panels_data ) ) {
43
- add_metadata( 'post', $post_id, 'panels_data', $panels_data );
44
- }
45
- }
46
- }
47
- }
48
-
49
- /**
50
- * Restore a revision.
51
- *
52
- * @param $post_id
53
- * @param $revision_id
54
- */
55
- function revisions_restore( $post_id, $revision_id ) {
56
- $panels_data = get_metadata( 'post', $revision_id, 'panels_data', true );
57
- if ( ! empty( $panels_data ) ) {
58
- update_post_meta( $post_id, 'panels_data', map_deep( $panels_data, array( 'SiteOrigin_Panels_Admin', 'double_slash_string' ) ) );
59
- } else {
60
- delete_post_meta( $post_id, 'panels_data' );
61
- }
62
- }
63
-
64
- /**
65
- * Add the Page Builder content revision field.
66
- *
67
- * @param $fields
68
- *
69
- * @return mixed
70
- */
71
- function revisions_fields( $fields ) {
72
- // Prevent the autosave message.
73
- // TODO figure out how to include Page Builder data into the autosave.
74
- if ( ! function_exists( 'get_current_screen' ) ) {
75
- return $fields;
76
- }
77
-
78
- $screen = get_current_screen();
79
- if ( ! empty( $screen ) && $screen->base == 'post' ) {
80
- return $fields;
81
- }
82
-
83
- $fields['panels_data_field'] = __( 'Page Builder Content', 'siteorigin-panels' );
84
-
85
- return $fields;
86
- }
87
-
88
- /**
89
- * Display the Page Builder content for the revision.
90
- *
91
- * @param $value
92
- * @param $field
93
- * @param $revision
94
- *
95
- * @return string
96
- */
97
- function revisions_field( $value, $field, $revision ) {
98
- $parent_id = wp_is_post_revision( $revision->ID );
99
- $panels_data = get_metadata( 'post', $revision->ID, 'panels_data', true );
100
-
101
- if ( empty( $panels_data ) ) {
102
- return '';
103
- }
104
-
105
- return siteorigin_panels_render( $parent_id, false, $panels_data );
106
- }
107
- }
1
+ <?php
2
+
3
+ /**
4
+ * Class SiteOrigin_Panels_Revisions
5
+ *
6
+ * Handles Page Builder revisions.
7
+ */
8
+ class SiteOrigin_Panels_Revisions {
9
+
10
+ function __construct() {
11
+ add_action( 'save_post', array( $this, 'save_post' ), 11, 2 );
12
+ add_action( 'wp_restore_post_revision', array( $this, 'revisions_restore' ), 10, 2 );
13
+
14
+ add_filter( '_wp_post_revision_fields', array( $this, 'revisions_fields' ) );
15
+ add_filter( '_wp_post_revision_field_panels_data_field', array( $this, 'revisions_field' ), 10, 3 );
16
+ }
17
+
18
+ /**
19
+ * @return SiteOrigin_Panels_Admin
20
+ */
21
+ public static function single() {
22
+ static $single;
23
+ return empty( $single ) ? $single = new self() : $single;
24
+ }
25
+
26
+ /**
27
+ * Store the Page Builder meta in the revision.
28
+ *
29
+ * @param $post_id
30
+ * @param $post
31
+ */
32
+ function save_post( $post_id, $post ) {
33
+ if( is_preview() ) return;
34
+
35
+ $parent_id = wp_is_post_revision( $post_id );
36
+ if ( $parent_id ) {
37
+ // Check whether the panels data needs to be copied to the revision.
38
+ $panels_data = get_metadata( 'post', $post_id, 'panels_data', true );
39
+ if ( empty( $panels_data ) ) {
40
+ // If the panels data meta exists for the post parent, copy it into the revision.
41
+ $panels_data = get_post_meta( $parent_id, 'panels_data', true );
42
+ if ( ! empty( $panels_data ) ) {
43
+ add_metadata( 'post', $post_id, 'panels_data', $panels_data );
44
+ }
45
+ }
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Restore a revision.
51
+ *
52
+ * @param $post_id
53
+ * @param $revision_id
54
+ */
55
+ function revisions_restore( $post_id, $revision_id ) {
56
+ $panels_data = get_metadata( 'post', $revision_id, 'panels_data', true );
57
+ if ( ! empty( $panels_data ) ) {
58
+ update_post_meta( $post_id, 'panels_data', map_deep( $panels_data, array( 'SiteOrigin_Panels_Admin', 'double_slash_string' ) ) );
59
+ } else {
60
+ delete_post_meta( $post_id, 'panels_data' );
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Add the Page Builder content revision field.
66
+ *
67
+ * @param $fields
68
+ *
69
+ * @return mixed
70
+ */
71
+ function revisions_fields( $fields ) {
72
+ // Prevent the autosave message.
73
+ // TODO figure out how to include Page Builder data into the autosave.
74
+ if ( ! function_exists( 'get_current_screen' ) ) {
75
+ return $fields;
76
+ }
77
+
78
+ $screen = get_current_screen();
79
+ if ( ! empty( $screen ) && $screen->base == 'post' ) {
80
+ return $fields;
81
+ }
82
+
83
+ $fields['panels_data_field'] = __( 'Page Builder Content', 'siteorigin-panels' );
84
+
85
+ return $fields;
86
+ }
87
+
88
+ /**
89
+ * Display the Page Builder content for the revision.
90
+ *
91
+ * @param $value
92
+ * @param $field
93
+ * @param $revision
94
+ *
95
+ * @return string
96
+ */
97
+ function revisions_field( $value, $field, $revision ) {
98
+ $parent_id = wp_is_post_revision( $revision->ID );
99
+ $panels_data = get_metadata( 'post', $revision->ID, 'panels_data', true );
100
+
101
+ if ( empty( $panels_data ) ) {
102
+ return '';
103
+ }
104
+
105
+ return siteorigin_panels_render( $parent_id, false, $panels_data );
106
+ }
107
+ }
inc/settings.php CHANGED
@@ -1,662 +1,668 @@
1
- <?php
2
-
3
- /**
4
- * Class to handle Page Builder settings.
5
- *
6
- * Class SiteOrigin_Panels_Settings
7
- */
8
- class SiteOrigin_Panels_Settings {
9
-
10
- private $settings;
11
- private $fields;
12
- private $settings_saved;
13
-
14
- function __construct() {
15
- $this->settings = array();
16
- $this->fields = array();
17
- $this->settings_saved = false;
18
-
19
- // Admin actions
20
- add_action( 'admin_enqueue_scripts', array( $this, 'admin_scripts' ) );
21
- add_action( 'admin_menu', array( $this, 'add_settings_page' ) );
22
- add_action( 'after_setup_theme', array( $this, 'clear_cache' ), 100 );
23
-
24
- // Default filters for fields and defaults
25
- add_filter( 'siteorigin_panels_settings_defaults', array( $this, 'settings_defaults' ) );
26
- add_filter( 'siteorigin_panels_default_add_widget_class', array( $this, 'add_widget_class' ) );
27
- add_filter( 'siteorigin_panels_settings_fields', array( $this, 'settings_fields' ) );
28
- }
29
-
30
- /**
31
- * @return SiteOrigin_Panels_Settings
32
- */
33
- static function single() {
34
- static $single;
35
- return empty( $single ) ? $single = new self() : $single;
36
- }
37
-
38
- function clear_cache() {
39
- $this->settings = array();
40
- }
41
-
42
- /**
43
- * Get a settings value
44
- *
45
- * @param string $key
46
- *
47
- * @return array|bool|mixed|null|void
48
- */
49
- function get( $key = '' ) {
50
-
51
- if ( empty( $this->settings ) ) {
52
-
53
- // Get the settings, attempt to fetch new settings first.
54
- $current_settings = get_option( 'siteorigin_panels_settings', false );
55
-
56
- if ( $current_settings === false ) {
57
- // We can't find the settings, so try access old settings
58
- $current_settings = get_option( 'siteorigin_panels_display', array() );
59
- $post_types = get_option( 'siteorigin_panels_post_types' );
60
- if ( ! empty( $post_types ) ) {
61
- $current_settings['post-types'] = $post_types;
62
- }
63
-
64
- // Store the old settings in the new field
65
- update_option( 'siteorigin_panels_settings', $current_settings );
66
- }
67
-
68
- // Get the settings provided by the theme
69
- $theme_settings = get_theme_support( 'siteorigin-panels' );
70
- if ( ! empty( $theme_settings ) ) {
71
- $theme_settings = $theme_settings[0];
72
- } else {
73
- $theme_settings = array();
74
- }
75
-
76
- $this->settings = wp_parse_args( $theme_settings, apply_filters( 'siteorigin_panels_settings_defaults', array() ) );
77
- $this->settings = wp_parse_args( $current_settings, $this->settings );
78
-
79
- // Filter these settings
80
- $this->settings = apply_filters( 'siteorigin_panels_settings', $this->settings );
81
- }
82
-
83
- if ( ! empty( $key ) ) {
84
- return isset( $this->settings[ $key ] ) ? $this->settings[ $key ] : null;
85
- }
86
-
87
- return $this->settings;
88
- }
89
-
90
- /**
91
- * Set a settings value
92
- *
93
- * @param $key
94
- * @param $value
95
- */
96
- function set( $key, $value ) {
97
- $current_settings = get_option( 'siteorigin_panels_settings', array() );
98
- $current_settings[ $key ] = $value;
99
- update_option( 'siteorigin_panels_settings', $current_settings );
100
- }
101
-
102
- /**
103
- * Add default settings for the Page Builder settings.
104
- *
105
- * @param $defaults
106
- *
107
- * @return mixed
108
- */
109
- function settings_defaults( $defaults ) {
110
- $defaults['home-page'] = false;
111
- $defaults['home-page-default'] = false;
112
- $defaults['home-template'] = 'home-panels.php';
113
- $defaults['affiliate-id'] = apply_filters( 'siteorigin_panels_affiliate_id', false );
114
- $defaults['display-teaser'] = true;
115
- $defaults['display-learn'] = true;
116
- $defaults['load-on-attach'] = false;
117
- $defaults['use-classic'] = true;
118
-
119
- // The general fields
120
- $defaults['post-types'] = array( 'page', 'post' );
121
- $defaults['live-editor-quick-link'] = true;
122
- $defaults['admin-post-state'] = true;
123
- $defaults['admin-widget-count'] = false;
124
- $defaults['parallax-motion'] = '';
125
- $defaults['sidebars-emulator'] = true;
126
- $defaults['layout-block-default-mode'] = 'preview';
127
-
128
- // Widgets fields
129
- $defaults['title-html'] = '<h3 class="widget-title">{{title}}</h3>';
130
- $defaults['add-widget-class'] = apply_filters( 'siteorigin_panels_default_add_widget_class', true );
131
- $defaults['bundled-widgets'] = get_option( 'siteorigin_panels_is_using_bundled', false );
132
- $defaults['recommended-widgets'] = true;
133
- $defaults['instant-open-widgets'] = false;
134
-
135
- // The layout fields
136
- $defaults['responsive'] = true;
137
- $defaults['tablet-layout'] = false;
138
- $defaults['legacy-layout'] = 'auto';
139
- $defaults['tablet-width'] = 1024;
140
- $defaults['mobile-width'] = 780;
141
- $defaults['margin-bottom'] = 30;
142
- $defaults['margin-bottom-last-row'] = false;
143
- $defaults['margin-sides'] = 30;
144
- $defaults['full-width-container'] = 'body';
145
-
146
- // Content fields
147
- $defaults['copy-content'] = true;
148
- $defaults['copy-styles'] = false;
149
-
150
- return $defaults;
151
- }
152
-
153
- /**
154
- * Set the option on whether to add widget classes for known themes
155
- *
156
- * @param $add_class
157
- *
158
- * @return bool
159
- */
160
- function add_widget_class( $add_class ) {
161
-
162
- switch ( get_option( 'stylesheet' ) ) {
163
- case 'twentysixteen';
164
- $add_class = false;
165
- break;
166
- }
167
-
168
-
169
- return $add_class;
170
- }
171
-
172
- /**
173
- * Enqueue admin scripts
174
- *
175
- * @param $prefix
176
- */
177
- function admin_scripts( $prefix ) {
178
- if ( $prefix != 'settings_page_siteorigin_panels' ) {
179
- return;
180
- }
181
- wp_enqueue_style(
182
- 'siteorigin-panels-settings',
183
- siteorigin_panels_url( 'settings/admin-settings.css' ),
184
- array(),
185
- SITEORIGIN_PANELS_VERSION
186
- );
187
- wp_enqueue_script(
188
- 'siteorigin-panels-settings',
189
- siteorigin_panels_url( 'settings/admin-settings' . SITEORIGIN_PANELS_JS_SUFFIX . '.js' ),
190
- array(),
191
- SITEORIGIN_PANELS_VERSION
192
- );
193
- }
194
-
195
- /**
196
- * Add the Page Builder settings page
197
- */
198
- function add_settings_page() {
199
- $page = add_options_page( __( 'SiteOrigin Page Builder', 'siteorigin-panels' ), __( 'Page Builder', 'siteorigin-panels' ), 'manage_options', 'siteorigin_panels', array(
200
- $this,
201
- 'display_settings_page'
202
- ) );
203
- add_action( 'load-' . $page, array( $this, 'add_help_tab' ) );
204
- add_action( 'load-' . $page, array( $this, 'save_settings' ) );
205
- }
206
-
207
- /**
208
- * Display the Page Builder settings page
209
- */
210
- function display_settings_page() {
211
- $settings_fields = $this->fields = apply_filters( 'siteorigin_panels_settings_fields', array() );
212
- include plugin_dir_path( __FILE__ ) . '../settings/tpl/settings.php';
213
- }
214
-
215
- /**
216
- * Add a settings help tab
217
- */
218
- function add_help_tab() {
219
- $screen = get_current_screen();
220
- ob_start();
221
- include plugin_dir_path( __FILE__ ) . '../settings/tpl/help.php';
222
- $content = ob_get_clean();
223
-
224
- $screen->add_help_tab( array(
225
- 'id' => 'panels-help-tab',
226
- 'title' => __( 'Page Builder Settings', 'siteorigin-panels' ),
227
- 'content' => $content
228
- ) );
229
- }
230
-
231
- /**
232
- * Add the default Page Builder settings.
233
- *
234
- * @param $fields
235
- *
236
- * @return mixed
237
- */
238
- function settings_fields( $fields ) {
239
- // The post types fields
240
-
241
- $fields['general'] = array(
242
- 'title' => __( 'General', 'siteorigin-panels' ),
243
- 'fields' => array(),
244
- );
245
-
246
- $fields['general']['fields']['post-types'] = array(
247
- 'type' => 'select_multi',
248
- 'label' => __( 'Post Types', 'siteorigin-panels' ),
249
- 'options' => $this->get_post_types(),
250
- 'description' => __( 'The post types on which to use Page Builder.', 'siteorigin-panels' ),
251
- );
252
-
253
- $fields['general']['fields']['use-classic'] = array(
254
- 'type' => 'checkbox',
255
- 'label' => __( 'Use Classic Editor for new posts', 'siteorigin-panels' ),
256
- 'description' => __( 'New posts of the above Post Types will be created using the Classic Editor.', 'siteorigin-panels' )
257
- );
258
-
259
- $fields['general']['fields']['live-editor-quick-link'] = array(
260
- 'type' => 'checkbox',
261
- 'label' => __( 'Live Editor Quick Link', 'siteorigin-panels' ),
262
- 'description' => __( 'Display a Live Editor button in the admin bar.', 'siteorigin-panels' ),
263
- );
264
-
265
- $fields['general']['fields']['admin-post-state'] = array(
266
- 'type' => 'checkbox',
267
- 'label' => __( 'Display Post State', 'siteorigin-panels' ),
268
- 'description' => sprintf(
269
- __( "Display a %sSiteOrigin Page Builder%s post state in the admin lists of posts/pages to indicate Page Builder is active.", 'siteorigin-panels' ),
270
- '<strong>',
271
- '</strong>'
272
- ),
273
- );
274
-
275
- $fields['general']['fields']['admin-widget-count'] = array(
276
- 'type' => 'checkbox',
277
- 'label' => __( 'Display Widget Count', 'siteorigin-panels' ),
278
- 'description' => __( "Display a widget count in the admin lists of posts/pages where you're using Page Builder.", 'siteorigin-panels' ),
279
- );
280
-
281
- $fields['general']['fields']['parallax-motion'] = array(
282
- 'type' => 'float',
283
- 'label' => __( 'Limit Parallax Motion', 'siteorigin-panels' ),
284
- 'description' => __( 'How many pixels of scrolling result in a single pixel of parallax motion. 0 means automatic. Lower values give more noticeable effect.', 'siteorigin-panels' ),
285
- );
286
-
287
- $fields['general']['fields']['sidebars-emulator'] = array(
288
- 'type' => 'checkbox',
289
- 'label' => __( 'Sidebars Emulator', 'siteorigin-panels' ),
290
- 'description' => __( 'Page Builder will create an emulated sidebar, that contains all widgets in the page.', 'siteorigin-panels' ),
291
- );
292
-
293
- $fields['general']['fields']['display-teaser'] = array(
294
- 'type' => 'checkbox',
295
- 'label' => __('Upgrade Teaser', 'siteorigin-panels'),
296
- 'description' => sprintf(
297
- __('Display the %sSiteOrigin Premium%s upgrade teaser in the Page Builder toolbar.', 'siteorigin-panels'),
298
- '<a href="siteorigin.com/downloads/premium/" target="_blank" rel="noopener noreferrer">',
299
- '</a>'
300
- )
301
- );
302
-
303
- $fields['general']['fields']['load-on-attach'] = array(
304
- 'type' => 'checkbox',
305
- 'label' => __( 'Default To Page Builder Interface', 'siteorigin-panels' ),
306
- 'description' => sprintf(
307
- __( 'New Classic Editor posts/pages that you create will start with the Page Builder loaded. The %s"Use Classic Editor for new posts"%s setting must be enabled.', 'siteorigin-panels' ),
308
- '<strong>',
309
- '</strong>'
310
- )
311
- );
312
-
313
- $fields['general']['fields']['layout-block-default-mode'] = array(
314
- 'label' => __( 'Layout Block Default Mode', 'siteorigin-panels' ),
315
- 'type' => 'select',
316
- 'options' => array(
317
- 'edit' => __( 'Edit', 'siteorigin-panels' ),
318
- 'preview' => __( 'Preview', 'siteorigin-panels' ),
319
- ),
320
- 'description' => __( 'Whether to display layout blocks in edit mode or preview mode in the block editor.', 'siteorigin-panels' ),
321
- );
322
-
323
- // The widgets fields
324
-
325
- $fields['widgets'] = array(
326
- 'title' => __( 'Widgets', 'siteorigin-panels' ),
327
- 'fields' => array(),
328
- );
329
-
330
- $fields['widgets']['fields']['title-html'] = array(
331
- 'type' => 'html',
332
- 'label' => __( 'Widget Title HTML', 'siteorigin-panels' ),
333
- 'description' => __( 'The HTML used for widget titles. {{title}} is replaced with the widget title.', 'siteorigin-panels' ),
334
- );
335
-
336
- $fields['widgets']['fields']['add-widget-class'] = array(
337
- 'type' => 'checkbox',
338
- 'label' => __( 'Add Widget Class', 'siteorigin-panels' ),
339
- 'description' => __( "Add the widget class to Page Builder widgets. Disable this if you're experiencing conflicts.", 'siteorigin-panels' ),
340
- );
341
-
342
- $fields['widgets']['fields']['bundled-widgets'] = array(
343
- 'type' => 'checkbox',
344
- 'label' => __( 'Legacy Bundled Widgets', 'siteorigin-panels' ),
345
- 'description' => __( 'Load legacy widgets from Page Builder 1.', 'siteorigin-panels' ),
346
- );
347
-
348
- $fields['widgets']['fields']['recommended-widgets'] = array(
349
- 'type' => 'checkbox',
350
- 'label' => __( 'Recommended Widgets', 'siteorigin-panels' ),
351
- 'description' => __( 'Display recommend widgets in Page Builder add widget dialog.', 'siteorigin-panels' ),
352
- );
353
-
354
- $fields['widgets']['fields']['instant-open-widgets'] = array(
355
- 'type' => 'checkbox',
356
- 'label' => __( 'Instant Open Widgets', 'siteorigin-panels' ),
357
- 'description' => __( 'Open a widget form as soon as its added to a page.', 'siteorigin-panels' ),
358
- );
359
-
360
- // The layout fields
361
-
362
- $fields['layout'] = array(
363
- 'title' => __( 'Layout', 'siteorigin-panels' ),
364
- 'fields' => array(),
365
- );
366
-
367
- // The layout fields
368
-
369
- $fields['layout']['fields']['responsive'] = array(
370
- 'type' => 'checkbox',
371
- 'label' => __( 'Responsive Layout', 'siteorigin-panels' ),
372
- 'description' => __( 'Collapse widgets, rows and columns on mobile devices.', 'siteorigin-panels' ),
373
- );
374
-
375
- $fields['layout']['fields']['tablet-layout'] = array(
376
- 'type' => 'checkbox',
377
- 'label' => __( 'Use Tablet Layout', 'siteorigin-panels' ),
378
- 'description' => __( 'Collapses columns differently on tablet devices.', 'siteorigin-panels' ),
379
- );
380
-
381
- $fields['layout']['fields']['legacy-layout'] = array(
382
- 'type' => 'select',
383
- 'options' => array(
384
- 'auto' => __( 'Detect older browsers', 'siteorigin-panels' ),
385
- 'never' => __( 'Never', 'siteorigin-panels' ),
386
- 'always' => __( 'Always', 'siteorigin-panels' ),
387
- ),
388
- 'label' => __( 'Use Legacy Layout Engine', 'siteorigin-panels' ),
389
- 'description' => __( 'The CSS and HTML uses floats instead of flexbox for compatibility with very old browsers.', 'siteorigin-panels' ),
390
- );
391
-
392
- $fields['layout']['fields']['tablet-width'] = array(
393
- 'type' => 'number',
394
- 'unit' => 'px',
395
- 'label' => __( 'Tablet Width', 'siteorigin-panels' ),
396
- 'description' => __( 'Device width, in pixels, to collapse into a tablet view .', 'siteorigin-panels' ),
397
- );
398
-
399
- $fields['layout']['fields']['mobile-width'] = array(
400
- 'type' => 'number',
401
- 'unit' => 'px',
402
- 'label' => __( 'Mobile Width', 'siteorigin-panels' ),
403
- 'description' => __( 'Device width, in pixels, to collapse into a mobile view .', 'siteorigin-panels' ),
404
- );
405
-
406
- $fields['layout']['fields']['margin-bottom'] = array(
407
- 'type' => 'number',
408
- 'unit' => 'px',
409
- 'label' => __( 'Row/Widget Bottom Margin', 'siteorigin-panels' ),
410
- 'description' => __( 'Default margin below rows and widgets.', 'siteorigin-panels' ),
411
- );
412
-
413
- $fields['layout']['fields']['margin-bottom-last-row'] = array(
414
- 'type' => 'checkbox',
415
- 'label' => __( 'Last Row With Margin', 'siteorigin-panels' ),
416
- 'description' => __( 'Allow margin in last row.', 'siteorigin-panels' ),
417
- );
418
-
419
- $fields['layout']['fields']['margin-sides'] = array(
420
- 'type' => 'number',
421
- 'unit' => 'px',
422
- 'label' => __( 'Row Gutter', 'siteorigin-panels' ),
423
- 'description' => __( 'Default spacing between columns in each row.', 'siteorigin-panels' ),
424
- 'keywords' => 'margin',
425
- );
426
-
427
- $fields['layout']['fields']['full-width-container'] = array(
428
- 'type' => 'text',
429
- 'label' => __( 'Full Width Container', 'siteorigin-panels' ),
430
- 'description' => __( 'The container used for the full width layout.', 'siteorigin-panels' ),
431
- 'keywords' => 'full width, container, stretch',
432
- );
433
-
434
- // The content fields
435
-
436
- $fields['content'] = array(
437
- 'title' => __( 'Content', 'siteorigin-panels' ),
438
- 'fields' => array(),
439
- );
440
-
441
- $fields['content']['fields']['copy-content'] = array(
442
- 'type' => 'checkbox',
443
- 'label' => __( 'Copy Content', 'siteorigin-panels' ),
444
- 'description' => __( 'Copy content from Page Builder to post content.', 'siteorigin-panels' ),
445
- );
446
-
447
- $fields['content']['fields']['copy-styles'] = array(
448
- 'type' => 'checkbox',
449
- 'label' => __( 'Copy Styles', 'siteorigin-panels' ),
450
- 'description' => __( 'Include styles into your Post Content. This keeps page layouts, even when Page Builder is deactivated.', 'siteorigin-panels' ),
451
- );
452
-
453
- return $fields;
454
- }
455
-
456
- /**
457
- * Display a settings field
458
- *
459
- * @param $field_id
460
- * @param $field
461
- */
462
- function display_field( $field_id, $field ) {
463
- $value = siteorigin_panels_setting( $field_id );
464
-
465
- $field_name = 'panels_setting[' . $field_id . ']';
466
-
467
- switch ( $field['type'] ) {
468
- case 'text':
469
- case 'float':
470
- ?><input name="<?php echo esc_attr( $field_name ) ?>"
471
- class="panels-setting-<?php echo esc_attr( $field['type'] ) ?>" type="text"
472
- value="<?php echo esc_attr( $value ) ?>" /> <?php
473
- break;
474
-
475
- case 'number':
476
- ?>
477
- <input name="<?php echo esc_attr( $field_name ) ?>" type="number"
478
- class="panels-setting-<?php echo esc_attr( $field['type'] ) ?>"
479
- value="<?php echo esc_attr( $value ) ?>"/>
480
- <?php
481
- if ( ! empty( $field['unit'] ) ) {
482
- echo esc_html( $field['unit'] );
483
- }
484
- break;
485
-
486
- case 'html':
487
- ?><textarea name="<?php echo esc_attr( $field_name ) ?>"
488
- class="panels-setting-<?php echo esc_attr( $field['type'] ) ?> widefat"
489
- rows="<?php echo ! empty( $field['rows'] ) ? intval( $field['rows'] ) : 2 ?>"><?php echo esc_textarea( $value ) ?></textarea> <?php
490
- break;
491
-
492
- case 'checkbox':
493
- ?>
494
- <label class="widefat">
495
- <input name="<?php echo esc_attr( $field_name ) ?>"
496
- type="checkbox" <?php checked( ! empty( $value ) ) ?> />
497
- <?php echo ! empty( $field['checkbox_text'] ) ? esc_html( $field['checkbox_text'] ) : __( 'Enabled', 'siteorigin-panels' ) ?>
498
- </label>
499
- <?php
500
- break;
501
-
502
- case 'select':
503
- ?>
504
- <select name="<?php echo esc_attr( $field_name ) ?>">
505
- <?php foreach ( $field['options'] as $option_id => $option ) : ?>
506
- <option
507
- value="<?php echo esc_attr( $option_id ) ?>" <?php selected( $option_id, $value ) ?>><?php echo esc_html( $option ) ?></option>
508
- <?php endforeach; ?>
509
- </select>
510
- <?php
511
- break;
512
-
513
- case 'select_multi':
514
- foreach ( $field['options'] as $option_id => $option ) {
515
- ?>
516
- <label class="widefat">
517
- <input name="<?php echo esc_attr( $field_name ) ?>[<?php echo esc_attr( $option_id ) ?>]"
518
- type="checkbox" <?php checked( in_array( $option_id, $value ) ) ?> />
519
- <?php echo esc_html( $option ) ?>
520
- </label>
521
- <?php
522
- }
523
-
524
- break;
525
- }
526
- }
527
-
528
- /**
529
- * Save the Page Builder settings.
530
- */
531
- function save_settings() {
532
- $screen = get_current_screen();
533
- if ( $screen->base != 'settings_page_siteorigin_panels' ) {
534
- return;
535
- }
536
-
537
- if ( ! current_user_can( 'manage_options' ) ) {
538
- return;
539
- }
540
- if ( empty( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'panels-settings' ) ) {
541
- return;
542
- }
543
- if ( empty( $_POST['panels_setting'] ) ) {
544
- return;
545
- }
546
-
547
- $values = array();
548
- $post = stripslashes_deep( $_POST['panels_setting'] );
549
- $settings_fields = $this->fields = apply_filters( 'siteorigin_panels_settings_fields', array() );
550
-
551
- if ( empty( $settings_fields ) ) {
552
- return;
553
- }
554
-
555
- foreach ( $settings_fields as $section_id => $section ) {
556
- if ( empty( $section['fields'] ) ) {
557
- continue;
558
- }
559
-
560
- foreach ( $section['fields'] as $field_id => $field ) {
561
- // Sanitize the fields
562
- switch ( $field['type'] ) {
563
- case 'text' :
564
- $values[ $field_id ] = ! empty( $post[ $field_id ] ) ? sanitize_text_field( $post[ $field_id ] ) : '';
565
- break;
566
-
567
- case 'number':
568
- if ( $post[ $field_id ] != '' ) {
569
- $values[ $field_id ] = ! empty( $post[ $field_id ] ) ? intval( $post[ $field_id ] ) : 0;
570
- } else {
571
- $values[ $field_id ] = '';
572
- }
573
- break;
574
-
575
- case 'float':
576
- if ( $post[ $field_id ] != '' ) {
577
- $values[ $field_id ] = ! empty( $post[ $field_id ] ) ? floatval( $post[ $field_id ] ) : 0;
578
- } else {
579
- $values[ $field_id ] = '';
580
- }
581
- break;
582
-
583
- case 'html':
584
- $values[ $field_id ] = ! empty( $post[ $field_id ] ) ? $post[ $field_id ] : '';
585
- $values[ $field_id ] = wp_kses_post( $values[ $field_id ] );
586
- $values[ $field_id ] = force_balance_tags( $values[ $field_id ] );
587
- break;
588
-
589
- case 'checkbox':
590
- $values[ $field_id ] = ! empty( $post[ $field_id ] );
591
- break;
592
-
593
- case 'select':
594
- $values[ $field_id ] = ! empty( $post[ $field_id ] ) ? $post[ $field_id ] : '';
595
- if ( ! in_array( $values[ $field_id ], array_keys( $field['options'] ) ) ) {
596
- unset( $values[ $field_id ] );
597
- }
598
- break;
599
-
600
- case 'select_multi':
601
- $values[ $field_id ] = array();
602
- $multi_values = array();
603
- foreach ( $field['options'] as $option_id => $option ) {
604
- $multi_values[ $option_id ] = ! empty( $post[ $field_id ][ $option_id ] );
605
- }
606
- foreach ( $multi_values as $k => $v ) {
607
- if ( $v ) {
608
- $values[ $field_id ][] = $k;
609
- }
610
- }
611
-
612
- break;
613
- }
614
- }
615
- }
616
-
617
- // Don't let mobile width go below 320
618
- $values[ 'mobile-width' ] = max( $values[ 'mobile-width' ], 320 );
619
-
620
- // Save the values to the database
621
- update_option( 'siteorigin_panels_settings', $values );
622
- do_action( 'siteorigin_panels_save_settings', $values );
623
- $this->settings = wp_parse_args( $values, $this->settings );
624
- $this->settings_saved = true;
625
- }
626
-
627
- /**
628
- * Get a post type array
629
- *
630
- * @return array
631
- */
632
- function get_post_types() {
633
- $post_types = get_post_types( array( '_builtin' => false ) );
634
-
635
- $types = array(
636
- 'page' => 'page',
637
- 'post' => 'post'
638
- );
639
-
640
- // Don't use `array_merge` here as it will break things if a post type has a numeric slug.
641
- foreach ( $post_types as $key => $value ) {
642
- $types[ $key ] = $value;
643
- }
644
-
645
- // These are post types we know we don't want to show Page Builder on
646
- unset( $types['ml-slider'] );
647
-
648
- foreach ( $types as $type_id => $type ) {
649
- $type_object = get_post_type_object( $type_id );
650
-
651
- if ( ! $type_object->show_ui ) {
652
- unset( $types[ $type_id ] );
653
- continue;
654
- }
655
-
656
- $types[ $type_id ] = $type_object->label;
657
- }
658
-
659
- return apply_filters( 'siteorigin_panels_settings_enabled_post_types', $types );
660
- }
661
-
662
- }
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Class to handle Page Builder settings.
5
+ *
6
+ * Class SiteOrigin_Panels_Settings
7
+ */
8
+ class SiteOrigin_Panels_Settings {
9
+
10
+ private $settings;
11
+ private $fields;
12
+ private $settings_saved;
13
+
14
+ function __construct() {
15
+ $this->settings = array();
16
+ $this->fields = array();
17
+ $this->settings_saved = false;
18
+
19
+ // Admin actions
20
+ add_action( 'admin_enqueue_scripts', array( $this, 'admin_scripts' ) );
21
+ add_action( 'admin_menu', array( $this, 'add_settings_page' ) );
22
+ add_action( 'after_setup_theme', array( $this, 'clear_cache' ), 100 );
23
+
24
+ // Default filters for fields and defaults
25
+ add_filter( 'siteorigin_panels_settings_defaults', array( $this, 'settings_defaults' ) );
26
+ add_filter( 'siteorigin_panels_default_add_widget_class', array( $this, 'add_widget_class' ) );
27
+ add_filter( 'siteorigin_panels_settings_fields', array( $this, 'settings_fields' ) );
28
+ }
29
+
30
+ /**
31
+ * @return SiteOrigin_Panels_Settings
32
+ */
33
+ static function single() {
34
+ static $single;
35
+ return empty( $single ) ? $single = new self() : $single;
36
+ }
37
+
38
+ function clear_cache() {
39
+ $this->settings = array();
40
+ }
41
+
42
+ /**
43
+ * Get a settings value
44
+ *
45
+ * @param string $key
46
+ *
47
+ * @return array|bool|mixed|null|void
48
+ */
49
+ function get( $key = '' ) {
50
+
51
+ if ( empty( $this->settings ) ) {
52
+
53
+ // Get the settings, attempt to fetch new settings first.
54
+ $current_settings = get_option( 'siteorigin_panels_settings', false );
55
+
56
+ if ( $current_settings === false ) {
57
+ // We can't find the settings, so try access old settings
58
+ $current_settings = get_option( 'siteorigin_panels_display', array() );
59
+ $post_types = get_option( 'siteorigin_panels_post_types' );
60
+ if ( ! empty( $post_types ) ) {
61
+ $current_settings['post-types'] = $post_types;
62
+ }
63
+
64
+ // Store the old settings in the new field
65
+ update_option( 'siteorigin_panels_settings', $current_settings );
66
+ }
67
+
68
+ // Get the settings provided by the theme
69
+ $theme_settings = get_theme_support( 'siteorigin-panels' );
70
+ if ( ! empty( $theme_settings ) ) {
71
+ $theme_settings = $theme_settings[0];
72
+ } else {
73
+ $theme_settings = array();
74
+ }
75
+
76
+ $this->settings = wp_parse_args( $theme_settings, apply_filters( 'siteorigin_panels_settings_defaults', array() ) );
77
+ $this->settings = wp_parse_args( $current_settings, $this->settings );
78
+
79
+ // Filter these settings
80
+ $this->settings = apply_filters( 'siteorigin_panels_settings', $this->settings );
81
+ }
82
+
83
+ if ( ! empty( $key ) ) {
84
+ return isset( $this->settings[ $key ] ) ? $this->settings[ $key ] : null;
85
+ }
86
+
87
+ return $this->settings;
88
+ }
89
+
90
+ /**
91
+ * Set a settings value
92
+ *
93
+ * @param $key
94
+ * @param $value
95
+ */
96
+ function set( $key, $value ) {
97
+ $current_settings = get_option( 'siteorigin_panels_settings', array() );
98
+ $current_settings[ $key ] = $value;
99
+ update_option( 'siteorigin_panels_settings', $current_settings );
100
+ }
101
+
102
+ /**
103
+ * Add default settings for the Page Builder settings.
104
+ *
105
+ * @param $defaults
106
+ *
107
+ * @return mixed
108
+ */
109
+ function settings_defaults( $defaults ) {
110
+ $defaults['home-page'] = false;
111
+ $defaults['home-page-default'] = false;
112
+ $defaults['home-template'] = 'home-panels.php';
113
+ $defaults['affiliate-id'] = apply_filters( 'siteorigin_panels_affiliate_id', false );
114
+ $defaults['display-teaser'] = true;
115
+ $defaults['display-learn'] = true;
116
+ $defaults['load-on-attach'] = false;
117
+ $defaults['use-classic'] = true;
118
+
119
+ // The general fields
120
+ $defaults['post-types'] = array( 'page', 'post' );
121
+ $defaults['live-editor-quick-link'] = true;
122
+ $defaults['admin-post-state'] = true;
123
+ $defaults['admin-widget-count'] = false;
124
+ $defaults['parallax-motion'] = '';
125
+ $defaults['sidebars-emulator'] = true;
126
+ $defaults['layout-block-default-mode'] = 'preview';
127
+
128
+ // Widgets fields
129
+ $defaults['title-html'] = '<h3 class="widget-title">{{title}}</h3>';
130
+ $defaults['add-widget-class'] = apply_filters( 'siteorigin_panels_default_add_widget_class', true );
131
+ $defaults['bundled-widgets'] = get_option( 'siteorigin_panels_is_using_bundled', false );
132
+ $defaults['recommended-widgets'] = true;
133
+ $defaults['instant-open-widgets'] = false;
134
+
135
+ // The layout fields
136
+ $defaults['responsive'] = true;
137
+ $defaults['tablet-layout'] = false;
138
+ $defaults['legacy-layout'] = 'auto';
139
+ $defaults['tablet-width'] = 1024;
140
+ $defaults['mobile-width'] = 780;
141
+ $defaults['margin-bottom'] = 30;
142
+ $defaults['margin-bottom-last-row'] = false;
143
+ $defaults['margin-sides'] = 30;
144
+ $defaults['full-width-container'] = 'body';
145
+
146
+ // Content fields
147
+ $defaults['copy-content'] = true;
148
+ $defaults['copy-styles'] = false;
149
+
150
+ return $defaults;
151
+ }
152
+
153
+ /**
154
+ * Set the option on whether to add widget classes for known themes
155
+ *
156
+ * @param $add_class
157
+ *
158
+ * @return bool
159
+ */
160
+ function add_widget_class( $add_class ) {
161
+
162
+ switch ( get_option( 'stylesheet' ) ) {
163
+ case 'twentysixteen';
164
+ $add_class = false;
165
+ break;
166
+ }
167
+
168
+
169
+ return $add_class;
170
+ }
171
+
172
+ /**
173
+ * Enqueue admin scripts
174
+ *
175
+ * @param $prefix
176
+ */
177
+ function admin_scripts( $prefix ) {
178
+ if ( $prefix != 'settings_page_siteorigin_panels' ) {
179
+ return;
180
+ }
181
+ wp_enqueue_style(
182
+ 'siteorigin-panels-settings',
183
+ siteorigin_panels_url( 'settings/admin-settings.css' ),
184
+ array(),
185
+ SITEORIGIN_PANELS_VERSION
186
+ );
187
+ wp_enqueue_script(
188
+ 'siteorigin-panels-settings',
189
+ siteorigin_panels_url( 'settings/admin-settings' . SITEORIGIN_PANELS_JS_SUFFIX . '.js' ),
190
+ array(),
191
+ SITEORIGIN_PANELS_VERSION
192
+ );
193
+ }
194
+
195
+ /**
196
+ * Add the Page Builder settings page
197
+ */
198
+ function add_settings_page() {
199
+ $page = add_options_page( __( 'SiteOrigin Page Builder', 'siteorigin-panels' ), __( 'Page Builder', 'siteorigin-panels' ), 'manage_options', 'siteorigin_panels', array(
200
+ $this,
201
+ 'display_settings_page'
202
+ ) );
203
+ add_action( 'load-' . $page, array( $this, 'add_help_tab' ) );
204
+ add_action( 'load-' . $page, array( $this, 'save_settings' ) );
205
+ }
206
+
207
+ /**
208
+ * Display the Page Builder settings page
209
+ */
210
+ function display_settings_page() {
211
+ $settings_fields = $this->fields = apply_filters( 'siteorigin_panels_settings_fields', array() );
212
+ include plugin_dir_path( __FILE__ ) . '../settings/tpl/settings.php';
213
+ }
214
+
215
+ /**
216
+ * Add a settings help tab
217
+ */
218
+ function add_help_tab() {
219
+ $screen = get_current_screen();
220
+ ob_start();
221
+ include plugin_dir_path( __FILE__ ) . '../settings/tpl/help.php';
222
+ $content = ob_get_clean();
223
+
224
+ $screen->add_help_tab( array(
225
+ 'id' => 'panels-help-tab',
226
+ 'title' => __( 'Page Builder Settings', 'siteorigin-panels' ),
227
+ 'content' => $content
228
+ ) );
229
+ }
230
+
231
+ /**
232
+ * Add the default Page Builder settings.
233
+ *
234
+ * @param $fields
235
+ *
236
+ * @return mixed
237
+ */
238
+ function settings_fields( $fields ) {
239
+ // The post types fields
240
+
241
+ $fields['general'] = array(
242
+ 'title' => __( 'General', 'siteorigin-panels' ),
243
+ 'fields' => array(),
244
+ );
245
+
246
+ $fields['general']['fields']['post-types'] = array(
247
+ 'type' => 'select_multi',
248
+ 'label' => __( 'Post Types', 'siteorigin-panels' ),
249
+ 'options' => $this->get_post_types(),
250
+ 'description' => __( 'The post types on which to use Page Builder.', 'siteorigin-panels' ),
251
+ );
252
+
253
+ $fields['general']['fields']['use-classic'] = array(
254
+ 'type' => 'checkbox',
255
+ 'label' => __( 'Use Classic Editor for new posts', 'siteorigin-panels' ),
256
+ 'description' => __( 'New posts of the above Post Types will be created using the Classic Editor.', 'siteorigin-panels' )
257
+ );
258
+
259
+ $fields['general']['fields']['live-editor-quick-link'] = array(
260
+ 'type' => 'checkbox',
261
+ 'label' => __( 'Live Editor Quick Link', 'siteorigin-panels' ),
262
+ 'description' => __( 'Display a Live Editor button in the admin bar.', 'siteorigin-panels' ),
263
+ );
264
+
265
+ $fields['general']['fields']['admin-post-state'] = array(
266
+ 'type' => 'checkbox',
267
+ 'label' => __( 'Display Post State', 'siteorigin-panels' ),
268
+ 'description' => sprintf(
269
+ __( "Display a %sSiteOrigin Page Builder%s post state in the admin lists of posts/pages to indicate Page Builder is active.", 'siteorigin-panels' ),
270
+ '<strong>',
271
+ '</strong>'
272
+ ),
273
+ );
274
+
275
+ $fields['general']['fields']['admin-widget-count'] = array(
276
+ 'type' => 'checkbox',
277
+ 'label' => __( 'Display Widget Count', 'siteorigin-panels' ),
278
+ 'description' => __( "Display a widget count in the admin lists of posts/pages where you're using Page Builder.", 'siteorigin-panels' ),
279
+ );
280
+
281
+ $fields['general']['fields']['parallax-motion'] = array(
282
+ 'type' => 'float',
283
+ 'label' => __( 'Limit Parallax Motion', 'siteorigin-panels' ),
284
+ 'description' => __( 'How many pixels of scrolling result in a single pixel of parallax motion. 0 means automatic. Lower values give more noticeable effect.', 'siteorigin-panels' ),
285
+ );
286
+
287
+ $fields['general']['fields']['sidebars-emulator'] = array(
288
+ 'type' => 'checkbox',
289
+ 'label' => __( 'Sidebars Emulator', 'siteorigin-panels' ),
290
+ 'description' => __( 'Page Builder will create an emulated sidebar, that contains all widgets in the page.', 'siteorigin-panels' ),
291
+ );
292
+
293
+ $fields['general']['fields']['display-teaser'] = array(
294
+ 'type' => 'checkbox',
295
+ 'label' => __('Upgrade Teaser', 'siteorigin-panels'),
296
+ 'description' => sprintf(
297
+ __('Display the %sSiteOrigin Premium%s upgrade teaser in the Page Builder toolbar.', 'siteorigin-panels'),
298
+ '<a href="https://siteorigin.com/downloads/premium/" target="_blank" rel="noopener noreferrer">',
299
+ '</a>'
300
+ )
301
+ );
302
+
303
+ $fields['general']['fields']['load-on-attach'] = array(
304
+ 'type' => 'checkbox',
305
+ 'label' => __( 'Default To Page Builder Interface', 'siteorigin-panels' ),
306
+ 'description' => sprintf(
307
+ __( 'New Classic Editor posts/pages that you create will start with the Page Builder loaded. The %s"Use Classic Editor for new posts"%s setting must be enabled.', 'siteorigin-panels' ),
308
+ '<strong>',
309
+ '</strong>'
310
+ )
311
+ );
312
+
313
+ $fields['general']['fields']['layout-block-default-mode'] = array(
314
+ 'label' => __( 'Layout Block Default Mode', 'siteorigin-panels' ),
315
+ 'type' => 'select',
316
+ 'options' => array(
317
+ 'edit' => __( 'Edit', 'siteorigin-panels' ),
318
+ 'preview' => __( 'Preview', 'siteorigin-panels' ),
319
+ ),
320
+ 'description' => __( 'Whether to display layout blocks in edit mode or preview mode in the block editor.', 'siteorigin-panels' ),
321
+ );
322
+
323
+ // The widgets fields
324
+
325
+ $fields['widgets'] = array(
326
+ 'title' => __( 'Widgets', 'siteorigin-panels' ),
327
+ 'fields' => array(),
328
+ );
329
+
330
+ $fields['widgets']['fields']['title-html'] = array(
331
+ 'type' => 'html',
332
+ 'label' => __( 'Widget Title HTML', 'siteorigin-panels' ),
333
+ 'description' => __( 'The HTML used for widget titles. {{title}} is replaced with the widget title.', 'siteorigin-panels' ),
334
+ );
335
+
336
+ $fields['widgets']['fields']['add-widget-class'] = array(
337
+ 'type' => 'checkbox',
338
+ 'label' => __( 'Add Widget Class', 'siteorigin-panels' ),
339
+ 'description' => __( "Add the widget class to Page Builder widgets. Disable this if you're experiencing conflicts.", 'siteorigin-panels' ),
340
+ );
341
+
342
+ $fields['widgets']['fields']['bundled-widgets'] = array(
343
+ 'type' => 'checkbox',
344
+ 'label' => __( 'Legacy Bundled Widgets', 'siteorigin-panels' ),
345
+ 'description' => __( 'Load legacy widgets from Page Builder 1.', 'siteorigin-panels' ),
346
+ );
347
+
348
+ $fields['widgets']['fields']['recommended-widgets'] = array(
349
+ 'type' => 'checkbox',
350
+ 'label' => __( 'Recommended Widgets', 'siteorigin-panels' ),
351
+ 'description' => __( 'Display recommend widgets in Page Builder add widget dialog.', 'siteorigin-panels' ),
352
+ );
353
+
354
+ $fields['widgets']['fields']['instant-open-widgets'] = array(
355
+ 'type' => 'checkbox',
356
+ 'label' => __( 'Instant Open Widgets', 'siteorigin-panels' ),
357
+ 'description' => __( 'Open a widget form as soon as its added to a page.', 'siteorigin-panels' ),
358
+ );
359
+
360
+ // The layout fields
361
+
362
+ $fields['layout'] = array(
363
+ 'title' => __( 'Layout', 'siteorigin-panels' ),
364
+ 'fields' => array(),
365
+ );
366
+
367
+ // The layout fields
368
+
369
+ $fields['layout']['fields']['responsive'] = array(
370
+ 'type' => 'checkbox',
371
+ 'label' => __( 'Responsive Layout', 'siteorigin-panels' ),
372
+ 'description' => __( 'Collapse widgets, rows and columns on mobile devices.', 'siteorigin-panels' ),
373
+ );
374
+
375
+ $fields['layout']['fields']['tablet-layout'] = array(
376
+ 'type' => 'checkbox',
377
+ 'label' => __( 'Use Tablet Layout', 'siteorigin-panels' ),
378
+ 'description' => __( 'Collapses columns differently on tablet devices.', 'siteorigin-panels' ),
379
+ );
380
+
381
+ $fields['layout']['fields']['legacy-layout'] = array(
382
+ 'type' => 'select',
383
+ 'options' => array(
384
+ 'auto' => __( 'Detect older browsers', 'siteorigin-panels' ),
385
+ 'never' => __( 'Never', 'siteorigin-panels' ),
386
+ 'always' => __( 'Always', 'siteorigin-panels' ),
387
+ ),
388
+ 'label' => __( 'Use Legacy Layout Engine', 'siteorigin-panels' ),
389
+ 'description' => __( 'The CSS and HTML uses floats instead of flexbox for compatibility with very old browsers.', 'siteorigin-panels' ),
390
+ );
391
+
392
+ $fields['layout']['fields']['tablet-width'] = array(
393
+ 'type' => 'number',
394
+ 'unit' => 'px',
395
+ 'label' => __( 'Tablet Width', 'siteorigin-panels' ),
396
+ 'description' => __( 'Device width, in pixels, to collapse into a tablet view .', 'siteorigin-panels' ),
397
+ );
398
+
399
+ $fields['layout']['fields']['mobile-width'] = array(
400
+ 'type' => 'number',
401
+ 'unit' => 'px',
402
+ 'label' => __( 'Mobile Width', 'siteorigin-panels' ),
403
+ 'description' => __( 'Device width, in pixels, to collapse into a mobile view .', 'siteorigin-panels' ),
404
+ );
405
+
406
+ $fields['layout']['fields']['margin-bottom'] = array(
407
+ 'type' => 'number',
408
+ 'unit' => 'px',
409
+ 'label' => __( 'Row/Widget Bottom Margin', 'siteorigin-panels' ),
410
+ 'description' => __( 'Default margin below rows and widgets.', 'siteorigin-panels' ),
411
+ );
412
+
413
+ $fields['layout']['fields']['margin-bottom-last-row'] = array(
414
+ 'type' => 'checkbox',
415
+ 'label' => __( 'Last Row With Margin', 'siteorigin-panels' ),
416
+ 'description' => __( 'Allow margin in last row.', 'siteorigin-panels' ),
417
+ );
418
+
419
+ $fields['layout']['fields']['margin-sides'] = array(
420
+ 'type' => 'number',
421
+ 'unit' => 'px',
422
+ 'label' => __( 'Row Gutter', 'siteorigin-panels' ),
423
+ 'description' => __( 'Default spacing between columns in each row.', 'siteorigin-panels' ),
424
+ 'keywords' => 'margin',
425
+ );
426
+
427
+ $fields['layout']['fields']['full-width-container'] = array(
428
+ 'type' => 'text',
429
+ 'label' => __( 'Full Width Container', 'siteorigin-panels' ),
430
+ 'description' => __( 'The container used for the full width layout.', 'siteorigin-panels' ),
431
+ 'keywords' => 'full width, container, stretch',
432
+ );
433
+
434
+ // The content fields
435
+
436
+ $fields['content'] = array(
437
+ 'title' => __( 'Content', 'siteorigin-panels' ),
438
+ 'fields' => array(),
439
+ );
440
+
441
+ $fields['content']['fields']['copy-content'] = array(
442
+ 'type' => 'checkbox',
443
+ 'label' => __( 'Copy Content', 'siteorigin-panels' ),
444
+ 'description' => __( 'Copy content from Page Builder to post content.', 'siteorigin-panels' ),
445
+ );
446
+
447
+ $fields['content']['fields']['copy-styles'] = array(
448
+ 'type' => 'checkbox',
449
+ 'label' => __( 'Copy Styles', 'siteorigin-panels' ),
450
+ 'description' => __( 'Include styles into your Post Content. This keeps page layouts, even when Page Builder is deactivated.', 'siteorigin-panels' ),
451
+ );
452
+
453
+ return $fields;
454
+ }
455
+
456
+ /**
457
+ * Display a settings field
458
+ *
459
+ * @param $field_id
460
+ * @param $field
461
+ */
462
+ function display_field( $field_id, $field ) {
463
+ $value = siteorigin_panels_setting( $field_id );
464
+
465
+ $field_name = 'panels_setting[' . $field_id . ']';
466
+
467
+ switch ( $field['type'] ) {
468
+ case 'text':
469
+ case 'float':
470
+ ?><input name="<?php echo esc_attr( $field_name ) ?>"
471
+ class="panels-setting-<?php echo esc_attr( $field['type'] ) ?>" type="text"
472
+ value="<?php echo esc_attr( $value ) ?>" /> <?php
473
+ break;
474
+
475
+ case 'password':
476
+ ?><input name="<?php echo esc_attr( $field_name ) ?>"
477
+ class="panels-setting-<?php echo esc_attr( $field['type'] ) ?>" type="password"
478
+ value="<?php echo esc_attr( $value ) ?>" /> <?php
479
+ break;
480
+
481
+ case 'number':
482
+ ?>
483
+ <input name="<?php echo esc_attr( $field_name ) ?>" type="number"
484
+ class="panels-setting-<?php echo esc_attr( $field['type'] ) ?>"
485
+ value="<?php echo esc_attr( $value ) ?>"/>
486
+ <?php
487
+ if ( ! empty( $field['unit'] ) ) {
488
+ echo esc_html( $field['unit'] );
489
+ }
490
+ break;
491
+
492
+ case 'html':
493
+ ?><textarea name="<?php echo esc_attr( $field_name ) ?>"
494
+ class="panels-setting-<?php echo esc_attr( $field['type'] ) ?> widefat"
495
+ rows="<?php echo ! empty( $field['rows'] ) ? intval( $field['rows'] ) : 2 ?>"><?php echo esc_textarea( $value ) ?></textarea> <?php
496
+ break;
497
+
498
+ case 'checkbox':
499
+ ?>
500
+ <label class="widefat">
501
+ <input name="<?php echo esc_attr( $field_name ) ?>"
502
+ type="checkbox" <?php checked( ! empty( $value ) ) ?> />
503
+ <?php echo ! empty( $field['checkbox_text'] ) ? esc_html( $field['checkbox_text'] ) : __( 'Enabled', 'siteorigin-panels' ) ?>
504
+ </label>
505
+ <?php
506
+ break;
507
+
508
+ case 'select':
509
+ ?>
510
+ <select name="<?php echo esc_attr( $field_name ) ?>">
511
+ <?php foreach ( $field['options'] as $option_id => $option ) : ?>
512
+ <option
513
+ value="<?php echo esc_attr( $option_id ) ?>" <?php selected( $option_id, $value ) ?>><?php echo esc_html( $option ) ?></option>
514
+ <?php endforeach; ?>
515
+ </select>
516
+ <?php
517
+ break;
518
+
519
+ case 'select_multi':
520
+ foreach ( $field['options'] as $option_id => $option ) {
521
+ ?>
522
+ <label class="widefat">
523
+ <input name="<?php echo esc_attr( $field_name ) ?>[<?php echo esc_attr( $option_id ) ?>]"
524
+ type="checkbox" <?php checked( in_array( $option_id, $value ) ) ?> />
525
+ <?php echo esc_html( $option ) ?>
526
+ </label>
527
+ <?php
528
+ }
529
+
530
+ break;
531
+ }
532
+ }
533
+
534
+ /**
535
+ * Save the Page Builder settings.
536
+ */
537
+ function save_settings() {
538
+ $screen = get_current_screen();
539
+ if ( $screen->base != 'settings_page_siteorigin_panels' ) {
540
+ return;
541
+ }
542
+
543
+ if ( ! current_user_can( 'manage_options' ) ) {
544
+ return;
545
+ }
546
+ if ( empty( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'panels-settings' ) ) {
547
+ return;
548
+ }
549
+ if ( empty( $_POST['panels_setting'] ) ) {
550
+ return;
551
+ }
552
+
553
+ $values = array();
554
+ $post = stripslashes_deep( $_POST['panels_setting'] );
555
+ $settings_fields = $this->fields = apply_filters( 'siteorigin_panels_settings_fields', array() );
556
+
557
+ if ( empty( $settings_fields ) ) {
558
+ return;
559
+ }
560
+
561
+ foreach ( $settings_fields as $section_id => $section ) {
562
+ if ( empty( $section['fields'] ) ) {
563
+ continue;
564
+ }
565
+
566
+ foreach ( $section['fields'] as $field_id => $field ) {
567
+ // Sanitize the fields
568
+ switch ( $field['type'] ) {
569
+ case 'text' :
570
+ $values[ $field_id ] = ! empty( $post[ $field_id ] ) ? sanitize_text_field( $post[ $field_id ] ) : '';
571
+ break;
572
+
573
+ case 'number':
574
+ if ( $post[ $field_id ] != '' ) {
575
+ $values[ $field_id ] = ! empty( $post[ $field_id ] ) ? intval( $post[ $field_id ] ) : 0;
576
+ } else {
577
+ $values[ $field_id ] = '';
578
+ }
579
+ break;
580
+
581
+ case 'float':
582
+ if ( $post[ $field_id ] != '' ) {
583
+ $values[ $field_id ] = ! empty( $post[ $field_id ] ) ? floatval( $post[ $field_id ] ) : 0;
584
+ } else {
585
+ $values[ $field_id ] = '';
586
+ }
587
+ break;
588
+
589
+ case 'html':
590
+ $values[ $field_id ] = ! empty( $post[ $field_id ] ) ? $post[ $field_id ] : '';
591
+ $values[ $field_id ] = wp_kses_post( $values[ $field_id ] );
592
+ $values[ $field_id ] = force_balance_tags( $values[ $field_id ] );
593
+ break;
594
+
595
+ case 'checkbox':
596
+ $values[ $field_id ] = ! empty( $post[ $field_id ] );
597
+ break;
598
+
599
+ case 'select':
600
+ $values[ $field_id ] = ! empty( $post[ $field_id ] ) ? $post[ $field_id ] : '';
601
+ if ( ! in_array( $values[ $field_id ], array_keys( $field['options'] ) ) ) {
602
+ unset( $values[ $field_id ] );
603
+ }
604
+ break;
605
+
606
+ case 'select_multi':
607
+ $values[ $field_id ] = array();
608
+ $multi_values = array();
609
+ foreach ( $field['options'] as $option_id => $option ) {
610
+ $multi_values[ $option_id ] = ! empty( $post[ $field_id ][ $option_id ] );
611
+ }
612
+ foreach ( $multi_values as $k => $v ) {
613
+ if ( $v ) {
614
+ $values[ $field_id ][] = $k;
615
+ }
616
+ }
617
+
618
+ break;
619
+ }
620
+ }
621
+ }
622
+
623
+ // Don't let mobile width go below 320
624
+ $values[ 'mobile-width' ] = max( $values[ 'mobile-width' ], 320 );
625
+
626
+ // Save the values to the database
627
+ update_option( 'siteorigin_panels_settings', $values );
628
+ do_action( 'siteorigin_panels_save_settings', $values );
629
+ $this->settings = wp_parse_args( $values, $this->settings );
630
+ $this->settings_saved = true;
631
+ }
632
+
633
+ /**
634
+ * Get a post type array
635
+ *
636
+ * @return array
637
+ */
638
+ function get_post_types() {
639
+ $post_types = get_post_types( array( '_builtin' => false ) );
640
+
641
+ $types = array(
642
+ 'page' => 'page',
643
+ 'post' => 'post'
644
+ );
645
+
646
+ // Don't use `array_merge` here as it will break things if a post type has a numeric slug.
647
+ foreach ( $post_types as $key => $value ) {
648
+ $types[ $key ] = $value;
649
+ }
650
+
651
+ // These are post types we know we don't want to show Page Builder on
652
+ unset( $types['ml-slider'] );
653
+
654
+ foreach ( $types as $type_id => $type ) {
655
+ $type_object = get_post_type_object( $type_id );
656
+
657
+ if ( ! $type_object->show_ui ) {
658
+ unset( $types[ $type_id ] );
659
+ continue;
660
+ }
661
+
662
+ $types[ $type_id ] = $type_object->label;
663
+ }
664
+
665
+ return apply_filters( 'siteorigin_panels_settings_enabled_post_types', $types );
666
+ }
667
+
668
+ }
inc/sidebars-emulator.php CHANGED
@@ -1,224 +1,224 @@
1
- <?php
2
-
3
- class SiteOrigin_Panels_Sidebars_Emulator {
4
-
5
- private $all_posts_widgets;
6
-
7
- function __construct() {
8
- $this->all_posts_widgets = array();
9
- add_action( 'widgets_init', array( $this, 'register_widgets' ) );
10
- add_filter( 'sidebars_widgets', array( $this, 'add_widgets_to_sidebars' ) );
11
- }
12
-
13
- /**
14
- * Get the single instance.
15
- *
16
- * @return SiteOrigin_Panels_Sidebars_Emulator
17
- */
18
- static function single() {
19
- static $single;
20
- return empty( $single ) ? $single = new self() : $single;
21
- }
22
-
23
- /**
24
- * @param string $name The name of the function
25
- * @param array $args
26
- *
27
- * @return mixed
28
- */
29
- function __call( $name, $args ) {
30
-
31
- // Check if this is a filter option call
32
- preg_match( '/filter_option_widget_(.+)/', $name, $opt_matches );
33
- if ( ! empty( $opt_matches ) && count( $opt_matches ) > 1 ) {
34
- $opt_name = $opt_matches[1];
35
- global $wp_widget_factory;
36
- foreach ( $wp_widget_factory->widgets as $widget ) {
37
- $widget_class = get_class( $widget );
38
- if ( $widget->id_base != $opt_name ) {
39
- continue;
40
- }
41
-
42
- foreach ( $this->all_posts_widgets as $post_widgets ) {
43
- foreach ( $post_widgets as $widget_instance ) {
44
- if ( empty( $widget_instance['panels_info']['class'] ) ) {
45
- continue;
46
- }
47
-
48
- $instance_class = $widget_instance['panels_info']['class'];
49
- $sidebar_id = $this->get_sidebar_id( $widget_instance );
50
- if ( $instance_class == $widget_class && ! empty( $sidebar_id ) ) {
51
- //The option value uses only the widget id number as keys
52
- preg_match( '/-([0-9]+$)/', $sidebar_id, $num_match );
53
- $args[0][ $num_match[1] ] = $widget_instance;
54
- }
55
- }
56
- }
57
- }
58
-
59
- return $args[0];
60
- }
61
-
62
- }
63
-
64
- /**
65
- * Register all the current widgets so we can filter the get_option('widget_...') values to add instances
66
- */
67
- function register_widgets() {
68
- $current_url = add_query_arg( false, false );
69
- $cache_key = md5( $current_url );
70
-
71
- // Get the ID of the current post
72
- $post_id = wp_cache_get( $cache_key, 'siteorigin_url_to_postid' );
73
- if ( false === $post_id ) {
74
- $post_id = url_to_postid( $current_url );
75
- wp_cache_set( $cache_key, $post_id, 'siteorigin_url_to_postid', 3 * HOUR_IN_SECONDS );
76
- }
77
-
78
- if ( empty( $post_id ) ) {
79
- // Maybe this is the home page
80
- $current_url_path = parse_url( $current_url, PHP_URL_PATH );
81
- $home_url_path = parse_url( trailingslashit( home_url() ), PHP_URL_PATH );
82
-
83
- if ( $current_url_path === $home_url_path && get_option( 'page_on_front' ) != 0 ) {
84
- $post_id = absint( get_option( 'page_on_front' ) );
85
- }
86
- }
87
-
88
- if ( empty( $post_id ) ) {
89
- return;
90
- }
91
-
92
- $panels_data = get_post_meta( $post_id, 'panels_data', true );
93
- $widget_option_names = $this->get_widget_option_names( $post_id, $panels_data );
94
- $widget_option_names = array_unique( $widget_option_names );
95
-
96
- foreach ( $widget_option_names as $widget_option_name ) {
97
- add_filter( 'option_' . $widget_option_name, array( $this, 'filter_option_' . $widget_option_name ) );
98
- }
99
- }
100
-
101
- /**
102
- * Recursively get all widget option names from $panels_data and store widget instances in $this->all_posts_widgets.
103
- *
104
- * @param int|string $post_id
105
- * @param array $panels_data
106
- *
107
- * @return array A list of widget option names from the post and its Layout Builder widgets.
108
- */
109
- private function get_widget_option_names( $post_id, $panels_data ) {
110
- if( empty( $panels_data ) || empty( $panels_data[ 'widgets' ] ) ) {
111
- return array();
112
- }
113
-
114
- if( empty( $this->all_posts_widgets[ $post_id ] ) ) {
115
- $this->all_posts_widgets[ $post_id ] = array();
116
- }
117
-
118
- $widget_option_names = array();
119
- $widgets = $panels_data['widgets'];
120
- foreach ( $widgets as $i => $widget_instance ) {
121
- if ( empty( $widget_instance['panels_info']['class'] ) ) {
122
- continue;
123
- }
124
-
125
- if( $widget_instance['panels_info']['class'] === 'SiteOrigin_Panels_Widgets_Layout' ) {
126
- // Add the widget option names from the layout widget
127
- $widget_option_names = array_merge( $widget_option_names, $this->get_widget_option_names( $post_id, $widget_instance[ 'panels_data' ] ) );
128
- }
129
- $sidebar_id = $this->get_sidebar_id( $widget_instance );
130
- if ( ! empty( $sidebar_id ) ) {
131
- $widget_option_names[] = $widget_instance['option_name'];
132
- }
133
- $this->all_posts_widgets[ $post_id ][] = $widget_instance;
134
- }
135
-
136
- return $widget_option_names;
137
- }
138
-
139
- /**
140
- * This should be called when a post is saved to set ids required for `is_active_widget` checks. It's necessary to
141
- * do this separately for widgets that call `is_active_widget` in their constructors, e.g. some of Jetpack's widgets
142
- * like Twitter Timeline, Milestone etc.
143
- *
144
- * @param $widgets array The widgets in the layout from $panels_data for which to generate ids.
145
- * @param $post_id int The post id which is used to derive ids.
146
- * @param int $start This keeps track of recursive depth.
147
- *
148
- * @return array The widgets array containing updated widgets.
149
- */
150
- public function generate_sidebar_widget_ids( $widgets, $post_id, $start = 1 ) {
151
- foreach ( $widgets as $i => &$widget_instance ) {
152
- $id_val = $post_id . strval( ( 10000 * $start ) + intval( $i ) );
153
- $widget_class = $widget_instance['panels_info']['class'];
154
-
155
-
156
- if( $widget_instance['panels_info']['class'] === 'SiteOrigin_Panels_Widgets_Layout' ) {
157
- if ( ! empty( $widget_instance['panels_data']['widgets'] ) ) {
158
- // Recursively set widget ids in layout widgets.
159
- $widget_instance[ 'panels_data' ]['widgets'] = $this->generate_sidebar_widget_ids( $widget_instance[ 'panels_data' ]['widgets'], $post_id, ++$start );
160
- }
161
- }
162
-
163
- /** @var WP_Widget $widget */
164
- $widget = SiteOrigin_Panels::get_widget_instance( $widget_class );
165
- if ( ! empty( $widget ) ) {
166
- $widget_instance['so_sidebar_emulator_id'] = $widget->id_base . '-' . $id_val;
167
- $widget_instance['option_name'] = $widget->option_name;
168
- }
169
- }
170
-
171
- return $widgets;
172
- }
173
-
174
- /**
175
- * Add a sidebar for SiteOrigin Panels widgets so they are correctly detected by is_active_widget
176
- *
177
- * @param $sidebars_widgets
178
- *
179
- * @return array
180
- */
181
- function add_widgets_to_sidebars( $sidebars_widgets ) {
182
- if ( empty( $this->all_posts_widgets ) ) {
183
- return $sidebars_widgets;
184
- }
185
-
186
- foreach ( array_keys( $this->all_posts_widgets ) as $post_id ) {
187
- $post_widgets = $this->all_posts_widgets[ $post_id ];
188
- foreach ( $post_widgets as $widget_instance ) {
189
- $sidebar_id = $this->get_sidebar_id( $widget_instance );
190
- if ( empty( $sidebar_id ) ) {
191
- continue;
192
- }
193
- //Sidebars widgets and the global $wp_registered widgets use full widget ids as keys
194
- $siteorigin_panels_widget_ids[] = $sidebar_id;
195
- }
196
- if ( ! empty( $siteorigin_panels_widget_ids ) ) {
197
- $sidebars_widgets[ 'sidebar-siteorigin_panels-post-' . $post_id ] = $siteorigin_panels_widget_ids;
198
- }
199
- }
200
-
201
- return $sidebars_widgets;
202
- }
203
-
204
- /**
205
- * The 'id' key was changed to 'so_sidebar_emulator_id' to try avoid conflicts with widgets that already had
206
- * an 'id' key.
207
- *
208
- * @param $instance
209
- *
210
- * @return null
211
- */
212
- private function get_sidebar_id( $instance ) {
213
- $sidebar_id = null;
214
- if ( ! empty( $instance['id'] ) ) {
215
- $sidebar_id = $instance['id'];
216
- } else if ( ! empty( $instance['so_sidebar_emulator_id'] ) ) {
217
- $sidebar_id = $instance['so_sidebar_emulator_id'];
218
- }
219
-
220
- return $sidebar_id;
221
- }
222
- }
223
-
224
- SiteOrigin_Panels_Sidebars_Emulator::single();
1
+ <?php
2
+
3
+ class SiteOrigin_Panels_Sidebars_Emulator {
4
+
5
+ private $all_posts_widgets;
6
+
7
+ function __construct() {
8
+ $this->all_posts_widgets = array();
9
+ add_action( 'widgets_init', array( $this, 'register_widgets' ) );
10
+ add_filter( 'sidebars_widgets', array( $this, 'add_widgets_to_sidebars' ) );
11
+ }
12
+
13
+ /**
14
+ * Get the single instance.
15
+ *
16
+ * @return SiteOrigin_Panels_Sidebars_Emulator
17
+ */
18
+ static function single() {
19
+ static $single;
20
+ return empty( $single ) ? $single = new self() : $single;
21
+ }
22
+
23
+ /**
24
+ * @param string $name The name of the function
25
+ * @param array $args
26
+ *
27
+ * @return mixed
28
+ */
29
+ function __call( $name, $args ) {
30
+
31
+ // Check if this is a filter option call
32
+ preg_match( '/filter_option_widget_(.+)/', $name, $opt_matches );
33
+ if ( ! empty( $opt_matches ) && count( $opt_matches ) > 1 ) {
34
+ $opt_name = $opt_matches[1];
35
+ global $wp_widget_factory;
36
+ foreach ( $wp_widget_factory->widgets as $widget ) {
37
+ $widget_class = get_class( $widget );
38
+ if ( $widget->id_base != $opt_name ) {
39
+ continue;
40
+ }
41
+
42
+ foreach ( $this->all_posts_widgets as $post_widgets ) {
43
+ foreach ( $post_widgets as $widget_instance ) {
44
+ if ( empty( $widget_instance['panels_info']['class'] ) ) {
45
+ continue;
46
+ }
47
+
48
+ $instance_class = $widget_instance['panels_info']['class'];
49
+ $sidebar_id = $this->get_sidebar_id( $widget_instance );
50
+ if ( $instance_class == $widget_class && ! empty( $sidebar_id ) ) {
51
+ //The option value uses only the widget id number as keys
52
+ preg_match( '/-([0-9]+$)/', $sidebar_id, $num_match );
53
+ $args[0][ $num_match[1] ] = $widget_instance;
54
+ }
55
+ }
56
+ }
57
+ }
58
+
59
+ return $args[0];
60
+ }
61
+
62
+ }
63
+
64
+ /**
65
+ * Register all the current widgets so we can filter the get_option('widget_...') values to add instances
66
+ */
67
+ function register_widgets() {
68
+ $current_url = add_query_arg( false, false );
69
+ $cache_key = md5( $current_url );
70
+
71
+ // Get the ID of the current post
72
+ $post_id = wp_cache_get( $cache_key, 'siteorigin_url_to_postid' );
73
+ if ( false === $post_id ) {
74
+ $post_id = url_to_postid( $current_url );
75
+ wp_cache_set( $cache_key, $post_id, 'siteorigin_url_to_postid', 3 * HOUR_IN_SECONDS );
76
+ }
77
+
78
+ if ( empty( $post_id ) ) {
79
+ // Maybe this is the home page
80
+ $current_url_path = parse_url( $current_url, PHP_URL_PATH );
81
+ $home_url_path = parse_url( trailingslashit( home_url() ), PHP_URL_PATH );
82
+
83
+ if ( $current_url_path === $home_url_path && get_option( 'page_on_front' ) != 0 ) {
84
+ $post_id = absint( get_option( 'page_on_front' ) );
85
+ }
86
+ }
87
+
88
+ if ( empty( $post_id ) ) {
89
+ return;
90
+ }
91
+
92
+ $panels_data = get_post_meta( $post_id, 'panels_data', true );
93
+ $widget_option_names = $this->get_widget_option_names( $post_id, $panels_data );
94
+ $widget_option_names = array_unique( $widget_option_names );
95
+
96
+ foreach ( $widget_option_names as $widget_option_name ) {
97
+ add_filter( 'option_' . $widget_option_name, array( $this, 'filter_option_' . $widget_option_name ) );
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Recursively get all widget option names from $panels_data and store widget instances in $this->all_posts_widgets.
103
+ *
104
+ * @param int|string $post_id
105
+ * @param array $panels_data
106
+ *
107
+ * @return array A list of widget option names from the post and its Layout Builder widgets.
108
+ */
109
+ private function get_widget_option_names( $post_id, $panels_data ) {
110
+ if( empty( $panels_data ) || empty( $panels_data[ 'widgets' ] ) ) {
111
+ return array();
112
+ }
113
+
114
+ if( empty( $this->all_posts_widgets[ $post_id ] ) ) {
115
+ $this->all_posts_widgets[ $post_id ] = array();
116
+ }
117
+
118
+ $widget_option_names = array();
119
+ $widgets = $panels_data['widgets'];
120
+ foreach ( $widgets as $i => $widget_instance ) {
121
+ if ( empty( $widget_instance['panels_info']['class'] ) ) {
122
+ continue;
123
+ }
124
+
125
+ if( $widget_instance['panels_info']['class'] === 'SiteOrigin_Panels_Widgets_Layout' ) {
126
+ // Add the widget option names from the layout widget
127
+ $widget_option_names = array_merge( $widget_option_names, $this->get_widget_option_names( $post_id, $widget_instance[ 'panels_data' ] ) );
128
+ }
129
+ $sidebar_id = $this->get_sidebar_id( $widget_instance );
130
+ if ( ! empty( $sidebar_id ) ) {
131
+ $widget_option_names[] = $widget_instance['option_name'];
132
+ }
133
+ $this->all_posts_widgets[ $post_id ][] = $widget_instance;
134
+ }
135
+
136
+ return $widget_option_names;
137
+ }
138
+
139
+ /**
140
+ * This should be called when a post is saved to set ids required for `is_active_widget` checks. It's necessary to
141
+ * do this separately for widgets that call `is_active_widget` in their constructors, e.g. some of Jetpack's widgets
142
+ * like Twitter Timeline, Milestone etc.
143
+ *
144
+ * @param $widgets array The widgets in the layout from $panels_data for which to generate ids.
145
+ * @param $post_id int The post id which is used to derive ids.
146
+ * @param int $start This keeps track of recursive depth.
147
+ *
148
+ * @return array The widgets array containing updated widgets.
149
+ */
150
+ public function generate_sidebar_widget_ids( $widgets, $post_id, $start = 1 ) {
151
+ foreach ( $widgets as $i => &$widget_instance ) {
152
+ $id_val = $post_id . strval( ( 10000 * $start ) + intval( $i ) );
153
+ $widget_class = $widget_instance['panels_info']['class'];
154
+
155
+
156
+ if( $widget_instance['panels_info']['class'] === 'SiteOrigin_Panels_Widgets_Layout' ) {
157
+ if ( ! empty( $widget_instance['panels_data']['widgets'] ) ) {
158
+ // Recursively set widget ids in layout widgets.
159
+ $widget_instance[ 'panels_data' ]['widgets'] = $this->generate_sidebar_widget_ids( $widget_instance[ 'panels_data' ]['widgets'], $post_id, ++$start );
160
+ }
161
+ }
162
+
163
+ /** @var WP_Widget $widget */
164
+ $widget = SiteOrigin_Panels::get_widget_instance( $widget_class );
165
+ if ( ! empty( $widget ) ) {
166
+ $widget_instance['so_sidebar_emulator_id'] = $widget->id_base . '-' . $id_val;
167
+ $widget_instance['option_name'] = $widget->option_name;
168
+ }
169
+ }
170
+
171
+ return $widgets;
172
+ }
173
+
174
+ /**
175
+ * Add a sidebar for SiteOrigin Panels widgets so they are correctly detected by is_active_widget
176
+ *
177
+ * @param $sidebars_widgets
178
+ *
179
+ * @return array
180
+ */
181
+ function add_widgets_to_sidebars( $sidebars_widgets ) {
182
+ if ( empty( $this->all_posts_widgets ) ) {
183
+ return $sidebars_widgets;
184
+ }
185
+
186
+ foreach ( array_keys( $this->all_posts_widgets ) as $post_id ) {
187
+ $post_widgets = $this->all_posts_widgets[ $post_id ];
188
+ foreach ( $post_widgets as $widget_instance ) {
189
+ $sidebar_id = $this->get_sidebar_id( $widget_instance );
190
+ if ( empty( $sidebar_id ) ) {
191
+ continue;
192
+ }
193
+ //Sidebars widgets and the global $wp_registered widgets use full widget ids as keys
194
+ $siteorigin_panels_widget_ids[] = $sidebar_id;
195
+ }
196
+ if ( ! empty( $siteorigin_panels_widget_ids ) ) {
197
+ $sidebars_widgets[ 'sidebar-siteorigin_panels-post-' . $post_id ] = $siteorigin_panels_widget_ids;
198
+ }
199
+ }
200
+
201
+ return $sidebars_widgets;
202
+ }
203
+
204
+ /**
205
+ * The 'id' key was changed to 'so_sidebar_emulator_id' to try avoid conflicts with widgets that already had
206
+ * an 'id' key.
207
+ *
208
+ * @param $instance
209
+ *
210
+ * @return null
211
+ */
212
+ private function get_sidebar_id( $instance ) {
213
+ $sidebar_id = null;
214
+ if ( ! empty( $instance['id'] ) ) {
215
+ $sidebar_id = $instance['id'];
216
+ } else if ( ! empty( $instance['so_sidebar_emulator_id'] ) ) {
217
+ $sidebar_id = $instance['so_sidebar_emulator_id'];
218
+ }
219
+
220
+ return $sidebar_id;
221
+ }
222
+ }
223
+
224
+ SiteOrigin_Panels_Sidebars_Emulator::single();
inc/styles-admin.php CHANGED
@@ -1,513 +1,517 @@
1
- <?php
2
-
3
- class SiteOrigin_Panels_Styles_Admin {
4
-
5
- function __construct() {
6
- add_action( 'wp_ajax_so_panels_style_form', array( $this, 'action_style_form' ) );
7
-
8
- add_filter( 'siteorigin_panels_data', array( $this, 'convert_data' ) );
9
- add_filter( 'siteorigin_panels_prebuilt_layout', array( $this, 'convert_data' ) );
10
- }
11
-
12
- public static function single() {
13
- static $single;
14
- return empty( $single ) ? $single = new self() : $single;
15
- }
16
-
17
- /**
18
- * Admin action for handling fetching the style fields
19
- */
20
- function action_style_form() {
21
- if ( empty( $_REQUEST['_panelsnonce'] ) || ! wp_verify_nonce( $_REQUEST['_panelsnonce'], 'panels_action' ) ) {
22
- wp_die(
23
- __( 'The supplied nonce is invalid.', 'siteorigin-panels' ),
24
- __( 'Invalid nonce.', 'siteorigin-panels' ),
25
- 403
26
- );
27
- }
28
-
29
- $type = $_REQUEST['type'];
30
-
31
- if ( ! in_array( $type, array( 'row', 'cell', 'widget' ) ) ) {
32
- wp_die(
33
- __( 'Please specify the type of style form to be rendered.', 'siteorigin-panels' ),
34
- __( 'Missing style form type.', 'siteorigin-panels' ),
35
- 400
36
- );
37
- }
38
-
39
- $current = isset( $_REQUEST['style'] ) ? $_REQUEST['style'] : array();
40
- $post_id = empty( $_REQUEST['postId'] ) ? 0 : $_REQUEST['postId'];
41
-
42
- $args = ! empty( $_POST['args'] ) ? json_decode( stripslashes( $_POST['args'] ), true ) : array();
43
-
44
- switch ( $type ) {
45
- case 'row':
46
- $this->render_styles_fields( 'row', '<h3>' . __( 'Row Styles', 'siteorigin-panels' ) . '</h3>', '', $current, $post_id, $args );
47
- break;
48
-
49
- case 'cell':
50
- $cell_number = isset( $args['index'] ) ? ' ' . ( intval( $args['index'] ) + 1 ) : '';
51
- $this->render_styles_fields( 'cell', '<h3>' . sprintf( __( 'Cell%s Styles', 'siteorigin-panels' ), $cell_number ) . '</h3>', '', $current, $post_id, $args );
52
- break;
53
-
54
- case 'widget':
55
- $this->render_styles_fields( 'widget', '<h3>' . __( 'Widget Styles', 'siteorigin-panels' ) . '</h3>', '', $current, $post_id, $args );
56
- break;
57
- }
58
-
59
- wp_die();
60
- }
61
-
62
- /**
63
- * Render all the style fields
64
- *
65
- * @param $section
66
- * @param string $before
67
- * @param string $after
68
- * @param array $current
69
- * @param int $post_id
70
- * @param array $args Arguments passed by the builder
71
- *
72
- * @return bool
73
- */
74
- function render_styles_fields( $section, $before = '', $after = '', $current = array(), $post_id = 0, $args = array() ) {
75
- $fields = array();
76
- $fields = apply_filters( 'siteorigin_panels_' . $section . '_style_fields', $fields, $post_id, $args );
77
- $fields = apply_filters( 'siteorigin_panels_general_style_fields', $fields, $post_id, $args );
78
- if ( empty( $fields ) ) {
79
- return false;
80
- }
81
-
82
- $groups = array(
83
- 'attributes' => array(
84
- 'name' => __( 'Attributes', 'siteorigin-panels' ),
85
- 'priority' => 5
86
- ),
87
- 'layout' => array(
88
- 'name' => __( 'Layout', 'siteorigin-panels' ),
89
- 'priority' => 10
90
- ),
91
- 'design' => array(
92
- 'name' => __( 'Design', 'siteorigin-panels' ),
93
- 'priority' => 15
94
- ),
95
- );
96
-
97
- // Check if we need a default group
98
- foreach ( $fields as $field_id => $field ) {
99
- if ( empty( $field['group'] ) || $field['group'] == 'theme' ) {
100
- if ( empty( $groups['theme'] ) ) {
101
- $groups['theme'] = array(
102
- 'name' => __( 'Theme', 'siteorigin-panels' ),
103
- 'priority' => 10
104
- );
105
- }
106
- $fields[ $field_id ]['group'] = 'theme';
107
- }
108
- }
109
- $groups = apply_filters( 'siteorigin_panels_' . $section . '_style_groups', $groups, $post_id, $args );
110
- $groups = apply_filters( 'siteorigin_panels_general_style_groups', $groups, $post_id, $args );
111
-
112
- // Sort the style fields and groups by priority
113
- uasort( $fields, array( $this, 'sort_fields' ) );
114
- uasort( $groups, array( $this, 'sort_fields' ) );
115
-
116
- echo $before;
117
-
118
- $group_counts = array();
119
- foreach ( $fields as $field_id => $field ) {
120
- if ( empty( $group_counts[ $field['group'] ] ) ) {
121
- $group_counts[ $field['group'] ] = 0;
122
- }
123
- $group_counts[ $field['group'] ] ++;
124
- }
125
-
126
- foreach ( $groups as $group_id => $group ) {
127
-
128
- if ( empty( $group_counts[ $group_id ] ) ) {
129
- continue;
130
- }
131
-
132
- ?>
133
- <div class="style-section-wrapper">
134
- <div class="style-section-head">
135
- <h4><?php echo esc_html( $group['name'] ) ?></h4>
136
- </div>
137
- <div class="style-section-fields" style="display: none">
138
- <?php
139
- foreach ( $fields as $field_id => $field ) {
140
- $default = isset( $field[ 'default' ] ) ? $field[ 'default' ] : false;
141
-
142
- if ( $field['group'] == $group_id ) {
143
- ?>
144
- <div class="style-field-wrapper">
145
- <?php if ( ! empty( $field['name'] ) ) : ?>
146
- <label><?php echo $field['name'] ?></label>
147
- <?php endif; ?>
148
- <div
149
- class="style-field style-field-<?php echo sanitize_html_class( $field['type'] ) ?>">
150
- <?php $this->render_style_field( $field, isset( $current[ $field_id ] ) ? $current[ $field_id ] : $default, $field_id, $current ) ?>
151
- </div>
152
- </div>
153
- <?php
154
-
155
- }
156
-
157
- }
158
- ?>
159
- </div>
160
- </div>
161
- <?php
162
- }
163
-
164
- echo $after;
165
- }
166
-
167
- /**
168
- * Generate the style field
169
- *
170
- * @param array $field Everything needed to display the field
171
- * @param $current
172
- * @param $field_id
173
- * @param $current_styles
174
- */
175
- function render_style_field( $field, $current, $field_id, $current_styles ) {
176
- $field_name = 'style[' . $field_id . ']';
177
-
178
- echo '<div class="style-input-wrapper">';
179
- switch ( $field['type'] ) {
180
- case 'measurement' :
181
-
182
- if ( ! empty( $field['multiple'] ) ) {
183
- ?>
184
- <div class="measurement-inputs">
185
- <div class="measurement-wrapper">
186
- <input type="text" class="measurement-value measurement-top"
187
- placeholder="<?php _e( 'Top', 'siteorigin-panels' ) ?>"/>
188
- </div>
189
- <div class="measurement-wrapper">
190
- <input type="text" class="measurement-value measurement-right"
191
- placeholder="<?php _e( 'Right', 'siteorigin-panels' ) ?>"/>
192
- </div>
193
- <div class="measurement-wrapper">
194
- <input type="text" class="measurement-value measurement-bottom"
195
- placeholder="<?php _e( 'Bottom', 'siteorigin-panels' ) ?>"/>
196
- </div>
197
- <div class="measurement-wrapper">
198
- <input type="text" class="measurement-value measurement-left"
199
- placeholder="<?php _e( 'Left', 'siteorigin-panels' ) ?>"/>
200
- </div>
201
- </div>
202
- <?php
203
- } else {
204
- ?><input type="text" class="measurement-value measurement-value-single"/><?php
205
- }
206
-
207
- ?>
208
- <select
209
- class="measurement-unit measurement-unit-<?php echo ! empty( $field['multiple'] ) ? 'multiple' : 'single' ?>">
210
- <?php foreach ( $this->measurements_list() as $measurement ): ?>
211
- <option
212
- value="<?php echo esc_html( $measurement ) ?>"><?php echo esc_html( $measurement ) ?></option>
213
- <?php endforeach ?>
214
- </select>
215
- <input type="hidden" name="<?php echo esc_attr( $field_name ) ?>"
216
- value="<?php echo esc_attr( $current ) ?>"/>
217
- <?php
218
- break;
219
-
220
- case 'color' :
221
- ?>
222
- <input type="text" name="<?php echo esc_attr( $field_name ) ?>"
223
- value="<?php echo esc_attr( $current ) ?>" class="so-wp-color-field"/>
224
- <?php
225
- break;
226
-
227
- case 'image' :
228
- $image = false;
229
- if ( ! empty( $current ) ) {
230
- $image = SiteOrigin_Panels_Styles::get_attachment_image_src( $current, 'thumbnail' );
231
- }
232
-
233
- $fallback_url = ( ! empty( $current_styles[ $field_id . '_fallback' ] ) && $current_styles[ $field_id . '_fallback' ] !== 'false' ? $current_styles[ $field_id . '_fallback' ] : '' );
234
- $fallback_field_name = 'style[' . $field_id . '_fallback]';
235
-
236
- ?>
237
- <div class="so-image-selector">
238
- <div class="current-image" <?php if ( ! empty( $image ) ) {
239
- echo 'style="background-image: url(' . esc_url( $image[0] ) . ');"';
240
- } ?>>
241
- </div>
242
-
243
- <div class="select-image">
244
- <?php _e( 'Select Image', 'siteorigin-panels' ) ?>
245
- </div>
246
- <input type="hidden" name="<?php echo esc_attr( $field_name ) ?>"
247
- value="<?php echo intval( $current ) ?>"/>
248
- </div>
249
- <a href="#" class="remove-image<?php if ( empty( $current ) ) echo ' hidden' ?>"><?php _e( 'Remove', 'siteorigin-panels' ) ?></a>
250
-
251
- <input type="text" value="<?php echo esc_url( $fallback_url ) ?>"
252
- placeholder="<?php esc_attr_e( 'External URL', 'siteorigin-panels' ) ?>"
253
- name="<?php echo esc_attr( $fallback_field_name ) ?>"
254
- class="image-fallback widefat" />
255
- <?php
256
- break;
257
-
258
- case 'url' :
259
- case 'text' :
260
- ?><input type="text" name="<?php echo esc_attr( $field_name ) ?>"
261
- value="<?php echo esc_attr( $current ) ?>" class="widefat" /><?php
262
- break;
263
-
264
- case 'checkbox' :
265
- $current = (bool) $current;
266
- ?>
267
- <label class="so-checkbox-label">
268
- <input type="checkbox" name="<?php echo esc_attr( $field_name ) ?>" <?php checked( $current ) ?> />
269
- <?php echo esc_html( isset( $field['label'] ) ? $field['label'] : __( 'Enabled', 'siteorigin-panels' ) ) ?>
270
- </label>
271
- <?php
272
- break;
273
-
274
- case 'select' :
275
- ?>
276
- <select name="<?php echo esc_attr( $field_name ) ?>">
277
- <?php foreach ( $field['options'] as $k => $v ) : ?>
278
- <option
279
- value="<?php echo esc_attr( $k ) ?>" <?php selected( $current, $k ) ?>><?php echo esc_html( $v ) ?></option>
280
- <?php endforeach; ?>
281
- </select>
282
- <?php
283
- break;
284
-
285
- case 'radio' :
286
- $radio_id = $field_name . '-' . uniqid();
287
- foreach ( $field['options'] as $k => $v ) :
288
- ?>
289
- <label for="<?php echo esc_attr( $radio_id . '-' . $k ) ?>">
290
- <input type="radio" name="<?php echo esc_attr( $radio_id ) ?>"
291
- id="<?php echo esc_attr( $radio_id . '-' . $k ) ?>"
292
- value="<?php echo esc_attr( $k ) ?>" <?php checked( $k, $current ) ?>> <?php echo esc_html( $v ) ?>
293
- </label>
294
- <?php
295
- endforeach;
296
- break;
297
-
298
- case 'textarea' :
299
- case 'code' :
300
- ?><textarea type="text" name="<?php echo esc_attr( $field_name ) ?>"
301
- class="widefat <?php if ( $field['type'] == 'code' ) {
302
- echo 'so-field-code';
303
- } ?>" rows="4"><?php echo esc_textarea( $current ) ?></textarea><?php
304
- break;
305
- }
306
-
307
- echo '</div>';
308
-
309
- if ( ! empty( $field['description'] ) ) {
310
- ?><p class="so-description"><?php echo wp_kses_post( $field['description'] ) ?></p><?php
311
- }
312
- }
313
-
314
- /**
315
- * Sanitize the style fields in panels_data
316
- *
317
- * @param $panels_data
318
- *
319
- * @return mixed
320
- */
321
- function sanitize_all( $panels_data ) {
322
- if ( ! empty( $panels_data['widgets'] ) ) {
323
- // Sanitize the widgets
324
- for ( $i = 0; $i < count( $panels_data['widgets'] ); $i ++ ) {
325
- if ( empty( $panels_data['widgets'][ $i ]['panels_info']['style'] ) ) {
326
- continue;
327
- }
328
- $panels_data['widgets'][ $i ]['panels_info']['style'] = $this->sanitize_style_fields( 'widget', $panels_data['widgets'][ $i ]['panels_info']['style'] );
329
- }
330
- }
331
-
332
- if ( ! empty( $panels_data['grids'] ) ) {
333
- // The rows
334
- for ( $i = 0; $i < count( $panels_data['grids'] ); $i ++ ) {
335
- if ( empty( $panels_data['grids'][ $i ]['style'] ) ) {
336
- continue;
337
- }
338
- $panels_data['grids'][ $i ]['style'] = $this->sanitize_style_fields( 'row', $panels_data['grids'][ $i ]['style'] );
339
- }
340
- }
341
-
342
- if ( ! empty( $panels_data['grid_cells'] ) ) {
343
- // And finally, the cells
344
- for ( $i = 0; $i < count( $panels_data['grid_cells'] ); $i ++ ) {
345
- if ( empty( $panels_data['grid_cells'][ $i ]['style'] ) ) {
346
- continue;
347
- }
348
- $panels_data['grid_cells'][ $i ]['style'] = $this->sanitize_style_fields( 'cell', $panels_data['grid_cells'][ $i ]['style'] );
349
- }
350
- }
351
-
352
- return $panels_data;
353
- }
354
-
355
- /**
356
- * Sanitize style fields.
357
- *
358
- * @param $section
359
- * @param $styles
360
- *
361
- * @return array Sanitized styles
362
- */
363
- function sanitize_style_fields( $section, $styles ) {
364
- // Use the filter to get the fields for this section.
365
- if ( empty( $fields_cache[ $section ] ) ) {
366
- // This filter doesn't pass in the arguments $post_id and $args
367
- // Plugins looking to extend fields, should always add their fields if these are empty
368
- $fields_cache[ $section ] = array();
369
- $fields_cache[ $section ] = apply_filters( 'siteorigin_panels_' . $section . '_style_fields', $fields_cache[ $section ], false, false );
370
- $fields_cache[ $section ] = apply_filters( 'siteorigin_panels_general_style_fields', $fields_cache[ $section ], false, false );
371
- }
372
- $fields = $fields_cache[ $section ];
373
-
374
- if ( empty( $fields ) ) {
375
- return array();
376
- }
377
-
378
- $return = array();
379
- foreach ( $fields as $k => $field ) {
380
- // Skip this if no field type is set
381
- if ( empty( $field['type'] ) ) {
382
- continue;
383
- }
384
-
385
- // Handle the special case of a checkbox
386
- if ( $field['type'] == 'checkbox' ) {
387
- $return[ $k ] = ! empty( $styles[ $k ] ) ? true : '';
388
- continue;
389
- }
390
-
391
- // Ignore this if we don't even have a value for the style, unless 'image' field which might have a fallback.
392
- if ( ! isset( $styles[ $k ] ) || ( $field['type'] != 'image' && $styles[ $k ] == '' ) ) {
393
- continue;
394
- }
395
-
396
- switch ( $field['type'] ) {
397
- case 'color' :
398
- $color = $styles[ $k ];
399
- if ( preg_match( '|^#([A-Fa-f0-9]{3,8})$|', $color ) ) {
400
- $return[ $k ] = $color;
401
- } else {
402
- $return[ $k ] = '';
403
- }
404
- break;
405
- case 'image' :
406
- $return[ $k ] = ! empty( $styles[ $k ] ) ? sanitize_text_field( $styles[ $k ] ) : false;
407
- $fallback_name = $k . '_fallback';
408
- if ( empty( $styles[ $k ] ) && empty( $styles[ $fallback_name ] ) ) {
409
- break;
410
- }
411
- $return[ $fallback_name ] = ! empty( $styles[ $fallback_name ] ) ? esc_url_raw( $styles[ $fallback_name ] ) : false;
412
- break;
413
- case 'url' :
414
- $return[ $k ] = esc_url_raw( $styles[ $k ] );
415
- break;
416
- case 'measurement' :
417
- $measurements = array_map( 'preg_quote', $this->measurements_list() );
418
- if ( ! empty( $field['multiple'] ) ) {
419
- if ( preg_match_all( '/(?:(-?[0-9\.,]+).*?(' . implode( '|', $measurements ) . ')+)/', $styles[ $k ], $match ) ) {
420
- $return[ $k ] = $styles[ $k ];
421
- } else {
422
- $return[ $k ] = '';
423
- }
424
- } else {
425
- if ( preg_match( '/([-?0-9\.,]+).*?(' . implode( '|', $measurements ) . ')/', $styles[ $k ], $match ) ) {
426
- $return[ $k ] = $match[1] . $match[2];
427
- } else {
428
- $return[ $k ] = '';
429
- }
430
- }
431
- break;
432
- case 'select' :
433
- case 'radio' :
434
- if ( ! empty( $styles[ $k ] ) && in_array( $styles[ $k ], array_keys( $field['options'] ) ) ) {
435
- $return[ $k ] = $styles[ $k ];
436
- }
437
- break;
438
- default:
439
- // Just pass the value through.
440
- $return[ $k ] = $styles[ $k ];
441
- break;
442
-
443
- }
444
- }
445
-
446
- return $return;
447
- }
448
-
449
- /**
450
- * Convert the single string attribute of the grid style into an array.
451
- *
452
- * @param $panels_data
453
- *
454
- * @return mixed
455
- */
456
- function convert_data( $panels_data ) {
457
- if ( empty( $panels_data ) || empty( $panels_data['grids'] ) || ! is_array( $panels_data['grids'] ) ) {
458
- return $panels_data;
459
- }
460
-
461
- foreach( $panels_data['grids'] as & $grid ) {
462
- if ( ! is_array( $grid ) || empty( $grid ) || empty( $grid['style'] ) ) {
463
- continue;
464
- }
465
-
466
- if ( is_string( $grid['style'] ) ) {
467
- $grid['style'] = array(
468
- $grid['style']
469
- );
470
- }
471
- }
472
-
473
- return $panels_data;
474
- }
475
-
476
- /**
477
- * Get list of supported mesurements
478
- *
479
- * @return array
480
- */
481
- function measurements_list() {
482
- $measurements = array(
483
- 'px',
484
- '%',
485
- 'in',
486
- 'cm',
487
- 'mm',
488
- 'em',
489
- 'ex',
490
- 'pt',
491
- 'pc',
492
- 'rem'
493
- );
494
-
495
- // Allow themes and plugins to trim or enhance the list.
496
- return apply_filters( 'siteorigin_panels_style_get_measurements_list', $measurements );
497
- }
498
-
499
- /**
500
- * User sort function to sort by the priority key value.
501
- *
502
- * @param $a
503
- * @param $b
504
- *
505
- * @return int
506
- */
507
- static function sort_fields( $a, $b ) {
508
- return ( ( isset( $a['priority'] ) ? $a['priority'] : 10 ) > ( isset( $b['priority'] ) ? $b['priority'] : 10 ) ) ? 1 : - 1;
509
- }
510
- }
511
-
512
- // Initialise all the default styling
513
- SiteOrigin_Panels_Styles::single();
 
 
 
 
1
+ <?php
2
+
3
+ class SiteOrigin_Panels_Styles_Admin {
4
+
5
+ function __construct() {
6
+ add_action( 'wp_ajax_so_panels_style_form', array( $this, 'action_style_form' ) );
7
+
8
+ add_filter( 'siteorigin_panels_data', array( $this, 'convert_data' ) );
9
+ add_filter( 'siteorigin_panels_prebuilt_layout', array( $this, 'convert_data' ) );
10
+ }
11
+
12
+ public static function single() {
13
+ static $single;
14
+ return empty( $single ) ? $single = new self() : $single;
15
+ }
16
+
17
+ /**
18
+ * Admin action for handling fetching the style fields
19
+ */
20
+ function action_style_form() {
21
+ if ( empty( $_REQUEST['_panelsnonce'] ) || ! wp_verify_nonce( $_REQUEST['_panelsnonce'], 'panels_action' ) ) {
22
+ wp_die(
23
+ __( 'The supplied nonce is invalid.', 'siteorigin-panels' ),
24
+ __( 'Invalid nonce.', 'siteorigin-panels' ),
25
+ 403
26
+ );
27
+ }
28
+
29
+ $type = $_REQUEST['type'];
30
+
31
+ if ( ! in_array( $type, array( 'row', 'cell', 'widget' ) ) ) {
32
+ wp_die(
33
+ __( 'Please specify the type of style form to be rendered.', 'siteorigin-panels' ),
34
+ __( 'Missing style form type.', 'siteorigin-panels' ),
35
+ 400
36
+ );
37
+ }
38
+
39
+ $current = isset( $_REQUEST['style'] ) ? $_REQUEST['style'] : array();
40
+ $post_id = empty( $_REQUEST['postId'] ) ? 0 : $_REQUEST['postId'];
41
+
42
+ $args = ! empty( $_POST['args'] ) ? json_decode( stripslashes( $_POST['args'] ), true ) : array();
43
+
44
+ switch ( $type ) {
45
+ case 'row':
46
+ $this->render_styles_fields( 'row', '<h3>' . __( 'Row Styles', 'siteorigin-panels' ) . '</h3>', '', $current, $post_id, $args );
47
+ break;
48
+
49
+ case 'cell':
50
+ $cell_number = isset( $args['index'] ) ? ' ' . ( intval( $args['index'] ) + 1 ) : '';
51
+ $this->render_styles_fields( 'cell', '<h3>' . sprintf( __( 'Cell%s Styles', 'siteorigin-panels' ), $cell_number ) . '</h3>', '', $current, $post_id, $args );
52
+ break;
53
+
54
+ case 'widget':
55
+ $this->render_styles_fields( 'widget', '<h3>' . __( 'Widget Styles', 'siteorigin-panels' ) . '</h3>', '', $current, $post_id, $args );
56
+ break;
57
+ }
58
+
59
+ wp_die();
60
+ }
61
+
62
+ /**
63
+ * Render all the style fields
64
+ *
65
+ * @param $section
66
+ * @param string $before
67
+ * @param string $after
68
+ * @param array $current
69
+ * @param int $post_id
70
+ * @param array $args Arguments passed by the builder
71
+ *
72
+ * @return bool
73
+ */
74
+ function render_styles_fields( $section, $before = '', $after = '', $current = array(), $post_id = 0, $args = array() ) {
75
+ $fields = array();
76
+ $fields = apply_filters( 'siteorigin_panels_' . $section . '_style_fields', $fields, $post_id, $args );
77
+ $fields = apply_filters( 'siteorigin_panels_general_style_fields', $fields, $post_id, $args );
78
+ if ( empty( $fields ) ) {
79
+ return false;
80
+ }
81
+
82
+ $groups = array(
83
+ 'attributes' => array(
84
+ 'name' => __( 'Attributes', 'siteorigin-panels' ),
85
+ 'priority' => 5
86
+ ),
87
+ 'layout' => array(
88
+ 'name' => __( 'Layout', 'siteorigin-panels' ),
89
+ 'priority' => 10
90
+ ),
91
+ 'mobile_layout' => array(
92
+ 'name' => __( 'Mobile Layout', 'siteorigin-panels' ),
93
+ 'priority' => 11
94
+ ),
95
+ 'design' => array(
96
+ 'name' => __( 'Design', 'siteorigin-panels' ),
97
+ 'priority' => 15
98
+ ),
99
+ );
100
+
101
+ // Check if we need a default group
102
+ foreach ( $fields as $field_id => $field ) {
103
+ if ( empty( $field['group'] ) || $field['group'] == 'theme' ) {
104
+ if ( empty( $groups['theme'] ) ) {
105
+ $groups['theme'] = array(
106
+ 'name' => __( 'Theme', 'siteorigin-panels' ),
107
+ 'priority' => 10
108
+ );
109
+ }
110
+ $fields[ $field_id ]['group'] = 'theme';
111
+ }
112
+ }
113
+ $groups = apply_filters( 'siteorigin_panels_' . $section . '_style_groups', $groups, $post_id, $args );
114
+ $groups = apply_filters( 'siteorigin_panels_general_style_groups', $groups, $post_id, $args );
115
+
116
+ // Sort the style fields and groups by priority
117
+ uasort( $fields, array( $this, 'sort_fields' ) );
118
+ uasort( $groups, array( $this, 'sort_fields' ) );
119
+
120
+ echo $before;
121
+
122
+ $group_counts = array();
123
+ foreach ( $fields as $field_id => $field ) {
124
+ if ( empty( $group_counts[ $field['group'] ] ) ) {
125
+ $group_counts[ $field['group'] ] = 0;
126
+ }
127
+ $group_counts[ $field['group'] ] ++;
128
+ }
129
+
130
+ foreach ( $groups as $group_id => $group ) {
131
+
132
+ if ( empty( $group_counts[ $group_id ] ) ) {
133
+ continue;
134
+ }
135
+
136
+ ?>
137
+ <div class="style-section-wrapper">
138
+ <div class="style-section-head">
139
+ <h4><?php echo esc_html( $group['name'] ) ?></h4>
140
+ </div>
141
+ <div class="style-section-fields" style="display: none">
142
+ <?php
143
+ foreach ( $fields as $field_id => $field ) {
144
+ $default = isset( $field[ 'default' ] ) ? $field[ 'default' ] : false;
145
+
146
+ if ( $field['group'] == $group_id ) {
147
+ ?>
148
+ <div class="style-field-wrapper">
149
+ <?php if ( ! empty( $field['name'] ) ) : ?>
150
+ <label><?php echo $field['name'] ?></label>
151
+ <?php endif; ?>
152
+ <div
153
+ class="style-field style-field-<?php echo sanitize_html_class( $field['type'] ) ?>">
154
+ <?php $this->render_style_field( $field, isset( $current[ $field_id ] ) ? $current[ $field_id ] : $default, $field_id, $current ) ?>
155
+ </div>
156
+ </div>
157
+ <?php
158
+
159
+ }
160
+
161
+ }
162
+ ?>
163
+ </div>
164
+ </div>
165
+ <?php
166
+ }
167
+
168
+ echo $after;
169
+ }
170
+
171
+ /**
172
+ * Generate the style field
173
+ *
174
+ * @param array $field Everything needed to display the field
175
+ * @param $current
176
+ * @param $field_id
177
+ * @param $current_styles
178
+ */
179
+ function render_style_field( $field, $current, $field_id, $current_styles ) {
180
+ $field_name = 'style[' . $field_id . ']';
181
+
182
+ echo '<div class="style-input-wrapper">';
183
+ switch ( $field['type'] ) {
184
+ case 'measurement' :
185
+
186
+ if ( ! empty( $field['multiple'] ) ) {
187
+ ?>
188
+ <div class="measurement-inputs">
189
+ <div class="measurement-wrapper">
190
+ <input type="text" class="measurement-value measurement-top"
191
+ placeholder="<?php _e( 'Top', 'siteorigin-panels' ) ?>"/>
192
+ </div>
193
+ <div class="measurement-wrapper">
194
+ <input type="text" class="measurement-value measurement-right"
195
+ placeholder="<?php _e( 'Right', 'siteorigin-panels' ) ?>"/>
196
+ </div>
197
+ <div class="measurement-wrapper">
198
+ <input type="text" class="measurement-value measurement-bottom"
199
+ placeholder="<?php _e( 'Bottom', 'siteorigin-panels' ) ?>"/>
200
+ </div>
201
+ <div class="measurement-wrapper">
202
+ <input type="text" class="measurement-value measurement-left"
203
+ placeholder="<?php _e( 'Left', 'siteorigin-panels' ) ?>"/>
204
+ </div>
205
+ </div>
206
+ <?php
207
+ } else {
208
+ ?><input type="text" class="measurement-value measurement-value-single"/><?php
209
+ }
210
+
211
+ ?>
212
+ <select
213
+ class="measurement-unit measurement-unit-<?php echo ! empty( $field['multiple'] ) ? 'multiple' : 'single' ?>">
214
+ <?php foreach ( $this->measurements_list() as $measurement ): ?>
215
+ <option
216
+ value="<?php echo esc_html( $measurement ) ?>"><?php echo esc_html( $measurement ) ?></option>
217
+ <?php endforeach ?>
218
+ </select>
219
+ <input type="hidden" name="<?php echo esc_attr( $field_name ) ?>"
220
+ value="<?php echo esc_attr( $current ) ?>"/>
221
+ <?php
222
+ break;
223
+
224
+ case 'color' :
225
+ ?>
226
+ <input type="text" name="<?php echo esc_attr( $field_name ) ?>"
227
+ value="<?php echo esc_attr( $current ) ?>" class="so-wp-color-field"/>
228
+ <?php
229
+ break;
230
+
231
+ case 'image' :
232
+ $image = false;
233
+ if ( ! empty( $current ) ) {
234
+ $image = SiteOrigin_Panels_Styles::get_attachment_image_src( $current, 'thumbnail' );
235
+ }
236
+
237
+ $fallback_url = ( ! empty( $current_styles[ $field_id . '_fallback' ] ) && $current_styles[ $field_id . '_fallback' ] !== 'false' ? $current_styles[ $field_id . '_fallback' ] : '' );
238
+ $fallback_field_name = 'style[' . $field_id . '_fallback]';
239
+
240
+ ?>
241
+ <div class="so-image-selector">
242
+ <div class="current-image" <?php if ( ! empty( $image ) ) {
243
+ echo 'style="background-image: url(' . esc_url( $image[0] ) . ');"';
244
+ } ?>>
245
+ </div>
246
+
247
+ <div class="select-image">
248
+ <?php _e( 'Select Image', 'siteorigin-panels' ) ?>
249
+ </div>
250
+ <input type="hidden" name="<?php echo esc_attr( $field_name ) ?>"
251
+ value="<?php echo intval( $current ) ?>"/>
252
+ </div>
253
+ <a href="#" class="remove-image<?php if ( empty( $current ) ) echo ' hidden' ?>"><?php _e( 'Remove', 'siteorigin-panels' ) ?></a>
254
+
255
+ <input type="text" value="<?php echo esc_url( $fallback_url ) ?>"
256
+ placeholder="<?php esc_attr_e( 'External URL', 'siteorigin-panels' ) ?>"
257
+ name="<?php echo esc_attr( $fallback_field_name ) ?>"
258
+ class="image-fallback widefat" />
259
+ <?php
260
+ break;
261
+
262
+ case 'url' :
263
+ case 'text' :
264
+ ?><input type="text" name="<?php echo esc_attr( $field_name ) ?>"
265
+ value="<?php echo esc_attr( $current ) ?>" class="widefat" /><?php
266
+ break;
267
+
268
+ case 'checkbox' :
269
+ $current = (bool) $current;
270
+ ?>
271
+ <label class="so-checkbox-label">
272
+ <input type="checkbox" name="<?php echo esc_attr( $field_name ) ?>" <?php checked( $current ) ?> />
273
+ <?php echo esc_html( isset( $field['label'] ) ? $field['label'] : __( 'Enabled', 'siteorigin-panels' ) ) ?>
274
+ </label>
275
+ <?php
276
+ break;
277
+
278
+ case 'select' :
279
+ ?>
280
+ <select name="<?php echo esc_attr( $field_name ) ?>">
281
+ <?php foreach ( $field['options'] as $k => $v ) : ?>
282
+ <option
283
+ value="<?php echo esc_attr( $k ) ?>" <?php selected( $current, $k ) ?>><?php echo esc_html( $v ) ?></option>
284
+ <?php endforeach; ?>
285
+ </select>
286
+ <?php
287
+ break;
288
+
289
+ case 'radio' :
290
+ $radio_id = $field_name . '-' . uniqid();
291
+ foreach ( $field['options'] as $k => $v ) :
292
+ ?>
293
+ <label for="<?php echo esc_attr( $radio_id . '-' . $k ) ?>">
294
+ <input type="radio" name="<?php echo esc_attr( $radio_id ) ?>"
295
+ id="<?php echo esc_attr( $radio_id . '-' . $k ) ?>"
296
+ value="<?php echo esc_attr( $k ) ?>" <?php checked( $k, $current ) ?>> <?php echo esc_html( $v ) ?>
297
+ </label>
298
+ <?php
299
+ endforeach;
300
+ break;
301
+
302
+ case 'textarea' :
303
+ case 'code' :
304
+ ?><textarea type="text" name="<?php echo esc_attr( $field_name ) ?>"
305
+ class="widefat <?php if ( $field['type'] == 'code' ) {
306
+ echo 'so-field-code';
307
+ } ?>" rows="4"><?php echo esc_textarea( $current ) ?></textarea><?php
308
+ break;
309
+ }
310
+
311
+ echo '</div>';
312
+
313
+ if ( ! empty( $field['description'] ) ) {
314
+ ?><p class="so-description"><?php echo wp_kses_post( $field['description'] ) ?></p><?php
315
+ }
316
+ }
317
+
318
+ /**
319
+ * Sanitize the style fields in panels_data
320
+ *
321
+ * @param $panels_data
322
+ *
323
+ * @return mixed
324
+ */
325
+ function sanitize_all( $panels_data ) {
326
+ if ( ! empty( $panels_data['widgets'] ) ) {
327
+ // Sanitize the widgets
328
+ for ( $i = 0; $i < count( $panels_data['widgets'] ); $i ++ ) {
329
+ if ( empty( $panels_data['widgets'][ $i ]['panels_info']['style'] ) ) {
330
+ continue;
331
+ }
332
+ $panels_data['widgets'][ $i ]['panels_info']['style'] = $this->sanitize_style_fields( 'widget', $panels_data['widgets'][ $i ]['panels_info']['style'] );
333
+ }
334
+ }
335
+
336
+ if ( ! empty( $panels_data['grids'] ) ) {
337
+ // The rows
338
+ for ( $i = 0; $i < count( $panels_data['grids'] ); $i ++ ) {
339
+ if ( empty( $panels_data['grids'][ $i ]['style'] ) ) {
340
+ continue;
341
+ }
342
+ $panels_data['grids'][ $i ]['style'] = $this->sanitize_style_fields( 'row', $panels_data['grids'][ $i ]['style'] );
343
+ }
344
+ }
345
+
346
+ if ( ! empty( $panels_data['grid_cells'] ) ) {
347
+ // And finally, the cells
348
+ for ( $i = 0; $i < count( $panels_data['grid_cells'] ); $i ++ ) {
349
+ if ( empty( $panels_data['grid_cells'][ $i ]['style'] ) ) {
350
+ continue;
351
+ }
352
+ $panels_data['grid_cells'][ $i ]['style'] = $this->sanitize_style_fields( 'cell', $panels_data['grid_cells'][ $i ]['style'] );
353
+ }
354
+ }
355
+
356
+ return $panels_data;
357
+ }
358
+
359
+ /**
360
+ * Sanitize style fields.
361
+ *
362
+ * @param $section
363
+ * @param $styles
364
+ *
365
+ * @return array Sanitized styles
366
+ */
367
+ function sanitize_style_fields( $section, $styles ) {
368
+ // Use the filter to get the fields for this section.
369
+ if ( empty( $fields_cache[ $section ] ) ) {
370
+ // This filter doesn't pass in the arguments $post_id and $args
371
+ // Plugins looking to extend fields, should always add their fields if these are empty
372
+ $fields_cache[ $section ] = array();
373
+ $fields_cache[ $section ] = apply_filters( 'siteorigin_panels_' . $section . '_style_fields', $fields_cache[ $section ], false, false );
374
+ $fields_cache[ $section ] = apply_filters( 'siteorigin_panels_general_style_fields', $fields_cache[ $section ], false, false );
375
+ }
376
+ $fields = $fields_cache[ $section ];
377
+
378
+ if ( empty( $fields ) ) {
379
+ return array();
380
+ }
381
+
382
+ $return = array();
383
+ foreach ( $fields as $k => $field ) {
384
+ // Skip this if no field type is set
385
+ if ( empty( $field['type'] ) ) {
386
+ continue;
387
+ }
388
+
389
+ // Handle the special case of a checkbox
390
+ if ( $field['type'] == 'checkbox' ) {
391
+ $return[ $k ] = ! empty( $styles[ $k ] ) ? true : '';
392
+ continue;
393
+ }
394
+
395
+ // Ignore this if we don't even have a value for the style, unless 'image' field which might have a fallback.
396
+ if ( ! isset( $styles[ $k ] ) || ( $field['type'] != 'image' && $styles[ $k ] == '' ) ) {
397
+ continue;
398
+ }
399
+
400
+ switch ( $field['type'] ) {
401
+ case 'color' :
402
+ $color = $styles[ $k ];
403
+ if ( preg_match( '|^#([A-Fa-f0-9]{3,8})$|', $color ) ) {
404
+ $return[ $k ] = $color;
405
+ } else {
406
+ $return[ $k ] = '';
407
+ }
408
+ break;
409
+ case 'image' :
410
+ $return[ $k ] = ! empty( $styles[ $k ] ) ? sanitize_text_field( $styles[ $k ] ) : false;
411
+ $fallback_name = $k . '_fallback';
412
+ if ( empty( $styles[ $k ] ) && empty( $styles[ $fallback_name ] ) ) {
413
+ break;
414
+ }
415
+ $return[ $fallback_name ] = ! empty( $styles[ $fallback_name ] ) ? esc_url_raw( $styles[ $fallback_name ] ) : false;
416
+ break;
417
+ case 'url' :
418
+ $return[ $k ] = esc_url_raw( $styles[ $k ] );
419
+ break;
420
+ case 'measurement' :
421
+ $measurements = array_map( 'preg_quote', $this->measurements_list() );
422
+ if ( ! empty( $field['multiple'] ) ) {
423
+ if ( preg_match_all( '/(?:(-?[0-9\.,]+).*?(' . implode( '|', $measurements ) . ')+)/', $styles[ $k ], $match ) ) {
424
+ $return[ $k ] = $styles[ $k ];
425
+ } else {
426
+ $return[ $k ] = '';
427
+ }
428
+ } else {
429
+ if ( preg_match( '/([-?0-9\.,]+).*?(' . implode( '|', $measurements ) . ')/', $styles[ $k ], $match ) ) {
430
+ $return[ $k ] = $match[1] . $match[2];
431
+ } else {
432
+ $return[ $k ] = '';
433
+ }
434
+ }
435
+ break;
436
+ case 'select' :
437
+ case 'radio' :
438
+ if ( ! empty( $styles[ $k ] ) && in_array( $styles[ $k ], array_keys( $field['options'] ) ) ) {
439
+ $return[ $k ] = $styles[ $k ];
440
+ }
441
+ break;
442
+ default:
443
+ // Just pass the value through.
444
+ $return[ $k ] = $styles[ $k ];
445
+ break;
446
+
447
+ }
448
+ }
449
+
450
+ return $return;
451
+ }
452
+
453
+ /**
454
+ * Convert the single string attribute of the grid style into an array.
455
+ *
456
+ * @param $panels_data
457
+ *
458
+ * @return mixed
459
+ */
460
+ function convert_data( $panels_data ) {
461
+ if ( empty( $panels_data ) || empty( $panels_data['grids'] ) || ! is_array( $panels_data['grids'] ) ) {
462
+ return $panels_data;
463
+ }
464
+
465
+ foreach( $panels_data['grids'] as & $grid ) {
466
+ if ( ! is_array( $grid ) || empty( $grid ) || empty( $grid['style'] ) ) {
467
+ continue;
468
+ }
469
+
470
+ if ( is_string( $grid['style'] ) ) {
471
+ $grid['style'] = array(
472
+ $grid['style']
473
+ );
474
+ }
475
+ }
476
+
477
+ return $panels_data;
478
+ }
479
+
480
+ /**
481
+ * Get list of supported mesurements
482
+ *
483
+ * @return array
484
+ */
485
+ function measurements_list() {
486
+ $measurements = array(
487
+ 'px',
488
+ '%',
489
+ 'in',
490
+ 'cm',
491
+ 'mm',
492
+ 'em',
493
+ 'ex',
494
+ 'pt',
495
+ 'pc',
496
+ 'rem'
497
+ );
498
+
499
+ // Allow themes and plugins to trim or enhance the list.
500
+ return apply_filters( 'siteorigin_panels_style_get_measurements_list', $measurements );
501
+ }
502
+
503
+ /**
504
+ * User sort function to sort by the priority key value.
505
+ *
506
+ * @param $a
507
+ * @param $b
508
+ *
509
+ * @return int
510
+ */
511
+ static function sort_fields( $a, $b ) {
512
+ return ( ( isset( $a['priority'] ) ? $a['priority'] : 10 ) > ( isset( $b['priority'] ) ? $b['priority'] : 10 ) ) ? 1 : - 1;
513
+ }
514
+ }
515
+
516
+ // Initialise all the default styling
517
+ SiteOrigin_Panels_Styles::single();
inc/styles.php CHANGED
@@ -1,713 +1,750 @@
1
- <?php
2
-
3
- /**
4
- * Class for handling all the default styling.
5
- *
6
- * Class SiteOrigin_Panels_Default_Styles
7
- */
8
- class SiteOrigin_Panels_Styles {
9
-
10
- public function __construct() {
11
- add_action( 'wp_enqueue_scripts', array( $this, 'register_scripts' ), 5 );
12
-
13
- // Adding all the fields
14
- add_filter( 'siteorigin_panels_row_style_fields', array( $this, 'row_style_fields' ) );
15
- add_filter( 'siteorigin_panels_cell_style_fields', array( $this, 'cell_style_fields' ) );
16
- add_filter( 'siteorigin_panels_widget_style_fields', array( $this, 'widget_style_fields' ) );
17
-
18
- // Style wrapper attributes
19
- add_filter( 'siteorigin_panels_row_style_attributes', array( $this, 'general_style_attributes' ), 10, 2 );
20
- add_filter( 'siteorigin_panels_row_style_attributes', array( $this, 'row_style_attributes' ), 10, 2 );
21
- add_filter( 'siteorigin_panels_row_style_attributes', array( $this, 'vantage_row_style_attributes' ), 11, 2 );
22
- add_filter( 'siteorigin_panels_cell_style_attributes', array( $this, 'general_style_attributes' ), 10, 2 );
23
- add_filter( 'siteorigin_panels_widget_style_attributes', array( $this, 'general_style_attributes' ), 10, 2 );
24
-
25
- // Style wrapper CSS
26
- add_filter( 'siteorigin_panels_row_style_css', array( $this, 'general_style_css' ), 10, 2 );
27
- add_filter( 'siteorigin_panels_cell_style_css', array( $this, 'general_style_css' ), 10, 2 );
28
- add_filter( 'siteorigin_panels_widget_style_css', array( $this, 'general_style_css' ), 10, 2 );
29
-
30
- add_filter( 'siteorigin_panels_row_style_mobile_css', array( $this, 'general_style_mobile_css' ), 10, 2 );
31
- add_filter( 'siteorigin_panels_cell_style_mobile_css', array( $this, 'general_style_mobile_css' ), 10, 2 );
32
- add_filter( 'siteorigin_panels_widget_style_mobile_css', array( $this, 'general_style_mobile_css' ), 10, 2 );
33
-
34
- // Main filter to add any custom CSS.
35
- add_filter( 'siteorigin_panels_css_object', array( $this, 'filter_css_object' ), 10, 4 );
36
-
37
- // Filtering specific attributes
38
- add_filter( 'siteorigin_panels_css_row_margin_bottom', array( $this, 'filter_row_bottom_margin' ), 10, 2 );
39
- add_filter( 'siteorigin_panels_css_row_gutter', array( $this, 'filter_row_gutter' ), 10, 2 );
40
- add_filter( 'siteorigin_panels_css_widget_css', array( $this, 'filter_widget_style_css' ), 10, 2 );
41
- }
42
-
43
- public static function single() {
44
- static $single;
45
- return empty( $single ) ? $single = new self() : $single;
46
- }
47
-
48
- static function register_scripts() {
49
- wp_register_script(
50
- 'siteorigin-panels-front-styles',
51
- siteorigin_panels_url( 'js/styling' . SITEORIGIN_PANELS_VERSION_SUFFIX . SITEORIGIN_PANELS_JS_SUFFIX . '.js' ),
52
- array( 'jquery' ),
53
- SITEORIGIN_PANELS_VERSION
54
- );
55
- wp_register_script(
56
- 'siteorigin-parallax',
57
- siteorigin_panels_url( 'js/siteorigin-parallax' . SITEORIGIN_PANELS_JS_SUFFIX . '.js' ),
58
- array( 'jquery' ),
59
- SITEORIGIN_PANELS_VERSION
60
- );
61
- wp_localize_script( 'siteorigin-panels-front-styles', 'panelsStyles', array(
62
- 'fullContainer' => apply_filters( 'siteorigin_panels_full_width_container', siteorigin_panels_setting( 'full-width-container' ) )
63
- ) );
64
- }
65
-
66
- /**
67
- * These are general styles that apply to all elements
68
- *
69
- * @param $label
70
- *
71
- * @return array
72
- */
73
- static function get_general_style_fields( $id, $label ) {
74
- $fields = array();
75
-
76
- // All the attribute fields
77
-
78
- $fields['id'] = array(
79
- 'name' => sprintf( __( '%s ID', 'siteorigin-panels' ), $label ),
80
- 'type' => 'text',
81
- 'group' => 'attributes',
82
- 'description' => sprintf( __( 'A custom ID used for this %s.', 'siteorigin-panels' ), strtolower( $label ) ),
83
- 'priority' => 4,
84
- );
85
-
86
- $fields['class'] = array(
87
- 'name' => sprintf( __( '%s Class', 'siteorigin-panels' ), $label ),
88
- 'type' => 'text',
89
- 'group' => 'attributes',
90
- 'description' => __( 'A CSS class', 'siteorigin-panels' ),
91
- 'priority' => 5,
92
- );
93
-
94
- $fields[ $id . '_css' ] = array(
95
- 'name' => __( 'CSS Styles', 'siteorigin-panels' ),
96
- 'type' => 'code',
97
- 'group' => 'attributes',
98
- 'description' => __( 'One style attribute per line.', 'siteorigin-panels' ),
99
- 'priority' => 10,
100
- );
101
-
102
- $fields[ 'mobile_css' ] = array(
103
- 'name' => __( 'Mobile CSS Styles', 'siteorigin-panels' ),
104
- 'type' => 'code',
105
- 'group' => 'attributes',
106
- 'description' => __( 'CSS applied when in mobile view.', 'siteorigin-panels' ),
107
- 'priority' => 11,
108
- );
109
-
110
- // The layout fields
111
-
112
- $fields['padding'] = array(
113
- 'name' => __( 'Padding', 'siteorigin-panels' ),
114
- 'type' => 'measurement',
115
- 'group' => 'layout',
116
- 'description' => sprintf( __( 'Padding around the entire %s.', 'siteorigin-panels' ), strtolower( $label ) ),
117
- 'priority' => 7,
118
- 'multiple' => true
119
- );
120
-
121
- $fields['mobile_padding'] = array(
122
- 'name' => __( 'Mobile Padding', 'siteorigin-panels' ),
123
- 'type' => 'measurement',
124
- 'group' => 'layout',
125
- 'description' => __( 'Padding when on mobile devices.', 'siteorigin-panels' ),
126
- 'priority' => 8,
127
- 'multiple' => true
128
- );
129
-
130
- // The general design fields
131
-
132
- $fields['background'] = array(
133
- 'name' => __( 'Background Color', 'siteorigin-panels' ),
134
- 'type' => 'color',
135
- 'group' => 'design',
136
- 'description' => sprintf( __( 'Background color of the %s.', 'siteorigin-panels' ), strtolower( $label ) ),
137
- 'priority' => 5,
138
- );
139
-
140
- $fields['background_image_attachment'] = array(
141
- 'name' => __( 'Background Image', 'siteorigin-panels' ),
142
- 'type' => 'image',
143
- 'group' => 'design',
144
- 'description' => sprintf( __( 'Background image of the %s.', 'siteorigin-panels' ), strtolower( $label ) ),
145
- 'priority' => 6,
146
- );
147
-
148
- $fields['background_display'] = array(
149
- 'name' => __( 'Background Image Display', 'siteorigin-panels' ),
150
- 'type' => 'select',
151
- 'group' => 'design',
152
- 'options' => array(
153
- 'tile' => __( 'Tiled Image', 'siteorigin-panels' ),
154
- 'cover' => __( 'Cover', 'siteorigin-panels' ),
155
- 'center' => __( 'Centered, with original size', 'siteorigin-panels' ),
156
- 'contain' => __( 'Contain', 'siteorigin-panels' ),
157
- 'fixed' => __( 'Fixed', 'siteorigin-panels' ),
158
- 'parallax' => __( 'Parallax', 'siteorigin-panels' ),
159
- 'parallax-original' => __( 'Parallax (Original Size)', 'siteorigin-panels' ),
160
- ),
161
- 'description' => __( 'How the background image is displayed.', 'siteorigin-panels' ),
162
- 'priority' => 7,
163
- );
164
-
165
- $fields['border_color'] = array(
166
- 'name' => __( 'Border Color', 'siteorigin-panels' ),
167
- 'type' => 'color',
168
- 'group' => 'design',
169
- 'description' => sprintf( __( 'Border color of the %s.', 'siteorigin-panels' ), strtolower( $label ) ),
170
- 'priority' => 10,
171
- );
172
-
173
- return $fields;
174
- }
175
-
176
- /**
177
- * All the row styling fields
178
- *
179
- * @param $fields
180
- *
181
- * @return array
182
- */
183
- static function row_style_fields( $fields ) {
184
- // Add the general fields
185
- $fields = wp_parse_args( $fields, self::get_general_style_fields( 'row', __( 'Row', 'siteorigin-panels' ) ) );
186
-
187
- $fields['cell_class'] = array(
188
- 'name' => __( 'Cell Class', 'siteorigin-panels' ),
189
- 'type' => 'text',
190
- 'group' => 'attributes',
191
- 'description' => __( 'Class added to all cells in this row.', 'siteorigin-panels' ),
192
- 'priority' => 6,
193
- );
194
-
195
- // Add the layout fields
196
-
197
- $fields['bottom_margin'] = array(
198
- 'name' => __( 'Bottom Margin', 'siteorigin-panels' ),
199
- 'type' => 'measurement',
200
- 'group' => 'layout',
201
- 'description' => sprintf( __( 'Space below the row. Default is %spx.', 'siteorigin-panels' ), siteorigin_panels_setting( 'margin-bottom' ) ),
202
- 'priority' => 5,
203
- );
204
-
205
- $fields['gutter'] = array(
206
- 'name' => __( 'Gutter', 'siteorigin-panels' ),
207
- 'type' => 'measurement',
208
- 'group' => 'layout',
209
- 'description' => sprintf( __( 'Amount of space between cells. Default is %spx.', 'siteorigin-panels' ), siteorigin_panels_setting( 'margin-sides' ) ),
210
- 'priority' => 6,
211
- );
212
-
213
- $fields['row_stretch'] = array(
214
- 'name' => __( 'Row Layout', 'siteorigin-panels' ),
215
- 'type' => 'select',
216
- 'group' => 'layout',
217
- 'options' => array(
218
- '' => __( 'Standard', 'siteorigin-panels' ),
219
- 'full' => __( 'Full Width', 'siteorigin-panels' ),
220
- 'full-stretched' => __( 'Full Width Stretched', 'siteorigin-panels' ),
221
- 'full-stretched-padded' => __( 'Full Width Stretched Padded', 'siteorigin-panels' ),
222
- ),
223
- 'priority' => 10,
224
- );
225
-
226
- $fields['collapse_behaviour'] = array(
227
- 'name' => __( 'Collapse Behaviour', 'siteorigin-panels' ),
228
- 'type' => 'select',
229
- 'group' => 'layout',
230
- 'options' => array(
231
- '' => __( 'Standard', 'siteorigin-panels' ),
232
- 'no_collapse' => __( 'No Collapse', 'siteorigin-panels' ),
233
- ),
234
- 'priority' => 15,
235
- );
236
-
237
- $fields['collapse_order'] = array(
238
- 'name' => __( 'Collapse Order', 'siteorigin-panels' ),
239
- 'type' => 'select',
240
- 'group' => 'layout',
241
- 'options' => array(
242
- '' => __( 'Default', 'siteorigin-panels' ),
243
- 'left-top' => __( 'Left on Top', 'siteorigin-panels' ),
244
- 'right-top' => __( 'Right on Top', 'siteorigin-panels' ),
245
- ),
246
- 'priority' => 16,
247
- );
248
-
249
- if ( siteorigin_panels_setting( 'legacy-layout' ) != 'always' ) {
250
- $fields['cell_alignment'] = array(
251
- 'name' => __( 'Cell Vertical Alignment', 'siteorigin-panels' ),
252
- 'type' => 'select',
253
- 'group' => 'layout',
254
- 'options' => array(
255
- 'flex-start' => __( 'Top', 'siteorigin-panels' ),
256
- 'center' => __( 'Center', 'siteorigin-panels' ),
257
- 'flex-end' => __( 'Bottom', 'siteorigin-panels' ),
258
- 'stretch' => __( 'Stretch', 'siteorigin-panels' ),
259
- ),
260
- 'priority' => 17,
261
- );
262
- }
263
-
264
- return $fields;
265
- }
266
-
267
- /**
268
- * All the cell styling fields
269
- *
270
- * @param $fields
271
- *
272
- * @return array
273
- */
274
- static function cell_style_fields( $fields ) {
275
- // Add the general fields
276
- $fields = wp_parse_args( $fields, self::get_general_style_fields( 'cell', __( 'Cell', 'siteorigin-panels' ) ) );
277
-
278
- $fields['vertical_alignment'] = array(
279
- 'name' => __( 'Vertical Alignment', 'siteorigin-panels' ),
280
- 'type' => 'select',
281
- 'group' => 'layout',
282
- 'options' => array(
283
- 'auto' => __( 'Use row setting', 'siteorigin-panels' ),
284
- 'flex-start' => __( 'Top', 'siteorigin-panels' ),
285
- 'center' => __( 'Center', 'siteorigin-panels' ),
286
- 'flex-end' => __( 'Bottom', 'siteorigin-panels' ),
287
- 'stretch' => __( 'Stretch', 'siteorigin-panels' ),
288
- ),
289
- 'priority' => 16,
290
- );
291
-
292
- $fields['font_color'] = array(
293
- 'name' => __( 'Font Color', 'siteorigin-panels' ),
294
- 'type' => 'color',
295
- 'group' => 'design',
296
- 'description' => __( 'Color of text inside this cell.', 'siteorigin-panels' ),
297
- 'priority' => 15,
298
- );
299
-
300
- $fields['link_color'] = array(
301
- 'name' => __( 'Links Color', 'siteorigin-panels' ),
302
- 'type' => 'color',
303
- 'group' => 'design',
304
- 'description' => __( 'Color of links inside this cell.', 'siteorigin-panels' ),
305
- 'priority' => 16,
306
- );
307
-
308
- return $fields;
309
- }
310
-
311
- /**
312
- * @param $fields
313
- *
314
- * @return array
315
- */
316
- static function widget_style_fields( $fields ) {
317
-
318
- // Add the general fields
319
- $fields = wp_parse_args( $fields, self::get_general_style_fields( 'widget', __( 'Widget', 'siteorigin-panels' ) ) );
320
-
321
- $fields['margin'] = array(
322
- 'name' => __( 'Margin', 'siteorigin-panels' ),
323
- 'type' => 'measurement',
324
- 'group' => 'layout',
325
- 'description' => __( 'Margins around the widget.', 'siteorigin-panels' ),
326
- 'priority' => 6,
327
- 'multiple' => true
328
- );
329
-
330
- // How lets add the design fields
331
-
332
- $fields['font_color'] = array(
333
- 'name' => __( 'Font Color', 'siteorigin-panels' ),
334
- 'type' => 'color',
335
- 'group' => 'design',
336
- 'description' => __( 'Color of text inside this widget.', 'siteorigin-panels' ),
337
- 'priority' => 15,
338
- );
339
-
340
- $fields['link_color'] = array(
341
- 'name' => __( 'Links Color', 'siteorigin-panels' ),
342
- 'type' => 'color',
343
- 'group' => 'design',
344
- 'description' => __( 'Color of links inside this widget.', 'siteorigin-panels' ),
345
- 'priority' => 16,
346
- );
347
-
348
- return $fields;
349
- }
350
-
351
- /**
352
- * Style attributes that apply to rows, cells and widgets
353
- *
354
- * @param $attributes
355
- * @param $style
356
- *
357
- * @return array $attributes
358
- */
359
- static function general_style_attributes( $attributes, $style ){
360
- if ( ! empty( $style['class'] ) ) {
361
- if( ! is_array( $style['class'] ) ) {
362
- $style['class'] = explode( ' ', $style[ 'class' ] );
363
- }
364
- $attributes['class'] = array_merge( $attributes['class'], $style['class'] );
365
- }
366
-
367
- if ( ! empty( $style['background_display'] ) &&
368
- ! empty( $style['background_image_attachment'] )
369
- ) {
370
-
371
- $url = self::get_attachment_image_src( $style['background_image_attachment'], 'full' );
372
-
373
- if (
374
- ! empty( $url ) &&
375
- ( $style['background_display'] == 'parallax' || $style['background_display'] == 'parallax-original' )
376
- ) {
377
- wp_enqueue_script( 'siteorigin-parallax' );
378
- $parallax_args = array(
379
- 'backgroundUrl' => $url[0],
380
- 'backgroundSize' => array( $url[1], $url[2] ),
381
- 'backgroundSizing' => $style['background_display'] == 'parallax-original' ? 'original' : 'scaled',
382
- 'limitMotion' => siteorigin_panels_setting( 'parallax-motion' ) ? floatval( siteorigin_panels_setting( 'parallax-motion' ) ) : 'auto',
383
- );
384
- $attributes['data-siteorigin-parallax'] = json_encode( $parallax_args );
385
- }
386
- }
387
-
388
- if ( ! empty( $style['id'] ) ) {
389
- $attributes['id'] = sanitize_html_class( $style['id'] );
390
- }
391
-
392
- return $attributes;
393
- }
394
-
395
- static function row_style_attributes( $attributes, $style ) {
396
- if ( ! empty( $style['row_stretch'] ) ) {
397
- $attributes['class'][] = 'siteorigin-panels-stretch';
398
- $attributes['data-stretch-type'] = $style['row_stretch'];
399
- wp_enqueue_script( 'siteorigin-panels-front-styles' );
400
- }
401
-
402
- return $attributes;
403
- }
404
-
405
- static function vantage_row_style_attributes( $attributes, $style ) {
406
- if ( isset( $style['class'] ) && $style['class'] == 'wide-grey' && ! empty( $attributes['style'] ) ) {
407
- $attributes['style'] = preg_replace( '/padding-left: 1000px; padding-right: 1000px;/', '', $attributes['style'] );
408
- }
409
-
410
- return $attributes;
411
- }
412
-
413
- /**
414
- * Get the CSS styles that apply to all rows, cells and widgets
415
- *
416
- * @param $css
417
- * @param $style
418
- *
419
- * @return mixed
420
- */
421
- static function general_style_css( $css, $style ){
422
-
423
- if ( ! empty( $style['background'] ) ) {
424
- $css[ 'background-color' ] = $style['background'];
425
- }
426
-
427
- if ( ! empty( $style['background_display'] ) &&
428
- ! ( empty( $style['background_image_attachment'] ) && empty( $style['background_image_attachment_fallback'] ) )
429
- ) {
430
- $url = self::get_attachment_image_src( $style['background_image_attachment'], 'full' );
431
-
432
- if ( empty( $url ) && ! empty( $style['background_image_attachment_fallback'] ) ) {
433
- $url = $style['background_image_attachment_fallback'];
434
- }
435
-
436
- if ( ! empty( $url ) ) {
437
- $css['background-image'] = 'url(' .( is_array( $url ) ? $url[0] : $url ) . ')';
438
-
439
- switch ( $style['background_display'] ) {
440
- case 'parallax':
441
- case 'parallax-original':
442
- $css[ 'background-position' ] = 'center center';
443
- $css[ 'background-repeat' ] = 'no-repeat';
444
- break;
445
- case 'tile':
446
- $css[ 'background-repeat' ] = 'repeat';
447
- break;
448
- case 'cover':
449
- $css[ 'background-position' ] = 'center center';
450
- $css[ 'background-size' ] = 'cover';
451
- break;
452
- case 'contain':
453
- $css[ 'background-size' ] = 'contain';
454
- break;
455
- case 'center':
456
- $css[ 'background-position' ] = 'center center';
457
- $css[ 'background-repeat' ] = 'no-repeat';
458
- break;
459
- case 'fixed':
460
- $css[ 'background-attachment' ] = 'fixed';
461
- $css[ 'background-position' ] = 'center center';
462
- $css[ 'background-size' ] = 'cover';
463
- break;
464
- }
465
- }
466
- }
467
-
468
- if ( ! empty( $style[ 'border_color' ] ) ) {
469
- $css[ 'border' ] = '1px solid ' . $style['border_color'];
470
- }
471
-
472
- if ( ! empty( $style[ 'font_color' ] ) ) {
473
- $css[ 'color' ] = $style['font_color'];
474
- }
475
-
476
- if( ! empty( $style[ 'padding' ] ) ) {
477
- $css['padding'] = $style[ 'padding' ];
478
- }
479
-
480
- // Find which key the CSS is stored in
481
- foreach( array( 'row_css', 'cell_css', 'widget_css', '' ) as $css_key ) {
482
- if( empty( $css_key ) || ! empty( $style[ $css_key ] ) ) {
483
- break;
484
- }
485
- }
486
- if ( ! empty( $css_key ) && ! empty( $style[ $css_key ] ) ) {
487
- preg_match_all( '/^([A-Za-z0-9\-]+?):(.+?);?$/m', $style[ $css_key ], $matches );
488
-
489
- if ( ! empty( $matches[0] ) ) {
490
- for ( $i = 0; $i < count( $matches[0] ); $i ++ ) {
491
- $css[ $matches[1][ $i ] ] = $matches[2][ $i ];
492
- }
493
- }
494
- }
495
-
496
- return $css;
497
- }
498
-
499
- /**
500
- * Get the mobile styling for rows, cells and widgets
501
- *
502
- * @param $css
503
- * @param $style
504
- *
505
- * @return mixed
506
- */
507
- static function general_style_mobile_css( $css, $style ){
508
- if( ! empty( $style['mobile_padding'] ) ) {
509
- $css['padding'] = $style[ 'mobile_padding' ];
510
- }
511
-
512
- if ( ! empty( $style['background_display'] ) &&
513
- $style['background_display'] == 'fixed' &&
514
- ! ( empty( $style['background_image_attachment'] ) && empty( $style['background_image_attachment_fallback'] ) )
515
- ) {
516
- $css[ 'background-attachment' ] = 'scroll';
517
- }
518
-
519
- if ( ! empty( $style[ 'mobile_css' ] ) ) {
520
- preg_match_all( '/^([A-Za-z0-9\-]+?):(.+?);?$/m', $style[ 'mobile_css' ], $matches );
521
-
522
- if ( ! empty( $matches[0] ) ) {
523
- for ( $i = 0; $i < count( $matches[0] ); $i ++ ) {
524
- $css[ $matches[1][ $i ] ] = $matches[2][ $i ];
525
- }
526
- }
527
- }
528
-
529
- return $css;
530
- }
531
-
532
- /**
533
- * @param SiteOrigin_Panels_Css_Builder $css
534
- * @param $panels_data
535
- * @param $post_id
536
- *
537
- * @return mixed
538
- */
539
- static function filter_css_object( $css, $panels_data, $post_id, $layout ) {
540
- $mobile_width = siteorigin_panels_setting( 'mobile-width' );
541
- if( empty( $layout ) ) {
542
- return $css;
543
- }
544
-
545
- foreach( $layout as $ri => $row ) {
546
- if( empty( $row[ 'style' ] ) ) $row[ 'style' ] = array();
547
-
548
- $standard_css = apply_filters( 'siteorigin_panels_row_style_css', array(), $row['style'] );
549
- $mobile_css = apply_filters( 'siteorigin_panels_row_style_mobile_css', array(), $row['style'] );
550
-
551
- if ( ! empty( $standard_css ) ) {
552
- $css->add_row_css(
553
- $post_id,
554
- $ri,
555
- '> .panel-row-style',
556
- $standard_css
557
- );
558
- }
559
- if ( ! empty( $mobile_css ) ) {
560
- $css->add_row_css(
561
- $post_id,
562
- $ri,
563
- '> .panel-row-style',
564
- $mobile_css,
565
- $mobile_width
566
- );
567
- }
568
-
569
- // Add in flexbox alignment to the main row element
570
- if ( siteorigin_panels_setting( 'legacy-layout' ) != 'always' && ! SiteOrigin_Panels::is_legacy_browser() && ! empty( $row['style']['cell_alignment'] ) ) {
571
- $css->add_row_css(
572
- $post_id,
573
- $ri,
574
- array( '.panel-no-style', '.panel-has-style > .panel-row-style' ),
575
- array(
576
- '-webkit-align-items' => $row['style']['cell_alignment'],
577
- 'align-items' => $row['style']['cell_alignment'],
578
- )
579
- );
580
- }
581
-
582
- // Process the cells if there are any
583
- if( empty( $row[ 'cells' ] ) ) continue;
584
-
585
- foreach( $row[ 'cells' ] as $ci => $cell ) {
586
- if( empty( $cell[ 'style' ] ) ) $cell[ 'style' ] = array();
587
-
588
- $standard_css = apply_filters( 'siteorigin_panels_cell_style_css', array(), $cell['style'] );
589
- $mobile_css = apply_filters( 'siteorigin_panels_cell_style_mobile_css', array(), $cell['style'] );
590
-
591
- if ( ! empty( $standard_css ) ) {
592
- $css->add_cell_css(
593
- $post_id,
594
- $ri,
595
- $ci,
596
- '> .panel-cell-style',
597
- $standard_css
598
- );
599
- }
600
- if ( ! empty( $mobile_css ) ) {
601
- $css->add_cell_css(
602
- $post_id,
603
- $ri,
604
- $ci,
605
- '> .panel-cell-style',
606
- $mobile_css,
607
- $mobile_width
608
- );
609
- }
610
-
611
- if ( ! empty( $cell[ 'style' ]['vertical_alignment'] ) ) {
612
- $css->add_cell_css(
613
- $post_id,
614
- $ri,
615
- $ci,
616
- '',
617
- array(
618
- 'align-self' => $cell[ 'style' ]['vertical_alignment']
619
- )
620
- );
621
- }
622
-
623
- // Process the widgets if there are any
624
- if( empty( $cell[ 'widgets' ] ) ) continue;
625
-
626
- foreach( $cell['widgets'] as $wi => $widget ) {
627
- if ( empty( $widget['panels_info'] ) ) continue;
628
- if ( empty( $widget['panels_info']['style'] ) ) $widget['panels_info']['style'] = array();
629
-
630
- $standard_css = apply_filters( 'siteorigin_panels_widget_style_css', array(), $widget['panels_info']['style'] );
631
- $mobile_css = apply_filters( 'siteorigin_panels_widget_style_mobile_css', array(), $widget['panels_info']['style'] );
632
-
633
- if( ! empty( $standard_css ) ) {
634
- $css->add_widget_css(
635
- $post_id,
636
- $ri,
637
- $ci,
638
- $wi,
639
- '> .panel-widget-style',
640
- $standard_css
641
- );
642
- }
643
-
644
- if( ! empty( $mobile_css ) ) {
645
- $css->add_widget_css(
646
- $post_id,
647
- $ri,
648
- $ci,
649
- $wi,
650
- '> .panel-widget-style',
651
- $mobile_css,
652
- $mobile_width
653
- );
654
- }
655
-
656
- if ( ! empty( $widget['panels_info']['style']['link_color'] ) ) {
657
- $css->add_widget_css( $post_id, $ri, $ci, $wi, ' a', array(
658
- 'color' => $widget['panels_info']['style']['link_color']
659
- ) );
660
- }
661
- }
662
- }
663
- }
664
-
665
- return $css;
666
- }
667
-
668
- static function filter_row_bottom_margin( $margin, $grid ) {
669
- if ( ! empty( $grid['style']['bottom_margin'] ) ) {
670
- $margin = $grid['style']['bottom_margin'];
671
- }
672
-
673
- return $margin;
674
- }
675
-
676
- static function filter_row_gutter( $gutter, $grid ) {
677
- if ( ! empty( $grid['style']['gutter'] ) ) {
678
- $gutter = $grid['style']['gutter'];
679
- }
680
-
681
- return $gutter;
682
- }
683
-
684
- /**
685
- * Adds widget specific styles not included in the general style fields.
686
- *
687
- * @param $widget_css The CSS properties and values
688
- * @param $widget_style_data The style settings as obtained from the style fields.
689
- *
690
- * @return mixed
691
- */
692
- static function filter_widget_style_css( $widget_css, $widget_style_data ) {
693
- if ( ! empty( $widget_style_data['margin'] ) ) {
694
- $widget_css['margin'] = $widget_style_data['margin'];
695
- }
696
-
697
- return $widget_css;
698
- }
699
-
700
- public static function get_attachment_image_src( $image, $size = 'full' ){
701
- if( empty( $image ) ) {
702
- return false;
703
- }
704
- else if( is_numeric( $image ) ) {
705
- return wp_get_attachment_image_src( $image, $size );
706
- }
707
- else if( is_string( $image ) ) {
708
- preg_match( '/(.*?)\#([0-9]+)x([0-9]+)$/', $image, $matches );
709
- return ! empty( $matches ) ? $matches : false;
710
- }
711
- }
712
-
713
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Class for handling all the default styling.
5
+ *
6
+ * Class SiteOrigin_Panels_Default_Styles
7
+ */
8
+ class SiteOrigin_Panels_Styles {
9
+
10
+ public function __construct() {
11
+ add_action( 'wp_enqueue_scripts', array( $this, 'register_scripts' ), 5 );
12
+
13
+ // Adding all the fields
14
+ add_filter( 'siteorigin_panels_row_style_fields', array( $this, 'row_style_fields' ) );
15
+ add_filter( 'siteorigin_panels_cell_style_fields', array( $this, 'cell_style_fields' ) );
16
+ add_filter( 'siteorigin_panels_widget_style_fields', array( $this, 'widget_style_fields' ) );
17
+
18
+ // Style wrapper attributes
19
+ add_filter( 'siteorigin_panels_row_style_attributes', array( $this, 'general_style_attributes' ), 10, 2 );
20
+ add_filter( 'siteorigin_panels_row_style_attributes', array( $this, 'row_style_attributes' ), 10, 2 );
21
+ add_filter( 'siteorigin_panels_row_style_attributes', array( $this, 'vantage_row_style_attributes' ), 11, 2 );
22
+ add_filter( 'siteorigin_panels_cell_style_attributes', array( $this, 'general_style_attributes' ), 10, 2 );
23
+ add_filter( 'siteorigin_panels_widget_style_attributes', array( $this, 'general_style_attributes' ), 10, 2 );
24
+
25
+ // Style wrapper CSS
26
+ add_filter( 'siteorigin_panels_row_style_css', array( $this, 'general_style_css' ), 10, 2 );
27
+ add_filter( 'siteorigin_panels_cell_style_css', array( $this, 'general_style_css' ), 10, 2 );
28
+ add_filter( 'siteorigin_panels_widget_style_css', array( $this, 'general_style_css' ), 10, 2 );
29
+
30
+ add_filter( 'siteorigin_panels_row_style_mobile_css', array( $this, 'general_style_mobile_css' ), 10, 2 );
31
+ add_filter( 'siteorigin_panels_cell_style_mobile_css', array( $this, 'general_style_mobile_css' ), 10, 2 );
32
+ add_filter( 'siteorigin_panels_widget_style_mobile_css', array( $this, 'general_style_mobile_css' ), 10, 2 );
33
+
34
+ // Main filter to add any custom CSS.
35
+ add_filter( 'siteorigin_panels_css_object', array( $this, 'filter_css_object' ), 10, 4 );
36
+
37
+ // Filtering specific attributes
38
+ add_filter( 'siteorigin_panels_css_row_margin_bottom', array( $this, 'filter_row_bottom_margin' ), 10, 2 );
39
+ add_filter( 'siteorigin_panels_css_row_mobile_margin_bottom', array( $this, 'filter_row_mobile_bottom_margin' ), 10, 2 );
40
+ add_filter( 'siteorigin_panels_css_row_gutter', array( $this, 'filter_row_gutter' ), 10, 2 );
41
+ add_filter( 'siteorigin_panels_css_widget_css', array( $this, 'filter_widget_style_css' ), 10, 2 );
42
+ }
43
+
44
+ public static function single() {
45
+ static $single;
46
+ return empty( $single ) ? $single = new self() : $single;
47
+ }
48
+
49
+ static function register_scripts() {
50
+ wp_register_script(
51
+ 'siteorigin-panels-front-styles',
52
+ siteorigin_panels_url( 'js/styling' . SITEORIGIN_PANELS_VERSION_SUFFIX . SITEORIGIN_PANELS_JS_SUFFIX . '.js' ),
53
+ array( 'jquery' ),
54
+ SITEORIGIN_PANELS_VERSION
55
+ );
56
+ wp_register_script(
57
+ 'siteorigin-parallax',
58
+ siteorigin_panels_url( 'js/siteorigin-parallax' . SITEORIGIN_PANELS_JS_SUFFIX . '.js' ),
59
+ array( 'jquery' ),
60
+ SITEORIGIN_PANELS_VERSION
61
+ );
62
+ wp_localize_script( 'siteorigin-panels-front-styles', 'panelsStyles', array(
63
+ 'fullContainer' => apply_filters( 'siteorigin_panels_full_width_container', siteorigin_panels_setting( 'full-width-container' ) )
64
+ ) );
65
+ }
66
+
67
+ /**
68
+ * These are general styles that apply to all elements
69
+ *
70
+ * @param $label
71
+ *
72
+ * @return array
73
+ */
74
+ static function get_general_style_fields( $id, $label ) {
75
+ $fields = array();
76
+
77
+ // All the attribute fields
78
+
79
+ $fields['id'] = array(
80
+ 'name' => sprintf( __( '%s ID', 'siteorigin-panels' ), $label ),
81
+ 'type' => 'text',
82
+ 'group' => 'attributes',
83
+ 'description' => sprintf( __( 'A custom ID used for this %s.', 'siteorigin-panels' ), strtolower( $label ) ),
84
+ 'priority' => 4,
85
+ );
86
+
87
+ $fields['class'] = array(
88
+ 'name' => sprintf( __( '%s Class', 'siteorigin-panels' ), $label ),
89
+ 'type' => 'text',
90
+ 'group' => 'attributes',
91
+ 'description' => __( 'A CSS class', 'siteorigin-panels' ),
92
+ 'priority' => 5,
93
+ );
94
+
95
+ $fields[ $id . '_css' ] = array(
96
+ 'name' => __( 'CSS Styles', 'siteorigin-panels' ),
97
+ 'type' => 'code',
98
+ 'group' => 'attributes',
99
+ 'description' => __( 'One style attribute per line.', 'siteorigin-panels' ),
100
+ 'priority' => 10,
101
+ );
102
+
103
+ $fields[ 'mobile_css' ] = array(
104
+ 'name' => __( 'Mobile CSS Styles', 'siteorigin-panels' ),
105
+ 'type' => 'code',
106
+ 'group' => 'attributes',
107
+ 'description' => __( 'CSS applied when in mobile view.', 'siteorigin-panels' ),
108
+ 'priority' => 11,
109
+ );
110
+
111
+ // The layout fields
112
+
113
+ $fields['padding'] = array(
114
+ 'name' => __( 'Padding', 'siteorigin-panels' ),
115
+ 'type' => 'measurement',
116
+ 'group' => 'layout',
117
+ 'description' => sprintf( __( 'Padding around the entire %s.', 'siteorigin-panels' ), strtolower( $label ) ),
118
+ 'priority' => 7,
119
+ 'multiple' => true
120
+ );
121
+
122
+ // Mobile layout fields
123
+
124
+ $fields['mobile_padding'] = array(
125
+ 'name' => __( 'Mobile Padding', 'siteorigin-panels' ),
126
+ 'type' => 'measurement',
127
+ 'group' => 'mobile_layout',
128
+ 'description' => __( 'Padding when on mobile devices.', 'siteorigin-panels' ),
129
+ 'priority' => 8,
130
+ 'multiple' => true
131
+ );
132
+
133
+ // The general design fields
134
+
135
+ $fields['background'] = array(
136
+ 'name' => __( 'Background Color', 'siteorigin-panels' ),
137
+ 'type' => 'color',
138
+ 'group' => 'design',
139
+ 'description' => sprintf( __( 'Background color of the %s.', 'siteorigin-panels' ), strtolower( $label ) ),
140
+ 'priority' => 5,
141
+ );
142
+
143
+ $fields['background_image_attachment'] = array(
144
+ 'name' => __( 'Background Image', 'siteorigin-panels' ),
145
+ 'type' => 'image',
146
+ 'group' => 'design',
147
+ 'description' => sprintf( __( 'Background image of the %s.', 'siteorigin-panels' ), strtolower( $label ) ),
148
+ 'priority' => 6,
149
+ );
150
+
151
+ $fields['background_display'] = array(
152
+ 'name' => __( 'Background Image Display', 'siteorigin-panels' ),
153
+ 'type' => 'select',
154
+ 'group' => 'design',
155
+ 'options' => array(
156
+ 'tile' => __( 'Tiled Image', 'siteorigin-panels' ),
157
+ 'cover' => __( 'Cover', 'siteorigin-panels' ),
158
+ 'center' => __( 'Centered, with original size', 'siteorigin-panels' ),
159
+ 'contain' => __( 'Contain', 'siteorigin-panels' ),
160
+ 'fixed' => __( 'Fixed', 'siteorigin-panels' ),
161
+ 'parallax' => __( 'Parallax', 'siteorigin-panels' ),
162
+ 'parallax-original' => __( 'Parallax (Original Size)', 'siteorigin-panels' ),
163
+ ),
164
+ 'description' => __( 'How the background image is displayed.', 'siteorigin-panels' ),
165
+ 'priority' => 7,
166
+ );
167
+
168
+ $fields['border_color'] = array(
169
+ 'name' => __( 'Border Color', 'siteorigin-panels' ),
170
+ 'type' => 'color',
171
+ 'group' => 'design',
172
+ 'description' => sprintf( __( 'Border color of the %s.', 'siteorigin-panels' ), strtolower( $label ) ),
173
+ 'priority' => 10,
174
+ );
175
+
176
+ return $fields;
177
+ }
178
+
179
+ /**
180
+ * All the row styling fields
181
+ *
182
+ * @param $fields
183
+ *
184
+ * @return array
185
+ */
186
+ static function row_style_fields( $fields ) {
187
+ // Add the general fields
188
+ $fields = wp_parse_args( $fields, self::get_general_style_fields( 'row', __( 'Row', 'siteorigin-panels' ) ) );
189
+
190
+ $fields['cell_class'] = array(
191
+ 'name' => __( 'Cell Class', 'siteorigin-panels' ),
192
+ 'type' => 'text',
193
+ 'group' => 'attributes',
194
+ 'description' => __( 'Class added to all cells in this row.', 'siteorigin-panels' ),
195
+ 'priority' => 6,
196
+ );
197
+
198
+ // Add the layout fields
199
+
200
+ $fields['bottom_margin'] = array(
201
+ 'name' => __( 'Bottom Margin', 'siteorigin-panels' ),
202
+ 'type' => 'measurement',
203
+ 'group' => 'layout',
204
+ 'description' => sprintf( __( 'Space below the row. Default is %spx.', 'siteorigin-panels' ), siteorigin_panels_setting( 'margin-bottom' ) ),
205
+ 'priority' => 5,
206
+ );
207
+
208
+ $fields['gutter'] = array(
209
+ 'name' => __( 'Gutter', 'siteorigin-panels' ),
210
+ 'type' => 'measurement',
211
+ 'group' => 'layout',
212
+ 'description' => sprintf( __( 'Amount of space between cells. Default is %spx.', 'siteorigin-panels' ), siteorigin_panels_setting( 'margin-sides' ) ),
213
+ 'priority' => 6,
214
+ );
215
+
216
+ $fields['row_stretch'] = array(
217
+ 'name' => __( 'Row Layout', 'siteorigin-panels' ),
218
+ 'type' => 'select',
219
+ 'group' => 'layout',
220
+ 'options' => array(
221
+ '' => __( 'Standard', 'siteorigin-panels' ),
222
+ 'full' => __( 'Full Width', 'siteorigin-panels' ),
223
+ 'full-stretched' => __( 'Full Width Stretched', 'siteorigin-panels' ),
224
+ 'full-stretched-padded' => __( 'Full Width Stretched Padded', 'siteorigin-panels' ),
225
+ ),
226
+ 'priority' => 10,
227
+ );
228
+
229
+ $fields['collapse_behaviour'] = array(
230
+ 'name' => __( 'Collapse Behaviour', 'siteorigin-panels' ),
231
+ 'type' => 'select',
232
+ 'group' => 'layout',
233
+ 'options' => array(
234
+ '' => __( 'Standard', 'siteorigin-panels' ),
235
+ 'no_collapse' => __( 'No Collapse', 'siteorigin-panels' ),
236
+ ),
237
+ 'priority' => 15,
238
+ );
239
+
240
+ $fields['collapse_order'] = array(
241
+ 'name' => __( 'Collapse Order', 'siteorigin-panels' ),
242
+ 'type' => 'select',
243
+ 'group' => 'layout',
244
+ 'options' => array(
245
+ '' => __( 'Default', 'siteorigin-panels' ),
246
+ 'left-top' => __( 'Left on Top', 'siteorigin-panels' ),
247
+ 'right-top' => __( 'Right on Top', 'siteorigin-panels' ),
248
+ ),
249
+ 'priority' => 16,
250
+ );
251
+
252
+ if ( siteorigin_panels_setting( 'legacy-layout' ) != 'always' ) {
253
+ $fields['cell_alignment'] = array(
254
+ 'name' => __( 'Cell Vertical Alignment', 'siteorigin-panels' ),
255
+ 'type' => 'select',
256
+ 'group' => 'layout',
257
+ 'options' => array(
258
+ 'flex-start' => __( 'Top', 'siteorigin-panels' ),
259
+ 'center' => __( 'Center', 'siteorigin-panels' ),
260
+ 'flex-end' => __( 'Bottom', 'siteorigin-panels' ),
261
+ 'stretch' => __( 'Stretch', 'siteorigin-panels' ),
262
+ ),
263
+ 'priority' => 17,
264
+ );
265
+ }
266
+
267
+ // Add the mobile layout fields
268
+
269
+ $fields['mobile_bottom_margin'] = array(
270
+ 'name' => __( 'Mobile Bottom Margin', 'siteorigin-panels' ),
271
+ 'type' => 'measurement',
272
+ 'group' => 'mobile_layout',
273
+ 'description' => sprintf( __( 'Space below the row on mobile devices. Default is %spx.', 'siteorigin-panels' ), siteorigin_panels_setting( 'margin-bottom' ) ),
274
+ 'priority' => 5,
275
+ );
276
+
277
+ return $fields;
278
+ }
279
+
280
+ /**
281
+ * All the cell styling fields
282
+ *
283
+ * @param $fields
284
+ *
285
+ * @return array
286
+ */
287
+ static function cell_style_fields( $fields ) {
288
+ // Add the general fields
289
+ $fields = wp_parse_args( $fields, self::get_general_style_fields( 'cell', __( 'Cell', 'siteorigin-panels' ) ) );
290
+
291
+ $fields['vertical_alignment'] = array(
292
+ 'name' => __( 'Vertical Alignment', 'siteorigin-panels' ),
293
+ 'type' => 'select',
294
+ 'group' => 'layout',
295
+ 'options' => array(
296
+ 'auto' => __( 'Use row setting', 'siteorigin-panels' ),
297
+ 'flex-start' => __( 'Top', 'siteorigin-panels' ),
298
+ 'center' => __( 'Center', 'siteorigin-panels' ),
299
+ 'flex-end' => __( 'Bottom', 'siteorigin-panels' ),
300
+ 'stretch' => __( 'Stretch', 'siteorigin-panels' ),
301
+ ),
302
+ 'priority' => 16,
303
+ );
304
+
305
+ $fields['font_color'] = array(
306
+ 'name' => __( 'Font Color', 'siteorigin-panels' ),
307
+ 'type' => 'color',
308
+ 'group' => 'design',
309
+ 'description' => __( 'Color of text inside this cell.', 'siteorigin-panels' ),
310
+ 'priority' => 15,
311
+ );
312
+
313
+ $fields['link_color'] = array(
314
+ 'name' => __( 'Links Color', 'siteorigin-panels' ),
315
+ 'type' => 'color',
316
+ 'group' => 'design',
317
+ 'description' => __( 'Color of links inside this cell.', 'siteorigin-panels' ),
318
+ 'priority' => 16,
319
+ );
320
+
321
+ return $fields;
322
+ }
323
+
324
+ /**
325
+ * @param $fields
326
+ *
327
+ * @return array
328
+ */
329
+ static function widget_style_fields( $fields ) {
330
+
331
+ // Add the general fields
332
+ $fields = wp_parse_args( $fields, self::get_general_style_fields( 'widget', __( 'Widget', 'siteorigin-panels' ) ) );
333
+
334
+ $fields['margin'] = array(
335
+ 'name' => __( 'Margin', 'siteorigin-panels' ),
336
+ 'type' => 'measurement',
337
+ 'group' => 'layout',
338
+ 'description' => __( 'Margins around the widget.', 'siteorigin-panels' ),
339
+ 'priority' => 6,
340
+ 'multiple' => true
341
+ );
342
+
343
+ // How lets add the design fields
344
+
345
+ $fields['font_color'] = array(
346
+ 'name' => __( 'Font Color', 'siteorigin-panels' ),
347
+ 'type' => 'color',
348
+ 'group' => 'design',
349
+ 'description' => __( 'Color of text inside this widget.', 'siteorigin-panels' ),
350
+ 'priority' => 15,
351
+ );
352
+
353
+ $fields['link_color'] = array(
354
+ 'name' => __( 'Links Color', 'siteorigin-panels' ),
355
+ 'type' => 'color',
356
+ 'group' => 'design',
357
+ 'description' => __( 'Color of links inside this widget.', 'siteorigin-panels' ),
358
+ 'priority' => 16,
359
+ );
360
+
361
+ return $fields;
362
+ }
363
+
364
+ /**
365
+ * Style attributes that apply to rows, cells and widgets
366
+ *
367
+ * @param $attributes
368
+ * @param $style
369
+ *
370
+ * @return array $attributes
371
+ */
372
+ static function general_style_attributes( $attributes, $style ){
373
+ if ( ! empty( $style['class'] ) ) {
374
+ if( ! is_array( $style['class'] ) ) {
375
+ $style['class'] = explode( ' ', $style[ 'class' ] );
376
+ }
377
+ $attributes['class'] = array_merge( $attributes['class'], $style['class'] );
378
+ }
379
+
380
+ if ( ! empty( $style['background_display'] ) &&
381
+ ! empty( $style['background_image_attachment'] )
382
+ ) {
383
+
384
+ $url = self::get_attachment_image_src( $style['background_image_attachment'], 'full' );
385
+
386
+ if (
387
+ ! empty( $url ) &&
388
+ ( $style['background_display'] == 'parallax' || $style['background_display'] == 'parallax-original' )
389
+ ) {
390
+ wp_enqueue_script( 'siteorigin-parallax' );
391
+ $parallax_args = array(
392
+ 'backgroundUrl' => $url[0],
393
+ 'backgroundSize' => array( $url[1], $url[2] ),
394
+ 'backgroundSizing' => $style['background_display'] == 'parallax-original' ? 'original' : 'scaled',
395
+ 'limitMotion' => siteorigin_panels_setting( 'parallax-motion' ) ? floatval( siteorigin_panels_setting( 'parallax-motion' ) ) : 'auto',
396
+ );
397
+ $attributes['data-siteorigin-parallax'] = json_encode( $parallax_args );
398
+ }
399
+ }
400
+
401
+ if ( ! empty( $style['id'] ) ) {
402
+ $attributes['id'] = sanitize_html_class( $style['id'] );
403
+ }
404
+
405
+ return $attributes;
406
+ }
407
+
408
+ static function row_style_attributes( $attributes, $style ) {
409
+ if ( ! empty( $style['row_stretch'] ) ) {
410
+ $attributes['class'][] = 'siteorigin-panels-stretch';
411
+ $attributes['data-stretch-type'] = $style['row_stretch'];
412
+ wp_enqueue_script( 'siteorigin-panels-front-styles' );
413
+ }
414
+
415
+ return $attributes;
416
+ }
417
+
418
+ static function vantage_row_style_attributes( $attributes, $style ) {
419
+ if ( isset( $style['class'] ) && $style['class'] == 'wide-grey' && ! empty( $attributes['style'] ) ) {
420
+ $attributes['style'] = preg_replace( '/padding-left: 1000px; padding-right: 1000px;/', '', $attributes['style'] );
421
+ }
422
+
423
+ return $attributes;
424
+ }
425
+
426
+ /**
427
+ * Get the CSS styles that apply to all rows, cells and widgets
428
+ *
429
+ * @param $css
430
+ * @param $style
431
+ *
432
+ * @return mixed
433
+ */
434
+ static function general_style_css( $css, $style ){
435
+
436
+ if ( ! empty( $style['background'] ) ) {
437
+ $css[ 'background-color' ] = $style['background'];
438
+ }
439
+
440
+ if ( ! empty( $style['background_display'] ) &&
441
+ ! ( empty( $style['background_image_attachment'] ) && empty( $style['background_image_attachment_fallback'] ) )
442
+ ) {
443
+ $url = self::get_attachment_image_src( $style['background_image_attachment'], 'full' );
444
+
445
+ if ( empty( $url ) && ! empty( $style['background_image_attachment_fallback'] ) ) {
446
+ $url = $style['background_image_attachment_fallback'];
447
+ }
448
+
449
+ if ( ! empty( $url ) ) {
450
+ $css['background-image'] = 'url(' .( is_array( $url ) ? $url[0] : $url ) . ')';
451
+
452
+ switch ( $style['background_display'] ) {
453
+ case 'parallax':
454
+ case 'parallax-original':
455
+ $css[ 'background-position' ] = 'center center';
456
+ $css[ 'background-repeat' ] = 'no-repeat';
457
+ break;
458
+ case 'tile':
459
+ $css[ 'background-repeat' ] = 'repeat';
460
+ break;
461
+ case 'cover':
462
+ $css[ 'background-position' ] = 'center center';
463
+ $css[ 'background-size' ] = 'cover';
464
+ break;
465
+ case 'contain':
466
+ $css[ 'background-size' ] = 'contain';
467
+ break;
468
+ case 'center':
469
+ $css[ 'background-position' ] = 'center center';
470
+ $css[ 'background-repeat' ] = 'no-repeat';
471
+ break;
472
+ case 'fixed':
473
+ $css[ 'background-attachment' ] = 'fixed';
474
+ $css[ 'background-position' ] = 'center center';
475
+ $css[ 'background-size' ] = 'cover';
476
+ break;
477
+ }
478
+ }
479
+ }
480
+
481
+ if ( ! empty( $style[ 'border_color' ] ) ) {
482
+ $css[ 'border' ] = '1px solid ' . $style['border_color'];
483
+ }
484
+
485
+ if ( ! empty( $style[ 'font_color' ] ) ) {
486
+ $css[ 'color' ] = $style['font_color'];
487
+ }
488
+
489
+ if( ! empty( $style[ 'padding' ] ) ) {
490
+ $css['padding'] = $style[ 'padding' ];
491
+ }
492
+
493
+ // Find which key the CSS is stored in
494
+ foreach( array( 'row_css', 'cell_css', 'widget_css', '' ) as $css_key ) {
495
+ if( empty( $css_key ) || ! empty( $style[ $css_key ] ) ) {
496
+ break;
497
+ }
498
+ }
499
+ if ( ! empty( $css_key ) && ! empty( $style[ $css_key ] ) ) {
500
+ preg_match_all( '/^([A-Za-z0-9\-]+?):(.+?);?$/m', $style[ $css_key ], $matches );
501
+
502
+ if ( ! empty( $matches[0] ) ) {
503
+ for ( $i = 0; $i < count( $matches[0] ); $i ++ ) {
504
+ $css[ $matches[1][ $i ] ] = $matches[2][ $i ];
505
+ }
506
+ }
507
+ }
508
+
509
+ return $css;
510
+ }
511
+
512
+ /**
513
+ * Get the mobile styling for rows, cells and widgets
514
+ *
515
+ * @param $css
516
+ * @param $style
517
+ *
518
+ * @return mixed
519
+ */
520
+ static function general_style_mobile_css( $css, $style ){
521
+ if( ! empty( $style['mobile_padding'] ) ) {
522
+ $css['padding'] = $style[ 'mobile_padding' ];
523
+ }
524
+
525
+ if ( ! empty( $style['background_display'] ) &&
526
+ $style['background_display'] == 'fixed' &&
527
+ ! ( empty( $style['background_image_attachment'] ) && empty( $style['background_image_attachment_fallback'] ) )
528
+ ) {
529
+ $css[ 'background-attachment' ] = 'scroll';
530
+ }
531
+
532
+ if ( ! empty( $style[ 'mobile_css' ] ) ) {
533
+ preg_match_all( '/^([A-Za-z0-9\-]+?):(.+?);?$/m', $style[ 'mobile_css' ], $matches );
534
+
535
+ if ( ! empty( $matches[0] ) ) {
536
+ for ( $i = 0; $i < count( $matches[0] ); $i ++ ) {
537
+ $css[ $matches[1][ $i ] ] = $matches[2][ $i ];
538
+ }
539
+ }
540
+ }
541
+
542
+ return $css;
543
+ }
544
+
545
+ /**
546
+ * @param SiteOrigin_Panels_Css_Builder $css
547
+ * @param $panels_data
548
+ * @param $post_id
549
+ *
550
+ * @return mixed
551
+ */
552
+ static function filter_css_object( $css, $panels_data, $post_id, $layout ) {
553
+ $mobile_width = siteorigin_panels_setting( 'mobile-width' );
554
+ if( empty( $layout ) ) {
555
+ return $css;
556
+ }
557
+
558
+ foreach( $layout as $ri => $row ) {
559
+ if( empty( $row[ 'style' ] ) ) $row[ 'style' ] = array();
560
+
561
+ $standard_css = apply_filters( 'siteorigin_panels_row_style_css', array(), $row['style'] );
562
+ $mobile_css = apply_filters( 'siteorigin_panels_row_style_mobile_css', array(), $row['style'] );
563
+
564
+ if ( ! empty( $standard_css ) ) {
565
+ $css->add_row_css(
566
+ $post_id,
567
+ $ri,
568
+ '> .panel-row-style',
569
+ $standard_css
570
+ );
571
+ }
572
+ if ( ! empty( $mobile_css ) ) {
573
+ $css->add_row_css(
574
+ $post_id,
575
+ $ri,
576
+ '> .panel-row-style',
577
+ $mobile_css,
578
+ $mobile_width
579
+ );
580
+ }
581
+
582
+ // Add in flexbox alignment to the main row element
583
+ if ( siteorigin_panels_setting( 'legacy-layout' ) != 'always' && ! SiteOrigin_Panels::is_legacy_browser() && ! empty( $row['style']['cell_alignment'] ) ) {
584
+ $css->add_row_css(
585
+ $post_id,
586
+ $ri,
587
+ array( '.panel-no-style', '.panel-has-style > .panel-row-style' ),
588
+ array(
589
+ '-webkit-align-items' => $row['style']['cell_alignment'],
590
+ 'align-items' => $row['style']['cell_alignment'],
591
+ )
592
+ );
593
+ }
594
+
595
+ // Process the cells if there are any
596
+ if( empty( $row[ 'cells' ] ) ) continue;
597
+
598
+ foreach( $row[ 'cells' ] as $ci => $cell ) {
599
+ if( empty( $cell[ 'style' ] ) ) $cell[ 'style' ] = array();
600
+
601
+ $standard_css = apply_filters( 'siteorigin_panels_cell_style_css', array(), $cell['style'] );
602
+ $mobile_css = apply_filters( 'siteorigin_panels_cell_style_mobile_css', array(), $cell['style'] );
603
+
604
+ if ( ! empty( $standard_css ) ) {
605
+ $css->add_cell_css(
606
+ $post_id,
607
+ $ri,
608
+ $ci,
609
+ '> .panel-cell-style',
610
+ $standard_css
611
+ );
612
+ }
613
+ if ( ! empty( $mobile_css ) ) {
614
+ $css->add_cell_css(
615
+ $post_id,
616
+ $ri,
617
+ $ci,
618
+ '> .panel-cell-style',
619
+ $mobile_css,
620
+ $mobile_width
621
+ );
622
+ }
623
+
624
+ if ( ! empty( $cell[ 'style' ]['vertical_alignment'] ) ) {
625
+ $css->add_cell_css(
626
+ $post_id,
627
+ $ri,
628
+ $ci,
629
+ '',
630
+ array(
631
+ 'align-self' => $cell[ 'style' ]['vertical_alignment']
632
+ )
633
+ );
634
+ }
635
+
636
+ // Process the widgets if there are any
637
+ if( empty( $cell[ 'widgets' ] ) ) continue;
638
+
639
+ foreach( $cell['widgets'] as $wi => $widget ) {
640
+ if ( empty( $widget['panels_info'] ) ) continue;
641
+ if ( empty( $widget['panels_info']['style'] ) ) $widget['panels_info']['style'] = array();
642
+
643
+ $standard_css = apply_filters( 'siteorigin_panels_widget_style_css', array(), $widget['panels_info']['style'] );
644
+ $mobile_css = apply_filters( 'siteorigin_panels_widget_style_mobile_css', array(), $widget['panels_info']['style'] );
645
+
646
+ if( ! empty( $standard_css ) ) {
647
+ $css->add_widget_css(
648
+ $post_id,
649
+ $ri,
650
+ $ci,
651
+ $wi,
652
+ '> .panel-widget-style',
653
+ $standard_css
654
+ );
655
+ }
656
+
657
+ if( ! empty( $mobile_css ) ) {
658
+ $css->add_widget_css(
659
+ $post_id,
660
+ $ri,
661
+ $ci,
662
+ $wi,
663
+ '> .panel-widget-style',
664
+ $mobile_css,
665
+ $mobile_width
666
+ );
667
+ }
668
+
669
+ if ( ! empty( $widget['panels_info']['style']['link_color'] ) ) {
670
+ $css->add_widget_css( $post_id, $ri, $ci, $wi, ' a', array(
671
+ 'color' => $widget['panels_info']['style']['link_color']
672
+ ) );
673
+ }
674
+ }
675
+ }
676
+ }
677
+
678
+ return $css;
679
+ }
680
+
681
+ /**
682
+ * Add in custom styles for the row's bottom margin
683
+ *
684
+ * @param $margin
685
+ * @param $grid
686
+ *
687
+ * @return mixed
688
+ */
689
+ static function filter_row_bottom_margin( $margin, $grid ) {
690
+ if ( ! empty( $grid['style']['bottom_margin'] ) ) {
691
+ $margin = $grid['style']['bottom_margin'];
692
+ }
693
+
694
+ return $margin;
695
+ }
696
+
697
+ /**
698
+ * Add in custom styles for a row's mobile bottom margin
699
+ *
700
+ * @param $margin
701
+ * @param $grid
702
+ *
703
+ * @return mixed
704
+ */
705
+ static function filter_row_mobile_bottom_margin( $margin, $grid ) {
706
+ if ( ! empty( $grid['style']['mobile_bottom_margin'] ) ) {
707
+ $margin = $grid['style']['mobile_bottom_margin'];
708
+ }
709
+
710
+ return $margin;
711
+ }
712
+
713
+ static function filter_row_gutter( $gutter, $grid ) {
714
+ if ( ! empty( $grid['style']['gutter'] ) ) {
715
+ $gutter = $grid['style']['gutter'];
716
+ }
717
+
718
+ return $gutter;
719
+ }
720
+
721
+ /**
722
+ * Adds widget specific styles not included in the general style fields.
723
+ *
724
+ * @param $widget_css The CSS properties and values
725
+ * @param $widget_style_data The style settings as obtained from the style fields.
726
+ *
727
+ * @return mixed
728
+ */
729
+ static function filter_widget_style_css( $widget_css, $widget_style_data ) {
730
+ if ( ! empty( $widget_style_data['margin'] ) ) {
731
+ $widget_css['margin'] = $widget_style_data['margin'];
732
+ }
733
+
734
+ return $widget_css;
735
+ }
736
+
737
+ public static function get_attachment_image_src( $image, $size = 'full' ){
738
+ if( empty( $image ) ) {
739
+ return false;
740
+ }
741
+ else if( is_numeric( $image ) ) {
742
+ return wp_get_attachment_image_src( $image, $size );
743
+ }
744
+ else if( is_string( $image ) ) {
745
+ preg_match( '/(.*?)\#([0-9]+)x([0-9]+)$/', $image, $matches );
746
+ return ! empty( $matches ) ? $matches : false;
747
+ }
748
+ }
749
+
750
+ }
inc/widget-shortcode.php CHANGED
@@ -1,122 +1,122 @@
1
- <?php
2
-
3
- class SiteOrigin_Panels_Widget_Shortcode {
4
-
5
- static $text_widgets = array(
6
- 'SiteOrigin_Widget_Editor_Widget',
7
- 'SiteOrigin_Panels_Widgets_Layout',
8
- 'WP_Widget_Black_Studio_TinyMCE',
9
- 'WP_Widget_Text',
10
- );
11
-
12
- static function init() {
13
- add_shortcode( 'siteorigin_widget', 'SiteOrigin_Panels_Widget_Shortcode::shortcode' );
14
- }
15
-
16
- static function add_filters() {
17
- add_filter( 'siteorigin_panels_the_widget_html', 'SiteOrigin_Panels_Widget_Shortcode::widget_html', 10, 4 );
18
- }
19
-
20
- static function remove_filters(){
21
- remove_filter( 'siteorigin_panels_the_widget_html', 'SiteOrigin_Panels_Widget_Shortcode::widget_html' );
22
- }
23
-
24
- /**
25
- * This shortcode just displays a widget based on the given arguments
26
- *
27
- * @param $attr
28
- * @param $content
29
- *
30
- * @return string
31
- */
32
- static function shortcode( $attr, $content ){
33
- $attr = shortcode_atts( array(
34
- 'class' => false,
35
- 'id' => '',
36
- ), $attr, 'siteorigin_widget' );
37
-
38
- $attr[ 'class' ] = html_entity_decode( $attr[ 'class' ] );
39
- $attr[ 'class' ] = apply_filters( 'siteorigin_panels_widget_class', $attr[ 'class' ] );
40
-
41
- $the_widget = ! empty( $attr[ 'class' ] ) ? SiteOrigin_Panels::get_widget_instance( $attr['class'] ) : null;
42
- if( ! empty( $the_widget ) ) {
43
-
44
- $data = self::decode_data( $content );
45
-
46
- $widget_args = ! empty( $data[ 'args' ] ) ? $data[ 'args' ] : array();
47
- $widget_instance = ! empty( $data[ 'instance' ] ) ? $data[ 'instance' ] : array();
48
-
49
- $widget_args = wp_parse_args( array(
50
- 'before_widget' => '',
51
- 'after_widget' => '',
52
- 'before_title' => '<h3 class="widget-title">',
53
- 'after_title' => '</h3>',
54
- ), $widget_args );
55
-
56
- ob_start();
57
- $the_widget->widget( $widget_args, $widget_instance );
58
- return ob_get_clean();
59
- }
60
- }
61
-
62
- /**
63
- * Get the shortcode for a specific widget
64
- *
65
- * @param $widget
66
- * @param $args
67
- * @param $instance
68
- *
69
- * @return string
70
- */
71
- static function get_shortcode( $widget, $args, $instance ){
72
- unset( $instance[ 'panels_info' ] );
73
-
74
- $data = array(
75
- 'instance' => $instance,
76
- 'args' => $args,
77
- );
78
-
79
- // This allows other plugins to implement their own shortcode. For example, to work when Page Builder isn't active
80
- $shortcode_name = apply_filters( 'siteorigin_panels_cache_shortcode', 'siteorigin_widget', $widget, $instance, $args );
81
-
82
- $shortcode = '[' . $shortcode_name . ' ';
83
- $shortcode .= 'class="' . htmlentities( preg_replace( '/\\\\+/', '\\\\\\\\', get_class( $widget ) ) ) . '"]';
84
- $shortcode .= self::encode_data( $data ) ;
85
- $shortcode .= '[/' . $shortcode_name . ']';
86
-
87
- return $shortcode;
88
- }
89
-
90
- /**
91
- * A filter to replace widgets with
92
- */
93
- static function widget_html( $html, $widget, $args, $instance ){
94
- if(
95
- empty( $GLOBALS[ 'SITEORIGIN_PANELS_POST_CONTENT_RENDER' ] ) ||
96
- // Don't try create HTML if there already is some
97
- ! empty( $html ) ||
98
- ! is_object( $widget ) ||
99
- // Skip for known text based widgets
100
- in_array( get_class( $widget ), self::$text_widgets )
101
- ) {
102
- return $html;
103
- }
104
-
105
- return self::get_shortcode( $widget, $args, $instance );
106
- }
107
-
108
- static function encode_data( $data ){
109
- return '<input type="hidden" value="' . htmlentities( json_encode( $data ), ENT_QUOTES ) . '" />';
110
- }
111
-
112
- static function decode_data( $string ){
113
- preg_match( '/value="([^"]*)"/', trim( $string ), $matches );
114
- if( ! empty( $matches[1] ) ) {
115
- $data = json_decode( html_entity_decode( $matches[1], ENT_QUOTES ), true );
116
- return $data;
117
- }
118
- else {
119
- return array();
120
- }
121
- }
122
- }
1
+ <?php
2
+
3
+ class SiteOrigin_Panels_Widget_Shortcode {
4
+
5
+ static $text_widgets = array(
6
+ 'SiteOrigin_Widget_Editor_Widget',
7
+ 'SiteOrigin_Panels_Widgets_Layout',
8
+ 'WP_Widget_Black_Studio_TinyMCE',
9
+ 'WP_Widget_Text',
10
+ );
11
+
12
+ static function init() {
13
+ add_shortcode( 'siteorigin_widget', 'SiteOrigin_Panels_Widget_Shortcode::shortcode' );
14
+ }
15
+
16
+ static function add_filters() {
17
+ add_filter( 'siteorigin_panels_the_widget_html', 'SiteOrigin_Panels_Widget_Shortcode::widget_html', 10, 4 );
18
+ }
19
+
20
+ static function remove_filters(){
21
+ remove_filter( 'siteorigin_panels_the_widget_html', 'SiteOrigin_Panels_Widget_Shortcode::widget_html' );
22
+ }
23
+
24
+ /**
25
+ * This shortcode just displays a widget based on the given arguments
26
+ *
27
+ * @param $attr
28
+ * @param $content
29
+ *
30
+ * @return string
31
+ */
32
+ static function shortcode( $attr, $content ){
33
+ $attr = shortcode_atts( array(
34
+ 'class' => false,
35
+ 'id' => '',
36
+ ), $attr, 'siteorigin_widget' );
37
+
38
+ $attr[ 'class' ] = html_entity_decode( $attr[ 'class' ] );
39
+ $attr[ 'class' ] = apply_filters( 'siteorigin_panels_widget_class', $attr[ 'class' ] );
40
+
41
+ $the_widget = ! empty( $attr[ 'class' ] ) ? SiteOrigin_Panels::get_widget_instance( $attr['class'] ) : null;
42
+ if( ! empty( $the_widget ) ) {
43
+
44
+ $data = self::decode_data( $content );
45
+
46
+ $widget_args = ! empty( $data[ 'args' ] ) ? $data[ 'args' ] : array();
47
+ $widget_instance = ! empty( $data[ 'instance' ] ) ? $data[ 'instance' ] : array();
48
+
49
+ $widget_args = wp_parse_args( array(
50
+ 'before_widget' => '',
51
+ 'after_widget' => '',
52
+ 'before_title' => '<h3 class="widget-title">',
53
+ 'after_title' => '</h3>',
54
+ ), $widget_args );
55
+
56
+ ob_start();
57
+ $the_widget->widget( $widget_args, $widget_instance );
58
+ return ob_get_clean();
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Get the shortcode for a specific widget
64
+ *
65
+ * @param $widget
66
+ * @param $args
67
+ * @param $instance
68
+ *
69
+ * @return string
70
+ */
71
+ static function get_shortcode( $widget, $args, $instance ){
72
+ unset( $instance[ 'panels_info' ] );
73
+
74
+ $data = array(
75
+ 'instance' => $instance,
76
+ 'args' => $args,
77
+ );
78
+
79
+ // This allows other plugins to implement their own shortcode. For example, to work when Page Builder isn't active
80
+ $shortcode_name = apply_filters( 'siteorigin_panels_cache_shortcode', 'siteorigin_widget', $widget, $instance, $args );
81
+
82
+ $shortcode = '[' . $shortcode_name . ' ';
83
+ $shortcode .= 'class="' . htmlentities( preg_replace( '/\\\\+/', '\\\\\\\\', get_class( $widget ) ) ) . '"]';
84
+ $shortcode .= self::encode_data( $data ) ;
85
+ $shortcode .= '[/' . $shortcode_name . ']';
86
+
87
+ return $shortcode;
88
+ }
89
+
90
+ /**
91
+ * A filter to replace widgets with
92
+ */
93
+ static function widget_html( $html, $widget, $args, $instance ){
94
+ if(
95
+ empty( $GLOBALS[ 'SITEORIGIN_PANELS_POST_CONTENT_RENDER' ] ) ||
96
+ // Don't try create HTML if there already is some
97
+ ! empty( $html ) ||
98
+ ! is_object( $widget ) ||
99
+ // Skip for known text based widgets
100
+ in_array( get_class( $widget ), self::$text_widgets )
101
+ ) {
102
+ return $html;
103
+ }
104
+
105
+ return self::get_shortcode( $widget, $args, $instance );
106
+ }
107
+
108
+ static function encode_data( $data ){
109
+ return '<input type="hidden" value="' . htmlentities( json_encode( $data ), ENT_QUOTES ) . '" />';
110
+ }
111
+
112
+ static function decode_data( $string ){
113
+ preg_match( '/value="([^"]*)"/', trim( $string ), $matches );
114
+ if( ! empty( $matches[1] ) ) {
115
+ $data = json_decode( html_entity_decode( $matches[1], ENT_QUOTES ), true );
116
+ return $data;
117
+ }
118
+ else {
119
+ return array();
120
+ }
121
+ }
122
+ }
inc/widgets/layout.php CHANGED
@@ -1,134 +1,134 @@
1
- <?php
2
-
3
- /**
4
- * This widget give you the full Page Builder interface inside a widget. Fully nestable.
5
- *
6
- * Class SiteOrigin_Panels_Widgets_Builder
7
- */
8
- class SiteOrigin_Panels_Widgets_Layout extends WP_Widget {
9
- function __construct() {
10
- parent::__construct(
11
- 'siteorigin-panels-builder',
12
- // TRANSLATORS: This is the name of a widget
13
- __( 'Layout Builder', 'siteorigin-panels' ),
14
- array(
15
- 'description' => __( 'A complete SiteOrigin Page Builder layout as a widget.', 'siteorigin-panels' ),
16
- 'panels_title' => false,
17
- ),
18
- array(
19
- )
20
- );
21
- }
22
-
23
- function widget($args, $instance) {
24
- if( empty($instance['panels_data']) ) return;
25
-
26
- if( is_string( $instance['panels_data'] ) ) {
27
- $instance['panels_data'] = json_decode( $instance['panels_data'], true );
28
- }
29
- if(empty($instance['panels_data']['widgets'])) return;
30
-
31
- if( ! empty( $instance['panels_data']['widgets'] ) ) {
32
- foreach( $instance['panels_data']['widgets'] as & $widget ) {
33
- $widget['panels_info']['class'] = str_replace( '&#92;', '\\', $widget['panels_info']['class'] );
34
- }
35
- }
36
-
37
- if( empty( $instance['builder_id'] ) ) $instance['builder_id'] = uniqid();
38
-
39
- echo $args['before_widget'];
40
- $is_content_render = ! empty( $GLOBALS['SITEORIGIN_PANELS_POST_CONTENT_RENDER'] ) &&
41
- siteorigin_panels_setting( 'copy-styles' );
42
- $is_preview_render = ! empty( $GLOBALS['SITEORIGIN_PANELS_PREVIEW_RENDER'] );
43
-
44
- echo SiteOrigin_Panels::renderer()->render(
45
- 'w'.$instance['builder_id'],
46
- true,
47
- $instance['panels_data'],
48
- $layout_data,
49
- $is_content_render || $is_preview_render
50
- );
51
- echo $args['after_widget'];
52
- }
53
-
54
- function update($new, $old) {
55
- $new['builder_id'] = uniqid();
56
-
57
- if( is_string($new['panels_data']) && ! empty( $new['panels_data'] ) ) {
58
- // This is still in a string format, so we'll convert it to an array for sanitization
59
- $new['panels_data'] = json_decode( $new['panels_data'], true );
60
- }
61
-
62
- if ( ! empty( $new['panels_data'] ) ) {
63
- if ( ! empty( $new['panels_data']['widgets'] ) ) {
64
- $new['panels_data']['widgets'] = SiteOrigin_Panels_Admin::single()->process_raw_widgets(
65
- $new['panels_data']['widgets'],
66
- ! empty( $old['panels_data']['widgets'] ) ? $old['panels_data']['widgets'] : false
67
- );
68
- foreach( $new['panels_data']['widgets'] as & $widget ) {
69
- $widget['panels_info']['class'] = str_replace( '\\', '&#92;', $widget['panels_info']['class'] );
70
- }
71
- }
72
-
73
- $new['panels_data'] = SiteOrigin_Panels_Styles_Admin::single()->sanitize_all( $new['panels_data'] );
74
- }
75
-
76
- return $new;
77
- }
78
-
79
- function form( $instance ){
80
-
81
- if ( ! is_admin() ) {
82
- ?>
83
- <p>
84
- <?php _e( 'This widget can currently only be used in the WordPress admin interface.', 'siteorigin-panels' ) ?>
85
- </p>
86
- <?php
87
- return;
88
- }
89
-
90
- $instance = wp_parse_args($instance, array(
91
- 'panels_data' => '',
92
- 'builder_id' => uniqid(),
93
- ) );
94
- $form_id = uniqid();
95
-
96
- if( ! empty( $instance['panels_data']['widgets'] ) ) {
97
- foreach( $instance['panels_data']['widgets'] as & $widget ) {
98
- $widget['panels_info']['class'] = str_replace( '&#92;', '\\', $widget['panels_info']['class'] );
99
- }
100
- }
101
-
102
- if( ! is_string( $instance['panels_data'] ) ) {
103
- $instance['panels_data'] = json_encode( $instance['panels_data'] );
104
- }
105
-
106
- ?>
107
- <div class="siteorigin-page-builder-widget" id="siteorigin-page-builder-widget-<?php echo esc_attr( $form_id ) ?>" data-builder-id="<?php echo esc_attr( $form_id ) ?>" data-type="layout_widget">
108
- <p>
109
- <button class="button-secondary siteorigin-panels-display-builder" ><?php _e('Open Builder', 'siteorigin-panels') ?></button>
110
- </p>
111
-
112
- <input type="hidden" data-panels-filter="json_parse" value="" class="panels-data" name="<?php echo $this->get_field_name('panels_data') ?>" id="<?php echo $this->get_field_id('panels_data') ?>" />
113
-
114
- <script type="text/javascript">
115
- ( function( panelsData ){
116
- // Create the panels_data input
117
- document.getElementById('<?php echo $this->get_field_id('panels_data') ?>').value = JSON.stringify( panelsData );
118
- } )( <?php echo $instance['panels_data']; ?> );
119
- </script>
120
-
121
- <input type="hidden" value="<?php echo esc_attr( $instance['builder_id'] ) ?>" name="<?php echo $this->get_field_name('builder_id') ?>" />
122
- </div>
123
- <script type="text/javascript">
124
- if(
125
- typeof jQuery.fn.soPanelsSetupBuilderWidget != 'undefined' &&
126
- ( ! jQuery('body').hasClass('wp-customizer') || jQuery( "#siteorigin-page-builder-widget-<?php echo esc_attr( $form_id ) ?>").closest( '.panel-dialog' ).length )
127
- ) {
128
- jQuery( "#siteorigin-page-builder-widget-<?php echo esc_attr( $form_id ) ?>").soPanelsSetupBuilderWidget();
129
- }
130
- </script>
131
- <?php
132
- }
133
-
134
- }
1
+ <?php
2
+
3
+ /**
4
+ * This widget give you the full Page Builder interface inside a widget. Fully nestable.
5
+ *
6
+ * Class SiteOrigin_Panels_Widgets_Builder
7
+ */
8
+ class SiteOrigin_Panels_Widgets_Layout extends WP_Widget {
9
+ function __construct() {
10
+ parent::__construct(
11
+ 'siteorigin-panels-builder',
12
+ // TRANSLATORS: This is the name of a widget
13
+ __( 'Layout Builder', 'siteorigin-panels' ),
14
+ array(
15
+ 'description' => __( 'A complete SiteOrigin Page Builder layout as a widget.', 'siteorigin-panels' ),
16
+ 'panels_title' => false,
17
+ ),
18
+ array(
19
+ )
20
+ );
21
+ }
22
+
23
+ function widget($args, $instance) {
24
+ if( empty($instance['panels_data']) ) return;
25
+
26
+ if( is_string( $instance['panels_data'] ) ) {
27
+ $instance['panels_data'] = json_decode( $instance['panels_data'], true );
28
+ }
29
+ if(empty($instance['panels_data']['widgets'])) return;
30
+
31
+ if( ! empty( $instance['panels_data']['widgets'] ) ) {
32
+ foreach( $instance['panels_data']['widgets'] as & $widget ) {
33
+ $widget['panels_info']['class'] = str_replace( '&#92;', '\\', $widget['panels_info']['class'] );
34
+ }
35
+ }
36
+
37
+ if( empty( $instance['builder_id'] ) ) $instance['builder_id'] = uniqid();
38
+
39
+ echo $args['before_widget'];
40
+ $is_content_render = ! empty( $GLOBALS['SITEORIGIN_PANELS_POST_CONTENT_RENDER'] ) &&
41
+ siteorigin_panels_setting( 'copy-styles' );
42
+ $is_preview_render = ! empty( $GLOBALS['SITEORIGIN_PANELS_PREVIEW_RENDER'] );
43
+
44
+ echo SiteOrigin_Panels::renderer()->render(
45
+ 'w'.$instance['builder_id'],
46
+ true,
47
+ $instance['panels_data'],
48
+ $layout_data,
49
+ $is_content_render || $is_preview_render
50
+ );
51
+ echo $args['after_widget'];
52
+ }
53
+
54
+ function update($new, $old) {
55
+ $new['builder_id'] = uniqid();
56
+
57
+ if( is_string($new['panels_data']) && ! empty( $new['panels_data'] ) ) {
58
+ // This is still in a string format, so we'll convert it to an array for sanitization
59
+ $new['panels_data'] = json_decode( $new['panels_data'], true );
60
+ }
61
+
62
+ if ( ! empty( $new['panels_data'] ) ) {
63
+ if ( ! empty( $new['panels_data']['widgets'] ) ) {
64
+ $new['panels_data']['widgets'] = SiteOrigin_Panels_Admin::single()->process_raw_widgets(
65
+ $new['panels_data']['widgets'],
66
+ ! empty( $old['panels_data']['widgets'] ) ? $old['panels_data']['widgets'] : false
67
+ );
68
+ foreach( $new['panels_data']['widgets'] as & $widget ) {
69
+ $widget['panels_info']['class'] = str_replace( '\\', '&#92;', $widget['panels_info']['class'] );
70
+ }
71
+ }
72
+
73
+ $new['panels_data'] = SiteOrigin_Panels_Styles_Admin::single()->sanitize_all( $new['panels_data'] );
74
+ }
75
+
76
+ return $new;
77
+ }
78
+
79
+ function form( $instance ){
80
+
81
+ if ( ! is_admin() ) {
82
+ ?>
83
+ <p>
84
+ <?php _e( 'This widget can currently only be used in the WordPress admin interface.', 'siteorigin-panels' ) ?>
85
+ </p>
86
+ <?php
87
+ return;
88
+ }
89
+
90
+ $instance = wp_parse_args($instance, array(
91
+ 'panels_data' => '',
92
+ 'builder_id' => uniqid(),
93
+ ) );
94
+ $form_id = uniqid();
95
+
96
+ if( ! empty( $instance['panels_data']['widgets'] ) ) {
97
+ foreach( $instance['panels_data']['widgets'] as & $widget ) {
98
+ $widget['panels_info']['class'] = str_replace( '&#92;', '\\', $widget['panels_info']['class'] );
99
+ }
100
+ }
101
+
102
+ if( ! is_string( $instance['panels_data'] ) ) {
103
+ $instance['panels_data'] = json_encode( $instance['panels_data'] );
104
+ }
105
+
106
+ ?>
107
+ <div class="siteorigin-page-builder-widget" id="siteorigin-page-builder-widget-<?php echo esc_attr( $form_id ) ?>" data-builder-id="<?php echo esc_attr( $form_id ) ?>" data-type="layout_widget">
108
+ <p>
109
+ <button class="button-secondary siteorigin-panels-display-builder" ><?php _e('Open Builder', 'siteorigin-panels') ?></button>
110
+ </p>
111
+
112
+ <input type="hidden" data-panels-filter="json_parse" value="" class="panels-data" name="<?php echo $this->get_field_name('panels_data') ?>" id="<?php echo $this->get_field_id('panels_data') ?>" />
113
+
114
+ <script type="text/javascript">
115
+ ( function( panelsData ){
116
+ // Create the panels_data input
117
+ document.getElementById('<?php echo $this->get_field_id('panels_data') ?>').value = JSON.stringify( panelsData );
118
+ } )( <?php echo $instance['panels_data']; ?> );
119
+ </script>
120
+
121
+ <input type="hidden" value="<?php echo esc_attr( $instance['builder_id'] ) ?>" name="<?php echo $this->get_field_name('builder_id') ?>" />
122
+ </div>
123
+ <script type="text/javascript">
124
+ if(
125
+ typeof jQuery.fn.soPanelsSetupBuilderWidget != 'undefined' &&
126
+ ( ! jQuery('body').hasClass('wp-customizer') || jQuery( "#siteorigin-page-builder-widget-<?php echo esc_attr( $form_id ) ?>").closest( '.panel-dialog' ).length )
127
+ ) {
128
+ jQuery( "#siteorigin-page-builder-widget-<?php echo esc_attr( $form_id ) ?>").soPanelsSetupBuilderWidget();
129
+ }
130
+ </script>
131
+ <?php
132
+ }
133
+
134
+ }
inc/widgets/post-loop-helper.php CHANGED
@@ -1,100 +1,100 @@
1
- <?php
2
-
3
- /**
4
- * A helper widget for the main SiteOrigin_Panels_Widgets_PostLoop class
5
- *
6
- * Class SiteOrigin_Panels_Widgets_PostLoop_Helper
7
- */
8
- class SiteOrigin_Panels_Widgets_PostLoop_Helper extends SiteOrigin_Widget {
9
-
10
- /**
11
- * SiteOrigin_Panels_Widgets_PostLoop_Helper constructor.
12
- *
13
- * @param array $templates
14
- */
15
- function __construct( $templates ) {
16
-
17
- $template_options = array();
18
- if( ! empty( $templates ) ) {
19
- foreach( $templates as $template ) {
20
- $headers = get_file_data( locate_template( $template ), array(
21
- 'loop_name' => 'Loop Name',
22
- ) );
23
- $template_options[ $template ] = esc_html( ! empty( $headers['loop_name'] ) ? $headers['loop_name'] : $template );
24
- }
25
- }
26
-
27
- parent::__construct(
28
- 'siteorigin-panels-postloop-helper',
29
- __( 'Post Loop', 'siteorigin-panels' ),
30
- array(
31
- 'description' => __( 'Displays a post loop.', 'siteorigin-panels' ),
32
- 'has_preview' => false,
33
- ),
34
- array(),
35
- array(
36
- 'title' => array(
37
- 'type' => 'text',
38
- 'label' => __( 'Title', 'siteorigin-panels' ),
39
- ),
40
- 'template' => array(
41
- 'type' => 'select',
42
- 'label' => __( 'Template', 'siteorigin-panels' ),
43
- 'options' => $template_options,
44
- 'default' => 'loop.php',
45
- ),
46
- 'more' => array(
47
- 'type' => 'checkbox',
48
- 'label' => __( 'More link', 'so-widgets-bundle' ),
49
- 'description' => __( 'If the template supports it, cut posts and display the more link.', 'siteorigin-panels' ),
50
- 'default' => false,
51
- ),
52
- 'posts' => array(
53
- 'type' => 'posts',
54
- 'label' => __( 'Posts query', 'so-widgets-bundle' ),
55
- 'hide' => true
56
- ),
57
- )
58
- );
59
- }
60
-
61
- /**
62
- * Convert this instance into one that's compatible with the posts field
63
- *
64
- * @param $instance
65
- *
66
- * @return mixed
67
- */
68
- function modify_instance( $instance ) {
69
- if( ! empty( $instance['post_type'] ) ) {
70
- $value = array();
71
-
72
- if( ! empty( $instance['post_type'] ) ) $value['post_type'] = $instance['post_type'];
73
- if( ! empty( $instance['posts_per_page'] ) ) $value['posts_per_page'] = $instance['posts_per_page'];
74
- if( ! empty( $instance['order'] ) ) $value['order'] = $instance['order'];
75
- if( ! empty( $instance['orderby'] ) ) $value['orderby'] = $instance['orderby'];
76
- if( ! empty( $instance['sticky'] ) ) $value['sticky'] = $instance['sticky'];
77
- if( ! empty( $instance['additional'] ) ) $value['additional'] = $instance['additional'];
78
- $instance[ 'posts' ] = $value;
79
-
80
- unset( $instance[ 'post_type' ] );
81
- unset( $instance[ 'posts_per_page' ] );
82
- unset( $instance[ 'order' ] );
83
- unset( $instance[ 'orderby' ] );
84
- unset( $instance[ 'sticky' ] );
85
- unset( $instance[ 'additional' ] );
86
- }
87
-
88
- return $instance;
89
- }
90
-
91
- /**
92
- * @param array $args
93
- * @param array $instance
94
- *
95
- * @return bool
96
- */
97
- function widget( $args, $instance ) {
98
- return false;
99
- }
100
- }
1
+ <?php
2
+
3
+ /**
4
+ * A helper widget for the main SiteOrigin_Panels_Widgets_PostLoop class
5
+ *
6
+ * Class SiteOrigin_Panels_Widgets_PostLoop_Helper
7
+ */
8
+ class SiteOrigin_Panels_Widgets_PostLoop_Helper extends SiteOrigin_Widget {
9
+
10
+ /**
11
+ * SiteOrigin_Panels_Widgets_PostLoop_Helper constructor.
12
+ *
13
+ * @param array $templates
14
+ */
15
+ function __construct( $templates ) {
16
+
17
+ $template_options = array();
18
+ if( ! empty( $templates ) ) {
19
+ foreach( $templates as $template ) {
20
+ $headers = get_file_data( locate_template( $template ), array(
21
+ 'loop_name' => 'Loop Name',
22
+ ) );
23
+ $template_options[ $template ] = esc_html( ! empty( $headers['loop_name'] ) ? $headers['loop_name'] : $template );
24
+ }
25
+ }
26
+
27
+ parent::__construct(
28
+ 'siteorigin-panels-postloop-helper',
29
+ __( 'Post Loop', 'siteorigin-panels' ),
30
+ array(
31
+ 'description' => __( 'Displays a post loop.', 'siteorigin-panels' ),
32
+ 'has_preview' => false,
33
+ ),
34
+ array(),
35
+ array(
36
+ 'title' => array(
37
+ 'type' => 'text',
38
+ 'label' => __( 'Title', 'siteorigin-panels' ),
39
+ ),
40
+ 'template' => array(
41
+ 'type' => 'select',
42
+ 'label' => __( 'Template', 'siteorigin-panels' ),
43
+ 'options' => $template_options,
44
+ 'default' => 'loop.php',
45
+ ),
46
+ 'more' => array(
47
+ 'type' => 'checkbox',
48
+ 'label' => __( 'More link', 'so-widgets-bundle' ),
49
+ 'description' => __( 'If the template supports it, cut posts and display the more link.', 'siteorigin-panels' ),
50
+ 'default' => false,
51
+ ),
52
+ 'posts' => array(
53
+ 'type' => 'posts',
54
+ 'label' => __( 'Posts query', 'so-widgets-bundle' ),
55
+ 'hide' => true
56
+ ),
57
+ )
58
+ );
59
+ }
60
+
61
+ /**
62
+ * Convert this instance into one that's compatible with the posts field
63
+ *
64
+ * @param $instance
65
+ *
66
+ * @return mixed
67
+ */
68
+ function modify_instance( $instance ) {
69
+ if( ! empty( $instance['post_type'] ) ) {
70
+ $value = array();
71
+
72
+ if( ! empty( $instance['post_type'] ) ) $value['post_type'] = $instance['post_type'];
73
+ if( ! empty( $instance['posts_per_page'] ) ) $value['posts_per_page'] = $instance['posts_per_page'];
74
+ if( ! empty( $instance['order'] ) ) $value['order'] = $instance['order'];
75
+ if( ! empty( $instance['orderby'] ) ) $value['orderby'] = $instance['orderby'];
76
+ if( ! empty( $instance['sticky'] ) ) $value['sticky'] = $instance['sticky'];
77
+ if( ! empty( $instance['additional'] ) ) $value['additional'] = $instance['additional'];
78
+ $instance[ 'posts' ] = $value;
79
+
80
+ unset( $instance[ 'post_type' ] );
81
+ unset( $instance[ 'posts_per_page' ] );
82
+ unset( $instance[ 'order' ] );
83
+ unset( $instance[ 'orderby' ] );
84
+ unset( $instance[ 'sticky' ] );
85
+ unset( $instance[ 'additional' ] );
86
+ }
87
+
88
+ return $instance;
89
+ }
90
+
91
+ /**
92
+ * @param array $args
93
+ * @param array $instance
94
+ *
95
+ * @return bool
96
+ */
97
+ function widget( $args, $instance ) {
98
+ return false;
99
+ }
100
+ }
inc/widgets/post-loop.php CHANGED
@@ -1,422 +1,422 @@
1
- <?php
2
-
3
- /**
4
- * Display a loop of posts.
5
- *
6
- * Class SiteOrigin_Panels_Widgets_PostLoop
7
- */
8
- class SiteOrigin_Panels_Widgets_PostLoop extends WP_Widget {
9
-
10
- static $rendering_loop;
11
-
12
- static $current_loop_template;
13
- static $current_loop_instance;
14
-
15
- /**
16
- * @var SiteOrigin_Panels_Widgets_PostLoop_Helper
17
- */
18
- private $helper;
19
-
20
- function __construct() {
21
- parent::__construct(
22
- 'siteorigin-panels-postloop',
23
- __( 'Post Loop', 'siteorigin-panels' ),
24
- array(
25
- 'description' => __( 'Displays a post loop.', 'siteorigin-panels' ),
26
- ),
27
- array(
28
- 'width' => 800,
29
- )
30
- );
31
- }
32
-
33
- /**
34
- * Are we currently rendering a post loop
35
- *
36
- * @return bool
37
- */
38
- static function is_rendering_loop() {
39
- return self::$rendering_loop;
40
- }
41
-
42
- /**
43
- * Which post loop is currently being rendered
44
- *
45
- * @return array
46
- */
47
- static function get_current_loop_template() {
48
- return self::$current_loop_template;
49
- }
50
-
51
- /**
52
- * Which post loop is currently being rendered
53
- *
54
- * @return array
55
- */
56
- static function get_current_loop_instance() {
57
- return self::$current_loop_instance;
58
- }
59
-
60
- /**
61
- * Update the widget
62
- *
63
- * @param array $new
64
- * @param array $old
65
- * @return array
66
- */
67
- function update( $new, $old ){
68
- if( class_exists( 'SiteOrigin_Widget' ) && class_exists( 'SiteOrigin_Widget_Field_Posts' ) ) {
69
- $helper = $this->get_helper_widget( $this->get_loop_templates() );
70
- return $helper->update( $new, $old );
71
- }
72
- else {
73
- $new['more'] = !empty( $new['more'] );
74
- return $new;
75
- }
76
- }
77
-
78
- /**
79
- * @param array $args
80
- * @param array $instance
81
- */
82
- function widget( $args, $instance ) {
83
- if( empty( $instance['template'] ) ) return;
84
- if( is_admin() ) return;
85
-
86
- static $depth = 0;
87
- $depth++;
88
- if( $depth > 1 ) {
89
- // Because of infinite loops, don't render this post loop if its inside another
90
- $depth--;
91
- echo $args['before_widget'].$args['after_widget'];
92
- return;
93
- }
94
-
95
- $query_args = $instance;
96
- //If Widgets Bundle post selector is available and a posts query has been saved using it.
97
- if ( function_exists( 'siteorigin_widget_post_selector_process_query' ) && ! empty( $instance['posts'] ) ) {
98
- $query_args = siteorigin_widget_post_selector_process_query($instance['posts']);
99
- $query_args['additional'] = empty($instance['additional']) ? array() : $instance['additional'];
100
- }
101
- else {
102
- if ( ! empty( $instance['posts'] ) ) {
103
- // This is using the new WB 1.9 posts field
104
- $query_args = wp_parse_args( $instance['posts'], $query_args );
105
- }
106
-
107
- if( ! empty( $query_args['sticky'] ) ) {
108
- switch( $query_args['sticky'] ){
109
- case 'ignore' :
110
- $query_args['ignore_sticky_posts'] = 1;
111
- break;
112
- case 'only' :
113
- $query_args['post__in'] = get_option( 'sticky_posts' );
114
- break;
115
- case 'exclude' :
116
- $query_args['post__not_in'] = get_option( 'sticky_posts' );
117
- break;
118
- }
119
- }
120
- unset($query_args['template']);
121
- unset($query_args['title']);
122
- unset($query_args['sticky']);
123
- if (empty($query_args['additional'])) {
124
- $query_args['additional'] = array();
125
- }
126
- }
127
- $query_args = wp_parse_args($query_args['additional'], $query_args);
128
- unset($query_args['additional']);
129
-
130
- global $wp_rewrite;
131
-
132
- if( $wp_rewrite->using_permalinks() ) {
133
-
134
- if( get_query_var('paged') ) {
135
- // When the widget appears on a sub page.
136
- $query_args['paged'] = get_query_var('paged');
137
- }
138
- elseif( strpos( $_SERVER['REQUEST_URI'], '/page/' ) !== false ) {
139
- // When the widget appears on the home page.
140
- preg_match('/\/page\/([0-9]+)\//', $_SERVER['REQUEST_URI'], $matches);
141
- if(!empty($matches[1])) $query_args['paged'] = intval($matches[1]);
142
- else $query_args['paged'] = 1;
143
- }
144
- else $query_args['paged'] = 1;
145
- }
146
- else {
147
- // Get current page number when we're not using permalinks
148
- $query_args['paged'] = isset($_GET['paged']) ? intval($_GET['paged']) : 1;
149
- }
150
-
151
- // Exclude the current post to prevent possible infinite loop
152
-
153
- global $siteorigin_panels_current_post;
154
-
155
- if( !empty($siteorigin_panels_current_post) ){
156
- if( !empty( $query_args['post__not_in'] ) ){
157
- if( !is_array( $query_args['post__not_in'] ) ){
158
- $query_args['post__not_in'] = explode( ',', $query_args['post__not_in'] );
159
- $query_args['post__not_in'] = array_map( 'intval', $query_args['post__not_in'] );
160
- }
161
- $query_args['post__not_in'][] = $siteorigin_panels_current_post;
162
- }
163
- else {
164
- $query_args['post__not_in'] = array( $siteorigin_panels_current_post );
165
- }
166
- }
167
-
168
- if( !empty($query_args['post__in']) && !is_array($query_args['post__in']) ) {
169
- $query_args['post__in'] = explode(',', $query_args['post__in']);
170
- $query_args['post__in'] = array_map('intval', $query_args['post__in']);
171
- }
172
-
173
- // Create the query
174
- query_posts( apply_filters( 'siteorigin_panels_postloop_query_args', $query_args ) );
175
- echo $args['before_widget'];
176
-
177
- // Filter the title
178
- $instance['title'] = apply_filters('widget_title', $instance['title'], $instance, $this->id_base);
179
- if ( !empty( $instance['title'] ) ) {
180
- echo $args['before_title'] . $instance['title'] . $args['after_title'];
181
- }
182
-
183
- global $more; $old_more = $more; $more = empty($instance['more']);
184
- self::$rendering_loop = true;
185
- self::$current_loop_instance = $instance;
186
- self::$current_loop_template = $instance['template'];
187
- if(strpos('/'.$instance['template'], '/content') !== false) {
188
- while( have_posts() ) {
189
- the_post();
190
- locate_template($instance['template'], true, false);
191
- }
192
- }
193
- else {
194
- locate_template($instance['template'], true, false);
195
- }
196
- self::$rendering_loop = false;
197
- self::$current_loop_instance = null;
198
- self::$current_loop_template = null;
199
-
200
- echo $args['after_widget'];
201
-
202
- // Reset everything
203
- wp_reset_query();
204
- $depth--;
205
- }
206
-
207
- /**
208
- * Display the form for the post loop.
209
- *
210
- * @param array $instance
211
- * @return string|void
212
- */
213
- function form( $instance ) {
214
- $templates = $this->get_loop_templates();
215
- if( empty($templates) ) {
216
- ?><p><?php _e("Your theme doesn't have any post loops.", 'siteorigin-panels') ?></p><?php
217
- return;
218
- }
219
-
220
- // If the Widgets Bundle is installed and the post selector is available, use that.
221
- // Otherwise revert back to our own form fields.
222
- if( class_exists( 'SiteOrigin_Widget' ) && class_exists( 'SiteOrigin_Widget_Field_Posts' ) ) {
223
- $helper = $this->get_helper_widget( $templates );
224
- $helper->form( $instance );
225
- }
226
- else {
227
- $instance = wp_parse_args( $instance, array(
228
- 'title' => '',
229
- 'template' => 'loop.php',
230
-
231
- // Query args
232
- 'post_type' => 'post',
233
- 'posts_per_page' => '',
234
-
235
- 'order' => 'DESC',
236
- 'orderby' => 'date',
237
-
238
- 'sticky' => '',
239
-
240
- 'additional' => '',
241
- 'more' => false,
242
- ) );
243
-
244
- ?>
245
- <p>
246
- <label for="<?php echo $this->get_field_id( 'title' ) ?>"><?php _e( 'Title', 'siteorigin-panels' ) ?></label>
247
- <input type="text" class="widefat" name="<?php echo $this->get_field_name( 'title' ) ?>" id="<?php echo $this->get_field_id( 'title' ) ?>" value="<?php echo esc_attr( $instance['title'] ) ?>">
248
- </p>
249
- <p>
250
- <label for="<?php echo $this->get_field_id('template') ?>"><?php _e('Template', 'siteorigin-panels') ?></label>
251
- <select id="<?php echo $this->get_field_id( 'template' ) ?>" name="<?php echo $this->get_field_name( 'template' ) ?>">
252
- <?php foreach($templates as $template) : ?>
253
- <option value="<?php echo esc_attr($template) ?>" <?php selected($instance['template'], $template) ?>>
254
- <?php
255
- $headers = get_file_data( locate_template($template), array(
256
- 'loop_name' => 'Loop Name',
257
- ) );
258
- echo esc_html(!empty($headers['loop_name']) ? $headers['loop_name'] : $template);
259
- ?>
260
- </option>
261
- <?php endforeach; ?>
262
- </select>
263
- </p>
264
-
265
- <p>
266
- <label for="<?php echo $this->get_field_id('more') ?>"><?php _e('More Link', 'siteorigin-panels') ?></label>
267
- <input type="checkbox" class="widefat" id="<?php echo $this->get_field_id( 'more' ) ?>" name="<?php echo $this->get_field_name( 'more' ) ?>" <?php checked( $instance['more'] ) ?> /><br/>
268
- <small><?php _e('If the template supports it, cut posts and display the more link.', 'siteorigin-panels') ?></small>
269
- </p>
270
- <?php
271
-
272
- if ( ! empty( $instance['posts'] ) ) {
273
- $instance = wp_parse_args( $instance['posts'] , $instance );
274
- unset( $instance['posts'] );
275
- //unset post__in and taxonomies?
276
- }
277
- // Get all the loop template files
278
- $post_types = get_post_types(array('public' => true));
279
- $post_types = array_values($post_types);
280
- $post_types = array_diff($post_types, array('attachment', 'revision', 'nav_menu_item'));
281
- ?>
282
- <p>
283
- <label for="<?php echo $this->get_field_id('post_type') ?>"><?php _e('Post Type', 'siteorigin-panels') ?></label>
284
- <select id="<?php echo $this->get_field_id( 'post_type' ) ?>" name="<?php echo $this->get_field_name( 'post_type' ) ?>" value="<?php echo esc_attr($instance['post_type']) ?>">
285
- <?php foreach($post_types as $type) : ?>
286
- <option value="<?php echo esc_attr($type) ?>" <?php selected($instance['post_type'], $type) ?>><?php echo esc_html($type) ?></option>
287
- <?php endforeach; ?>
288
- </select>
289
- </p>
290
-
291
- <p>
292
- <label for="<?php echo $this->get_field_id('posts_per_page') ?>"><?php _e('Posts Per Page', 'siteorigin-panels') ?></label>
293
- <input type="text" class="small-text" id="<?php echo $this->get_field_id( 'posts_per_page' ) ?>" name="<?php echo $this->get_field_name( 'posts_per_page' ) ?>" value="<?php echo esc_attr($instance['posts_per_page']) ?>" />
294
- </p>
295
-
296
- <p>
297
- <label <?php echo $this->get_field_id('orderby') ?>><?php _e('Order By', 'siteorigin-panels') ?></label>
298
- <select id="<?php echo $this->get_field_id( 'orderby' ) ?>" name="<?php echo $this->get_field_name( 'orderby' ) ?>" value="<?php echo esc_attr($instance['orderby']) ?>">
299
- <option value="none" <?php selected($instance['orderby'], 'none') ?>><?php esc_html_e('None', 'siteorigin-panels') ?></option>
300
- <option value="ID" <?php selected($instance['orderby'], 'ID') ?>><?php esc_html_e('Post ID', 'siteorigin-panels') ?></option>
301
- <option value="author" <?php selected($instance['orderby'], 'author') ?>><?php esc_html_e('Author', 'siteorigin-panels') ?></option>
302
- <option value="name" <?php selected($instance['orderby'], 'name') ?>><?php esc_html_e('Name', 'siteorigin-panels') ?></option>
303
- <option value="name" <?php selected($instance['orderby'], 'name') ?>><?php esc_html_e('Name', 'siteorigin-panels') ?></option>
304
- <option value="date" <?php selected($instance['orderby'], 'date') ?>><?php esc_html_e('Date', 'siteorigin-panels') ?></option>
305
- <option value="modified" <?php selected($instance['orderby'], 'modified') ?>><?php esc_html_e('Modified', 'siteorigin-panels') ?></option>
306
- <option value="parent" <?php selected($instance['orderby'], 'parent') ?>><?php esc_html_e('Parent', 'siteorigin-panels') ?></option>
307
- <option value="rand" <?php selected($instance['orderby'], 'rand') ?>><?php esc_html_e('Random', 'siteorigin-panels') ?></option>
308
- <option value="comment_count" <?php selected($instance['orderby'], 'comment_count') ?>><?php esc_html_e('Comment Count', 'siteorigin-panels') ?></option>
309
- <option value="menu_order" <?php selected($instance['orderby'], 'menu_order') ?>><?php esc_html_e('Menu Order', 'siteorigin-panels') ?></option>
310
- <option value="post__in" <?php selected($instance['orderby'], 'post__in') ?>><?php esc_html_e('Post In Order', 'siteorigin-panels') ?></option>
311
- </select>
312
- </p>
313
-
314
- <p>
315
- <label for="<?php echo $this->get_field_id('order') ?>"><?php _e('Order', 'siteorigin-panels') ?></label>
316
- <select id="<?php echo $this->get_field_id( 'order' ) ?>" name="<?php echo $this->get_field_name( 'order' ) ?>" value="<?php echo esc_attr($instance['order']) ?>">
317
- <option value="DESC" <?php selected($instance['order'], 'DESC') ?>><?php esc_html_e('Descending', 'siteorigin-panels') ?></option>
318
- <option value="ASC" <?php selected($instance['order'], 'ASC') ?>><?php esc_html_e('Ascending', 'siteorigin-panels') ?></option>
319
- </select>
320
- </p>
321
-
322
- <p>
323
- <label for="<?php echo $this->get_field_id('sticky') ?>"><?php _e('Sticky Posts', 'siteorigin-panels') ?></label>
324
- <select id="<?php echo $this->get_field_id( 'sticky' ) ?>" name="<?php echo $this->get_field_name( 'sticky' ) ?>" value="<?php echo esc_attr($instance['sticky']) ?>">
325
- <option value="" <?php selected($instance['sticky'], '') ?>><?php esc_html_e('Default', 'siteorigin-panels') ?></option>
326
- <option value="ignore" <?php selected($instance['sticky'], 'ignore') ?>><?php esc_html_e('Ignore Sticky', 'siteorigin-panels') ?></option>
327
- <option value="exclude" <?php selected($instance['sticky'], 'exclude') ?>><?php esc_html_e('Exclude Sticky', 'siteorigin-panels') ?></option>
328
- <option value="only" <?php selected($instance['sticky'], 'only') ?>><?php esc_html_e('Only Sticky', 'siteorigin-panels') ?></option>
329
- </select>
330
- </p>
331
-
332
- <p>
333
- <label for="<?php echo $this->get_field_id('additional') ?>"><?php _e('Additional ', 'siteorigin-panels') ?></label>
334
- <input type="text" class="widefat" id="<?php echo $this->get_field_id( 'additional' ) ?>" name="<?php echo $this->get_field_name( 'additional' ) ?>" value="<?php echo esc_attr($instance['additional']) ?>" />
335
- <small>
336
- <?php
337
- echo preg_replace(
338
- '/1\{ *(.*?) *\}/',
339
- '<a href="http://codex.wordpress.org/Function_Reference/query_posts">$1</a>',
340
- __('Additional query arguments. See 1{query_posts}.', 'siteorigin-panels')
341
- )
342
- ?>
343
- </small>
344
- </p>
345
- <?php
346
- }
347
- }
348
-
349
- /**
350
- * Get all the existing files
351
- *
352
- * @return array
353
- */
354
- function get_loop_templates(){
355
- $templates = array();
356
-
357
- $template_files = array(
358
- 'loop*.php',
359
- '*/loop*.php',
360
- 'content*.php',
361
- '*/content*.php',
362
- );
363
-
364
- $template_dirs = array( get_template_directory(), get_stylesheet_directory() );
365
- $template_dirs = apply_filters( 'siteorigin_panels_postloop_template_directory', $template_dirs );
366
- $template_dirs = array_unique( $template_dirs );
367
- foreach( $template_dirs as $dir ){
368
- foreach( $template_files as $template_file ) {
369
- foreach( (array) glob($dir.'/'.$template_file) as $file ) {
370
- if( file_exists( $file ) ) $templates[] = str_replace($dir.'/', '', $file);
371
- }
372
- }
373
- }
374
-
375
- $templates = array_unique( apply_filters( 'siteorigin_panels_postloop_templates', $templates ) );
376
- foreach ( $templates as $template_key => $template) {
377
- $invalid = false;
378
-
379
- // Ensure the provided file has a valid name and path
380
- if ( validate_file( $template ) != 0 ) {
381
- $invalid = true;
382
- }
383
-
384
- // Don't expect non-PHP files
385
- if ( substr( $template, -4 ) != '.php' ) {
386
- $invalid = true;
387
- }
388
-
389
- $template = locate_template( $template );
390
- if ( empty( $template ) || $invalid ) {
391
- unset( $templates[ $template_key ] );
392
- }
393
- }
394
- // Update array indexes to ensure logical indexing
395
- sort( $templates );
396
- sort( $templates );
397
-
398
- return $templates;
399
- }
400
-
401
-
402
- /**
403
- * Get the helper widget based on the Widgets Bundle's classes.
404
- *
405
- * @param $templates array Blog loop templates.
406
- *
407
- * @return mixed
408
- */
409
- private function get_helper_widget( $templates ) {
410
- if ( empty( $this->helper ) &&
411
- class_exists( 'SiteOrigin_Widget' ) &&
412
- class_exists( 'SiteOrigin_Widget_Field_Posts' ) ) {
413
- $this->helper = new SiteOrigin_Panels_Widgets_PostLoop_Helper( $templates );
414
- }
415
- // These ensure the form fields name attributes are correct.
416
- $this->helper->id_base = $this->id_base;
417
- $this->helper->id = $this->id;
418
- $this->helper->number = $this->number;
419
-
420
- return $this->helper;
421
- }
422
- }
1
+ <?php
2
+
3
+ /**
4
+ * Display a loop of posts.
5
+ *
6
+ * Class SiteOrigin_Panels_Widgets_PostLoop
7
+ */
8
+ class SiteOrigin_Panels_Widgets_PostLoop extends WP_Widget {
9
+
10
+ static $rendering_loop;
11
+
12
+ static $current_loop_template;
13
+ static $current_loop_instance;
14
+
15
+ /**
16
+ * @var SiteOrigin_Panels_Widgets_PostLoop_Helper
17
+ */
18
+ private $helper;
19
+
20
+ function __construct() {
21
+ parent::__construct(
22
+ 'siteorigin-panels-postloop',
23
+ __( 'Post Loop', 'siteorigin-panels' ),
24
+ array(
25
+ 'description' => __( 'Displays a post loop.', 'siteorigin-panels' ),
26
+ ),
27
+ array(
28
+ 'width' => 800,
29
+ )
30
+ );
31
+ }
32
+
33
+ /**
34
+ * Are we currently rendering a post loop
35
+ *
36
+ * @return bool
37
+ */
38
+ static function is_rendering_loop() {
39
+ return self::$rendering_loop;
40
+ }
41
+
42
+ /**
43
+ * Which post loop is currently being rendered
44
+ *
45
+ * @return array
46
+ */
47
+ static function get_current_loop_template() {
48
+ return self::$current_loop_template;
49
+ }
50
+
51
+ /**
52
+ * Which post loop is currently being rendered
53
+ *
54
+ * @return array
55
+ */
56
+ static function get_current_loop_instance() {
57
+ return self::$current_loop_instance;
58
+ }
59
+
60
+ /**
61
+ * Update the widget
62
+ *
63
+ * @param array $new
64
+ * @param array $old
65
+ * @return array
66
+ */
67
+ function update( $new, $old ){
68
+ if( class_exists( 'SiteOrigin_Widget' ) && class_exists( 'SiteOrigin_Widget_Field_Posts' ) ) {
69
+ $helper = $this->get_helper_widget( $this->get_loop_templates() );
70
+ return $helper->update( $new, $old );
71
+ }
72
+ else {
73
+ $new['more'] = !empty( $new['more'] );
74
+ return $new;
75
+ }
76
+ }
77
+
78
+ /**
79
+ * @param array $args
80
+ * @param array $instance
81
+ */
82
+ function widget( $args, $instance ) {
83
+ if( empty( $instance['template'] ) ) return;
84
+ if( is_admin() ) return;
85
+
86
+ static $depth = 0;
87
+ $depth++;
88
+ if( $depth > 1 ) {
89
+ // Because of infinite loops, don't render this post loop if its inside another
90
+ $depth--;
91
+ echo $args['before_widget'].$args['after_widget'];
92
+ return;
93
+ }
94
+
95
+ $query_args = $instance;
96
+ //If Widgets Bundle post selector is available and a posts query has been saved using it.
97
+ if ( function_exists( 'siteorigin_widget_post_selector_process_query' ) && ! empty( $instance['posts'] ) ) {
98
+ $query_args = siteorigin_widget_post_selector_process_query($instance['posts']);
99
+ $query_args['additional'] = empty($instance['additional']) ? array() : $instance['additional'];
100
+ }
101
+ else {
102
+ if ( ! empty( $instance['posts'] ) ) {
103
+ // This is using the new WB 1.9 posts field
104
+ $query_args = wp_parse_args( $instance['posts'], $query_args );
105
+ }
106
+
107
+ if( ! empty( $query_args['sticky'] ) ) {
108
+ switch( $query_args['sticky'] ){
109
+ case 'ignore' :
110
+ $query_args['ignore_sticky_posts'] = 1;
111
+ break;
112
+ case 'only' :
113
+ $query_args['post__in'] = get_option( 'sticky_posts' );
114
+ break;
115
+ case 'exclude' :
116
+ $query_args['post__not_in'] = get_option( 'sticky_posts' );
117
+ break;
118
+ }
119
+ }
120
+ unset($query_args['template']);
121
+ unset($query_args['title']);
122
+ unset($query_args['sticky']);
123
+ if (empty($query_args['additional'])) {
124
+ $query_args['additional'] = array();
125
+ }
126
+ }
127
+ $query_args = wp_parse_args($query_args['additional'], $query_args);
128
+ unset($query_args['additional']);
129
+
130
+ global $wp_rewrite;
131
+
132
+ if( $wp_rewrite->using_permalinks() ) {
133
+
134
+ if( get_query_var('paged') ) {
135
+ // When the widget appears on a sub page.
136
+ $query_args['paged'] = get_query_var('paged');
137
+ }
138
+ elseif( strpos( $_SERVER['REQUEST_URI'], '/page/' ) !== false ) {
139
+ // When the widget appears on the home page.
140
+ preg_match('/\/page\/([0-9]+)\//', $_SERVER['REQUEST_URI'], $matches);
141
+ if(!empty($matches[1])) $query_args['paged'] = intval($matches[1]);
142
+ else $query_args['paged'] = 1;
143
+ }
144
+ else $query_args['paged'] = 1;
145
+ }
146
+ else {
147
+ // Get current page number when we're not using permalinks
148
+ $query_args['paged'] = isset($_GET['paged']) ? intval($_GET['paged']) : 1;
149
+ }
150
+
151
+ // Exclude the current post to prevent possible infinite loop
152
+
153
+ global $siteorigin_panels_current_post;
154
+
155
+ if( !empty($siteorigin_panels_current_post) ){
156
+ if( !empty( $query_args['post__not_in'] ) ){
157
+ if( !is_array( $query_args['post__not_in'] ) ){
158
+ $query_args['post__not_in'] = explode( ',', $query_args['post__not_in'] );
159
+ $query_args['post__not_in'] = array_map( 'intval', $query_args['post__not_in'] );
160
+ }
161
+ $query_args['post__not_in'][] = $siteorigin_panels_current_post;
162
+ }
163
+ else {
164
+ $query_args['post__not_in'] = array( $siteorigin_panels_current_post );
165
+ }
166
+ }
167
+
168
+ if( !empty($query_args['post__in']) && !is_array($query_args['post__in']) ) {
169
+ $query_args['post__in'] = explode(',', $query_args['post__in']);
170
+ $query_args['post__in'] = array_map('intval', $query_args['post__in']);
171
+ }
172
+
173
+ // Create the query
174
+ query_posts( apply_filters( 'siteorigin_panels_postloop_query_args', $query_args ) );
175
+ echo $args['before_widget'];
176
+
177
+ // Filter the title
178
+ $instance['title'] = apply_filters('widget_title', $instance['title'], $instance, $this->id_base);
179
+ if ( !empty( $instance['title'] ) ) {
180
+ echo $args['before_title'] . $instance['title'] . $args['after_title'];
181
+ }
182
+
183
+ global $more; $old_more = $more; $more = empty($instance['more']);
184
+ self::$rendering_loop = true;
185
+ self::$current_loop_instance = $instance;
186
+ self::$current_loop_template = $instance['template'];
187
+ if(strpos('/'.$instance['template'], '/content') !== false) {
188
+ while( have_posts() ) {
189
+ the_post();
190
+ locate_template($instance['template'], true, false);
191
+ }
192
+ }
193
+ else {
194
+ locate_template($instance['template'], true, false);
195
+ }
196
+ self::$rendering_loop = false;
197
+ self::$current_loop_instance = null;
198
+ self::$current_loop_template = null;
199
+
200
+ echo $args['after_widget'];
201
+
202
+ // Reset everything
203
+ wp_reset_query();
204
+ $depth--;
205
+ }
206
+
207
+ /**
208
+ * Display the form for the post loop.
209
+ *
210
+ * @param array $instance
211
+ * @return string|void
212
+ */
213
+ function form( $instance ) {
214
+ $templates = $this->get_loop_templates();
215
+ if( empty($templates) ) {
216
+ ?><p><?php _e("Your theme doesn't have any post loops.", 'siteorigin-panels') ?></p><?php
217
+ return;
218
+ }
219
+
220
+ // If the Widgets Bundle is installed and the post selector is available, use that.
221
+ // Otherwise revert back to our own form fields.
222
+ if( class_exists( 'SiteOrigin_Widget' ) && class_exists( 'SiteOrigin_Widget_Field_Posts' ) ) {
223
+ $helper = $this->get_helper_widget( $templates );
224
+ $helper->form( $instance );
225
+ }
226
+ else {
227
+ $instance = wp_parse_args( $instance, array(
228
+ 'title' => '',
229
+ 'template' => 'loop.php',
230
+
231
+ // Query args
232
+ 'post_type' => 'post',
233
+ 'posts_per_page' => '',
234
+
235
+ 'order' => 'DESC',
236
+ 'orderby' => 'date',
237
+
238
+ 'sticky' => '',
239
+
240
+ 'additional' => '',
241
+ 'more' => false,
242
+ ) );
243
+
244
+ ?>
245
+ <p>
246
+ <label for="<?php echo $this->get_field_id( 'title' ) ?>"><?php _e( 'Title', 'siteorigin-panels' ) ?></label>
247
+ <input type="text" class="widefat" name="<?php echo $this->get_field_name( 'title' ) ?>" id="<?php echo $this->get_field_id( 'title' ) ?>" value="<?php echo esc_attr( $instance['title'] ) ?>">
248
+ </p>
249
+ <p>
250
+ <label for="<?php echo $this->get_field_id('template') ?>"><?php _e('Template', 'siteorigin-panels') ?></label>
251
+ <select id="<?php echo $this->get_field_id( 'template' ) ?>" name="<?php echo $this->get_field_name( 'template' ) ?>">
252
+ <?php foreach($templates as $template) : ?>
253
+ <option value="<?php echo esc_attr($template) ?>" <?php selected($instance['template'], $template) ?>>
254
+ <?php
255
+ $headers = get_file_data( locate_template($template), array(
256
+ 'loop_name' => 'Loop Name',
257
+ ) );
258
+ echo esc_html(!empty($headers['loop_name']) ? $headers['loop_name'] : $template);
259
+ ?>
260
+ </option>
261
+ <?php endforeach; ?>
262
+ </select>
263
+ </p>
264
+
265
+ <p>
266
+ <label for="<?php echo $this->get_field_id('more') ?>"><?php _e('More Link', 'siteorigin-panels') ?></label>
267
+ <input type="checkbox" class="widefat" id="<?php echo $this->get_field_id( 'more' ) ?>" name="<?php echo $this->get_field_name( 'more' ) ?>" <?php checked( $instance['more'] ) ?> /><br/>
268
+ <small><?php _e('If the template supports it, cut posts and display the more link.', 'siteorigin-panels') ?></small>
269
+ </p>
270
+ <?php
271
+
272
+ if ( ! empty( $instance['posts'] ) ) {
273
+ $instance = wp_parse_args( $instance['posts'] , $instance );
274
+ unset( $instance['posts'] );
275
+ //unset post__in and taxonomies?
276
+ }
277
+ // Get all the loop template files
278
+ $post_types = get_post_types(array('public' => true));
279
+ $post_types = array_values($post_types);
280
+ $post_types = array_diff($post_types, array('attachment', 'revision', 'nav_menu_item'));
281
+ ?>
282
+ <p>
283
+ <label for="<?php echo $this->get_field_id('post_type') ?>"><?php _e('Post Type', 'siteorigin-panels') ?></label>
284
+ <select id="<?php echo $this->get_field_id( 'post_type' ) ?>" name="<?php echo $this->get_field_name( 'post_type' ) ?>" value="<?php echo esc_attr($instance['post_type']) ?>">
285
+ <?php foreach($post_types as $type) : ?>
286
+ <option value="<?php echo esc_attr($type) ?>" <?php selected($instance['post_type'], $type) ?>><?php echo esc_html($type) ?></option>
287
+ <?php endforeach; ?>
288
+ </select>
289
+ </p>
290
+
291
+ <p>
292
+ <label for="<?php echo $this->get_field_id('posts_per_page') ?>"><?php _e('Posts Per Page', 'siteorigin-panels') ?></label>
293
+ <input type="text" class="small-text" id="<?php echo $this->get_field_id( 'posts_per_page' ) ?>" name="<?php echo $this->get_field_name( 'posts_per_page' ) ?>" value="<?php echo esc_attr($instance['posts_per_page']) ?>" />
294
+ </p>
295
+
296
+ <p>
297
+ <label <?php echo $this->get_field_id('orderby') ?>><?php _e('Order By', 'siteorigin-panels') ?></label>
298
+ <select id="<?php echo $this->get_field_id( 'orderby' ) ?>" name="<?php echo $this->get_field_name( 'orderby' ) ?>" value="<?php echo esc_attr($instance['orderby']) ?>">
299
+ <option value="none" <?php selected($instance['orderby'], 'none') ?>><?php esc_html_e('None', 'siteorigin-panels') ?></option>
300
+ <option value="ID" <?php selected($instance['orderby'], 'ID') ?>><?php esc_html_e('Post ID', 'siteorigin-panels') ?></option>
301
+ <option value="author" <?php selected($instance['orderby'], 'author') ?>><?php esc_html_e('Author', 'siteorigin-panels') ?></option>
302
+ <option value="name" <?php selected($instance['orderby'], 'name') ?>><?php esc_html_e('Name', 'siteorigin-panels') ?></option>
303
+ <option value="name" <?php selected($instance['orderby'], 'name') ?>><?php esc_html_e('Name', 'siteorigin-panels') ?></option>
304
+ <option value="date" <?php selected($instance['orderby'], 'date') ?>><?php esc_html_e('Date', 'siteorigin-panels') ?></option>
305
+ <option value="modified" <?php selected($instance['orderby'], 'modified') ?>><?php esc_html_e('Modified', 'siteorigin-panels') ?></option>
306
+ <option value="parent" <?php selected($instance['orderby'], 'parent') ?>><?php esc_html_e('Parent', 'siteorigin-panels') ?></option>
307
+ <option value="rand" <?php selected($instance['orderby'], 'rand') ?>><?php esc_html_e('Random', 'siteorigin-panels') ?></option>
308
+ <option value="comment_count" <?php selected($instance['orderby'], 'comment_count') ?>><?php esc_html_e('Comment Count', 'siteorigin-panels') ?></option>
309
+ <option value="menu_order" <?php selected($instance['orderby'], 'menu_order') ?>><?php esc_html_e('Menu Order', 'siteorigin-panels') ?></option>
310
+ <option value="post__in" <?php selected($instance['orderby'], 'post__in') ?>><?php esc_html_e('Post In Order', 'siteorigin-panels') ?></option>
311
+ </select>
312
+ </p>
313
+
314
+ <p>
315
+ <label for="<?php echo $this->get_field_id('order') ?>"><?php _e('Order', 'siteorigin-panels') ?></label>
316
+ <select id="<?php echo $this->get_field_id( 'order' ) ?>" name="<?php echo $this->get_field_name( 'order' ) ?>" value="<?php echo esc_attr($instance['order']) ?>">
317
+ <option value="DESC" <?php selected($instance['order'], 'DESC') ?>><?php esc_html_e('Descending', 'siteorigin-panels') ?></option>
318
+ <option value="ASC" <?php selected($instance['order'], 'ASC') ?>><?php esc_html_e('Ascending', 'siteorigin-panels') ?></option>
319
+ </select>
320
+ </p>
321
+
322
+ <p>
323
+ <label for="<?php echo $this->get_field_id('sticky') ?>"><?php _e('Sticky Posts', 'siteorigin-panels') ?></label>
324
+ <select id="<?php echo $this->get_field_id( 'sticky' ) ?>" name="<?php echo $this->get_field_name( 'sticky' ) ?>" value="<?php echo esc_attr($instance['sticky']) ?>">
325
+ <option value="" <?php selected($instance['sticky'], '') ?>><?php esc_html_e('Default', 'siteorigin-panels') ?></option>
326
+ <option value="ignore" <?php selected($instance['sticky'], 'ignore') ?>><?php esc_html_e('Ignore Sticky', 'siteorigin-panels') ?></option>
327
+ <option value="exclude" <?php selected($instance['sticky'], 'exclude') ?>><?php esc_html_e('Exclude Sticky', 'siteorigin-panels') ?></option>
328
+ <option value="only" <?php selected($instance['sticky'], 'only') ?>><?php esc_html_e('Only Sticky', 'siteorigin-panels') ?></option>
329
+ </select>
330
+ </p>
331
+
332
+ <p>
333
+ <label for="<?php echo $this->get_field_id('additional') ?>"><?php _e('Additional ', 'siteorigin-panels') ?></label>
334
+ <input type="text" class="widefat" id="<?php echo $this->get_field_id( 'additional' ) ?>" name="<?php echo $this->get_field_name( 'additional' ) ?>" value="<?php echo esc_attr($instance['additional']) ?>" />
335
+ <small>
336
+ <?php
337
+ echo preg_replace(
338
+ '/1\{ *(.*?) *\}/',
339
+ '<a href="http://codex.wordpress.org/Function_Reference/query_posts">$1</a>',
340
+ __('Additional query arguments. See 1{query_posts}.', 'siteorigin-panels')
341
+ )
342
+ ?>
343
+ </small>
344
+ </p>
345
+ <?php
346
+ }
347
+ }
348
+
349
+ /**
350
+ * Get all the existing files
351
+ *
352
+ * @return array
353
+ */
354
+ function get_loop_templates(){
355
+ $templates = array();
356
+
357
+ $template_files = array(
358
+ 'loop*.php',
359
+ '*/loop*.php',
360
+ 'content*.php',
361
+ '*/content*.php',
362
+ );
363
+
364
+ $template_dirs = array( get_template_directory(), get_stylesheet_directory() );
365
+ $template_dirs = apply_filters( 'siteorigin_panels_postloop_template_directory', $template_dirs );
366
+ $template_dirs = array_unique( $template_dirs );
367
+ foreach( $template_dirs as $dir ){
368
+ foreach( $template_files as $template_file ) {
369
+ foreach( (array) glob($dir.'/'.$template_file) as $file ) {
370
+ if( file_exists( $file ) ) $templates[] = str_replace($dir.'/', '', $file);
371
+ }
372
+ }
373
+ }
374
+
375
+ $templates = array_unique( apply_filters( 'siteorigin_panels_postloop_templates', $templates ) );
376
+ foreach ( $templates as $template_key => $template) {
377
+ $invalid = false;
378
+
379
+ // Ensure the provided file has a valid name and path
380
+ if ( validate_file( $template ) != 0 ) {
381
+ $invalid = true;
382
+ }
383
+
384
+ // Don't expect non-PHP files
385
+ if ( substr( $template, -4 ) != '.php' ) {
386
+ $invalid = true;
387
+ }
388
+
389
+ $template = locate_template( $template );
390
+ if ( empty( $template ) || $invalid ) {
391
+ unset( $templates[ $template_key ] );
392
+ }
393
+ }
394
+ // Update array indexes to ensure logical indexing
395
+ sort( $templates );
396
+ sort( $templates );
397
+
398
+ return $templates;
399
+ }
400
+
401
+
402
+ /**
403
+ * Get the helper widget based on the Widgets Bundle's classes.
404
+ *
405
+ * @param $templates array Blog loop templates.
406
+ *
407
+ * @return mixed
408
+ */
409
+ private function get_helper_widget( $templates ) {
410
+ if ( empty( $this->helper ) &&
411
+ class_exists( 'SiteOrigin_Widget' ) &&
412
+ class_exists( 'SiteOrigin_Widget_Field_Posts' ) ) {
413
+ $this->helper = new SiteOrigin_Panels_Widgets_PostLoop_Helper( $templates );
414
+ }
415
+ // These ensure the form fields name attributes are correct.
416
+ $this->helper->id_base = $this->id_base;
417
+ $this->helper->id = $this->id;
418
+ $this->helper->number = $this->number;
419
+
420
+ return $this->helper;
421
+ }
422
+ }
js/siteorigin-panels-2106.min.js DELETED
@@ -1 +0,0 @@
1
- !function o(n,a,r){function d(t,e){if(!a[t]){if(!n[t]){var i="function"==typeof require&&require;if(!e&&i)return i(t,!0);if(c)return c(t,!0);var s=new Error("Cannot find module '"+t+"'");throw s.code="MODULE_NOT_FOUND",s}var l=a[t]={exports:{}};n[t][0].call(l.exports,function(e){return d(n[t][1][e]||e)},l,l.exports,o,n,a,r)}return a[t].exports}for(var c="function"==typeof require&&require,e=0;e<r.length;e++)d(r[e]);return d}({1:[function(e,t,i){var s=window.panels;t.exports=Backbone.Collection.extend({model:s.model.cell,initialize:function(){},totalWeight:function(){var t=0;return this.each(function(e){t+=e.get("weight")}),t}})},{}],2:[function(e,t,i){var s=window.panels;t.exports=Backbone.Collection.extend({model:s.model.historyEntry,builder:null,maxSize:12,initialize:function(){this.on("add",this.onAddEntry,this)},addEntry:function(e,t){_.isEmpty(t)&&(t=this.builder.getPanelsData());var i=new s.model.historyEntry({text:e,data:JSON.stringify(t),time:parseInt((new Date).getTime()/1e3),collection:this});this.add(i)},onAddEntry:function(e){if(1<this.models.length){var t=this.at(this.models.length-2);(e.get("text")===t.get("text")&&e.get("time")-t.get("time")<15||e.get("data")===t.get("data"))&&(this.remove(e),t.set("count",t.get("count")+1))}for(;this.models.length>this.maxSize;)this.shift()}})},{}],3:[function(e,t,i){var s=window.panels;t.exports=Backbone.Collection.extend({model:s.model.row,empty:function(){for(var e;;){if(!(e=this.collection.first()))break;e.destroy()}}})},{}],4:[function(e,t,i){var s=window.panels;t.exports=Backbone.Collection.extend({model:s.model.widget,initialize:function(){}})},{}],5:[function(e,t,i){var s=window.panels,l=jQuery;t.exports=s.view.dialog.extend({dialogClass:"so-panels-dialog-add-builder",render:function(){this.renderDialog(this.parseDialogContent(l("#siteorigin-panels-dialog-builder").html(),{})),this.$(".so-content .siteorigin-panels-builder").append(this.builder.$el)},initializeDialog:function(){var e=this;this.once("open_dialog_complete",function(){e.builder.initSortable()}),this.on("open_dialog_complete",function(){e.builder.trigger("builder_resize")})}})},{}],6:[function(e,t,i){var s=window.panels,l=jQuery;t.exports=s.view.dialog.extend({historyEntryTemplate:_.template(s.helpers.utils.processTemplate(l("#siteorigin-panels-dialog-history-entry").html())),entries:{},currentEntry:null,revertEntry:null,selectedEntry:null,previewScrollTop:null,dialogClass:"so-panels-dialog-history",dialogIcon:"history",events:{"click .so-close":"closeDialog","click .so-restore":"restoreSelectedEntry"},initializeDialog:function(){this.entries=new s.collection.historyEntries,this.on("open_dialog",this.setCurrentEntry,this),this.on("open_dialog",this.renderHistoryEntries,this)},render:function(){var t=this;this.renderDialog(this.parseDialogContent(l("#siteorigin-panels-dialog-history").html(),{})),this.$("iframe.siteorigin-panels-history-iframe").load(function(){var e=l(this);e.show(),e.contents().scrollTop(t.previewScrollTop)})},setRevertEntry:function(e){this.revertEntry=new s.model.historyEntry({data:JSON.stringify(e.getPanelsData()),time:parseInt((new Date).getTime()/1e3)})},setCurrentEntry:function(){this.currentEntry=new s.model.historyEntry({data:JSON.stringify(this.builder.model.getPanelsData()),time:parseInt((new Date).getTime()/1e3)}),this.selectedEntry=this.currentEntry,this.previewEntry(this.currentEntry),this.$(".so-buttons .so-restore").addClass("disabled")},renderHistoryEntries:function(){var i=this,s=this.$(".history-entries").empty();this.currentEntry.get("data")===this.revertEntry.get("data")&&_.isEmpty(this.entries.models)||l(this.historyEntryTemplate({title:panelsOptions.loc.history.revert,count:1})).data("historyEntry",this.revertEntry).prependTo(s),this.entries.each(function(e){var t=i.historyEntryTemplate({title:panelsOptions.loc.history[e.get("text")],count:e.get("count")});l(t).data("historyEntry",e).prependTo(s)}),l(this.historyEntryTemplate({title:panelsOptions.loc.history.current,count:1})).data("historyEntry",this.currentEntry).addClass("so-selected").prependTo(s),s.find(".history-entry").click(function(){var e=jQuery(this);s.find(".history-entry").not(e).removeClass("so-selected"),e.addClass("so-selected");var t=e.data("historyEntry");i.selectedEntry=t,i.selectedEntry.cid!==i.currentEntry.cid?i.$(".so-buttons .so-restore").removeClass("disabled"):i.$(".so-buttons .so-restore").addClass("disabled"),i.previewEntry(t)}),this.updateEntryTimes()},previewEntry:function(e){var t=this.$("iframe.siteorigin-panels-history-iframe");t.hide(),this.previewScrollTop=t.contents().scrollTop(),this.$('form.history-form input[name="live_editor_panels_data"]').val(e.get("data")),this.$('form.history-form input[name="live_editor_post_ID"]').val(this.builder.config.postId),this.$("form.history-form").submit()},restoreSelectedEntry:function(){return this.$(".so-buttons .so-restore").hasClass("disabled")||(this.currentEntry.get("data")===this.selectedEntry.get("data")||("restore"!==this.selectedEntry.get("text")&&this.builder.addHistoryEntry("restore",this.builder.model.getPanelsData()),this.builder.model.loadPanelsData(JSON.parse(this.selectedEntry.get("data")))),this.closeDialog()),!1},updateEntryTimes:function(){var s=this;this.$(".history-entries .history-entry").each(function(){var e=jQuery(this),t=e.find(".timesince"),i=e.data("historyEntry");t.html(s.timeSince(i.get("time")))})},timeSince:function(e){var t,i=parseInt((new Date).getTime()/1e3)-e,s=[];return 3600<i&&(1===(t=Math.floor(i/3600))?s.push(panelsOptions.loc.time.hour.replace("%d",t)):s.push(panelsOptions.loc.time.hours.replace("%d",t)),i-=3600*t),60<i&&(1===(t=Math.floor(i/60))?s.push(panelsOptions.loc.time.minute.replace("%d",t)):s.push(panelsOptions.loc.time.minutes.replace("%d",t)),i-=60*t),0<i&&(1===i?s.push(panelsOptions.loc.time.second.replace("%d",i)):s.push(panelsOptions.loc.time.seconds.replace("%d",i))),_.isEmpty(s)?panelsOptions.loc.time.now:panelsOptions.loc.time.ago.replace("%s",s.slice(0,2).join(", "))}})},{}],7:[function(e,t,i){var s=window.panels,r=jQuery;t.exports=s.view.dialog.extend({directoryTemplate:_.template(s.helpers.utils.processTemplate(r("#siteorigin-panels-directory-items").html())),builder:null,dialogClass:"so-panels-dialog-prebuilt-layouts",dialogIcon:"layouts",layoutCache:{},currentTab:!1,directoryPage:1,events:{"click .so-close":"closeDialog","click .so-sidebar-tabs li a":"tabClickHandler","click .so-content .layout":"layoutClickHandler","keyup .so-sidebar-search":"searchHandler","click .so-screenshot, .so-title":"directoryItemClickHandler"},initializeDialog:function(){var e=this;this.on("open_dialog",function(){e.$(".so-sidebar-tabs li a").first().click(),e.$(".so-status").removeClass("so-panels-loading")}),this.on("button_click",this.toolbarButtonClick,this)},render:function(){this.renderDialog(this.parseDialogContent(r("#siteorigin-panels-dialog-prebuilt").html(),{})),this.initToolbar()},tabClickHandler:function(e){e.preventDefault(),this.selectedLayoutItem=null,this.uploadedLayout=null,this.updateButtonState(!1),this.$(".so-sidebar-tabs li").removeClass("tab-active");var t=r(e.target),i=t.attr("href").split("#")[1];t.parent().addClass("tab-active");this.$(".so-content").empty(),"import"==(this.currentTab=i)?this.displayImportExport():this.displayLayoutDirectory("",1,i),this.$(".so-sidebar-search").val("")},displayImportExport:function(){var e=this.$(".so-content").empty().removeClass("so-panels-loading");e.html(r("#siteorigin-panels-dialog-prebuilt-importexport").html());var l=this,o=l.$(".import-upload-ui"),t=new plupload.Uploader({runtimes:"html5,silverlight,flash,html4",browse_button:o.find(".file-browse-button").get(0),container:o.get(0),drop_element:o.find(".drag-upload-area").get(0),file_data_name:"panels_import_data",multiple_queues:!1,max_file_size:panelsOptions.plupload.max_file_size,url:panelsOptions.plupload.url,flash_swf_url:panelsOptions.plupload.flash_swf_url,silverlight_xap_url:panelsOptions.plupload.silverlight_xap_url,filters:[{title:panelsOptions.plupload.filter_title,extensions:"json"}],multipart_params:{action:"so_panels_import_layout"},init:{PostInit:function(e){e.features.dragdrop&&o.addClass("has-drag-drop"),o.find(".progress-precent").css("width","0%")},FilesAdded:function(e){o.find(".file-browse-button").blur(),o.find(".drag-upload-area").removeClass("file-dragover"),o.find(".progress-bar").fadeIn("fast"),l.$(".js-so-selected-file").text(panelsOptions.loc.prebuilt_loading),e.start()},UploadProgress:function(e,t){o.find(".progress-precent").css("width",t.percent+"%")},FileUploaded:function(e,t,i){var s=JSON.parse(i.response);_.isUndefined(s.widgets)?alert(panelsOptions.plupload.error_message):(l.uploadedLayout=s,o.find(".progress-bar").hide(),l.$(".js-so-selected-file").text(panelsOptions.loc.ready_to_insert.replace("%s",t.name)),l.updateButtonState(!0))},Error:function(){alert(panelsOptions.plupload.error_message)}}});t.init(),/Edge\/\d./i.test(navigator.userAgent)&&setTimeout(function(){t.refresh()},250),o.find(".drag-upload-area").on("dragover",function(){r(this).addClass("file-dragover")}).on("dragleave",function(){r(this).removeClass("file-dragover")}),e.find(".so-export").submit(function(e){var t=r(this),i=l.builder.model.getPanelsData(),s=r('input[name="post_title"]').val();s||(s=r('input[name="post_ID"]').val()),i.name=s,t.find('input[name="panels_export_data"]').val(JSON.stringify(i))})},displayLayoutDirectory:function(s,l,o){var n=this,a=this.$(".so-content").empty().addClass("so-panels-loading");if(void 0===s&&(s=""),void 0===l&&(l=1),void 0===o&&(o="directory-siteorigin"),o.match("^directory-")&&!panelsOptions.directory_enabled)return a.removeClass("so-panels-loading").html(r("#siteorigin-panels-directory-enable").html()),void a.find(".so-panels-enable-directory").click(function(e){e.preventDefault(),r.get(panelsOptions.ajaxurl,{action:"so_panels_directory_enable"},function(){}),panelsOptions.directory_enabled=!0,a.addClass("so-panels-loading"),n.displayLayoutDirectory(s,l,o)});r.get(panelsOptions.ajaxurl,{action:"so_panels_layouts_query",search:s,page:l,type:o},function(e){if(n.currentTab===o){a.removeClass("so-panels-loading").html(n.directoryTemplate(e));var t=a.find(".so-previous"),i=a.find(".so-next");l<=1?t.addClass("button-disabled"):t.click(function(e){e.preventDefault(),n.displayLayoutDirectory(s,l-1,n.currentTab)}),l===e.max_num_pages||0===e.max_num_pages?i.addClass("button-disabled"):i.click(function(e){e.preventDefault(),n.displayLayoutDirectory(s,l+1,n.currentTab)}),a.find(".so-screenshot").each(function(){var e=r(this),t=e.find(".so-screenshot-wrapper");if(t.css("height",t.width()/4*3+"px").addClass("so-loading"),""!==e.data("src"))var i=r("<img/>").attr("src",e.data("src")).load(function(){t.removeClass("so-loading").css("height","auto"),i.appendTo(t).hide().fadeIn("fast")});else r("<img/>").attr("src",panelsOptions.prebuiltDefaultScreenshot).appendTo(t).hide().fadeIn("fast")}),a.find(".so-directory-browse").html(e.title)}},"json")},directoryItemClickHandler:function(e){var t=this.$(e.target).closest(".so-directory-item");this.$(".so-directory-items").find(".selected").removeClass("selected"),t.addClass("selected"),this.selectedLayoutItem={lid:t.data("layout-id"),type:t.data("layout-type")},this.updateButtonState(!0)},toolbarButtonClick:function(e){if(!this.canAddLayout())return!1;var t=e.data("value");if(_.isUndefined(t))return!1;if(this.updateButtonState(!1),e.hasClass("so-needs-confirm")&&!e.hasClass("so-confirmed")){if(this.updateButtonState(!0),e.hasClass("so-confirming"))return;e.addClass("so-confirming");var i=e.html();return e.html('<span class="dashicons dashicons-yes"></span>'+e.data("confirm")),setTimeout(function(){e.removeClass("so-confirmed").html(i)},2500),setTimeout(function(){e.removeClass("so-confirming"),e.addClass("so-confirmed")},200),!1}this.addingLayout=!0,"import"===this.currentTab?this.addLayoutToBuilder(this.uploadedLayout,t):this.loadSelectedLayout().then(function(e){this.addLayoutToBuilder(e,t)}.bind(this))},canAddLayout:function(){return(this.selectedLayoutItem||this.uploadedLayout)&&!this.addingLayout},loadSelectedLayout:function(){this.setStatusMessage(panelsOptions.loc.prebuilt_loading,!0);var e=_.extend(this.selectedLayoutItem,{action:"so_panels_get_layout"}),i=new r.Deferred;return r.get(panelsOptions.ajaxurl,e,function(e){var t="";e.success?i.resolve(e.data):(t=e.data.message,i.reject(e.data)),this.setStatusMessage(t,!1,!e.success),this.updateButtonState(!0)}.bind(this)),i.promise()},searchHandler:function(e){13===e.keyCode&&this.displayLayoutDirectory(r(e.currentTarget).val(),1,this.currentTab)},updateButtonState:function(e){e=e&&(this.selectedLayoutItem||this.uploadedLayout);var t=this.$(".so-import-layout");t.prop("disabled",!e),e?t.removeClass("disabled"):t.addClass("disabled")},addLayoutToBuilder:function(e,t){this.builder.addHistoryEntry("prebuilt_loaded"),this.builder.model.loadPanelsData(e,t),this.addingLayout=!1,this.closeDialog()}})},{}],8:[function(e,t,i){var a=window.panels,c=jQuery;t.exports=a.view.dialog.extend({cellPreviewTemplate:_.template(a.helpers.utils.processTemplate(c("#siteorigin-panels-dialog-row-cell-preview").html())),editableLabel:!0,events:{"click .so-close":"closeDialog","click .so-toolbar .so-save":"saveHandler","click .so-toolbar .so-insert":"insertHandler","click .so-toolbar .so-delete":"deleteHandler","click .so-toolbar .so-duplicate":"duplicateHandler","change .row-set-form > *":"setCellsFromForm","click .row-set-form button.set-row":"setCellsFromForm"},rowView:null,dialogIcon:"add-row",dialogClass:"so-panels-dialog-row-edit",styleType:"row",dialogType:"edit",row:{cells:null,style:{}},cellStylesCache:[],initializeDialog:function(){this.on("open_dialog",function(){_.isUndefined(this.model)||_.isEmpty(this.model.get("cells"))?this.setRowModel(null):this.setRowModel(this.model),this.regenerateRowPreview(),this.renderStyles(),this.openSelectedCellStyles()},this),this.row={cells:new a.collection.cells([{weight:.5},{weight:.5}]),style:{}},this.dialogFormsLoaded=0;var e=this;this.on("form_loaded styles_loaded",function(){this.dialogFormsLoaded++,2===this.dialogFormsLoaded&&e.updateModel({refreshArgs:{silent:!0}})}),this.on("close_dialog",this.closeHandler),this.on("edit_label",function(e){if(e!==panelsOptions.loc.row.add&&e!==panelsOptions.loc.row.edit||(e=""),this.model.set("label",e),_.isEmpty(e)){var t="create"===this.dialogType?panelsOptions.loc.row.add:panelsOptions.loc.row.edit;this.$(".so-title").text(t)}}.bind(this))},setRowDialogType:function(e){this.dialogType=e},render:function(){var e="create"===this.dialogType?panelsOptions.loc.row.add:panelsOptions.loc.row.edit;this.renderDialog(this.parseDialogContent(c("#siteorigin-panels-dialog-row").html(),{title:e,dialogType:this.dialogType}));var t=this.$(".so-title");return this.model.has("label")&&!_.isEmpty(this.model.get("label"))&&t.text(this.model.get("label")),this.$(".so-edit-title").val(t.text()),this.builder.supports("addRow")||this.$(".so-buttons .so-duplicate").remove(),this.builder.supports("deleteRow")||this.$(".so-buttons .so-delete").remove(),_.isUndefined(this.model)||(this.$('input[name="cells"].so-row-field').val(this.model.get("cells").length),this.model.has("ratio")&&this.$('select[name="ratio"].so-row-field').val(this.model.get("ratio")),this.model.has("ratio_direction")&&this.$('select[name="ratio_direction"].so-row-field').val(this.model.get("ratio_direction"))),this.$("input.so-row-field").keyup(function(){c(this).trigger("change")}),this},renderStyles:function(){this.styles&&(this.styles.off("styles_loaded"),this.styles.remove()),this.styles=new a.view.styles,this.styles.model=this.model,this.styles.render("row",this.builder.config.postId,{builderType:this.builder.config.builderType,dialog:this});var t=this.$(".so-sidebar.so-right-sidebar");this.styles.attach(t),this.styles.on("styles_loaded",function(e){e||(this.styles.remove(),0===t.children().length&&(t.closest(".so-panels-dialog").removeClass("so-panels-dialog-has-right-sidebar"),t.hide()))},this)},setRowModel:function(e){return this.model=e,_.isEmpty(this.model)||(this.row={cells:this.model.get("cells").clone(),style:{},ratio:this.model.get("ratio"),ratio_direction:this.model.get("ratio_direction")},this.$('input[name="cells"].so-row-field').val(this.model.get("cells").length),this.model.has("ratio")&&this.$('select[name="ratio"].so-row-field').val(this.model.get("ratio")),this.model.has("ratio_direction")&&this.$('select[name="ratio_direction"].so-row-field').val(this.model.get("ratio_direction")),this.clearCellStylesCache()),this},regenerateRowPreview:function(){var t,r=this,d=this.$(".row-preview"),s=this.getSelectedCellIndex();d.empty(),this.row.cells.each(function(i,n){var o=c(this.cellPreviewTemplate({weight:i.get("weight")}));d.append(o),n==s&&o.find(".preview-cell-in").addClass("cell-selected");var e,a=o.prev();a.length&&((e=c('<div class="resize-handle"></div>')).appendTo(o).dblclick(function(){var e=r.row.cells.at(n-1),t=i.get("weight")+e.get("weight");i.set("weight",t/2),e.set("weight",t/2),r.scaleRowWidths()}),e.draggable({axis:"x",containment:d,start:function(e,t){var i=o.clone().appendTo(t.helper).css({position:"absolute",top:"0",width:o.outerWidth(),left:6,height:o.outerHeight()});i.find(".resize-handle").remove();var s=a.clone().appendTo(t.helper).css({position:"absolute",top:"0",width:a.outerWidth(),right:6,height:a.outerHeight()});s.find(".resize-handle").remove(),c(this).data({newCellClone:i,prevCellClone:s}),o.find("> .preview-cell-in").css("visibility","hidden"),a.find("> .preview-cell-in").css("visibility","hidden")},drag:function(e,t){var i=r.row.cells.at(n).get("weight"),s=r.row.cells.at(n-1).get("weight"),l=i-(t.position.left+6)/d.width(),o=s+(t.position.left+6)/d.width();t.helper.offset().left,d.offset().left;c(this).data("newCellClone").css("width",d.width()*l).find(".preview-cell-weight").html(Math.round(1e3*l)/10),c(this).data("prevCellClone").css("width",d.width()*o).find(".preview-cell-weight").html(Math.round(1e3*o)/10)},stop:function(e,t){c(this).data("newCellClone").remove(),c(this).data("prevCellClone").remove(),o.find(".preview-cell-in").css("visibility","visible"),a.find(".preview-cell-in").css("visibility","visible");var i=(t.position.left+6)/d.width(),s=r.row.cells.at(n),l=r.row.cells.at(n-1);.02<s.get("weight")-i&&.02<l.get("weight")+i&&(s.set("weight",s.get("weight")-i),l.set("weight",l.get("weight")+i)),r.scaleRowWidths(),t.helper.css("left",-6)}})),o.click(function(e){if(c(e.target).is(".preview-cell")||c(e.target).is(".preview-cell-in")){var t=c(e.target);t.closest(".row-preview").find(".preview-cell .preview-cell-in").removeClass("cell-selected"),t.addClass("cell-selected"),this.openSelectedCellStyles()}}.bind(this)),o.find(".preview-cell-weight").click(function(e){r.$(".resize-handle").css("pointer-event","none").draggable("disable"),d.find(".preview-cell-weight").each(function(){var e=jQuery(this).hide();c('<input type="text" class="preview-cell-weight-input no-user-interacted" />').val(parseFloat(e.html())).insertAfter(e).focus(function(){clearTimeout(t)}).keyup(function(e){9!==e.keyCode&&c(this).removeClass("no-user-interacted"),13===e.keyCode&&(e.preventDefault(),c(this).blur())}).keydown(function(e){if(9===e.keyCode){e.preventDefault();var t=d.find(".preview-cell-weight-input"),i=t.index(c(this));i===t.length-1?t.eq(0).focus().select():t.eq(i+1).focus().select()}}).blur(function(){d.find(".preview-cell-weight-input").each(function(e,t){isNaN(parseFloat(c(t).val()))&&c(t).val(Math.floor(1e3*r.row.cells.at(e).get("weight"))/10)}),t=setTimeout(function(){if(0===d.find(".preview-cell-weight-input").length)return!1;var l=[],o=[],n=0,a=0;if(d.find(".preview-cell-weight-input").each(function(e,t){var i=parseFloat(c(t).val());i=isNaN(i)?1/r.row.cells.length:Math.round(10*i)/1e3;var s=!c(t).hasClass("no-user-interacted");l.push(i),o.push(s),s?n+=i:a+=i}),0<n&&0<a&&0<1-n)for(var e=0;e<l.length;e++)o[e]||(l[e]=l[e]/a*(1-n));var t=_.reduce(l,function(e,t){return e+t});l=l.map(function(e){return e/t}),.01<Math.min.apply(Math,l)&&r.row.cells.each(function(e,t){e.set("weight",l[t])}),d.find(".preview-cell").each(function(e,t){var i=r.row.cells.at(e).get("weight");c(t).animate({width:Math.round(1e3*i)/10+"%"},250),c(t).find(".preview-cell-weight-input").val(Math.round(1e3*i)/10)}),d.find(".preview-cell").css("overflow","visible"),setTimeout(r.regenerateRowPreview.bind(r),260)},100)}).click(function(){c(this).select()})}),c(this).siblings(".preview-cell-weight-input").select()})},this),this.trigger("form_loaded",this)},getSelectedCellIndex:function(){var i=-1;return this.$(".preview-cell .preview-cell-in").each(function(e,t){c(t).is(".cell-selected")&&(i=e)}),i},openSelectedCellStyles:function(){if(!_.isUndefined(this.cellStyles)){if(this.cellStyles.stylesLoaded){var e={};try{e=this.getFormValues(".so-sidebar .so-visual-styles.so-cell-styles").style}catch(e){console.log("Error retrieving cell styles - "+e.message)}this.cellStyles.model.set("style",e)}this.cellStyles.detach()}if(this.cellStyles=this.getSelectedCellStyles(),this.cellStyles){var t=this.$(".so-sidebar.so-right-sidebar");this.cellStyles.attach(t),this.cellStyles.on("styles_loaded",function(e){e&&(t.closest(".so-panels-dialog").addClass("so-panels-dialog-has-right-sidebar"),t.show())})}},getSelectedCellStyles:function(){var e=this.getSelectedCellIndex();if(-1<e){var t=this.cellStylesCache[e];t||((t=new a.view.styles).model=this.row.cells.at(e),t.render("cell",this.builder.config.postId,{builderType:this.builder.config.builderType,dialog:this,index:e}),this.cellStylesCache[e]=t)}return t},clearCellStylesCache:function(){this.cellStylesCache.forEach(function(e){e.remove(),e.off("styles_loaded")}),this.cellStylesCache=[]},scaleRowWidths:function(){var s=this;this.$(".row-preview .preview-cell").each(function(e,t){var i=s.row.cells.at(e);c(t).css("width",100*i.get("weight")+"%").find(".preview-cell-weight").html(Math.round(1e3*i.get("weight"))/10)})},setCellsFromForm:function(){try{var e={cells:parseInt(this.$('.row-set-form input[name="cells"]').val()),ratio:parseFloat(this.$('.row-set-form select[name="ratio"]').val()),direction:this.$('.row-set-form select[name="ratio_direction"]').val()};_.isNaN(e.cells)&&(e.cells=1),isNaN(e.ratio)&&(e.ratio=1),e.cells<1?(e.cells=1,this.$('.row-set-form input[name="cells"]').val(e.cells)):12<e.cells&&(e.cells=12,this.$('.row-set-form input[name="cells"]').val(e.cells)),this.$('.row-set-form select[name="ratio"]').val(e.ratio);for(var t=[],i=this.row.cells.length!==e.cells,s=1,l=0;l<e.cells;l++)t.push(s),s*=e.ratio;var o=_.reduce(t,function(e,t){return e+t});if(t=_.map(t,function(e){return e/o}),t=_.filter(t,function(e){return.01<e}),"left"===e.direction&&(t=t.reverse()),this.row.cells=new a.collection.cells(this.row.cells.first(t.length)),_.each(t,function(e,t){var i=this.row.cells.at(t);i?i.set("weight",e):(i=new a.model.cell({weight:e,row:this.model}),this.row.cells.add(i))}.bind(this)),this.row.ratio=e.ratio,this.row.ratio_direction=e.direction,i)this.regenerateRowPreview();else{var n=this;this.$(".preview-cell").each(function(e,t){var i=n.row.cells.at(e).get("weight");c(t).animate({width:Math.round(1e3*i)/10+"%"},250),c(t).find(".preview-cell-weight").html(Math.round(1e3*i)/10)}),this.$(".preview-cell").css("overflow","visible"),setTimeout(n.regenerateRowPreview.bind(n),260)}}catch(e){console.log("Error setting cells - "+e.message)}this.$(".row-set-form .so-button-row-set").removeClass("button-primary")},tabClickHandler:function(e){"#row-layout"===e.attr("href")?this.$(".so-panels-dialog").addClass("so-panels-dialog-has-right-sidebar"):this.$(".so-panels-dialog").removeClass("so-panels-dialog-has-right-sidebar")},updateModel:function(e){if(e=_.extend({refresh:!0,refreshArgs:null},e),_.isEmpty(this.model)||(this.model.setCells(this.row.cells),this.model.set("ratio",this.row.ratio),this.model.set("ratio_direction",this.row.ratio_direction)),!_.isUndefined(this.styles)&&this.styles.stylesLoaded){var t={};try{t=this.getFormValues(".so-sidebar .so-visual-styles.so-row-styles").style}catch(e){console.log("Error retrieving row styles - "+e.message)}this.model.set("style",t)}if(!_.isUndefined(this.cellStyles)&&this.cellStyles.stylesLoaded){t={};try{t=this.getFormValues(".so-sidebar .so-visual-styles.so-cell-styles").style}catch(e){console.log("Error retrieving cell styles - "+e.message)}this.cellStyles.model.set("style",t)}e.refresh&&this.builder.model.refreshPanelsData(e.refreshArgs)},insertHandler:function(){this.builder.addHistoryEntry("row_added"),this.updateModel();var e=this.builder.getActiveCell({createCell:!1}),t={};return null!==e&&(t.at=this.builder.model.get("rows").indexOf(e.row)+1),this.model.collection=this.builder.model.get("rows"),this.builder.model.get("rows").add(this.model,t),this.closeDialog(),this.builder.model.refreshPanelsData(),!1},saveHandler:function(){return this.builder.addHistoryEntry("row_edited"),this.updateModel(),this.closeDialog(),this.builder.model.refreshPanelsData(),!1},deleteHandler:function(){return this.rowView.visualDestroyModel(),this.closeDialog({silent:!0}),!1},duplicateHandler:function(){this.builder.addHistoryEntry("row_duplicated");var e=this.model.clone(this.builder.model);return this.builder.model.get("rows").add(e,{at:this.builder.model.get("rows").indexOf(this.model)+1}),this.closeDialog({silent:!0}),!1},closeHandler:function(){this.clearCellStylesCache(),_.isUndefined(this.cellStyles)||(this.cellStyles=void 0)}})},{}],9:[function(e,t,i){var s=window.panels,l=jQuery,o=e("../view/widgets/js-widget");t.exports=s.view.dialog.extend({builder:null,sidebarWidgetTemplate:_.template(s.helpers.utils.processTemplate(l("#siteorigin-panels-dialog-widget-sidebar-widget").html())),dialogClass:"so-panels-dialog-edit-widget",dialogIcon:"add-widget",widgetView:!1,savingWidget:!1,editableLabel:!0,events:{"click .so-close":"saveHandler","click .so-nav.so-previous":"navToPrevious","click .so-nav.so-next":"navToNext","click .so-toolbar .so-delete":"deleteHandler","click .so-toolbar .so-duplicate":"duplicateHandler"},initializeDialog:function(){var e=this;this.listenTo(this.model,"change:values",this.handleChangeValues),this.listenTo(this.model,"destroy",this.remove),this.dialogFormsLoaded=0,this.on("form_loaded styles_loaded",function(){this.dialogFormsLoaded++,2===this.dialogFormsLoaded&&e.updateModel({refreshArgs:{silent:!0}})}),this.on("edit_label",function(e){e===panelsOptions.widgets[this.model.get("class")].title&&(e=""),this.model.set("label",e),_.isEmpty(e)&&this.$(".so-title").text(this.model.getWidgetField("title"))}.bind(this))},render:function(){this.renderDialog(this.parseDialogContent(l("#siteorigin-panels-dialog-widget").html(),{})),this.loadForm();var e=this.model.getWidgetField("title");this.$(".so-title .widget-name").html(e),this.$(".so-edit-title").val(e),this.builder.supports("addWidget")||this.$(".so-buttons .so-duplicate").remove(),this.builder.supports("deleteWidget")||this.$(".so-buttons .so-delete").remove(),this.styles=new s.view.styles,this.styles.model=this.model,this.styles.render("widget",this.builder.config.postId,{builderType:this.builder.config.builderType,dialog:this});var t=this.$(".so-sidebar.so-right-sidebar");this.styles.attach(t),this.styles.on("styles_loaded",function(e){e||(t.closest(".so-panels-dialog").removeClass("so-panels-dialog-has-right-sidebar"),t.remove())},this)},getPrevDialog:function(){var e=this.builder.$(".so-cells .cell .so-widget");if(e.length<=1)return!1;var t,i=e.index(this.widgetView.$el);if(0===i)return!1;do{if(t=e.eq(--i).data("view"),!_.isUndefined(t)&&!t.model.get("read_only"))return t.getEditDialog()}while(!_.isUndefined(t)&&0<i);return!1},getNextDialog:function(){var e=this.builder.$(".so-cells .cell .so-widget");if(e.length<=1)return!1;var t,i=e.index(this.widgetView.$el);if(i===e.length-1)return!1;do{if(t=e.eq(++i).data("view"),!_.isUndefined(t)&&!t.model.get("read_only"))return t.getEditDialog()}while(!_.isUndefined(t));return!1},loadForm:function(){if(this.$("> *").length){this.$(".so-content").addClass("so-panels-loading");var e={action:"so_panels_widget_form",widget:this.model.get("class"),instance:JSON.stringify(this.model.get("values")),raw:this.model.get("raw")},i=this.$(".so-content");l.post(panelsOptions.ajaxurl,e,null,"html").done(function(e){var t=e.replace(/{\$id}/g,this.model.cid);i.removeClass("so-panels-loading").html(t),this.trigger("form_loaded",this),this.$(".panel-dialog").trigger("panelsopen"),this.on("close_dialog",this.updateModel,this),0<i.find("> .widget-content").length&&o.addWidget(i,this.model.widget_id)}.bind(this)).fail(function(e){var t;t=e&&e.responseText?e.responseText:panelsOptions.forms.loadingFailed,i.removeClass("so-panels-loading").html(t)})}},updateModel:function(e){if(e=_.extend({refresh:!0,refreshArgs:null},e),this.savingWidget=!0,!this.model.get("missing")){var t=this.getFormValues();t=_.isUndefined(t.widgets)?{}:(t=t.widgets)[Object.keys(t)[0]],this.model.setValues(t),this.model.set("raw",!0)}if(this.styles.stylesLoaded){var i={};try{i=this.getFormValues(".so-sidebar .so-visual-styles").style}catch(e){}this.model.set("style",i)}this.savingWidget=!1,e.refresh&&this.builder.model.refreshPanelsData(e.refreshArgs)},handleChangeValues:function(){this.savingWidget||this.loadForm()},saveHandler:function(){this.builder.addHistoryEntry("widget_edited"),this.closeDialog()},deleteHandler:function(){return this.widgetView.visualDestroyModel(),this.closeDialog({silent:!0}),this.builder.model.refreshPanelsData(),!1},duplicateHandler:function(){return this.widgetView.duplicateHandler(),this.closeDialog({silent:!0}),this.builder.model.refreshPanelsData(),!1}})},{"../view/widgets/js-widget":31}],10:[function(e,t,i){var s=window.panels,o=jQuery;t.exports=s.view.dialog.extend({builder:null,widgetTemplate:_.template(s.helpers.utils.processTemplate(o("#siteorigin-panels-dialog-widgets-widget").html())),filter:{},dialogClass:"so-panels-dialog-add-widget",dialogIcon:"add-widget",events:{"click .so-close":"closeDialog","click .widget-type":"widgetClickHandler","keyup .so-sidebar-search":"searchHandler"},initializeDialog:function(){this.on("open_dialog",function(){this.filter.search="",this.filterWidgets(this.filter)},this),this.on("open_dialog_complete",function(){this.$(".so-sidebar-search").val("").focus(),this.balanceWidgetHeights()}),this.on("tab_click",this.tabClickHandler,this)},render:function(){this.renderDialog(this.parseDialogContent(o("#siteorigin-panels-dialog-widgets").html(),{})),_.each(panelsOptions.widgets,function(e){var t=o(this.widgetTemplate({title:e.title,description:e.description}));_.isUndefined(e.icon)&&(e.icon="dashicons dashicons-admin-generic"),o('<span class="widget-icon" />').addClass(e.icon).prependTo(t.find(".widget-type-wrapper")),t.data("class",e.class).appendTo(this.$(".widget-type-list"))},this);var i=this.$(".so-sidebar-tabs");_.each(panelsOptions.widget_dialog_tabs,function(e,t){o(this.dialogTabTemplate({title:e.title,tab:t})).data({message:e.message,filter:e.filter}).appendTo(i)},this),this.initTabs();var e=this;o(window).resize(function(){e.balanceWidgetHeights()})},tabClickHandler:function(e){this.filter=e.parent().data("filter"),this.filter.search=this.$(".so-sidebar-search").val();var t=e.parent().data("message");return _.isEmpty(t)&&(t=""),this.$(".so-toolbar .so-status").html(t),this.filterWidgets(this.filter),!1},searchHandler:function(e){if(13===e.which){var t=this.$(".widget-type-list .widget-type:visible");1===t.length&&t.click()}else this.filter.search=o(e.target).val().trim(),this.filterWidgets(this.filter)},filterWidgets:function(l){_.isUndefined(l)&&(l={}),_.isUndefined(l.groups)&&(l.groups=""),this.$(".widget-type-list .widget-type").each(function(){var e,t=o(this),i=t.data("class"),s=_.isUndefined(panelsOptions.widgets[i])?null:panelsOptions.widgets[i];(e=!!_.isEmpty(l.groups)||null!==s&&!_.isEmpty(_.intersection(l.groups,panelsOptions.widgets[i].groups)))&&(_.isUndefined(l.search)||""===l.search||-1===s.title.toLowerCase().indexOf(l.search.toLowerCase())&&(e=!1)),e?t.show():t.hide()}),this.balanceWidgetHeights()},widgetClickHandler:function(e){this.builder.trigger("before_user_adds_widget"),this.builder.addHistoryEntry("widget_added");var t=o(e.currentTarget),i=new s.model.widget({class:t.data("class")});i.cell=this.builder.getActiveCell(),i.cell.get("widgets").add(i),this.closeDialog(),this.builder.model.refreshPanelsData(),this.builder.trigger("after_user_adds_widget",i)},balanceWidgetHeights:function(e){var s=[[]],l=null,i=Math.round(this.$(".widget-type").parent().width()/this.$(".widget-type").width());this.$(".widget-type").css("clear","none").filter(":visible").each(function(e,t){e%i==0&&0!==e&&o(t).css("clear","both")}),this.$(".widget-type-wrapper").css("height","auto").filter(":visible").each(function(e,t){var i=o(t);null!==l&&l.position().top!==i.position().top&&(s[s.length]=[]),l=i,s[s.length-1].push(i)}),_.each(s,function(e,t){var i=_.max(e.map(function(e){return e.height()}));_.each(e,function(e){e.height(i)})})}})},{}],11:[function(e,t,i){t.exports={canCopyPaste:function(){return"undefined"!=typeof Storage&&panelsOptions.user},setModel:function(e){if(!this.canCopyPaste())return!1;var t=panels.helpers.serialize.serialize(e);return e instanceof panels.model.row?t.thingType="row-model":e instanceof panels.model.widget&&(t.thingType="widget-model"),localStorage["panels_clipboard_"+panelsOptions.user]=JSON.stringify(t),!0},isModel:function(e){if(!this.canCopyPaste())return!1;var t=localStorage["panels_clipboard_"+panelsOptions.user];return void 0!==t&&((t=JSON.parse(t)).thingType&&t.thingType===e)},getModel:function(e){if(!this.canCopyPaste())return null;var t=localStorage["panels_clipboard_"+panelsOptions.user];return void 0!==t&&(t=JSON.parse(t)).thingType&&t.thingType===e?panels.helpers.serialize.unserialize(t,t.thingType,null):null}}},{}],12:[function(e,t,i){t.exports={lock:function(){if("hidden"!==jQuery("body").css("overflow")){var e=[self.pageXOffset||document.documentElement.scrollLeft||document.body.scrollLeft,self.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop];jQuery("body").data({"scroll-position":e}).css("overflow","hidden"),_.isUndefined(e)||window.scrollTo(e[0],e[1])}},unlock:function(){if("hidden"===jQuery("body").css("overflow")&&!jQuery(".so-panels-dialog-wrapper").is(":visible")&&!jQuery(".so-panels-live-editor").is(":visible")){jQuery("body").css("overflow","visible");var e=jQuery("body").data("scroll-position");_.isUndefined(e)||window.scrollTo(e[0],e[1])}}}},{}],13:[function(e,t,i){t.exports={serialize:function(e){var t;if(e instanceof Backbone.Model){var i={};for(var s in e.attributes)if(e.attributes.hasOwnProperty(s)){if("builder"===s||"collection"===s)continue;(t=e.attributes[s])instanceof Backbone.Model||t instanceof Backbone.Collection?i[s]=this.serialize(t):i[s]=t}return i}if(e instanceof Backbone.Collection){for(var l=[],o=0;o<e.models.length;o++)(t=e.models[o])instanceof Backbone.Model||t instanceof Backbone.Collection?l.push(this.serialize(t)):l.push(t);return l}},unserialize:function(e,t,i){var s;switch(t){case"row-model":(s=new panels.model.row).builder=i;var l={style:e.style};e.hasOwnProperty("label")&&(l.label=e.label),e.hasOwnProperty("color_label")&&(l.color_label=e.color_label),s.set(l),s.setCells(this.unserialize(e.cells,"cell-collection",s));break;case"cell-model":(s=new panels.model.cell).row=i,s.set("weight",e.weight),s.set("style",e.style),s.set("widgets",this.unserialize(e.widgets,"widget-collection",s));break;case"widget-model":for(var o in(s=new panels.model.widget).cell=i,e)e.hasOwnProperty(o)&&s.set(o,e[o]);s.set("widget_id",panels.helpers.utils.generateUUID());break;case"cell-collection":s=new panels.collection.cells;for(var n=0;n<e.length;n++)s.push(this.unserialize(e[n],"cell-model",i));break;case"widget-collection":s=new panels.collection.widgets;for(n=0;n<e.length;n++)s.push(this.unserialize(e[n],"widget-model",i));break;default:console.log("Unknown Thing - "+t)}return s}}},{}],14:[function(e,t,i){t.exports={generateUUID:function(){var i=(new Date).getTime();return window.performance&&"function"==typeof window.performance.now&&(i+=performance.now()),"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){var t=(i+16*Math.random())%16|0;return i=Math.floor(i/16),("x"==e?t:3&t|8).toString(16)})},processTemplate:function(e){return _.isUndefined(e)||_.isNull(e)?"":e=(e=(e=e.replace(/{{%/g,"<%")).replace(/%}}/g,"%>")).trim()},selectElementContents:function(e){var t=document.createRange();t.selectNodeContents(e);var i=window.getSelection();i.removeAllRanges(),i.addRange(t)}}},{}],15:[function(e,t,i){var d=window.panels,c=jQuery;t.exports=function(a,r){return this.each(function(){var e=jQuery(this);if(!e.data("soPanelsBuilderWidgetInitialized")||r){var t=e.closest("form").find(".widget-id").val(),i=c.extend(!0,{},a);if(_.isUndefined(t)||!(-1<t.indexOf("__i__"))){var s=new d.model.builder,l=new d.view.builder({model:s,config:i}),o=e.closest(".so-panels-dialog-wrapper").data("view");_.isUndefined(o)||(o.on("close_dialog",function(){s.refreshPanelsData()}),o.on("open_dialog_complete",function(){l.trigger("builder_resize")}),o.model.on("destroy",function(){s.emptyRows().destroy()}),l.setDialogParents(panelsOptions.loc.layout_widget,o));var n=Boolean(e.closest(".widget-content").length);l.render().attach({container:e,dialog:n||"dialog"===e.data("mode"),type:e.data("type")}).setDataField(e.find("input.panels-data")),n||"dialog"===e.data("mode")?(l.setDialogParents(panelsOptions.loc.layout_widget,l.dialog),e.find(".siteorigin-panels-display-builder").click(function(e){e.preventDefault(),l.dialog.openDialog()})):e.find(".siteorigin-panels-display-builder").parent().remove(),c(document).trigger("panels_setup",l),e.data("soPanelsBuilderWidgetInitialized",!0)}}})}},{}],16:[function(e,t,i){var s={};window.panels=s,(window.siteoriginPanels=s).helpers={},s.helpers.clipboard=e("./helpers/clipboard"),s.helpers.utils=e("./helpers/utils"),s.helpers.serialize=e("./helpers/serialize"),s.helpers.pageScroll=e("./helpers/page-scroll"),s.model={},s.model.widget=e("./model/widget"),s.model.cell=e("./model/cell"),s.model.row=e("./model/row"),s.model.builder=e("./model/builder"),s.model.historyEntry=e("./model/history-entry"),s.collection={},s.collection.widgets=e("./collection/widgets"),s.collection.cells=e("./collection/cells"),s.collection.rows=e("./collection/rows"),s.collection.historyEntries=e("./collection/history-entries"),s.view={},s.view.widget=e("./view/widget"),s.view.cell=e("./view/cell"),s.view.row=e("./view/row"),s.view.builder=e("./view/builder"),s.view.dialog=e("./view/dialog"),s.view.styles=e("./view/styles"),s.view.liveEditor=e("./view/live-editor"),s.dialog={},s.dialog.builder=e("./dialog/builder"),s.dialog.widgets=e("./dialog/widgets"),s.dialog.widget=e("./dialog/widget"),s.dialog.prebuilt=e("./dialog/prebuilt"),s.dialog.row=e("./dialog/row"),s.dialog.history=e("./dialog/history"),s.utils={},s.utils.menu=e("./utils/menu"),jQuery.fn.soPanelsSetupBuilderWidget=e("./jquery/setup-builder-widget"),jQuery(function(i){var e,t,s,l,o=i("#siteorigin-panels-metabox");if(s=i("form#post"),o.length&&s.length)t=(e=o).find(".siteorigin-panels-data-field"),l={editorType:"tinyMCE",postId:i("#post_ID").val(),editorId:"#content",builderType:o.data("builder-type"),builderSupports:o.data("builder-supports"),loadOnAttach:panelsOptions.loadOnAttach&&1==i("#auto_draft").val(),loadLiveEditor:1==o.data("live-editor"),liveEditorPreview:e.data("preview-url")};else if(i(".siteorigin-panels-builder-form").length){var n=i(".siteorigin-panels-builder-form");e=n.find(".siteorigin-panels-builder-container"),t=n.find('input[name="panels_data"]'),l={editorType:"standalone",postId:(s=n).data("post-id"),editorId:"#post_content",builderType:n.data("type"),builderSupports:n.data("builder-supports"),loadLiveEditor:!1,liveEditorPreview:n.data("preview-url")}}if(!_.isUndefined(e)){var a=window.siteoriginPanels,r=new a.model.builder,d=new a.view.builder({model:r,config:l});i(document).trigger("before_panels_setup",d),d.render().attach({container:e}).setDataField(t).attachToEditor(),s.submit(function(){r.refreshPanelsData()}),e.removeClass("so-panels-loading"),i(document).trigger("panels_setup",d,window.panels)}i(document).on("widget-added",function(e,t){i(t).find(".siteorigin-page-builder-widget").soPanelsSetupBuilderWidget()}),i("body").hasClass("wp-customizer")||i(function(){i(".siteorigin-page-builder-widget").soPanelsSetupBuilderWidget()}),i(window).on("keyup",function(e){27===e.which&&i(".so-panels-dialog-wrapper, .so-panels-live-editor").filter(":visible").last().find(".so-title-bar .so-close, .live-editor-close").click()})})},{"./collection/cells":1,"./collection/history-entries":2,"./collection/rows":3,"./collection/widgets":4,"./dialog/builder":5,"./dialog/history":6,"./dialog/prebuilt":7,"./dialog/row":8,"./dialog/widget":9,"./dialog/widgets":10,"./helpers/clipboard":11,"./helpers/page-scroll":12,"./helpers/serialize":13,"./helpers/utils":14,"./jquery/setup-builder-widget":15,"./model/builder":17,"./model/cell":18,"./model/history-entry":19,"./model/row":20,"./model/widget":21,"./utils/menu":22,"./view/builder":23,"./view/cell":24,"./view/dialog":25,"./view/live-editor":26,"./view/row":27,"./view/styles":28,"./view/widget":29}],17:[function(e,t,i){t.exports=Backbone.Model.extend({layoutPosition:{BEFORE:"before",AFTER:"after",REPLACE:"replace"},rows:{},defaults:{data:{widgets:[],grids:[],grid_cells:[]}},initialize:function(){this.set("rows",new panels.collection.rows)},addRow:function(e,t,i){i=_.extend({noAnimate:!1},i);var s=new panels.collection.cells(t);e=_.extend({collection:this.get("rows"),cells:s},e);var l=new panels.model.row(e);return(l.builder=this).get("rows").add(l,i),l},loadPanelsData:function(s,e){try{e===this.layoutPosition.BEFORE?s=this.concatPanelsData(s,this.getPanelsData()):e===this.layoutPosition.AFTER&&(s=this.concatPanelsData(this.getPanelsData(),s)),this.emptyRows(),this.set("data",JSON.parse(JSON.stringify(s)),{silent:!0});var t,i=[];if(_.isUndefined(s.grid_cells))return void this.trigger("load_panels_data");for(var l=0;l<s.grid_cells.length;l++)t=parseInt(s.grid_cells[l].grid),_.isUndefined(i[t])&&(i[t]=[]),i[t].push(s.grid_cells[l]);var o=this;if(_.each(i,function(e,t){var i={};_.isUndefined(s.grids[t].style)||(i.style=s.grids[t].style),_.isUndefined(s.grids[t].ratio)||(i.ratio=s.grids[t].ratio),_.isUndefined(s.grids[t].ratio_direction)||(i.ratio_direction=s.grids[t].ratio_direction),_.isUndefined(s.grids[t].color_label)||(i.color_label=s.grids[t].color_label),_.isUndefined(s.grids[t].label)||(i.label=s.grids[t].label),o.addRow(i,e,{noAnimate:!0})}),_.isUndefined(s.widgets))return;_.each(s.widgets,function(e){var t=null;_.isUndefined(e.panels_info)?(t=e.info,delete e.info):(t=e.panels_info,delete e.panels_info);var i=o.get("rows").at(parseInt(t.grid)).get("cells").at(parseInt(t.cell)),s=new panels.model.widget({class:t.class,values:e});_.isUndefined(t.style)||s.set("style",t.style),_.isUndefined(t.read_only)||s.set("read_only",t.read_only),_.isUndefined(t.widget_id)?s.set("widget_id",panels.helpers.utils.generateUUID()):s.set("widget_id",t.widget_id),_.isUndefined(t.label)||s.set("label",t.label),(s.cell=i).get("widgets").add(s,{noAnimate:!0})}),this.trigger("load_panels_data")}catch(e){console.log("Error loading data: "+e.message)}},concatPanelsData:function(e,t){if(_.isUndefined(t)||_.isUndefined(t.grids)||_.isEmpty(t.grids)||_.isUndefined(t.grid_cells)||_.isEmpty(t.grid_cells))return e;if(_.isUndefined(e)||_.isUndefined(e.grids)||_.isEmpty(e.grids))return t;var i,s=e.grids.length,l=_.isUndefined(e.widgets)?0:e.widgets.length,o={grids:[],grid_cells:[],widgets:[]};for(o.grids=e.grids.concat(t.grids),_.isUndefined(e.grid_cells)||(o.grid_cells=e.grid_cells.slice()),_.isUndefined(e.widgets)||(o.widgets=e.widgets.slice()),i=0;i<t.grid_cells.length;i++){var n=t.grid_cells[i];n.grid=parseInt(n.grid)+s,o.grid_cells.push(n)}if(!_.isUndefined(t.widgets))for(i=0;i<t.widgets.length;i++){var a=t.widgets[i];a.panels_info.grid=parseInt(a.panels_info.grid)+s,a.panels_info.id=parseInt(a.panels_info.id)+l,o.widgets.push(a)}return o},getPanelsData:function(){var n={widgets:[],grids:[],grid_cells:[]},a=0;return this.get("rows").each(function(e,o){e.get("cells").each(function(e,l){e.get("widgets").each(function(e,t){var i={class:e.get("class"),raw:e.get("raw"),grid:o,cell:l,id:a++,widget_id:e.get("widget_id"),style:e.get("style"),label:e.get("label")};_.isEmpty(i.widget_id)&&(i.widget_id=panels.helpers.utils.generateUUID());var s=_.extend(_.clone(e.get("values")),{panels_info:i});n.widgets.push(s)}),n.grid_cells.push({grid:o,index:l,weight:e.get("weight"),style:e.get("style")})}),n.grids.push({cells:e.get("cells").length,style:e.get("style"),ratio:e.get("ratio"),ratio_direction:e.get("ratio_direction"),color_label:e.get("color_label"),label:e.get("label")})}),n},refreshPanelsData:function(e){e=_.extend({silent:!1},e);var t=this.get("data"),i=this.getPanelsData();this.set("data",i,{silent:!0}),e.silent||JSON.stringify(i)===JSON.stringify(t)||(this.trigger("change"),this.trigger("change:data"),this.trigger("refresh_panels_data",i,e))},emptyRows:function(){return _.invoke(this.get("rows").toArray(),"destroy"),this.get("rows").reset(),this},isValidLayoutPosition:function(e){return e===this.layoutPosition.BEFORE||e===this.layoutPosition.AFTER||e===this.layoutPosition.REPLACE},getPanelsDataFromHtml:function(e,h){var t,u=this,i=jQuery('<div id="wrapper">'+e+"</div>");if(i.find(".panel-layout .panel-grid").length){var p={grids:[],grid_cells:[],widgets:[]},g=new RegExp(panelsOptions.siteoriginWidgetRegex,"i"),f=(t=document.createElement("div"),function(e){return e&&"string"==typeof e&&(e=(e=e.replace(/<script[^>]*>([\S\s]*?)<\/script>/gim,"")).replace(/<\/?\w(?:[^"'>]|"[^"]*"|'[^']*')*>/gim,""),t.innerHTML=e,e=t.textContent,t.textContent=""),e}),w=function(e){var t,i=e.find("div");if(!i.length)return e.html();for(t=0;t<i.length-1&&jQuery.trim(i.eq(t).text())==jQuery.trim(i.eq(t+1).text());t++);var s=i.eq(t).find(".widget-title:header"),l="";return s.length&&(l=s.html(),s.remove()),{title:l,text:i.eq(t).html()}},s=i.find(".panel-layout").eq(0),l=function(e,t){return jQuery(t).closest(".panel-layout").is(s)};return i.find("> .panel-layout > .panel-grid").filter(l).each(function(c,e){var t=jQuery(e),i=t.find(".panel-grid-cell").filter(l);p.grids.push({cells:i.length,style:t.data("style"),ratio:t.data("ratio"),ratio_direction:t.data("ratio-direction"),color_label:t.data("color-label"),label:t.data("label")}),i.each(function(d,e){var t=jQuery(e),i=t.find(".so-panel").filter(l);p.grid_cells.push({grid:c,weight:_.isUndefined(t.data("weight"))?1:parseFloat(t.data("weight")),style:t.data("style")}),i.each(function(e,t){var i=jQuery(t),s=i.find(".panel-widget-style").length?i.find(".panel-widget-style").html():i.html(),l={grid:c,cell:d,style:i.data("style"),raw:!1,label:i.data("label")};s=s.trim();var o=g.exec(s);if(_.isNull(o)||""!==s.replace(g,"").trim())return-1!==s.indexOf("panel-layout")&&jQuery("<div>"+s+"</div>").find(".panel-layout .panel-grid").length?(l.class="SiteOrigin_Panels_Widgets_Layout",p.widgets.push({panels_data:u.getPanelsDataFromHtml(s,h),panels_info:l})):(l.class=h,p.widgets.push(_.extend(w(i),{filter:"1",type:"visual",panels_info:l}))),!0;try{var n=/class="(.*?)"/.exec(o[3]),a=jQuery(o[5]),r=JSON.parse(f(a.val())).instance;l.class=n[1].replace(/\\\\+/g,"\\"),l.raw=!1,r.panels_info=l,p.widgets.push(r)}catch(e){l.class=h,p.widgets.push(_.extend(w(i),{filter:"1",type:"visual",panels_info:l}))}return!0})})}),i.find(".panel-layout").remove(),i.find("style[data-panels-style-for-post]").remove(),i.html().replace(/^\s+|\s+$/gm,"").length&&(p.grids.push({cells:1,style:{}}),p.grid_cells.push({grid:p.grids.length-1,weight:1}),p.widgets.push({filter:"1",text:i.html().replace(/^\s+|\s+$/gm,""),title:"",type:"visual",panels_info:{class:h,raw:!1,grid:p.grids.length-1,cell:0}})),p}return{grid_cells:[{grid:0,weight:1}],grids:[{cells:1}],widgets:[{filter:"1",text:e,title:"",type:"visual",panels_info:{class:h,raw:!1,grid:0,cell:0}}]}}})},{}],18:[function(e,t,i){t.exports=Backbone.Model.extend({widgets:{},row:null,defaults:{weight:0,style:{}},indexes:null,initialize:function(){this.set("widgets",new panels.collection.widgets),this.on("destroy",this.onDestroy,this)},onDestroy:function(){_.invoke(this.get("widgets").toArray(),"destroy"),this.get("widgets").reset()},clone:function(e,t){_.isUndefined(e)&&(e=this.row),t=_.extend({cloneWidgets:!0},t);var i=new this.constructor(this.attributes);return i.set("collection",e.get("cells"),{silent:!0}),i.row=e,t.cloneWidgets&&this.get("widgets").each(function(e){i.get("widgets").add(e.clone(i,t),{silent:!0})}),i}})},{}],19:[function(e,t,i){t.exports=Backbone.Model.extend({defaults:{text:"",data:"",time:null,count:1}})},{}],20:[function(e,t,i){t.exports=Backbone.Model.extend({builder:null,defaults:{style:{}},indexes:null,initialize:function(){_.isEmpty(this.get("cells"))?this.set("cells",new panels.collection.cells):this.get("cells").each(function(e){e.row=this}.bind(this)),this.on("destroy",this.onDestroy,this)},setCells:function(n){var a=this.get("cells")||new panels.collection.cells,r=[];a.each(function(e,t){var i=n.at(t);if(i)e.set("weight",i.get("weight"));else{for(var s=a.at(n.length-1),l=e.get("widgets").models.slice(),o=0;o<l.length;o++)l[o].moveToCell(s,{silent:!1});r.push(e)}}),_.each(r,function(e){a.remove(e)}),n.length>a.length&&_.each(n.slice(a.length,n.length),function(e){e.set({collection:a}),e.row=this,a.add(e)}.bind(this)),this.reweightCells()},reweightCells:function(){var t=0,e=this.get("cells");e.each(function(e){t+=e.get("weight")}),e.each(function(e){e.set("weight",e.get("weight")/t)}),this.trigger("reweight_cells")},onDestroy:function(){_.invoke(this.get("cells").toArray(),"destroy"),this.get("cells").reset()},clone:function(e){_.isUndefined(e)&&(e=this.builder);var t=new this.constructor(this.attributes);t.set("collection",e.get("rows"),{silent:!0}),t.builder=e;var i=new panels.collection.cells;return this.get("cells").each(function(e){i.add(e.clone(t),{silent:!0})}),t.set("cells",i),t}})},{}],21:[function(e,t,i){t.exports=Backbone.Model.extend({cell:null,defaults:{class:null,missing:!1,values:{},raw:!1,style:{},read_only:!1,widget_id:""},indexes:null,initialize:function(){var e=this.get("class");!_.isUndefined(panelsOptions.widgets[e])&&panelsOptions.widgets[e].installed||this.set("missing",!0)},getWidgetField:function(e){return _.isUndefined(panelsOptions.widgets[this.get("class")])?"title"===e||"description"===e?panelsOptions.loc.missing_widget[e]:"":this.has("label")&&!_.isEmpty(this.get("label"))?this.get("label"):panelsOptions.widgets[this.get("class")][e]},moveToCell:function(e,t,i){return t=_.extend({silent:!0},t),this.cell=e,this.collection.remove(this,t),e.get("widgets").add(this,_.extend({at:i},t)),this.trigger("move_to_cell",e,i),this},setValues:function(e){var t=!1;JSON.stringify(e)!==JSON.stringify(this.get("values"))&&(t=!0),this.set("values",e,{silent:!0}),t&&(this.trigger("change",this),this.trigger("change:values"))},clone:function(e,t){_.isUndefined(e)&&(e=this.cell);var i=new this.constructor(this.attributes),s=JSON.parse(JSON.stringify(this.get("values"))),l=function(i){return _.each(i,function(e,t){_.isString(t)&&"_"===t[0]?delete i[t]:_.isObject(i[t])&&l(i[t])}),i};return s=l(s),"SiteOrigin_Panels_Widgets_Layout"===this.get("class")&&(s.builder_id=Math.random().toString(36).substr(2)),i.set("widget_id",""),i.set("values",s,{silent:!0}),i.set("collection",e.get("widgets"),{silent:!0}),i.cell=e,i.isDuplicate=!0,i},getTitle:function(){var e=panelsOptions.widgets[this.get("class")];if(_.isUndefined(e))return this.get("class").replace(/_/g," ");if(!_.isUndefined(e.panels_title)&&!1===e.panels_title)return panelsOptions.widgets[this.get("class")].description;var t=this.get("values"),i=["title","text"];for(var s in t)"_"!==s.charAt(0)&&"so_sidebar_emulator_id"!==s&&"option_name"!==s&&t.hasOwnProperty(s)&&i.push(s);for(var l in i=_.uniq(i))if(!_.isUndefined(t[i[l]])&&_.isString(t[i[l]])&&""!==t[i[l]]&&"on"!==t[i[l]]&&"_"!==i[l][0]&&!jQuery.isNumeric(t[i[l]])){var o=t[i[l]],n=(o=o.replace(/<\/?[^>]+(>|$)/g,"")).split(" ");return(n=n.slice(0,20)).join(" ")}return this.getWidgetField("description")}})},{}],22:[function(e,t,i){var s=window.panels,r=jQuery;t.exports=Backbone.View.extend({wrapperTemplate:_.template(s.helpers.utils.processTemplate(r("#siteorigin-panels-context-menu").html())),sectionTemplate:_.template(s.helpers.utils.processTemplate(r("#siteorigin-panels-context-menu-section").html())),contexts:[],active:!1,events:{"keyup .so-search-wrapper input":"searchKeyUp"},initialize:function(){this.listenContextMenu(),this.render(),this.attach()},listenContextMenu:function(){var t=this;r(window).on("contextmenu",function(e){return t.active&&!t.isOverEl(t.$el,e)?(t.closeMenu(),t.active=!1,e.preventDefault(),!1):!!t.active||(t.active=!1,t.trigger("activate_context",e,t),void(t.active&&(e.preventDefault(),t.openMenu({left:e.pageX,top:e.pageY}))))})},render:function(){this.setElement(this.wrapperTemplate())},attach:function(){this.$el.appendTo("body")},openMenu:function(e){this.trigger("open_menu"),r(window).on("keyup",{menu:this},this.keyboardListen),r(window).on("click",{menu:this},this.clickOutsideListen),this.$el.css("max-height",r(window).height()-20),e.left+this.$el.outerWidth()+10>=r(window).width()&&(e.left=r(window).width()-this.$el.outerWidth()-10),e.left<=0&&(e.left=10),e.top+this.$el.outerHeight()-r(window).scrollTop()+10>=r(window).height()&&(e.top=r(window).height()+r(window).scrollTop()-this.$el.outerHeight()-10),e.left<=0&&(e.left=10),this.$el.css({left:e.left+1,top:e.top+1}).show(),this.$(".so-search-wrapper input").focus()},closeMenu:function(){this.trigger("close_menu"),r(window).off("keyup",this.keyboardListen),r(window).off("click",this.clickOutsideListen),this.active=!1,this.$el.empty().hide()},keyboardListen:function(e){var t=e.data.menu;switch(e.which){case 27:t.closeMenu()}},clickOutsideListen:function(e){var t=e.data.menu;3!==e.which&&t.$el.is(":visible")&&!t.isOverEl(t.$el,e)&&t.closeMenu()},addSection:function(e,t,i,s){var l=this;t=_.extend({display:5,defaultDisplay:!1,search:!0,sectionTitle:"",searchPlaceholder:"",titleKey:"title"},t);var o=r(this.sectionTemplate({settings:t,items:i})).attr("id","panels-menu-section-"+e);this.$el.append(o),o.find(".so-item:not(.so-confirm)").click(function(){var e=r(this);s(e.data("key")),l.closeMenu()}),o.find(".so-item.so-confirm").click(function(){var e=r(this);if(e.hasClass("so-confirming"))return s(e.data("key")),void l.closeMenu();e.data("original-text",e.html()).addClass("so-confirming").html('<span class="dashicons dashicons-yes"></span> '+panelsOptions.loc.dropdown_confirm),setTimeout(function(){e.removeClass("so-confirming"),e.html(e.data("original-text"))},2500)}),o.data("settings",t).find(".so-search-wrapper input").trigger("keyup"),this.active=!0},hasSection:function(e){return 0<this.$el.find("#panels-menu-section-"+e).length},searchKeyUp:function(e){var t=r(e.currentTarget),i=t.closest(".so-section"),s=i.data("settings");if(38===e.which||40===e.which){var l=i.find("ul li:visible"),o=l.filter(".so-active").eq(0);if(o.length){l.removeClass("so-active");var n=l.index(o);38===e.which?o=n-1<0?l.last():l.eq(n-1):40===e.which&&(o=n+1>=l.length?l.first():l.eq(n+1))}else 38===e.which?o=l.last():40===e.which&&(o=l.first());return o.addClass("so-active"),!1}if(13===e.which)return 1===i.find("ul li:visible").length?i.find("ul li:visible").trigger("click"):i.find("ul li.so-active:visible").trigger("click"),!1;if(""===t.val())if(s.defaultDisplay){i.find(".so-item").hide();for(var a=0;a<s.defaultDisplay.length;a++)i.find('.so-item[data-key="'+s.defaultDisplay[a]+'"]').show()}else i.find(".so-item").show();else i.find(".so-item").hide().each(function(){var e=r(this);-1!==e.html().toLowerCase().indexOf(t.val().toLowerCase())&&e.show()});i.find(".so-item:visible:gt("+(s.display-1)+")").hide(),0===i.find(".so-item:visible").length&&""!==t.val()?i.find(".so-no-results").show():i.find(".so-no-results").hide()},isOverEl:function(e,t){var i=[[e.offset().left,e.offset().top],[e.offset().left+e.outerWidth(),e.offset().top+e.outerHeight()]];return t.pageX>=i[0][0]&&t.pageX<=i[1][0]&&t.pageY>=i[0][1]&&t.pageY<=i[1][1]}})},{}],23:[function(e,t,i){var a=window.panels,n=jQuery;t.exports=Backbone.View.extend({config:{},template:_.template(a.helpers.utils.processTemplate(n("#siteorigin-panels-builder").html())),dialogs:{},rowsSortable:null,dataField:!1,currentData:"",attachedToEditor:!1,attachedVisible:!1,liveEditor:void 0,menu:!1,activeCell:null,events:{"click .so-tool-button.so-widget-add":"displayAddWidgetDialog","click .so-tool-button.so-row-add":"displayAddRowDialog","click .so-tool-button.so-prebuilt-add":"displayAddPrebuiltDialog","click .so-tool-button.so-history":"displayHistoryDialog","click .so-tool-button.so-live-editor":"displayLiveEditor"},rows:null,initialize:function(e){var s=this;return this.config=_.extend({loadLiveEditor:!1,builderSupports:{}},e.config),this.config.builderSupports=_.extend({addRow:!0,editRow:!0,deleteRow:!0,moveRow:!0,addWidget:!0,editWidget:!0,deleteWidget:!0,moveWidget:!0,prebuilt:!0,history:!0,liveEditor:!0,revertToEditor:!0},this.config.builderSupports),e.config.loadLiveEditor&&this.on("builder_live_editor_added",function(){this.displayLiveEditor()}),this.dialogs={widgets:new a.dialog.widgets,row:new a.dialog.row,prebuilt:new a.dialog.prebuilt},_.each(this.dialogs,function(e,t,i){i[t].setBuilder(s)}),this.dialogs.row.setRowDialogType("create"),this.listenTo(this.model.get("rows"),"add",this.onAddRow),n(window).resize(function(e){e.target===window&&s.trigger("builder_resize")}),this.listenTo(this.model,"change:data load_panels_data",this.storeModelData),this.listenTo(this.model,"change:data load_panels_data",this.toggleWelcomeDisplay),this.on("content_change",this.handleContentChange,this),this.on("display_builder",this.handleDisplayBuilder,this),this.on("hide_builder",this.handleHideBuilder,this),this.on("builder_rendered builder_resize",this.handleBuilderSizing,this),this.on("display_builder",this.wrapEditorExpandAdjust,this),this.menu=new a.utils.menu({}),this.listenTo(this.menu,"activate_context",this.activateContextMenu),this.config.loadOnAttach&&this.on("builder_attached_to_editor",function(){this.displayAttachedBuilder({confirm:!1})},this),this},render:function(){return this.setElement(this.template()),this.$el.attr("id","siteorigin-panels-builder-"+this.cid).addClass("so-builder-container"),this.trigger("builder_rendered"),this},attach:function(e){(e=_.extend({container:!1,dialog:!1},e)).dialog?(this.dialog=new a.dialog.builder,this.dialog.builder=this):(this.$el.appendTo(e.container),this.metabox=e.container.closest(".postbox"),this.initSortable(),this.trigger("attached_to_container",e.container)),this.trigger("builder_attached"),this.supports("liveEditor")&&this.addLiveEditor(),this.supports("history")&&this.addHistoryBrowser();var t=this.$(".so-builder-toolbar"),i=this.$(".so-panels-welcome-message"),s=panelsOptions.loc.welcomeMessage,l=[];this.supports("addWidget")?l.push(s.addWidgetButton):t.find(".so-widget-add").hide(),this.supports("addRow")?l.push(s.addRowButton):t.find(".so-row-add").hide(),this.supports("prebuilt")?l.push(s.addPrebuiltButton):t.find(".so-prebuilt-add").hide();var o="";3===l.length?o=s.threeEnabled:2===l.length?o=s.twoEnabled:1===l.length?o=s.oneEnabled:0===l.length&&(o=s.addingDisabled);var n=_.template(a.helpers.utils.processTemplate(o))({items:l})+" "+s.docsMessage;return i.find(".so-message-wrapper").html(n),this},attachToEditor:function(){if("tinyMCE"!==this.config.editorType)return this;this.attachedToEditor=!0;var t=this.metabox,l=this;n("#wp-content-wrap .wp-editor-tabs").find(".wp-switch-editor").click(function(e){e.preventDefault(),n("#wp-content-editor-container").show(),n("#wp-content-wrap").removeClass("panels-active"),n("#content-resize-handle").show(),l.trigger("hide_builder")}).end().append(n('<button type="button" id="content-panels" class="hide-if-no-js wp-switch-editor switch-panels">'+t.find(".hndle span").html()+"</button>").click(function(e){l.displayAttachedBuilder({confirm:!0})&&e.preventDefault()})),this.supports("revertToEditor")&&t.find(".so-switch-to-standard").click(function(e){e.preventDefault(),confirm(panelsOptions.loc.confirm_stop_builder)&&(l.addHistoryEntry("back_to_editor"),l.model.loadPanelsData(!1),n("#wp-content-wrap").show(),t.hide(),n(window).resize(),l.attachedVisible=!1,l.trigger("hide_builder"))}).show(),t.insertAfter("#wp-content-wrap").hide().addClass("attached-to-editor");var e=this.model.get("data");_.isEmpty(e.widgets)&&_.isEmpty(e.grids)&&this.supports("revertToEditor")||this.displayAttachedBuilder({confirm:!1});var i=function(){var e=l.$(".so-builder-toolbar");if(l.$el.hasClass("so-display-narrow"))return e.css({top:0,left:0,width:"100%",position:"absolute"}),void l.$el.css("padding-top",e.outerHeight());var t=n(window).scrollTop()-l.$el.offset().top;"fixed"===n("#wpadminbar").css("position")&&(t+=n("#wpadminbar").outerHeight());var i=0,s=l.$el.outerHeight()-e.outerHeight()+20;i<t&&t<s?"fixed"!==e.css("position")&&e.css({top:n("#wpadminbar").outerHeight(),left:l.$el.offset().left,width:l.$el.outerWidth(),position:"fixed"}):e.css({top:Math.min(Math.max(t,0),l.$el.outerHeight()-e.outerHeight()+20),left:0,width:"100%",position:"absolute"}),l.$el.css("padding-top",e.outerHeight())};return this.on("builder_resize",i,this),n(document).scroll(i),i(),this.trigger("builder_attached_to_editor"),this},displayAttachedBuilder:function(e){if((e=_.extend({confirm:!0},e)).confirm){var t="undefined"!=typeof tinyMCE&&tinyMCE.get("content");if(""!==(t&&_.isFunction(t.getContent)?t.getContent():n("textarea#content").val())&&!confirm(panelsOptions.loc.confirm_use_builder))return!1}return n("#wp-content-wrap").hide(),n("#editor-expand-toggle").on("change.editor-expand",function(){n(this).prop("checked")||n("#wp-content-wrap").hide()}),this.metabox.show().find("> .inside").show(),n(window).resize(),n(document).scroll(),this.attachedVisible=!0,this.trigger("display_builder"),!0},initSortable:function(){if(!this.supports("moveRow"))return this;var o=this,e=o.$el.attr("id");return this.rowsSortable=this.$(".so-rows-container").sortable({appendTo:"#wpwrap",items:".so-row-container",handle:".so-row-move",connectWith:"#"+e+".so-rows-container,.block-editor .so-rows-container",axis:"y",tolerance:"pointer",scroll:!1,remove:function(e,t){o.model.get("rows").remove(n(t.item).data("view").model,{silent:!0}),o.model.refreshPanelsData()},receive:function(e,t){o.model.get("rows").add(n(t.item).data("view").model,{silent:!0,at:n(t.item).index()}),o.model.refreshPanelsData()},stop:function(e,t){var i=n(t.item),s=i.data("view"),l=o.model.get("rows");l.get(s.model)&&(o.addHistoryEntry("row_moved"),l.remove(s.model,{silent:!0}),l.add(s.model,{silent:!0,at:i.index()}),s.trigger("move",i.index()),o.model.refreshPanelsData())}}),this},refreshSortable:function(){_.isNull(this.rowsSortable)||this.rowsSortable.sortable("refresh")},setDataField:function(e,t){if(t=_.extend({load:!0},t),this.dataField=e,this.dataField.data("builder",this),t.load&&""!==e.val()){var i=this.dataField.val();try{i=JSON.parse(i)}catch(e){console.log("Failed to parse Page Builder layout data from supplied data field."),i={}}this.setData(i)}return this},setData:function(e){this.model.loadPanelsData(e),this.currentData=e,this.toggleWelcomeDisplay()},getData:function(){return this.model.get("data")},storeModelData:function(){var e=JSON.stringify(this.model.get("data"));n(this.dataField).val()!==e&&(n(this.dataField).val(e),n(this.dataField).trigger("change"),this.trigger("content_change"))},onAddRow:function(e,t,i){i=_.extend({noAnimate:!1},i);var s=new a.view.row({model:e});s.builder=this,s.render(),_.isUndefined(i.at)||t.length<=1?s.$el.appendTo(this.$(".so-rows-container")):s.$el.insertAfter(this.$(".so-rows-container .so-row-container").eq(i.at-1)),!1===i.noAnimate&&s.visualCreate(),this.refreshSortable(),s.resize(),this.trigger("row_added")},displayAddWidgetDialog:function(){this.dialogs.widgets.openDialog()},displayAddRowDialog:function(){var t=new a.model.row,e=new a.collection.cells([{weight:.5},{weight:.5}]);e.each(function(e){e.row=t}),t.set("cells",e),t.builder=this.model,this.dialogs.row.setRowModel(t),this.dialogs.row.openDialog()},displayAddPrebuiltDialog:function(){this.dialogs.prebuilt.openDialog()},displayHistoryDialog:function(){this.dialogs.history.openDialog()},pasteRowHandler:function(){var e=a.helpers.clipboard.getModel("row-model");!_.isEmpty(e)&&e instanceof a.model.row&&(this.addHistoryEntry("row_pasted"),e.builder=this.model,this.model.get("rows").add(e,{at:this.model.get("rows").indexOf(this.model)+1}),this.model.refreshPanelsData())},getActiveCell:function(e){if(e=_.extend({createCell:!0},e),!this.model.get("rows").length){if(!e.createCell)return null;this.model.addRow({},[{weight:1}],{noAnimate:!0})}var t=this.activeCell;return _.isEmpty(t)||-1===this.model.get("rows").indexOf(t.model.row)?this.model.get("rows").last().get("cells").first():t.model},addLiveEditor:function(){return _.isEmpty(this.config.liveEditorPreview)||(this.liveEditor=new a.view.liveEditor({builder:this,previewUrl:this.config.liveEditorPreview}),this.liveEditor.hasPreviewUrl()&&this.$(".so-builder-toolbar .so-live-editor").show(),this.trigger("builder_live_editor_added")),this},displayLiveEditor:function(){_.isUndefined(this.liveEditor)||this.liveEditor.open()},addHistoryBrowser:function(){if(_.isEmpty(this.config.liveEditorPreview))return this;this.dialogs.history=new a.dialog.history,(this.dialogs.history.builder=this).dialogs.history.entries.builder=this.model,this.dialogs.history.setRevertEntry(this.model),this.$(".so-builder-toolbar .so-history").show()},addHistoryEntry:function(e,t){_.isUndefined(t)&&(t=null),_.isUndefined(this.dialogs.history)||this.dialogs.history.entries.addEntry(e,t)},supports:function(e){return"rowAction"===e?this.supports("addRow")||this.supports("editRow")||this.supports("deleteRow"):"widgetAction"===e?this.supports("addWidget")||this.supports("editWidget")||this.supports("deleteWidget"):!_.isUndefined(this.config.builderSupports[e])&&this.config.builderSupports[e]},handleContentChange:function(){if(panelsOptions.copy_content&&this.attachedToEditor&&this.$el.is(":visible")){var e=this.model.getPanelsData();_.isEmpty(e.widgets)||n.post(panelsOptions.ajaxurl,{action:"so_panels_builder_content",panels_data:JSON.stringify(e),post_id:this.config.postId},function(e){""!==e&&this.updateEditorContent(e)}.bind(this))}},updateEditorContent:function(e){if("tinyMCE"!==this.config.editorType||"undefined"==typeof tinyMCE||_.isNull(tinyMCE.get("content"))){n(this.config.editorId).val(e).trigger("change").trigger("keyup")}else{var t=tinyMCE.get("content");t.setContent(e),t.fire("change"),t.fire("keyup")}this.triggerYoastSeoChange()},triggerYoastSeoChange:function(){if(n("#yoast_wpseo_focuskw_text_input").length){var e,t=document.getElementById("yoast_wpseo_focuskw_text_input");document.createEvent?(e=document.createEvent("HTMLEvents")).initEvent("keyup",!0,!0):(e=document.createEventObject()).eventType="keyup",e.eventName="keyup",document.createEvent?t.dispatchEvent(e):t.fireEvent("on"+e.eventType,e)}},handleDisplayBuilder:function(){var e="undefined"!=typeof tinyMCE&&tinyMCE.get("content"),t=e&&_.isFunction(e.getContent)?e.getContent():n("textarea#content").val();if((_.isEmpty(this.model.get("data"))||_.isEmpty(this.model.get("data").widgets)&&_.isEmpty(this.model.get("data").grids))&&""!==t){var i=panelsOptions.text_widget;if(_.isEmpty(i))return;this.model.loadPanelsData(this.model.getPanelsDataFromHtml(t,i)),this.model.trigger("change"),this.model.trigger("change:data")}n("#post-status-info").addClass("for-siteorigin-panels")},handleHideBuilder:function(){n("#post-status-info").show().removeClass("for-siteorigin-panels")},wrapEditorExpandAdjust:function(){try{for(var t,e=(n.hasData(window)&&n._data(window)).events.scroll,i=0;i<e.length;i++)if("editor-expand"===e[i].namespace){t=e[i],n(window).unbind("scroll",t.handler),n(window).bind("scroll",function(e){this.attachedVisible||t.handler(e)}.bind(this));break}}catch(e){return}},handleBuilderSizing:function(){var e=this.$el.width();return e&&(e<575?this.$el.addClass("so-display-narrow"):this.$el.removeClass("so-display-narrow")),this},setDialogParents:function(s,l){_.each(this.dialogs,function(e,t,i){i[t].setParent(s,l)}),this.on("add_dialog",function(e){e.setParent(s,l)},this)},toggleWelcomeDisplay:function(){this.model.get("rows").isEmpty()?this.$(".so-panels-welcome-message").show():this.$(".so-panels-welcome-message").hide()},activateContextMenu:function(t,i){var e=this;if(n.contains(e.$el.get(0),t.target)){var s=n([]).add(e.$(".so-panels-welcome-message:visible")).add(e.$(".so-rows-container > .so-row-container")).add(e.$(".so-cells > .cell")).add(e.$(".cell-wrapper > .so-widget")).filter(function(e){return i.isOverEl(n(this),t)}),l=s.last().data("view");void 0!==l&&void 0!==l.buildContextualMenu?l.buildContextualMenu(t,i):s.last().hasClass("so-panels-welcome-message")&&this.buildContextualMenu(t,i)}},buildContextualMenu:function(e,t){var i={};this.supports("addRow")&&(i.add_row={title:panelsOptions.loc.contextual.add_row}),a.helpers.clipboard.canCopyPaste()&&a.helpers.clipboard.isModel("row-model")&&this.supports("addRow")&&(i.paste_row={title:panelsOptions.loc.contextual.row_paste}),_.isEmpty(i)||t.addSection("builder-actions",{sectionTitle:panelsOptions.loc.contextual.row_actions,search:!1},i,function(e){switch(e){case"add_row":this.displayAddRowDialog();break;case"paste_row":this.pasteRowHandler()}}.bind(this))}})},{}],24:[function(e,t,i){var l=window.panels,r=jQuery;t.exports=Backbone.View.extend({template:_.template(l.helpers.utils.processTemplate(r("#siteorigin-panels-builder-cell").html())),events:{"click .cell-wrapper":"handleCellClick"},row:null,widgetSortable:null,initialize:function(){this.listenTo(this.model.get("widgets"),"add",this.onAddWidget)},render:function(){var e={weight:this.model.get("weight"),totalWeight:this.row.model.get("cells").totalWeight()};this.setElement(this.template(e)),this.$el.data("view",this);var i=this;return this.model.get("widgets").each(function(e){var t=new l.view.widget({model:e});t.cell=i,t.render(),t.$el.appendTo(i.$(".widgets-container"))}),this.initSortable(),this.initResizable(),this},initSortable:function(){if(!this.row.builder.supports("moveWidget"))return this;var o=this,e=o.row.builder,t=e.$el.attr("id"),n=e.model;return this.widgetSortable=this.$(".widgets-container").sortable({placeholder:"so-widget-sortable-highlight",connectWith:"#"+t+" .so-cells .cell .widgets-container,.block-editor .so-cells .cell .widgets-container",tolerance:"pointer",scroll:!1,over:function(e,t){o.row.builder.trigger("widget_sortable_move")},remove:function(e,t){o.model.get("widgets").remove(r(t.item).data("view").model,{silent:!0}),n.refreshPanelsData()},receive:function(e,t){var i=r(t.item).data("view").model;i.cell=o.model,o.model.get("widgets").add(i,{silent:!0,at:r(t.item).index()}),n.refreshPanelsData()},stop:function(e,t){var i=r(t.item),s=i.data("view"),l=i.closest(".cell").data("view");o.model.get("widgets").get(s.model)&&(o.row.builder.addHistoryEntry("widget_moved"),s.model.moveToCell(l.model,{},i.index()),s.cell=l,n.refreshPanelsData())},helper:function(e,t){var i=t.clone().css({width:t.outerWidth(),"z-index":1e4,position:"fixed"}).addClass("widget-being-dragged").appendTo("body");return 720<t.outerWidth()&&i.animate({"margin-left":e.pageX-t.offset().left-240,width:480},"fast"),i}}),this},refreshSortable:function(){_.isNull(this.widgetSortable)||this.widgetSortable.sortable("refresh")},initResizable:function(){if(!this.row.builder.supports("editRow"))return this;var o,n=this.$(".resize-handle").css("position","absolute"),e=this.row.$el,a=this;return n.draggable({axis:"x",containment:e,start:function(e,t){if(o=a.$el.prev().data("view"),!_.isUndefined(o)){var i=a.$el.clone().appendTo(t.helper).css({position:"absolute",top:"0",width:a.$el.outerWidth(),left:5,height:a.$el.outerHeight()});i.find(".resize-handle").remove();var s=o.$el.clone().appendTo(t.helper).css({position:"absolute",top:"0",width:o.$el.outerWidth(),right:5,height:o.$el.outerHeight()});s.find(".resize-handle").remove(),r(this).data({newCellClone:i,prevCellClone:s})}},drag:function(e,t){var i=a.row.$el.width()+10,s=a.model.get("weight")-(t.position.left+n.outerWidth()/2)/i,l=o.model.get("weight")+(t.position.left+n.outerWidth()/2)/i;r(this).data("newCellClone").css("width",i*s).find(".preview-cell-weight").html(Math.round(1e3*s)/10),r(this).data("prevCellClone").css("width",i*l).find(".preview-cell-weight").html(Math.round(1e3*l)/10)},stop:function(e,t){r(this).data("newCellClone").remove(),r(this).data("prevCellClone").remove();var i=a.row.$el.width()+10,s=a.model.get("weight")-(t.position.left+n.outerWidth()/2)/i,l=o.model.get("weight")+(t.position.left+n.outerWidth()/2)/i;.02<s&&.02<l&&(a.row.builder.addHistoryEntry("cell_resized"),a.model.set("weight",s),o.model.set("weight",l),a.row.resize()),t.helper.css("left",-n.outerWidth()/2),a.row.builder.model.refreshPanelsData()}}),this},onAddWidget:function(e,t,i){i=_.extend({noAnimate:!1},i);var s=new l.view.widget({model:e});s.cell=this,_.isUndefined(e.isDuplicate)&&(e.isDuplicate=!1),s.render({loadForm:e.isDuplicate}),_.isUndefined(i.at)||t.length<=1?s.$el.appendTo(this.$(".widgets-container")):s.$el.insertAfter(this.$(".widgets-container .so-widget").eq(i.at-1)),!1===i.noAnimate&&s.visualCreate(),this.refreshSortable(),this.row.resize(),this.row.builder.trigger("widget_added",s)},handleCellClick:function(e){this.row.builder.$el.find(".so-cells .cell").removeClass("cell-selected"),this.row.builder.activeCell!==this||this.model.get("widgets").length?(this.$el.addClass("cell-selected"),this.row.builder.activeCell=this):this.row.builder.activeCell=null},pasteHandler:function(){var e=l.helpers.clipboard.getModel("widget-model");!_.isEmpty(e)&&e instanceof l.model.widget&&(this.row.builder.addHistoryEntry("widget_pasted"),e.cell=this.model,this.model.get("widgets").add(e),this.row.builder.model.refreshPanelsData())},buildContextualMenu:function(e,t){var i=this;t.hasSection("add-widget-below")||t.addSection("add-widget-cell",{sectionTitle:panelsOptions.loc.contextual.add_widget_cell,searchPlaceholder:panelsOptions.loc.contextual.search_widgets,defaultDisplay:panelsOptions.contextual.default_widgets},panelsOptions.widgets,function(e){i.row.builder.trigger("before_user_adds_widget"),i.row.builder.addHistoryEntry("widget_added");var t=new l.model.widget({class:e});t.cell=i.model,t.cell.get("widgets").add(t),i.row.builder.model.refreshPanelsData(),i.row.builder.trigger("after_user_adds_widget",t)});var s={};this.row.builder.supports("addWidget")&&l.helpers.clipboard.isModel("widget-model")&&(s.paste={title:panelsOptions.loc.contextual.cell_paste_widget}),_.isEmpty(s)||t.addSection("cell-actions",{sectionTitle:panelsOptions.loc.contextual.cell_actions,search:!1},s,function(e){switch(e){case"paste":this.pasteHandler()}this.row.builder.model.refreshPanelsData()}.bind(this)),this.row.buildContextualMenu(e,t)}})},{}],25:[function(e,t,i){var o=window.panels,d=jQuery;t.exports=Backbone.View.extend({dialogTemplate:_.template(o.helpers.utils.processTemplate(d("#siteorigin-panels-dialog").html())),dialogTabTemplate:_.template(o.helpers.utils.processTemplate(d("#siteorigin-panels-dialog-tab").html())),tabbed:!1,rendered:!1,builder:!1,className:"so-panels-dialog-wrapper",dialogClass:"",dialogIcon:"",parentDialog:!1,dialogOpen:!1,editableLabel:!1,events:{"click .so-close":"closeDialog","click .so-nav.so-previous":"navToPrevious","click .so-nav.so-next":"navToNext"},initialize:function(){this.once("open_dialog",this.render),this.once("open_dialog",this.attach),this.once("open_dialog",this.setDialogClass),this.trigger("initialize_dialog",this),_.isUndefined(this.initializeDialog)||this.initializeDialog(),_.bindAll(this,"initSidebars","hasSidebar","onResize","toggleLeftSideBar","toggleRightSideBar")},getNextDialog:function(){return null},getPrevDialog:function(){return null},setDialogClass:function(){""!==this.dialogClass&&this.$(".so-panels-dialog").addClass(this.dialogClass)},setBuilder:function(e){return(this.builder=e).trigger("add_dialog",this,this.builder),this},attach:function(){return this.$el.appendTo("body"),this},parseDialogContent:function(e,t){t=_.extend({cid:this.cid},t);var i=d(_.template(o.helpers.utils.processTemplate(e))(t)),s={title:i.find(".title").html(),buttons:i.find(".buttons").html(),content:i.find(".content").html()};return i.has(".left-sidebar")&&(s.left_sidebar=i.find(".left-sidebar").html()),i.has(".right-sidebar")&&(s.right_sidebar=i.find(".right-sidebar").html()),s},renderDialog:function(e){if(e=_.extend({editableLabel:this.editableLabel,dialogIcon:this.dialogIcon},e),this.$el.html(this.dialogTemplate(e)).hide(),this.$el.data("view",this),this.$el.addClass("so-panels-dialog-wrapper"),!1!==this.parentDialog){var t=d('<h3 class="so-parent-link"></h3>').html(this.parentDialog.text+'<div class="so-separator"></div>');t.click(function(e){e.preventDefault(),this.closeDialog(),this.parentDialog.dialog.openDialog()}.bind(this)),this.$(".so-title-bar .so-title").before(t)}return this.$(".so-title-bar .so-title-editable").length&&this.initEditableLabel(),setTimeout(this.initSidebars,1),this},initSidebars:function(){var e=this.$(".so-show-left-sidebar").hide(),t=this.$(".so-show-right-sidebar").hide(),i=this.hasSidebar("left"),s=this.hasSidebar("right");(i||s)&&(d(window).on("resize",this.onResize),i&&(e.show(),e.on("click",this.toggleLeftSideBar)),s&&(t.show(),t.on("click",this.toggleRightSideBar))),this.onResize()},initTabs:function(){var e=this.$(".so-sidebar-tabs li a");if(0===e.length)return this;var l=this;return e.click(function(e){e.preventDefault();var t=d(this);l.$(".so-sidebar-tabs li").removeClass("tab-active"),l.$(".so-content .so-content-tabs > *").hide(),t.parent().addClass("tab-active");var i=t.attr("href");if(!_.isUndefined(i)&&"#"===i.charAt(0)){var s=i.split("#")[1];l.$(".so-content .so-content-tabs .tab-"+s).show()}l.trigger("tab_click",t)}),this.$(".so-sidebar-tabs li a").first().click(),this},initToolbar:function(){this.$(".so-toolbar .so-buttons .so-toolbar-button").click(function(e){e.preventDefault(),this.trigger("button_click",d(e.currentTarget))}.bind(this)),this.$(".so-toolbar .so-buttons .so-dropdown-button").click(function(e){e.preventDefault();var t=d(e.currentTarget).siblings(".so-dropdown-links-wrapper");t.is(".hidden")?t.removeClass("hidden"):t.addClass("hidden")}.bind(this)),d("html").click(function(l){this.$(".so-dropdown-links-wrapper").not(".hidden").each(function(e,t){var i=d(t),s=d(l.target);0!==s.length&&(s.is(".so-needs-confirm")&&!s.is(".so-confirmed")||s.is(".so-dropdown-button"))||i.addClass("hidden")})}.bind(this))},initEditableLabel:function(){var l=this.$(".so-title-bar .so-title-editable");l.keypress(function(e){var t="keypress"===e.type&&13===e.keyCode;if(t){var i=d(":tabbable"),s=i.index(l);i.eq(s+1).focus(),window.getSelection().removeAllRanges()}return!t}).blur(function(){var e=l.text().replace(/^\s+|\s+$/gm,"");e!==l.data("original-value").replace(/^\s+|\s+$/gm,"")&&(l.text(e),this.trigger("edit_label",e))}.bind(this)),l.focus(function(){l.data("original-value",l.text()),o.helpers.utils.selectElementContents(this)})},setupDialog:function(){this.openDialog(),this.closeDialog()},refreshDialogNav:function(){this.$(".so-title-bar .so-nav").show().removeClass("so-disabled");var e=this.getNextDialog(),t=this.$(".so-title-bar .so-next"),i=this.getPrevDialog(),s=this.$(".so-title-bar .so-previous");null===e?t.hide():!1===e&&t.addClass("so-disabled"),null===i?s.hide():!1===i&&s.addClass("so-disabled")},openDialog:function(e){(e=_.extend({silent:!1},e)).silent||this.trigger("open_dialog"),this.dialogOpen=!0,this.refreshDialogNav(),o.helpers.pageScroll.lock(),this.onResize(),this.$el.show(),e.silent||(this.trigger("open_dialog_complete"),this.builder.trigger("open_dialog",this),d(document).trigger("open_dialog",this))},closeDialog:function(e){(e=_.extend({silent:!1},e)).silent||this.trigger("close_dialog"),this.dialogOpen=!1,this.$el.hide(),o.helpers.pageScroll.unlock(),e.silent||(this.trigger("close_dialog_complete"),this.builder.trigger("close_dialog",this))},navToPrevious:function(){this.closeDialog();var e=this.getPrevDialog();null!==e&&!1!==e&&e.openDialog()},navToNext:function(){this.closeDialog();var e=this.getNextDialog();null!==e&&!1!==e&&e.openDialog()},getFormValues:function(e){_.isUndefined(e)&&(e=".so-content");var a,t=this.$(e),r={};return t.find("[name]").each(function(){var t=d(this);try{var e=/([A-Za-z_]+)\[(.*)\]/.exec(t.attr("name"));if(_.isEmpty(e))return!0;_.isUndefined(e[2])?a=t.attr("name"):(a=e[2].split("][")).unshift(e[1]),a=a.map(function(e){return!isNaN(parseFloat(e))&&isFinite(e)?parseInt(e):e});var i=r,s=null,l=!!_.isString(t.attr("type"))&&t.attr("type").toLowerCase();if("checkbox"===l)s=t.is(":checked")?""===t.val()||t.val():null;else if("radio"===l){if(!t.is(":checked"))return;s=t.val()}else if("SELECT"===t.prop("tagName")){var o=t.find("option:selected");1===o.length?s=t.find("option:selected").val():1<o.length&&(s=_.map(t.find("option:selected"),function(e,t){return d(e).val()}))}else s=t.val();if(!_.isUndefined(t.data("panels-filter")))switch(t.data("panels-filter")){case"json_parse":try{s=JSON.parse(s)}catch(e){s=""}}if(null!==s)for(var n=0;n<a.length;n++)n===a.length-1?""===a[n]?i.push(s):i[a[n]]=s:(_.isUndefined(i[a[n]])&&(""===a[n+1]?i[a[n]]=[]:i[a[n]]={}),i=i[a[n]])}catch(e){console.log("Field ["+t.attr("name")+"] could not be processed and was skipped - "+e.message)}}),r},setStatusMessage:function(e,t,i){var s=i?'<span class="dashicons dashicons-warning"></span>'+e:e;this.$(".so-toolbar .so-status").html(s),!_.isUndefined(t)&&t?this.$(".so-toolbar .so-status").addClass("so-panels-loading"):this.$(".so-toolbar .so-status").removeClass("so-panels-loading")},setParent:function(e,t){this.parentDialog={text:e,dialog:t}},onResize:function(){var s=window.matchMedia("(max-width: 980px)");["left","right"].forEach(function(e){var t=this.$(".so-"+e+"-sidebar"),i=this.$(".so-show-"+e+"-sidebar");this.hasSidebar(e)?(i.hide(),s.matches?(i.show(),i.closest(".so-title-bar").addClass("so-has-"+e+"-button"),t.hide(),t.closest(".so-panels-dialog").removeClass("so-panels-dialog-has-"+e+"-sidebar")):(i.hide(),i.closest(".so-title-bar").removeClass("so-has-"+e+"-button"),t.show(),t.closest(".so-panels-dialog").addClass("so-panels-dialog-has-"+e+"-sidebar"))):(t.hide(),i.hide())}.bind(this))},hasSidebar:function(e){return 0<this.$(".so-"+e+"-sidebar").children().length},toggleLeftSideBar:function(){this.toggleSidebar("left")},toggleRightSideBar:function(){this.toggleSidebar("right")},toggleSidebar:function(e){var t=this.$(".so-"+e+"-sidebar");t.is(":visible")?t.hide():t.show()}})},{}],26:[function(e,t,i){var s=window.panels,o=jQuery;t.exports=Backbone.View.extend({template:_.template(s.helpers.utils.processTemplate(o("#siteorigin-panels-live-editor").html())),previewScrollTop:0,loadTimes:[],previewFrameId:1,previewUrl:null,previewIframe:null,events:{"click .live-editor-close":"close","click .live-editor-save":"closeAndSave","click .live-editor-collapse":"collapse","click .live-editor-mode":"mobileToggle"},initialize:function(e){e=_.extend({builder:!1,previewUrl:!1},e),_.isEmpty(e.previewUrl)&&(e.previewUrl=panelsOptions.ajaxurl+"&action=so_panels_live_editor_preview"),this.builder=e.builder,this.previewUrl=e.previewUrl,this.listenTo(this.builder.model,"refresh_panels_data",this.handleRefreshData),this.listenTo(this.builder.model,"load_panels_data",this.handleLoadData)},render:function(){if(this.setElement(this.template()),this.$el.hide(),0<o("#submitdiv #save-post").length){var e=this.$el.find(".live-editor-save");e.text(e.data("save"))}var t=!1;o(document).mousedown(function(){t=!0}).mouseup(function(){t=!1});var i=this;return this.$el.on("mouseenter",".so-widget-wrapper",function(){var e=o(this).data("live-editor-preview-widget");t||void 0===e||!e.length||i.$(".so-preview-overlay").is(":visible")||(i.highlightElement(e),i.scrollToElement(e))}),this.$el.on("mouseleave",".so-widget-wrapper",function(){this.resetHighlights()}.bind(this)),this.listenTo(this.builder,"open_dialog",function(){this.resetHighlights()}),this},attach:function(){this.$el.appendTo("body")},open:function(){if(""===this.$el.html()&&this.render(),0===this.$el.closest("body").length&&this.attach(),s.helpers.pageScroll.lock(),this.$el.is(":visible"))return this;if(this.$el.show(),this.refreshPreview(this.builder.model.getPanelsData()),this.originalContainer=this.builder.$el.parent(),this.builder.$el.appendTo(this.$(".so-live-editor-builder")),this.builder.$(".so-tool-button.so-live-editor").hide(),this.builder.trigger("builder_resize"),"auto-draft"===o("#original_post_status").val()&&!this.autoSaved){var e=this;wp.autosave&&(""===o('#title[name="post_title"]').val()&&o('#title[name="post_title"]').val(panelsOptions.loc.draft).trigger("keydown"),o(document).one("heartbeat-tick.autosave",function(){e.autoSaved=!0,e.refreshPreview(e.builder.model.getPanelsData())}),wp.autosave.server.triggerSave())}},close:function(){if(!this.$el.is(":visible"))return this;this.$el.hide(),s.helpers.pageScroll.unlock(),this.builder.$el.appendTo(this.originalContainer),this.builder.$(".so-tool-button.so-live-editor").show(),this.builder.trigger("builder_resize")},closeAndSave:function(){this.close(),o('#submitdiv input[type="submit"][name="save"]').click()},collapse:function(){this.$el.toggleClass("so-collapsed")},highlightElement:function(e){_.isUndefined(this.resetHighlightTimeout)||clearTimeout(this.resetHighlightTimeout),this.previewIframe.contents().find("body").find(".panel-grid .panel-grid-cell .so-panel").filter(function(){return 0===o(this).parents(".so-panel").length}).not(e).addClass("so-panels-faded"),e.removeClass("so-panels-faded").addClass("so-panels-highlighted")},resetHighlights:function(){var e=this.previewIframe.contents().find("body");this.resetHighlightTimeout=setTimeout(function(){e.find(".panel-grid .panel-grid-cell .so-panel").removeClass("so-panels-faded so-panels-highlighted")},100)},scrollToElement:function(e){this.$(".so-preview iframe")[0].contentWindow.liveEditorScrollTo(e)},handleRefreshData:function(e){if(!this.$el.is(":visible"))return this;this.refreshPreview(e)},handleLoadData:function(){if(!this.$el.is(":visible"))return this;this.refreshPreview(this.builder.model.getPanelsData())},refreshPreview:function(e){var t=this.loadTimes.length?_.reduce(this.loadTimes,function(e,t){return e+t},0)/this.loadTimes.length:1e3;_.isNull(this.previewIframe)||this.$(".so-preview-overlay").is(":visible")||(this.previewScrollTop=this.previewIframe.contents().scrollTop()),this.$(".so-preview-overlay").show(),this.$(".so-preview-overlay .so-loading-bar").clearQueue().css("width","0%").animate({width:"100%"},parseInt(t)+100),this.postToIframe({live_editor_panels_data:JSON.stringify(e),live_editor_post_ID:this.builder.config.postId},this.previewUrl,this.$(".so-preview")),this.previewIframe.data("load-start",(new Date).getTime())},postToIframe:function(e,t,i){_.isNull(this.previewIframe)||this.previewIframe.remove();var s="siteorigin-panels-live-preview-"+this.previewFrameId;this.previewIframe=o('<iframe src="javascript:false;" />').attr({id:s,name:s}).appendTo(i),this.setupPreviewFrame(this.previewIframe);var l=o('<form id="soPostToPreviewFrame" method="post" />').attr({id:s,target:this.previewIframe.attr("id"),action:t}).appendTo("body");return o.each(e,function(e,t){o('<input type="hidden" />').attr({name:e,value:t}).appendTo(l)}),l.submit().remove(),this.previewFrameId++,this.previewIframe},setupPreviewFrame:function(e){var l=this;e.data("iframeready",!1).on("iframeready",function(){var e=o(this),t=e.contents();if(!e.data("iframeready")){e.data("iframeready",!0),void 0!==e.data("load-start")&&(l.loadTimes.unshift((new Date).getTime()-e.data("load-start")),_.isEmpty(l.loadTimes)||(l.loadTimes=l.loadTimes.slice(0,4))),setTimeout(function(){t.scrollTop(l.previewScrollTop),l.$(".so-preview-overlay").hide()},100);var i=t.find("#pl-"+l.builder.config.postId);i.find(".panel-grid .panel-grid-cell .so-panel").filter(function(){return o(this).closest(".panel-layout").is(i)}).each(function(e,t){var i=o(t),s=l.$(".so-live-editor-builder .so-widget-wrapper").eq(i.data("index"));s.data("live-editor-preview-widget",i),i.css({cursor:"pointer"}).mouseenter(function(){s.parent().addClass("so-hovered"),l.highlightElement(i)}).mouseleave(function(){s.parent().removeClass("so-hovered"),l.resetHighlights()}).click(function(e){e.preventDefault(),s.find(".title h4").click()})}),t.find("a").css({"pointer-events":"none"}).click(function(e){e.preventDefault()})}}).on("load",function(){var e=o(this);e.data("iframeready")||e.trigger("iframeready")})},hasPreviewUrl:function(){return""!==this.$("form.live-editor-form").attr("action")},mobileToggle:function(e){var t=o(e.currentTarget);this.$(".live-editor-mode").not(t).removeClass("so-active"),t.addClass("so-active"),this.$el.removeClass("live-editor-desktop-mode live-editor-tablet-mode live-editor-mobile-mode").addClass("live-editor-"+t.data("mode")+"-mode")}})},{}],27:[function(e,t,i){var n=window.panels,l=jQuery;t.exports=Backbone.View.extend({template:_.template(n.helpers.utils.processTemplate(l("#siteorigin-panels-builder-row").html())),events:{"click .so-row-settings":"editSettingsHandler","click .so-row-duplicate":"duplicateHandler","click .so-row-delete":"confirmedDeleteHandler","click .so-row-color":"rowColorChangeHandler"},builder:null,dialog:null,initialize:function(){var e=this.model.get("cells");this.listenTo(e,"add",this.handleCellAdd),this.listenTo(e,"remove",this.handleCellRemove),this.listenTo(this.model,"reweight_cells",this.resize),this.listenTo(this.model,"destroy",this.onModelDestroy);var t=this;e.each(function(e){t.listenTo(e.get("widgets"),"add",t.resize)}),e.on("add",function(e){t.listenTo(e.get("widgets"),"add",t.resize)},this),this.listenTo(this.model,"change:label",this.onLabelChange)},render:function(){var e=this.model.has("color_label")?this.model.get("color_label"):1,t=this.model.has("label")?this.model.get("label"):"";this.setElement(this.template({rowColorLabel:e,rowLabel:t})),this.$el.data("view",this);var i=this;return this.model.get("cells").each(function(e){var t=new n.view.cell({model:e});t.row=i,t.render(),t.$el.appendTo(i.$(".so-cells"))}),this.builder.supports("rowAction")?(this.builder.supports("editRow")||(this.$(".so-row-toolbar .so-dropdown-links-wrapper .so-row-settings").parent().remove(),this.$el.addClass("so-row-no-edit")),this.builder.supports("addRow")||(this.$(".so-row-toolbar .so-dropdown-links-wrapper .so-row-duplicate").parent().remove(),this.$el.addClass("so-row-no-duplicate")),this.builder.supports("deleteRow")||(this.$(".so-row-toolbar .so-dropdown-links-wrapper .so-row-delete").parent().remove(),this.$el.addClass("so-row-no-delete"))):(this.$(".so-row-toolbar .so-dropdown-wrapper").remove(),this.$el.addClass("so-row-no-actions")),this.builder.supports("moveRow")||(this.$(".so-row-toolbar .so-row-move").remove(),this.$el.addClass("so-row-no-move")),l.trim(this.$(".so-row-toolbar").html()).length||this.$(".so-row-toolbar").remove(),this.listenTo(this.builder,"widget_sortable_move",this.resize),this.listenTo(this.builder,"builder_resize",this.resize),this.resize(),this},visualCreate:function(){this.$el.hide().fadeIn("fast")},resize:function(e){if(this.$el.is(":visible")){this.$(".so-cells .cell-wrapper").css("min-height",0),this.$(".so-cells .resize-handle").css("height",0);var t=0;this.$(".so-cells .cell").each(function(){t=Math.max(t,l(this).height()),l(this).css("width",100*l(this).data("view").model.get("weight")+"%")}),this.$(".so-cells .cell-wrapper").css("min-height",Math.max(t,63)),this.$(".so-cells .resize-handle").css("height",this.$(".so-cells .cell-wrapper").outerHeight())}},onModelDestroy:function(){this.remove()},visualDestroyModel:function(){this.builder.addHistoryEntry("row_deleted");var e=this;this.$el.fadeOut("normal",function(){e.model.destroy(),e.builder.model.refreshPanelsData()})},onLabelChange:function(e,t){0==this.$(".so-row-label").length?this.$(".so-row-toolbar").prepend('<h3 class="so-row-label">'+t+"</h3>"):this.$(".so-row-label").text(t)},duplicateHandler:function(){this.builder.addHistoryEntry("row_duplicated");var e=this.model.clone(this.builder.model);this.builder.model.get("rows").add(e,{at:this.builder.model.get("rows").indexOf(this.model)+1}),this.builder.model.refreshPanelsData()},copyHandler:function(){n.helpers.clipboard.setModel(this.model)},pasteHandler:function(){var e=n.helpers.clipboard.getModel("row-model");!_.isEmpty(e)&&e instanceof n.model.row&&(this.builder.addHistoryEntry("row_pasted"),e.builder=this.builder.model,this.builder.model.get("rows").add(e,{at:this.builder.model.get("rows").indexOf(this.model)+1}),this.builder.model.refreshPanelsData())},confirmedDeleteHandler:function(e){var t=l(e.target);if(t.hasClass("dashicons")&&(t=t.parent()),t.hasClass("so-confirmed"))this.visualDestroyModel();else{var i=t.html();t.addClass("so-confirmed").html('<span class="dashicons dashicons-yes"></span>'+panelsOptions.loc.dropdown_confirm),setTimeout(function(){t.removeClass("so-confirmed").html(i)},2500)}},editSettingsHandler:function(){if(this.builder.supports("editRow"))return null===this.dialog&&(this.dialog=new n.dialog.row,this.dialog.setBuilder(this.builder).setRowModel(this.model),this.dialog.rowView=this),this.dialog.openDialog(),this},deleteHandler:function(){return this.model.destroy(),this},rowColorChangeHandler:function(e){this.$(".so-row-color").removeClass("so-row-color-selected");var t=l(e.target),i=t.data("color-label"),s=this.model.has("color_label")?this.model.get("color_label"):1;t.addClass("so-row-color-selected"),this.$el.removeClass("so-row-color-"+s),this.$el.addClass("so-row-color-"+i),this.model.set("color_label",i)},handleCellAdd:function(e){var t=new n.view.cell({model:e});t.row=this,t.render(),t.$el.appendTo(this.$(".so-cells"))},handleCellRemove:function(t){this.$(".so-cells > .cell").each(function(){var e=l(this).data("view");_.isUndefined(e)||e.model.cid===t.cid&&e.remove()})},buildContextualMenu:function(e,t){for(var i=[],s=1;s<5;s++)i.push({title:s+" "+panelsOptions.loc.contextual.column});this.builder.supports("addRow")&&t.addSection("add-row",{sectionTitle:panelsOptions.loc.contextual.add_row,search:!1},i,function(e){this.builder.addHistoryEntry("row_added");for(var t=Number(e)+1,i=[],s=0;s<t;s++)i.push({weight:100/t});var l=new n.model.row({collection:this.collection}),o=new n.collection.cells(i);o.each(function(e){e.row=l}),l.setCells(o),l.builder=this.builder.model,this.builder.model.get("rows").add(l,{at:this.builder.model.get("rows").indexOf(this.model)+1}),this.builder.model.refreshPanelsData()}.bind(this));var l={};this.builder.supports("editRow")&&(l.edit={title:panelsOptions.loc.contextual.row_edit}),n.helpers.clipboard.canCopyPaste()&&(l.copy={title:panelsOptions.loc.contextual.row_copy},this.builder.supports("addRow")&&n.helpers.clipboard.isModel("row-model")&&(l.paste={title:panelsOptions.loc.contextual.row_paste})),this.builder.supports("addRow")&&(l.duplicate={title:panelsOptions.loc.contextual.row_duplicate}),this.builder.supports("deleteRow")&&(l.delete={title:panelsOptions.loc.contextual.row_delete,confirm:!0}),_.isEmpty(l)||t.addSection("row-actions",{sectionTitle:panelsOptions.loc.contextual.row_actions,search:!1},l,function(e){switch(e){case"edit":this.editSettingsHandler();break;case"copy":this.copyHandler();break;case"paste":this.pasteHandler();break;case"duplicate":this.duplicateHandler();break;case"delete":this.visualDestroyModel()}}.bind(this))}})},{}],28:[function(e,t,i){window.panels;var d=jQuery;t.exports=Backbone.View.extend({stylesLoaded:!1,initialize:function(){},render:function(e,t,i){if(!_.isUndefined(e)){i=_.extend({builderType:"",dialog:null},i),this.$el.addClass("so-visual-styles so-"+e+"-styles so-panels-loading");var s={builderType:i.builderType};return"cell"===e&&(s.index=i.index),d.post(panelsOptions.ajaxurl,{action:"so_panels_style_form",type:e,style:this.model.get("style"),args:JSON.stringify(s),postId:t},null,"html").done(function(e){this.$el.html(e),this.setupFields(),this.stylesLoaded=!0,this.trigger("styles_loaded",!_.isEmpty(e)),_.isNull(i.dialog)||i.dialog.trigger("styles_loaded",!_.isEmpty(e))}.bind(this)).fail(function(e){var t;t=e&&e.responseText?e.responseText:panelsOptions.forms.loadingFailed,this.$el.html(t)}.bind(this)).always(function(){this.$el.removeClass("so-panels-loading")}.bind(this)),this}},attach:function(e){e.append(this.$el)},detach:function(){this.$el.detach()},setupFields:function(){this.$(".style-section-wrapper").each(function(){var t=d(this);t.find(".style-section-head").click(function(e){e.preventDefault(),t.find(".style-section-fields").slideToggle("fast")})}),_.isUndefined(d.fn.wpColorPicker)||(_.isObject(panelsOptions.wpColorPickerOptions.palettes)&&!d.isArray(panelsOptions.wpColorPickerOptions.palettes)&&(panelsOptions.wpColorPickerOptions.palettes=d.map(panelsOptions.wpColorPickerOptions.palettes,function(e){return e})),this.$(".so-wp-color-field").wpColorPicker(panelsOptions.wpColorPickerOptions)),this.$(".style-field-image").each(function(){var s=null,l=d(this);l.find(".so-image-selector").click(function(e){e.preventDefault(),null===s&&(s=wp.media({title:"choose",library:{type:"image"},button:{text:"Done",close:!0}})).on("select",function(){var t=s.state().get("selection").first().attributes,i=t.url;if(!_.isUndefined(t.sizes))try{i=t.sizes.thumbnail.url}catch(e){i=t.sizes.full.url}l.find(".current-image").css("background-image","url("+i+")"),l.find(".so-image-selector > input").val(t.id),l.find(".remove-image").removeClass("hidden")}),s.open()}),l.find(".remove-image").click(function(e){e.preventDefault(),l.find(".current-image").css("background-image","none"),l.find(".so-image-selector > input").val(""),l.find(".remove-image").addClass("hidden")})}),this.$(".style-field-measurement").each(function(){var e=d(this),n=e.find('input[type="text"]'),a=e.find("select"),r=e.find('input[type="hidden"]');n.focus(function(){d(this).select()});!function(e){if(""!==e){var t=/(?:([0-9\.,\-]+)(.*))+/,i=r.val().split(" "),s=[];for(var l in i){var o=t.exec(i[l]);_.isNull(o)||_.isUndefined(o[1])||_.isUndefined(o[2])||(s.push(o[1]),a.val(o[2]))}1===n.length?n.val(s.join(" ")):(1===s.length?s=[s[0],s[0],s[0],s[0]]:2===s.length?s=[s[0],s[1],s[0],s[1]]:3===s.length&&(s=[s[0],s[1],s[2],s[1]]),n.each(function(e,t){d(t).val(s[e])}))}}(r.val());var t=function(e){if(1===n.length){var t=n.val().split(" ").filter(function(e){return""!==e}).map(function(e){return e+a.val()}).join(" ");r.val(t)}else{var i=d(e.target),s=[],l=[],o=[];n.each(function(e,t){var i=""!==d(t).val()?parseFloat(d(t).val()):null;s.push(i),null===i?l.push(e):o.push(e)}),3===l.length&&o[0]===n.index(i)&&(n.val(i.val()),s=[i.val(),i.val(),i.val(),i.val()]),JSON.stringify(s)===JSON.stringify([null,null,null,null])?r.val(""):r.val(s.map(function(e){return(null===e?0:e)+a.val()}).join(" "))}};n.change(t),a.change(t)})}})},{}],29:[function(e,t,i){var s=window.panels,l=jQuery;t.exports=Backbone.View.extend({template:_.template(s.helpers.utils.processTemplate(l("#siteorigin-panels-builder-widget").html())),cell:null,dialog:null,events:{"click .widget-edit":"editHandler","click .title h4":"editHandler","click .actions .widget-duplicate":"duplicateHandler","click .actions .widget-delete":"deleteHandler"},initialize:function(){this.listenTo(this.model,"destroy",this.onModelDestroy),this.listenTo(this.model,"change:values",this.onModelChange),this.listenTo(this.model,"change:label",this.onLabelChange)},render:function(e){if(e=_.extend({loadForm:!1},e),this.setElement(this.template({title:this.model.getWidgetField("title"),description:this.model.getTitle(),widget_class:this.model.attributes.class})),this.$el.data("view",this),this.cell.row.builder.supports("editWidget")&&!this.model.get("read_only")||(this.$(".actions .widget-edit").remove(),this.$el.addClass("so-widget-no-edit")),this.cell.row.builder.supports("addWidget")||(this.$(".actions .widget-duplicate").remove(),this.$el.addClass("so-widget-no-duplicate")),this.cell.row.builder.supports("deleteWidget")||(this.$(".actions .widget-delete").remove(),this.$el.addClass("so-widget-no-delete")),this.cell.row.builder.supports("moveWidget")||this.$el.addClass("so-widget-no-move"),l.trim(this.$(".actions").html()).length||this.$(".actions").remove(),this.model.get("read_only")&&this.$el.addClass("so-widget-read-only"),0===_.size(this.model.get("values"))||e.loadForm){var t=this.getEditDialog();t.once("form_loaded",t.saveWidget,t),t.setupDialog()}return this.listenTo(this.cell.row.builder,"after_user_adds_widget",this.afterUserAddsWidgetHandler),this},visualCreate:function(){this.$el.hide().fadeIn("fast")},getEditDialog:function(){return null===this.dialog&&(this.dialog=new s.dialog.widget({model:this.model}),this.dialog.setBuilder(this.cell.row.builder),this.dialog.widgetView=this),this.dialog},editHandler:function(){return!this.cell.row.builder.supports("editWidget")||this.model.get("read_only")||this.getEditDialog().openDialog(),this},duplicateHandler:function(){this.cell.row.builder.addHistoryEntry("widget_duplicated");var e=this.model.clone(this.model.cell);return this.cell.model.get("widgets").add(e,{at:this.model.collection.indexOf(this.model)+1}),this.cell.row.builder.model.refreshPanelsData(),this},copyHandler:function(){s.helpers.clipboard.setModel(this.model)},deleteHandler:function(){return this.visualDestroyModel(),this},onModelChange:function(){this.$(".description").html(this.model.getTitle())},onLabelChange:function(e){this.$(".title > h4").text(e.getWidgetField("title"))},onModelDestroy:function(){this.remove()},visualDestroyModel:function(){return this.cell.row.builder.addHistoryEntry("widget_deleted"),this.$el.fadeOut("fast",function(){this.cell.row.resize(),this.model.destroy(),this.cell.row.builder.model.refreshPanelsData(),this.remove()}.bind(this)),this},buildContextualMenu:function(e,t){this.cell.row.builder.supports("addWidget")&&t.addSection("add-widget-below",{sectionTitle:panelsOptions.loc.contextual.add_widget_below,searchPlaceholder:panelsOptions.loc.contextual.search_widgets,defaultDisplay:panelsOptions.contextual.default_widgets},panelsOptions.widgets,function(e){this.cell.row.builder.trigger("before_user_adds_widget"),this.cell.row.builder.addHistoryEntry("widget_added");var t=new s.model.widget({class:e});t.cell=this.cell.model,this.cell.model.get("widgets").add(t,{at:this.model.collection.indexOf(this.model)+1}),this.cell.row.builder.model.refreshPanelsData(),this.cell.row.builder.trigger("after_user_adds_widget",t)}.bind(this));var i={};this.cell.row.builder.supports("editWidget")&&!this.model.get("read_only")&&(i.edit={title:panelsOptions.loc.contextual.widget_edit}),s.helpers.clipboard.canCopyPaste()&&(i.copy={title:panelsOptions.loc.contextual.widget_copy}),this.cell.row.builder.supports("addWidget")&&(i.duplicate={title:panelsOptions.loc.contextual.widget_duplicate}),this.cell.row.builder.supports("deleteWidget")&&(i.delete={title:panelsOptions.loc.contextual.widget_delete,confirm:!0}),_.isEmpty(i)||t.addSection("widget-actions",{sectionTitle:panelsOptions.loc.contextual.widget_actions,search:!1},i,function(e){switch(e){case"edit":this.editHandler();break;case"copy":this.copyHandler();break;case"duplicate":this.duplicateHandler();break;case"delete":this.visualDestroyModel()}}.bind(this)),this.cell.buildContextualMenu(e,t)},afterUserAddsWidgetHandler:function(e){this.model===e&&panelsOptions.instant_open&&setTimeout(this.editHandler.bind(this),350)}})},{}],30:[function(e,t,i){var a=jQuery,s={addWidget:function(e,t,i){var s=wp.customHtmlWidgets,l=a("<div></div>"),o=t.find(".widget-content:first");o.before(l);var n=new s.CustomHtmlWidgetControl({el:l,syncContainer:o});return n.initializeEditor(),n.editor.codemirror.refresh(),n}};t.exports=s},{}],31:[function(e,t,i){var l=e("./custom-html-widget"),o=e("./media-widget"),n=e("./text-widget"),s={CUSTOM_HTML:"custom_html",MEDIA_AUDIO:"media_audio",MEDIA_GALLERY:"media_gallery",MEDIA_IMAGE:"media_image",MEDIA_VIDEO:"media_video",TEXT:"text",addWidget:function(e,t){var i,s=e.find("> .id_base").val();switch(s){case this.CUSTOM_HTML:i=l;break;case this.MEDIA_AUDIO:case this.MEDIA_GALLERY:case this.MEDIA_IMAGE:case this.MEDIA_VIDEO:i=o;break;case this.TEXT:i=n}i.addWidget(s,e,t)}};t.exports=s},{"./custom-html-widget":30,"./media-widget":32,"./text-widget":33}],32:[function(e,t,i){var c=jQuery,s={addWidget:function(e,t,i){var s=wp.mediaWidgets,l=s.controlConstructors[e];if(l){var o=s.modelConstructors[e]||s.MediaWidgetModel,n=t.find("> .widget-content"),a=c('<div class="media-widget-control"></div>');n.before(a);var r={};n.find(".media-widget-instance-property").each(function(){var e=c(this);r[e.data("property")]=e.val()}),r.widget_id=i;var d=new l({el:a,syncContainer:n,model:new o(r)});return d.render(),d}}};t.exports=s},{}],33:[function(e,t,i){var c=jQuery,s={addWidget:function(e,t,i){var s=wp.textWidgets,l={},o=t.find(".visual");if(0<o.length){if(!o.val())return null;var n=c("<div></div>"),a=t.find(".widget-content:first");a.before(n),l={el:n,syncContainer:a}}else l={el:t};var r=new s.TextWidgetControl(l),d=wp.oldEditor?wp.oldEditor:wp.editor;return d&&d.hasOwnProperty("autop")&&(wp.editor.autop=d.autop,wp.editor.removep=d.removep,wp.editor.initialize=d.initialize),r.initializeEditor(),r}};t.exports=s},{}]},{},[16]);
 
js/{siteorigin-panels-2106.js → siteorigin-panels-2107.js} RENAMED
@@ -383,2727 +383,2727 @@ module.exports = panels.view.dialog.extend( {
383
  } );
384
 
385
  },{}],7:[function(require,module,exports){
386
- var panels = window.panels, $ = jQuery;
387
-
388
- module.exports = panels.view.dialog.extend( {
389
-
390
- directoryTemplate: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-directory-items' ).html() ) ),
391
-
392
- builder: null,
393
- dialogClass: 'so-panels-dialog-prebuilt-layouts',
394
- dialogIcon: 'layouts',
395
-
396
- layoutCache: {},
397
- currentTab: false,
398
- directoryPage: 1,
399
-
400
- events: {
401
- 'click .so-close': 'closeDialog',
402
- 'click .so-sidebar-tabs li a': 'tabClickHandler',
403
- 'click .so-content .layout': 'layoutClickHandler',
404
- 'keyup .so-sidebar-search': 'searchHandler',
405
-
406
- // The directory items
407
- 'click .so-screenshot, .so-title': 'directoryItemClickHandler'
408
- },
409
-
410
- /**
411
- * Initialize the prebuilt dialog.
412
- */
413
- initializeDialog: function () {
414
- var thisView = this;
415
-
416
- this.on( 'open_dialog', function () {
417
- thisView.$( '.so-sidebar-tabs li a' ).first().click();
418
- thisView.$( '.so-status' ).removeClass( 'so-panels-loading' );
419
- } );
420
-
421
- this.on( 'button_click', this.toolbarButtonClick, this );
422
- },
423
-
424
- /**
425
- * Render the prebuilt layouts dialog
426
- */
427
- render: function () {
428
- this.renderDialog( this.parseDialogContent( $( '#siteorigin-panels-dialog-prebuilt' ).html(), {} ) );
429
-
430
- this.initToolbar();
431
- },
432
-
433
- /**
434
- *
435
- * @param e
436
- * @return {boolean}
437
- */
438
- tabClickHandler: function ( e ) {
439
- e.preventDefault();
440
- // Reset selected item state when changing tabs
441
- this.selectedLayoutItem = null;
442
- this.uploadedLayout = null;
443
- this.updateButtonState( false );
444
-
445
- this.$( '.so-sidebar-tabs li' ).removeClass( 'tab-active' );
446
-
447
- var $$ = $( e.target );
448
- var tab = $$.attr( 'href' ).split( '#' )[1];
449
- $$.parent().addClass( 'tab-active' );
450
-
451
- var thisView = this;
452
-
453
- // Empty everything
454
- this.$( '.so-content' ).empty();
455
-
456
- thisView.currentTab = tab;
457
- if ( tab == 'import' ) {
458
- this.displayImportExport();
459
- } else {
460
- this.displayLayoutDirectory( '', 1, tab );
461
- }
462
-
463
- thisView.$( '.so-sidebar-search' ).val( '' );
464
- },
465
-
466
- /**
467
- * Display and setup the import/export form
468
- */
469
- displayImportExport: function () {
470
- var c = this.$( '.so-content' ).empty().removeClass( 'so-panels-loading' );
471
- c.html( $( '#siteorigin-panels-dialog-prebuilt-importexport' ).html() );
472
-
473
- var thisView = this;
474
- var uploadUi = thisView.$( '.import-upload-ui' );
475
-
476
- // Create the uploader
477
- var uploader = new plupload.Uploader( {
478
- runtimes: 'html5,silverlight,flash,html4',
479
-
480
- browse_button: uploadUi.find( '.file-browse-button' ).get( 0 ),
481
- container: uploadUi.get( 0 ),
482
- drop_element: uploadUi.find( '.drag-upload-area' ).get( 0 ),
483
-
484
- file_data_name: 'panels_import_data',
485
- multiple_queues: false,
486
- max_file_size: panelsOptions.plupload.max_file_size,
487
- url: panelsOptions.plupload.url,
488
- flash_swf_url: panelsOptions.plupload.flash_swf_url,
489
- silverlight_xap_url: panelsOptions.plupload.silverlight_xap_url,
490
- filters: [
491
- {title: panelsOptions.plupload.filter_title, extensions: 'json'}
492
- ],
493
-
494
- multipart_params: {
495
- action: 'so_panels_import_layout'
496
- },
497
-
498
- init: {
499
- PostInit: function ( uploader ) {
500
- if ( uploader.features.dragdrop ) {
501
- uploadUi.addClass( 'has-drag-drop' );
502
- }
503
- uploadUi.find( '.progress-precent' ).css( 'width', '0%' );
504
- },
505
- FilesAdded: function ( uploader ) {
506
- uploadUi.find( '.file-browse-button' ).blur();
507
- uploadUi.find( '.drag-upload-area' ).removeClass( 'file-dragover' );
508
- uploadUi.find( '.progress-bar' ).fadeIn( 'fast' );
509
- thisView.$( '.js-so-selected-file' ).text( panelsOptions.loc.prebuilt_loading );
510
- uploader.start();
511
- },
512
- UploadProgress: function ( uploader, file ) {
513
- uploadUi.find( '.progress-precent' ).css( 'width', file.percent + '%' );
514
- },
515
- FileUploaded: function ( uploader, file, response ) {
516
- var layout = JSON.parse( response.response );
517
- if ( ! _.isUndefined( layout.widgets ) ) {
518
-
519
- thisView.uploadedLayout = layout;
520
- uploadUi.find( '.progress-bar' ).hide();
521
- thisView.$( '.js-so-selected-file' ).text(
522
- panelsOptions.loc.ready_to_insert.replace( '%s', file.name )
523
- );
524
- thisView.updateButtonState( true );
525
- } else {
526
- alert( panelsOptions.plupload.error_message );
527
- }
528
- },
529
- Error: function () {
530
- alert( panelsOptions.plupload.error_message );
531
- }
532
- }
533
- } );
534
- uploader.init();
535
-
536
- if ( /Edge\/\d./i.test(navigator.userAgent) ){
537
- // A very dirty fix for a Microsoft Edge issue.
538
- // TODO find a more elegant fix if Edge gains market share
539
- setTimeout( function(){
540
- uploader.refresh();
541
- }, 250 );
542
- }
543
-
544
- // This is
545
- uploadUi.find( '.drag-upload-area' )
546
- .on( 'dragover', function () {
547
- $( this ).addClass( 'file-dragover' );
548
- } )
549
- .on( 'dragleave', function () {
550
- $( this ).removeClass( 'file-dragover' );
551
- } );
552
-
553
- // Handle exporting the file
554
- c.find( '.so-export' ).submit( function ( e ) {
555
- var $$ = $( this );
556
- var panelsData = thisView.builder.model.getPanelsData();
557
- var postName = $('input[name="post_title"]').val();
558
- if ( ! postName ) {
559
- postName = $('input[name="post_ID"]').val();
560
- }
561
- panelsData.name = postName;
562
- $$.find( 'input[name="panels_export_data"]' ).val( JSON.stringify( panelsData ) );
563
- } );
564
-
565
- },
566
-
567
- /**
568
- * Display the layout directory tab.
569
- *
570
- * @param query
571
- */
572
- displayLayoutDirectory: function ( search, page, type ) {
573
- var thisView = this;
574
- var c = this.$( '.so-content' ).empty().addClass( 'so-panels-loading' );
575
-
576
- if ( search === undefined ) {
577
- search = '';
578
- }
579
- if ( page === undefined ) {
580
- page = 1;
581
- }
582
- if ( type === undefined ) {
583
- type = 'directory-siteorigin';
584
- }
585
-
586
- if ( type.match('^directory-') && ! panelsOptions.directory_enabled ) {
587
- // Display the button to enable the prebuilt layout
588
- c.removeClass( 'so-panels-loading' ).html( $( '#siteorigin-panels-directory-enable' ).html() );
589
- c.find( '.so-panels-enable-directory' ).click( function ( e ) {
590
- e.preventDefault();
591
- // Sent the query to enable the directory, then enable the directory
592
- $.get(
593
- panelsOptions.ajaxurl,
594
- {action: 'so_panels_directory_enable'},
595
- function () {
596
-
597
- }
598
- );
599
-
600
- // Enable the layout directory
601
- panelsOptions.directory_enabled = true;
602
- c.addClass( 'so-panels-loading' );
603
- thisView.displayLayoutDirectory( search, page, type );
604
- } );
605
- return;
606
- }
607
-
608
- // Get all the items for the current query
609
- $.get(
610
- panelsOptions.ajaxurl,
611
- {
612
- action: 'so_panels_layouts_query',
613
- search: search,
614
- page: page,
615
- type: type,
616
- },
617
- function ( data ) {
618
- // Skip this if we're no longer viewing the layout directory
619
- if ( thisView.currentTab !== type ) {
620
- return;
621
- }
622
-
623
- // Add the directory items
624
- c.removeClass( 'so-panels-loading' ).html( thisView.directoryTemplate( data ) );
625
-
626
- // Lets setup the next and previous buttons
627
- var prev = c.find( '.so-previous' ), next = c.find( '.so-next' );
628
-
629
- if ( page <= 1 ) {
630
- prev.addClass( 'button-disabled' );
631
- } else {
632
- prev.click( function ( e ) {
633
- e.preventDefault();
634
- thisView.displayLayoutDirectory( search, page - 1, thisView.currentTab );
635
- } );
636
- }
637
-
638
- if ( page === data.max_num_pages || data.max_num_pages === 0 ) {
639
- next.addClass( 'button-disabled' );
640
- } else {
641
- next.click( function ( e ) {
642
- e.preventDefault();
643
- thisView.displayLayoutDirectory( search, page + 1, thisView.currentTab );
644
- } );
645
- }
646
-
647
- // Handle nice preloading of the screenshots
648
- c.find( '.so-screenshot' ).each( function () {
649
- var $$ = $( this ), $a = $$.find( '.so-screenshot-wrapper' );
650
- $a.css( 'height', ( $a.width() / 4 * 3 ) + 'px' ).addClass( 'so-loading' );
651
-
652
- if ( $$.data( 'src' ) !== '' ) {
653
- // Set the initial height
654
- var $img = $( '<img/>' ).attr( 'src', $$.data( 'src' ) ).load( function () {
655
- $a.removeClass( 'so-loading' ).css( 'height', 'auto' );
656
- $img.appendTo( $a ).hide().fadeIn( 'fast' );
657
- } );
658
- } else {
659
- $( '<img/>' ).attr( 'src', panelsOptions.prebuiltDefaultScreenshot ).appendTo( $a ).hide().fadeIn( 'fast' );
660
- }
661
-
662
- } );
663
-
664
- // Set the title
665
- c.find( '.so-directory-browse' ).html( data.title );
666
- },
667
- 'json'
668
- );
669
- },
670
-
671
- /**
672
- * Set the selected state for the clicked layout directory item and remove previously selected item.
673
- * Enable the toolbar buttons.
674
- */
675
- directoryItemClickHandler: function ( e ) {
676
- var $directoryItem = this.$( e.target ).closest( '.so-directory-item' );
677
- this.$( '.so-directory-items' ).find( '.selected' ).removeClass( 'selected' );
678
- $directoryItem.addClass( 'selected' );
679
- this.selectedLayoutItem = {lid: $directoryItem.data( 'layout-id' ), type: $directoryItem.data( 'layout-type' )};
680
- this.updateButtonState( true );
681
-
682
- },
683
-
684
- /**
685
- * Load a particular layout into the builder.
686
- *
687
- * @param id
688
- */
689
- toolbarButtonClick: function ( $button ) {
690
- if ( ! this.canAddLayout() ) {
691
- return false;
692
- }
693
- var position = $button.data( 'value' );
694
- if ( _.isUndefined( position ) ) {
695
- return false;
696
- }
697
- this.updateButtonState( false );
698
-
699
- if ( $button.hasClass( 'so-needs-confirm' ) && ! $button.hasClass( 'so-confirmed' ) ) {
700
- this.updateButtonState( true );
701
- if ( $button.hasClass( 'so-confirming' ) ) {
702
- return;
703
- }
704
- $button.addClass( 'so-confirming' );
705
- var originalText = $button.html();
706
- $button.html( '<span class="dashicons dashicons-yes"></span>' + $button.data( 'confirm' ) );
707
- setTimeout( function () {
708
- $button.removeClass( 'so-confirmed' ).html( originalText );
709
- }, 2500 );
710
- setTimeout( function () {
711
- $button.removeClass( 'so-confirming' );
712
- $button.addClass( 'so-confirmed' );
713
- }, 200 );
714
- return false;
715
- }
716
- this.addingLayout = true;
717
- if ( this.currentTab === 'import' ) {
718
- this.addLayoutToBuilder( this.uploadedLayout, position );
719
- } else {
720
- this.loadSelectedLayout().then( function ( layout ) {
721
- this.addLayoutToBuilder( layout, position );
722
- }.bind( this ) );
723
- }
724
- },
725
-
726
- canAddLayout: function () {
727
- return (
728
- this.selectedLayoutItem || this.uploadedLayout
729
- ) && ! this.addingLayout;
730
- },
731
-
732
- /**
733
- * Load the layout according to selectedLayoutItem.
734
- */
735
- loadSelectedLayout: function () {
736
- this.setStatusMessage( panelsOptions.loc.prebuilt_loading, true );
737
-
738
- var args = _.extend( this.selectedLayoutItem, {action: 'so_panels_get_layout'} );
739
- var deferredLayout = new $.Deferred();
740
-
741
- $.get(
742
- panelsOptions.ajaxurl,
743
- args,
744
- function ( layout ) {
745
- var msg = '';
746
- if ( ! layout.success ) {
747
- msg = layout.data.message;
748
- deferredLayout.reject( layout.data );
749
- } else {
750
- deferredLayout.resolve( layout.data );
751
- }
752
- this.setStatusMessage( msg, false, ! layout.success );
753
- this.updateButtonState( true );
754
- }.bind( this )
755
- );
756
- return deferredLayout.promise();
757
- },
758
-
759
- /**
760
- * Handle an update to the search
761
- */
762
- searchHandler: function ( e ) {
763
- if ( e.keyCode === 13 ) {
764
- this.displayLayoutDirectory( $( e.currentTarget ).val(), 1, this.currentTab );
765
- }
766
- },
767
-
768
- /**
769
- * Attempt to set the 'Insert' button's state according to the `enabled` argument, also checking whether the
770
- * requirements for inserting a layout have valid values.
771
- */
772
- updateButtonState: function ( enabled ) {
773
- enabled = enabled && (
774
- this.selectedLayoutItem || this.uploadedLayout
775
- );
776
- var $button = this.$( '.so-import-layout' );
777
- $button.prop( "disabled", ! enabled );
778
- if ( enabled ) {
779
- $button.removeClass( 'disabled' );
780
- } else {
781
- $button.addClass( 'disabled' );
782
- }
783
- },
784
-
785
- addLayoutToBuilder: function ( layout, position ) {
786
- this.builder.addHistoryEntry( 'prebuilt_loaded' );
787
- this.builder.model.loadPanelsData( layout, position );
788
- this.addingLayout = false;
789
- this.closeDialog();
790
- }
791
- } );
792
 
793
- },{}],8:[function(require,module,exports){
794
- var panels = window.panels, $ = jQuery;
795
-
796
- module.exports = panels.view.dialog.extend({
797
-
798
- cellPreviewTemplate: _.template( panels.helpers.utils.processTemplate( $('#siteorigin-panels-dialog-row-cell-preview').html() ) ),
799
-
800
- editableLabel: true,
801
-
802
- events: {
803
- 'click .so-close': 'closeDialog',
804
-
805
- // Toolbar buttons
806
- 'click .so-toolbar .so-save': 'saveHandler',
807
- 'click .so-toolbar .so-insert': 'insertHandler',
808
- 'click .so-toolbar .so-delete': 'deleteHandler',
809
- 'click .so-toolbar .so-duplicate': 'duplicateHandler',
810
-
811
- // Changing the row
812
- 'change .row-set-form > *': 'setCellsFromForm',
813
- 'click .row-set-form button.set-row': 'setCellsFromForm',
814
- },
815
-
816
- rowView: null,
817
- dialogIcon: 'add-row',
818
- dialogClass: 'so-panels-dialog-row-edit',
819
- styleType: 'row',
820
-
821
- dialogType: 'edit',
822
-
823
- /**
824
- * The current settings, not yet saved to the model
825
- */
826
- row: {
827
- // This will be a clone of cells collection.
828
- cells: null,
829
- // The style settings of the row
830
- style: {}
831
- },
832
-
833
- cellStylesCache: [],
834
-
835
- initializeDialog: function () {
836
- this.on('open_dialog', function () {
837
- if (!_.isUndefined(this.model) && !_.isEmpty(this.model.get('cells'))) {
838
- this.setRowModel(this.model);
839
- } else {
840
- this.setRowModel(null);
841
- }
842
-
843
- this.regenerateRowPreview();
844
- this.renderStyles();
845
- this.openSelectedCellStyles();
846
- }, this);
847
-
848
- // This is the default row layout
849
- this.row = {
850
- cells: new panels.collection.cells([{weight: 0.5}, {weight: 0.5}]),
851
- style: {}
852
- };
853
-
854
- // Refresh panels data after both dialog form components are loaded
855
- this.dialogFormsLoaded = 0;
856
- var thisView = this;
857
- this.on('form_loaded styles_loaded', function () {
858
- this.dialogFormsLoaded++;
859
- if (this.dialogFormsLoaded === 2) {
860
- thisView.updateModel({
861
- refreshArgs: {
862
- silent: true
863
- }
864
- });
865
- }
866
- });
867
-
868
- this.on('close_dialog', this.closeHandler);
869
-
870
- this.on( 'edit_label', function ( text ) {
871
- // If text is set to default values, just clear it.
872
- if ( text === panelsOptions.loc.row.add || text === panelsOptions.loc.row.edit ) {
873
- text = '';
874
- }
875
- this.model.set( 'label', text );
876
- if ( _.isEmpty( text ) ) {
877
- var title = this.dialogType === 'create' ? panelsOptions.loc.row.add : panelsOptions.loc.row.edit;
878
- this.$( '.so-title').text( title );
879
- }
880
- }.bind( this ) );
881
- },
882
-
883
- /**
884
- *
885
- * @param dialogType Either "edit" or "create"
886
- */
887
- setRowDialogType: function (dialogType) {
888
- this.dialogType = dialogType;
889
- },
890
-
891
- /**
892
- * Render the new row dialog
893
- */
894
- render: function () {
895
- var title = this.dialogType === 'create' ? panelsOptions.loc.row.add : panelsOptions.loc.row.edit;
896
- this.renderDialog( this.parseDialogContent( $( '#siteorigin-panels-dialog-row' ).html(), {
897
- title: title,
898
- dialogType: this.dialogType
899
- } ) );
900
-
901
- var titleElt = this.$( '.so-title' );
902
-
903
- if ( this.model.has( 'label' ) && ! _.isEmpty( this.model.get( 'label' ) ) ) {
904
- titleElt.text( this.model.get( 'label' ) );
905
- }
906
- this.$( '.so-edit-title' ).val( titleElt.text() );
907
-
908
- if (!this.builder.supports('addRow')) {
909
- this.$('.so-buttons .so-duplicate').remove();
910
- }
911
- if (!this.builder.supports('deleteRow')) {
912
- this.$('.so-buttons .so-delete').remove();
913
- }
914
-
915
- if (!_.isUndefined(this.model)) {
916
- // Set the initial value of the
917
- this.$( 'input[name="cells"].so-row-field' ).val( this.model.get( 'cells' ).length );
918
- if ( this.model.has( 'ratio' ) ) {
919
- this.$( 'select[name="ratio"].so-row-field' ).val( this.model.get( 'ratio' ) );
920
- }
921
- if ( this.model.has( 'ratio_direction' ) ) {
922
- this.$( 'select[name="ratio_direction"].so-row-field' ).val( this.model.get( 'ratio_direction' ) );
923
- }
924
- }
925
-
926
- this.$('input.so-row-field').keyup(function () {
927
- $(this).trigger('change');
928
- });
929
-
930
- return this;
931
- },
932
-
933
- renderStyles: function () {
934
- if ( this.styles ) {
935
- this.styles.off( 'styles_loaded' );
936
- this.styles.remove();
937
- }
938
-
939
- // Now we need to attach the style window
940
- this.styles = new panels.view.styles();
941
- this.styles.model = this.model;
942
- this.styles.render('row', this.builder.config.postId, {
943
- builderType: this.builder.config.builderType,
944
- dialog: this
945
- });
946
-
947
- var $rightSidebar = this.$('.so-sidebar.so-right-sidebar');
948
- this.styles.attach( $rightSidebar );
949
-
950
- // Handle the loading class
951
- this.styles.on('styles_loaded', function (hasStyles) {
952
- if ( ! hasStyles ) {
953
- // If we don't have styles remove the view.
954
- this.styles.remove();
955
-
956
- // If the sidebar is empty, hide it.
957
- if ( $rightSidebar.children().length === 0 ) {
958
- $rightSidebar.closest('.so-panels-dialog').removeClass('so-panels-dialog-has-right-sidebar');
959
- $rightSidebar.hide();
960
- }
961
- }
962
- }, this);
963
- },
964
-
965
- /**
966
- * Set the row model we'll be using for this dialog.
967
- *
968
- * @param model
969
- */
970
- setRowModel: function (model) {
971
- this.model = model;
972
-
973
- if (_.isEmpty(this.model)) {
974
- return this;
975
- }
976
-
977
- // Set the rows to be a copy of the model
978
- this.row = {
979
- cells: this.model.get('cells').clone(),
980
- style: {},
981
- ratio: this.model.get('ratio'),
982
- ratio_direction: this.model.get('ratio_direction'),
983
- };
984
-
985
- // Set the initial value of the cell field.
986
- this.$( 'input[name="cells"].so-row-field' ).val( this.model.get( 'cells' ).length );
987
- if ( this.model.has( 'ratio' ) ) {
988
- this.$( 'select[name="ratio"].so-row-field' ).val( this.model.get( 'ratio' ) );
989
- }
990
- if ( this.model.has( 'ratio_direction' ) ) {
991
- this.$( 'select[name="ratio_direction"].so-row-field' ).val( this.model.get( 'ratio_direction' ) );
992
- }
993
-
994
- this.clearCellStylesCache();
995
-
996
- return this;
997
- },
998
-
999
- /**
1000
- * Regenerate the row preview and resizing interface.
1001
- */
1002
- regenerateRowPreview: function () {
1003
- var thisDialog = this;
1004
- var rowPreview = this.$('.row-preview');
1005
-
1006
- // If no selected cell, select the first cell.
1007
- var selectedIndex = this.getSelectedCellIndex();
1008
-
1009
- rowPreview.empty();
1010
-
1011
- var timeout;
1012
-
1013
- // Represent the cells
1014
- this.row.cells.each(function (cellModel, i) {
1015
- var newCell = $(this.cellPreviewTemplate({weight: cellModel.get('weight')}));
1016
- rowPreview.append(newCell);
1017
-
1018
- if(i == selectedIndex) {
1019
- newCell.find('.preview-cell-in').addClass('cell-selected');
1020
- }
1021
-
1022
- var prevCell = newCell.prev();
1023
- var handle;
1024
-
1025
- if (prevCell.length) {
1026
- handle = $('<div class="resize-handle"></div>');
1027
- handle
1028
- .appendTo(newCell)
1029
- .dblclick(function () {
1030
- var prevCellModel = thisDialog.row.cells.at(i - 1);
1031
- var t = cellModel.get('weight') + prevCellModel.get('weight');
1032
- cellModel.set('weight', t / 2);
1033
- prevCellModel.set('weight', t / 2);
1034
- thisDialog.scaleRowWidths();
1035
- });
1036
-
1037
- handle.draggable({
1038
- axis: 'x',
1039
- containment: rowPreview,
1040
- start: function (e, ui) {
1041
-
1042
- // Create the clone for the current cell
1043
- var newCellClone = newCell.clone().appendTo(ui.helper).css({
1044
- position: 'absolute',
1045
- top: '0',
1046
- width: newCell.outerWidth(),
1047
- left: 6,
1048
- height: newCell.outerHeight()
1049
- });
1050
- newCellClone.find('.resize-handle').remove();
1051
-
1052
- // Create the clone for the previous cell
1053
- var prevCellClone = prevCell.clone().appendTo(ui.helper).css({
1054
- position: 'absolute',
1055
- top: '0',
1056
- width: prevCell.outerWidth(),
1057
- right: 6,
1058
- height: prevCell.outerHeight()
1059
- });
1060
- prevCellClone.find('.resize-handle').remove();
1061
-
1062
- $(this).data({
1063
- 'newCellClone': newCellClone,
1064
- 'prevCellClone': prevCellClone
1065
- });
1066
-
1067
- // Hide the
1068
- newCell.find('> .preview-cell-in').css('visibility', 'hidden');
1069
- prevCell.find('> .preview-cell-in').css('visibility', 'hidden');
1070
- },
1071
- drag: function (e, ui) {
1072
- // Calculate the new cell and previous cell widths as a percent
1073
- var cellWeight = thisDialog.row.cells.at(i).get('weight');
1074
- var prevCellWeight = thisDialog.row.cells.at(i - 1).get('weight');
1075
- var ncw = cellWeight - (
1076
- (
1077
- ui.position.left + 6
1078
- ) / rowPreview.width()
1079
- );
1080
- var pcw = prevCellWeight + (
1081
- (
1082
- ui.position.left + 6
1083
- ) / rowPreview.width()
1084
- );
1085
-
1086
- var helperLeft = ui.helper.offset().left - rowPreview.offset().left - 6;
1087
-
1088
- $(this).data('newCellClone').css('width', rowPreview.width() * ncw)
1089
- .find('.preview-cell-weight').html(Math.round(ncw * 1000) / 10);
1090
-
1091
- $(this).data('prevCellClone').css('width', rowPreview.width() * pcw)
1092
- .find('.preview-cell-weight').html(Math.round(pcw * 1000) / 10);
1093
- },
1094
- stop: function (e, ui) {
1095
- // Remove the clones
1096
- $(this).data('newCellClone').remove();
1097
- $(this).data('prevCellClone').remove();
1098
-
1099
- // Reshow the main cells
1100
- newCell.find('.preview-cell-in').css('visibility', 'visible');
1101
- prevCell.find('.preview-cell-in').css('visibility', 'visible');
1102
-
1103
- // Calculate the new cell weights
1104
- var offset = ui.position.left + 6;
1105
- var percent = offset / rowPreview.width();
1106
-
1107
- // Ignore this if any of the cells are below 2% in width.
1108
- var cellModel = thisDialog.row.cells.at(i);
1109
- var prevCellModel = thisDialog.row.cells.at(i - 1);
1110
- if (cellModel.get('weight') - percent > 0.02 && prevCellModel.get('weight') + percent > 0.02) {
1111
- cellModel.set('weight', cellModel.get('weight') - percent);
1112
- prevCellModel.set('weight', prevCellModel.get('weight') + percent);
1113
- }
1114
-
1115
- thisDialog.scaleRowWidths();
1116
- ui.helper.css('left', -6);
1117
- }
1118
- });
1119
- }
1120
-
1121
- newCell.click(function (event) {
1122
-
1123
- if ( ! ( $(event.target).is('.preview-cell') || $(event.target).is('.preview-cell-in') ) ) {
1124
- return;
1125
- }
1126
-
1127
- var cell = $(event.target);
1128
- cell.closest('.row-preview').find('.preview-cell .preview-cell-in').removeClass('cell-selected');
1129
- cell.addClass('cell-selected');
1130
-
1131
- this.openSelectedCellStyles();
1132
-
1133
- }.bind(this));
1134
-
1135
- // Make this row weight click editable
1136
- newCell.find('.preview-cell-weight').click(function (ci) {
1137
-
1138
- // Disable the draggable while entering values
1139
- thisDialog.$('.resize-handle').css('pointer-event', 'none').draggable('disable');
1140
-
1141
- rowPreview.find('.preview-cell-weight').each(function () {
1142
- var $$ = jQuery(this).hide();
1143
- $('<input type="text" class="preview-cell-weight-input no-user-interacted" />')
1144
- .val(parseFloat($$.html())).insertAfter($$)
1145
- .focus(function () {
1146
- clearTimeout(timeout);
1147
- })
1148
- .keyup(function (e) {
1149
- if (e.keyCode !== 9) {
1150
- // Only register the interaction if the user didn't press tab
1151
- $(this).removeClass('no-user-interacted');
1152
- }
1153
-
1154
- // Enter is clicked
1155
- if (e.keyCode === 13) {
1156
- e.preventDefault();
1157
- $(this).blur();
1158
- }
1159
- })
1160
- .keydown(function (e) {
1161
- if (e.keyCode === 9) {
1162
- e.preventDefault();
1163
-
1164
- // Tab will always cycle around the row inputs
1165
- var inputs = rowPreview.find('.preview-cell-weight-input');
1166
- var i = inputs.index($(this));
1167
- if (i === inputs.length - 1) {
1168
- inputs.eq(0).focus().select();
1169
- } else {
1170
- inputs.eq(i + 1).focus().select();
1171
- }
1172
- }
1173
- })
1174
- .blur(function () {
1175
- rowPreview.find('.preview-cell-weight-input').each(function (i, el) {
1176
- if (isNaN(parseFloat($(el).val()))) {
1177
- $(el).val(Math.floor(thisDialog.row.cells.at(i).get('weight') * 1000) / 10);
1178
- }
1179
- });
1180
-
1181
- timeout = setTimeout(function () {
1182
- // If there are no weight inputs, then skip this
1183
- if (rowPreview.find('.preview-cell-weight-input').length === 0) {
1184
- return false;
1185
- }
1186
-
1187
- // Go through all the inputs
1188
- var rowWeights = [],
1189
- rowChanged = [],
1190
- changedSum = 0,
1191
- unchangedSum = 0;
1192
-
1193
- rowPreview.find('.preview-cell-weight-input').each(function (i, el) {
1194
- var val = parseFloat($(el).val());
1195
- if (isNaN(val)) {
1196
- val = 1 / thisDialog.row.cells.length;
1197
- } else {
1198
- val = Math.round(val * 10) / 1000;
1199
- }
1200
-
1201
- // Check within 3 decimal points
1202
- var changed = !$(el).hasClass('no-user-interacted');
1203
-
1204
- rowWeights.push(val);
1205
- rowChanged.push(changed);
1206
-
1207
- if (changed) {
1208
- changedSum += val;
1209
- } else {
1210
- unchangedSum += val;
1211
- }
1212
- });
1213
-
1214
- if (changedSum > 0 && unchangedSum > 0 && (
1215
- 1 - changedSum
1216
- ) > 0) {
1217
- // Balance out the unchanged rows to occupy the weight left over by the changed sum
1218
- for (var i = 0; i < rowWeights.length; i++) {
1219
- if (!rowChanged[i]) {
1220
- rowWeights[i] = (
1221
- rowWeights[i] / unchangedSum
1222
- ) * (
1223
- 1 - changedSum
1224
- );
1225
- }
1226
- }
1227
- }
1228
-
1229
- // Last check to ensure total weight is 1
1230
- var sum = _.reduce(rowWeights, function (memo, num) {
1231
- return memo + num;
1232
- });
1233
- rowWeights = rowWeights.map(function (w) {
1234
- return w / sum;
1235
- });
1236
-
1237
- // Set the new cell weights and regenerate the preview.
1238
- if (Math.min.apply(Math, rowWeights) > 0.01) {
1239
- thisDialog.row.cells.each(function (cell, i) {
1240
- cell.set('weight', rowWeights[i]);
1241
- });
1242
- }
1243
-
1244
- // Now lets animate the cells into their new widths
1245
- rowPreview.find('.preview-cell').each(function (i, el) {
1246
- var cellWeight = thisDialog.row.cells.at(i).get('weight');
1247
- $(el).animate({'width': Math.round(cellWeight * 1000) / 10 + "%"}, 250);
1248
- $(el).find('.preview-cell-weight-input').val(Math.round(cellWeight * 1000) / 10);
1249
- });
1250
-
1251
- // So the draggable handle is not hidden.
1252
- rowPreview.find('.preview-cell').css('overflow', 'visible');
1253
- setTimeout(thisDialog.regenerateRowPreview.bind(thisDialog), 260);
1254
-
1255
- }, 100);
1256
- })
1257
- .click(function () {
1258
- $(this).select();
1259
- });
1260
- });
1261
-
1262
- $(this).siblings('.preview-cell-weight-input').select();
1263
-
1264
- });
1265
-
1266
- }, this);
1267
-
1268
- this.trigger('form_loaded', this);
1269
- },
1270
-
1271
- getSelectedCellIndex: function() {
1272
- var selectedIndex = -1;
1273
- this.$('.preview-cell .preview-cell-in').each(function(index, el) {
1274
- if($(el).is('.cell-selected')) {
1275
- selectedIndex = index;
1276
- }
1277
- });
1278
- return selectedIndex;
1279
- },
1280
-
1281
- openSelectedCellStyles: function() {
1282
- if (!_.isUndefined(this.cellStyles)) {
1283
- if (this.cellStyles.stylesLoaded) {
1284
- var style = {};
1285
- try {
1286
- style = this.getFormValues('.so-sidebar .so-visual-styles.so-cell-styles').style;
1287
- }
1288
- catch (err) {
1289
- console.log('Error retrieving cell styles - ' + err.message);
1290
- }
1291
-
1292
- this.cellStyles.model.set('style', style);
1293
- }
1294
- this.cellStyles.detach();
1295
- }
1296
-
1297
- this.cellStyles = this.getSelectedCellStyles();
1298
-
1299
- if ( this.cellStyles ) {
1300
- var $rightSidebar = this.$( '.so-sidebar.so-right-sidebar' );
1301
- this.cellStyles.attach( $rightSidebar );
1302
- this.cellStyles.on( 'styles_loaded', function ( hasStyles ) {
1303
- if ( hasStyles ) {
1304
- $rightSidebar.closest('.so-panels-dialog').addClass('so-panels-dialog-has-right-sidebar');
1305
- $rightSidebar.show();
1306
- }
1307
- } );
1308
- }
1309
- },
1310
-
1311
- getSelectedCellStyles: function () {
1312
- var cellIndex = this.getSelectedCellIndex();
1313
- if ( cellIndex > -1 ) {
1314
- var cellStyles = this.cellStylesCache[cellIndex];
1315
- if ( !cellStyles ) {
1316
- cellStyles = new panels.view.styles();
1317
- cellStyles.model = this.row.cells.at( cellIndex );
1318
- cellStyles.render( 'cell', this.builder.config.postId, {
1319
- builderType: this.builder.config.builderType,
1320
- dialog: this,
1321
- index: cellIndex,
1322
- } );
1323
- this.cellStylesCache[cellIndex] = cellStyles;
1324
- }
1325
- }
1326
-
1327
- return cellStyles;
1328
- },
1329
-
1330
- clearCellStylesCache: function () {
1331
- // Call remove() on all cell styles to remove data, event listeners etc.
1332
- this.cellStylesCache.forEach(function (cellStyles) {
1333
- cellStyles.remove();
1334
- cellStyles.off( 'styles_loaded' );
1335
- });
1336
- this.cellStylesCache = [];
1337
- },
1338
-
1339
- /**
1340
- * Visually scale the row widths based on the cell weights
1341
- */
1342
- scaleRowWidths: function () {
1343
- var thisDialog = this;
1344
- this.$('.row-preview .preview-cell').each(function (i, el) {
1345
- var cell = thisDialog.row.cells.at(i);
1346
- $(el)
1347
- .css('width', cell.get('weight') * 100 + "%")
1348
- .find('.preview-cell-weight').html(Math.round(cell.get('weight') * 1000) / 10);
1349
- });
1350
- },
1351
-
1352
- /**
1353
- * Get the weights from the
1354
- */
1355
- setCellsFromForm: function () {
1356
-
1357
- try {
1358
- var f = {
1359
- 'cells': parseInt(this.$('.row-set-form input[name="cells"]').val()),
1360
- 'ratio': parseFloat(this.$('.row-set-form select[name="ratio"]').val()),
1361
- 'direction': this.$('.row-set-form select[name="ratio_direction"]').val()
1362
- };
1363
-
1364
- if (_.isNaN(f.cells)) {
1365
- f.cells = 1;
1366
- }
1367
- if (isNaN(f.ratio)) {
1368
- f.ratio = 1;
1369
- }
1370
- if (f.cells < 1) {
1371
- f.cells = 1;
1372
- this.$('.row-set-form input[name="cells"]').val(f.cells);
1373
- }
1374
- else if (f.cells > 12) {
1375
- f.cells = 12;
1376
- this.$('.row-set-form input[name="cells"]').val(f.cells);
1377
- }
1378
-
1379
- this.$('.row-set-form select[name="ratio"]').val(f.ratio);
1380
-
1381
- var cells = [];
1382
- var cellCountChanged = (
1383
- this.row.cells.length !== f.cells
1384
- );
1385
-
1386
- // Now, lets create some cells
1387
- var currentWeight = 1;
1388
- for (var i = 0; i < f.cells; i++) {
1389
- cells.push(currentWeight);
1390
- currentWeight *= f.ratio;
1391
- }
1392
-
1393
- // Now lets make sure that the row weights add up to 1
1394
-
1395
- var totalRowWeight = _.reduce(cells, function (memo, weight) {
1396
- return memo + weight;
1397
- });
1398
- cells = _.map(cells, function (cell) {
1399
- return cell / totalRowWeight;
1400
- });
1401
-
1402
- // Don't return cells that are too small
1403
- cells = _.filter(cells, function (cell) {
1404
- return cell > 0.01;
1405
- });
1406
-
1407
- if (f.direction === 'left') {
1408
- cells = cells.reverse();
1409
- }
1410
-
1411
- // Discard deleted cells.
1412
- this.row.cells = new panels.collection.cells(this.row.cells.first(cells.length));
1413
-
1414
- _.each(cells, function (cellWeight, index) {
1415
- var cell = this.row.cells.at(index);
1416
- if (!cell) {
1417
- cell = new panels.model.cell({weight: cellWeight, row: this.model});
1418
- this.row.cells.add(cell);
1419
- } else {
1420
- cell.set('weight', cellWeight);
1421
- }
1422
- }.bind(this));
1423
-
1424
- this.row.ratio = f.ratio;
1425
- this.row.ratio_direction = f.direction;
1426
-
1427
- if (cellCountChanged) {
1428
- this.regenerateRowPreview();
1429
- } else {
1430
- var thisDialog = this;
1431
-
1432
- // Now lets animate the cells into their new widths
1433
- this.$('.preview-cell').each(function (i, el) {
1434
- var cellWeight = thisDialog.row.cells.at(i).get('weight');
1435
- $(el).animate({'width': Math.round(cellWeight * 1000) / 10 + "%"}, 250);
1436
- $(el).find('.preview-cell-weight').html(Math.round(cellWeight * 1000) / 10);
1437
- });
1438
-
1439
- // So the draggable handle is not hidden.
1440
- this.$('.preview-cell').css('overflow', 'visible');
1441
-
1442
- setTimeout(thisDialog.regenerateRowPreview.bind(thisDialog), 260);
1443
- }
1444
- }
1445
- catch (err) {
1446
- console.log('Error setting cells - ' + err.message);
1447
- }
1448
-
1449
-
1450
- // Remove the button primary class
1451
- this.$('.row-set-form .so-button-row-set').removeClass('button-primary');
1452
- },
1453
-
1454
- /**
1455
- * Handle a click on the dialog left bar tab
1456
- */
1457
- tabClickHandler: function ($t) {
1458
- if ($t.attr('href') === '#row-layout') {
1459
- this.$('.so-panels-dialog').addClass('so-panels-dialog-has-right-sidebar');
1460
- } else {
1461
- this.$('.so-panels-dialog').removeClass('so-panels-dialog-has-right-sidebar');
1462
- }
1463
- },
1464
-
1465
- /**
1466
- * Update the current model with what we have in the dialog
1467
- */
1468
- updateModel: function (args) {
1469
- args = _.extend({
1470
- refresh: true,
1471
- refreshArgs: null
1472
- }, args);
1473
-
1474
- // Set the cells
1475
- if (!_.isEmpty(this.model)) {
1476
- this.model.setCells( this.row.cells );
1477
- this.model.set( 'ratio', this.row.ratio );
1478
- this.model.set( 'ratio_direction', this.row.ratio_direction );
1479
- }
1480
-
1481
- // Update the row styles if they've loaded
1482
- if (!_.isUndefined(this.styles) && this.styles.stylesLoaded) {
1483
- // This is an edit dialog, so there are styles
1484
- var style = {};
1485
- try {
1486
- style = this.getFormValues('.so-sidebar .so-visual-styles.so-row-styles').style;
1487
- }
1488
- catch (err) {
1489
- console.log('Error retrieving row styles - ' + err.message);
1490
- }
1491
-
1492
- this.model.set('style', style);
1493
- }
1494
-
1495
- // Update the cell styles if any are showing.
1496
- if (!_.isUndefined(this.cellStyles) && this.cellStyles.stylesLoaded) {
1497
-
1498
- var style = {};
1499
- try {
1500
- style = this.getFormValues('.so-sidebar .so-visual-styles.so-cell-styles').style;
1501
- }
1502
- catch (err) {
1503
- console.log('Error retrieving cell styles - ' + err.message);
1504
- }
1505
-
1506
- this.cellStyles.model.set('style', style);
1507
- }
1508
-
1509
- if (args.refresh) {
1510
- this.builder.model.refreshPanelsData(args.refreshArgs);
1511
- }
1512
- },
1513
-
1514
- /**
1515
- * Insert the new row
1516
- */
1517
- insertHandler: function () {
1518
- this.builder.addHistoryEntry('row_added');
1519
-
1520
- this.updateModel();
1521
-
1522
- var activeCell = this.builder.getActiveCell({
1523
- createCell: false,
1524
- });
1525
-
1526
- var options = {};
1527
- if (activeCell !== null) {
1528
- options.at = this.builder.model.get('rows').indexOf(activeCell.row) + 1;
1529
- }
1530
-
1531
- // Set up the model and add it to the builder
1532
- this.model.collection = this.builder.model.get('rows');
1533
- this.builder.model.get('rows').add(this.model, options);
1534
-
1535
- this.closeDialog();
1536
-
1537
- this.builder.model.refreshPanelsData();
1538
-
1539
- return false;
1540
- },
1541
-
1542
- /**
1543
- * We'll just save this model and close the dialog
1544
- */
1545
- saveHandler: function () {
1546
- this.builder.addHistoryEntry('row_edited');
1547
- this.updateModel();
1548
- this.closeDialog();
1549
-
1550
- this.builder.model.refreshPanelsData();
1551
-
1552
- return false;
1553
- },
1554
-
1555
- /**
1556
- * The user clicks delete, so trigger deletion on the row model
1557
- */
1558
- deleteHandler: function () {
1559
- // Trigger a destroy on the model that will happen with a visual indication to the user
1560
- this.rowView.visualDestroyModel();
1561
- this.closeDialog({silent: true});
1562
-
1563
- return false;
1564
- },
1565
-
1566
- /**
1567
- * Duplicate this row
1568
- */
1569
- duplicateHandler: function () {
1570
- this.builder.addHistoryEntry('row_duplicated');
1571
-
1572
- var duplicateRow = this.model.clone(this.builder.model);
1573
-
1574
- this.builder.model.get('rows').add( duplicateRow, {
1575
- at: this.builder.model.get('rows').indexOf(this.model) + 1
1576
- } );
1577
-
1578
- this.closeDialog({silent: true});
1579
-
1580
- return false;
1581
- },
1582
-
1583
- closeHandler: function() {
1584
- this.clearCellStylesCache();
1585
- if( ! _.isUndefined(this.cellStyles) ) {
1586
- this.cellStyles = undefined;
1587
- }
1588
- },
1589
-
1590
- });
1591
 
1592
- },{}],9:[function(require,module,exports){
1593
- var panels = window.panels, $ = jQuery;
1594
- var jsWidget = require( '../view/widgets/js-widget' );
1595
-
1596
- module.exports = panels.view.dialog.extend( {
1597
-
1598
- builder: null,
1599
- sidebarWidgetTemplate: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-dialog-widget-sidebar-widget' ).html() ) ),
1600
-
1601
- dialogClass: 'so-panels-dialog-edit-widget',
1602
- dialogIcon: 'add-widget',
1603
-
1604
- widgetView: false,
1605
- savingWidget: false,
1606
- editableLabel: true,
1607
-
1608
- events: {
1609
- 'click .so-close': 'saveHandler',
1610
- 'click .so-nav.so-previous': 'navToPrevious',
1611
- 'click .so-nav.so-next': 'navToNext',
1612
-
1613
- // Action handlers
1614
- 'click .so-toolbar .so-delete': 'deleteHandler',
1615
- 'click .so-toolbar .so-duplicate': 'duplicateHandler'
1616
- },
1617
-
1618
- initializeDialog: function () {
1619
- var thisView = this;
1620
- this.listenTo( this.model, 'change:values', this.handleChangeValues );
1621
- this.listenTo( this.model, 'destroy', this.remove );
1622
-
1623
- // Refresh panels data after both dialog form components are loaded
1624
- this.dialogFormsLoaded = 0;
1625
- this.on( 'form_loaded styles_loaded', function () {
1626
- this.dialogFormsLoaded ++;
1627
- if ( this.dialogFormsLoaded === 2 ) {
1628
- thisView.updateModel( {
1629
- refreshArgs: {
1630
- silent: true
1631
- }
1632
- } );
1633
- }
1634
- } );
1635
-
1636
- this.on( 'edit_label', function ( text ) {
1637
- // If text is set to default value, just clear it.
1638
- if ( text === panelsOptions.widgets[ this.model.get( 'class' ) ][ 'title' ] ) {
1639
- text = '';
1640
- }
1641
- this.model.set( 'label', text );
1642
- if ( _.isEmpty( text ) ) {
1643
- this.$( '.so-title' ).text( this.model.getWidgetField( 'title' ) );
1644
- }
1645
- }.bind( this ) );
1646
- },
1647
-
1648
- /**
1649
- * Render the widget dialog.
1650
- */
1651
- render: function () {
1652
- // Render the dialog and attach it to the builder interface
1653
- this.renderDialog( this.parseDialogContent( $( '#siteorigin-panels-dialog-widget' ).html(), {} ) );
1654
- this.loadForm();
1655
-
1656
- var title = this.model.getWidgetField( 'title' );
1657
- this.$( '.so-title .widget-name' ).html( title );
1658
- this.$( '.so-edit-title' ).val( title );
1659
-
1660
- if( ! this.builder.supports( 'addWidget' ) ) {
1661
- this.$( '.so-buttons .so-duplicate' ).remove();
1662
- }
1663
- if( ! this.builder.supports( 'deleteWidget' ) ) {
1664
- this.$( '.so-buttons .so-delete' ).remove();
1665
- }
1666
-
1667
- // Now we need to attach the style window
1668
- this.styles = new panels.view.styles();
1669
- this.styles.model = this.model;
1670
- this.styles.render( 'widget', this.builder.config.postId, {
1671
- builderType: this.builder.config.builderType,
1672
- dialog: this
1673
- } );
1674
-
1675
- var $rightSidebar = this.$( '.so-sidebar.so-right-sidebar' );
1676
- this.styles.attach( $rightSidebar );
1677
-
1678
- // Handle the loading class
1679
- this.styles.on( 'styles_loaded', function ( hasStyles ) {
1680
- // If we don't have styles remove the empty sidebar.
1681
- if ( ! hasStyles ) {
1682
- $rightSidebar.closest( '.so-panels-dialog' ).removeClass( 'so-panels-dialog-has-right-sidebar' );
1683
- $rightSidebar.remove();
1684
- }
1685
- }, this );
1686
- },
1687
-
1688
- /**
1689
- * Get the previous widget editing dialog by looking at the dom.
1690
- * @returns {*}
1691
- */
1692
- getPrevDialog: function () {
1693
- var widgets = this.builder.$( '.so-cells .cell .so-widget' );
1694
- if ( widgets.length <= 1 ) {
1695
- return false;
1696
- }
1697
- var currentIndex = widgets.index( this.widgetView.$el );
1698
-
1699
- if ( currentIndex === 0 ) {
1700
- return false;
1701
- } else {
1702
- var widgetView;
1703
- do {
1704
- widgetView = widgets.eq( --currentIndex ).data( 'view' );
1705
- if ( ! _.isUndefined( widgetView ) && ! widgetView.model.get( 'read_only' ) ) {
1706
- return widgetView.getEditDialog();
1707
- }
1708
- } while( ! _.isUndefined( widgetView ) && currentIndex > 0 );
1709
- }
1710
-
1711
- return false;
1712
- },
1713
-
1714
- /**
1715
- * Get the next widget editing dialog by looking at the dom.
1716
- * @returns {*}
1717
- */
1718
- getNextDialog: function () {
1719
- var widgets = this.builder.$( '.so-cells .cell .so-widget' );
1720
- if ( widgets.length <= 1 ) {
1721
- return false;
1722
- }
1723
-
1724
- var currentIndex = widgets.index( this.widgetView.$el );
1725
-
1726
- if ( currentIndex === widgets.length - 1 ) {
1727
- return false;
1728
- } else {
1729
- var widgetView;
1730
- do {
1731
- widgetView = widgets.eq( ++currentIndex ).data( 'view' );
1732
- if ( ! _.isUndefined( widgetView ) && ! widgetView.model.get( 'read_only' ) ) {
1733
- return widgetView.getEditDialog();
1734
- }
1735
- } while( ! _.isUndefined( widgetView ) );
1736
- }
1737
-
1738
- return false;
1739
- },
1740
-
1741
- /**
1742
- * Load the widget form from the server.
1743
- * This is called when rendering the dialog for the first time.
1744
- */
1745
- loadForm: function () {
1746
- // don't load the form if this dialog hasn't been rendered yet
1747
- if ( ! this.$( '> *' ).length ) {
1748
- return;
1749
- }
1750
-
1751
- this.$( '.so-content' ).addClass( 'so-panels-loading' );
1752
-
1753
- var data = {
1754
- 'action': 'so_panels_widget_form',
1755
- 'widget': this.model.get( 'class' ),
1756
- 'instance': JSON.stringify( this.model.get( 'values' ) ),
1757
- 'raw': this.model.get( 'raw' )
1758
- };
1759
-
1760
- var $soContent = this.$( '.so-content' );
1761
-
1762
- $.post( panelsOptions.ajaxurl, data, null, 'html' )
1763
- .done( function ( result ) {
1764
- // Add in the CID of the widget model
1765
- var html = result.replace( /{\$id}/g, this.model.cid );
1766
-
1767
- // Load this content into the form
1768
- $soContent
1769
- .removeClass( 'so-panels-loading' )
1770
- .html( html );
1771
-
1772
- // Trigger all the necessary events
1773
- this.trigger( 'form_loaded', this );
1774
-
1775
- // For legacy compatibility, trigger a panelsopen event
1776
- this.$( '.panel-dialog' ).trigger( 'panelsopen' );
1777
-
1778
- // If the main dialog is closed from this point on, save the widget content
1779
- this.on( 'close_dialog', this.updateModel, this );
1780
-
1781
- var widgetContent = $soContent.find( '> .widget-content' );
1782
- // If there's a widget content wrapper, this is one of the new widgets in WP 4.8 which need some special
1783
- // handling in JS.
1784
- if ( widgetContent.length > 0 ) {
1785
- jsWidget.addWidget( $soContent, this.model.widget_id );
1786
- }
1787
-
1788
- }.bind( this ) )
1789
- .fail( function ( error ) {
1790
- var html;
1791
- if ( error && error.responseText ) {
1792
- html = error.responseText;
1793
- } else {
1794
- html = panelsOptions.forms.loadingFailed;
1795
- }
1796
-
1797
- $soContent
1798
- .removeClass( 'so-panels-loading' )
1799
- .html( html );
1800
- } );
1801
- },
1802
-
1803
- /**
1804
- * Save the widget from the form to the model
1805
- */
1806
- updateModel: function ( args ) {
1807
- args = _.extend( {
1808
- refresh: true,
1809
- refreshArgs: null
1810
- }, args );
1811
-
1812
- // Get the values from the form and assign the new values to the model
1813
- this.savingWidget = true;
1814
-
1815
- if ( ! this.model.get( 'missing' ) ) {
1816
- // Only get the values for non missing widgets.
1817
- var values = this.getFormValues();
1818
- if ( _.isUndefined( values.widgets ) ) {
1819
- values = {};
1820
- } else {
1821
- values = values.widgets;
1822
- values = values[Object.keys( values )[0]];
1823
- }
1824
-
1825
- this.model.setValues( values );
1826
- this.model.set( 'raw', true ); // We've saved from the widget form, so this is now raw
1827
- }
1828
-
1829
- if ( this.styles.stylesLoaded ) {
1830
- // If the styles view has loaded
1831
- var style = {};
1832
- try {
1833
- style = this.getFormValues( '.so-sidebar .so-visual-styles' ).style;
1834
- }
1835
- catch ( e ) {
1836
- }
1837
- this.model.set( 'style', style );
1838
- }
1839
-
1840
- this.savingWidget = false;
1841
-
1842
- if ( args.refresh ) {
1843
- this.builder.model.refreshPanelsData( args.refreshArgs );
1844
- }
1845
- },
1846
-
1847
- /**
1848
- *
1849
- */
1850
- handleChangeValues: function () {
1851
- if ( ! this.savingWidget ) {
1852
- // Reload the form when we've changed the model and we're not currently saving from the form
1853
- this.loadForm();
1854
- }
1855
- },
1856
-
1857
- /**
1858
- * Save a history entry for this widget. Called when the dialog is closed.
1859
- */
1860
- saveHandler: function () {
1861
- this.builder.addHistoryEntry( 'widget_edited' );
1862
- this.closeDialog();
1863
- },
1864
-
1865
- /**
1866
- * When the user clicks delete.
1867
- *
1868
- * @returns {boolean}
1869
- */
1870
- deleteHandler: function () {
1871
- this.widgetView.visualDestroyModel();
1872
- this.closeDialog( {silent: true} );
1873
- this.builder.model.refreshPanelsData();
1874
-
1875
- return false;
1876
- },
1877
-
1878
- duplicateHandler: function () {
1879
- // Call the widget duplicate handler directly
1880
- this.widgetView.duplicateHandler();
1881
-
1882
- this.closeDialog( {silent: true} );
1883
- this.builder.model.refreshPanelsData();
1884
-
1885
- return false;
1886
- }
1887
-
1888
- } );
1889
 
1890
- },{"../view/widgets/js-widget":31}],10:[function(require,module,exports){
1891
- var panels = window.panels, $ = jQuery;
1892
-
1893
- module.exports = panels.view.dialog.extend( {
1894
-
1895
- builder: null,
1896
- widgetTemplate: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-dialog-widgets-widget' ).html() ) ),
1897
- filter: {},
1898
-
1899
- dialogClass: 'so-panels-dialog-add-widget',
1900
- dialogIcon: 'add-widget',
1901
-
1902
- events: {
1903
- 'click .so-close': 'closeDialog',
1904
- 'click .widget-type': 'widgetClickHandler',
1905
- 'keyup .so-sidebar-search': 'searchHandler'
1906
- },
1907
-
1908
- /**
1909
- * Initialize the widget adding dialog
1910
- */
1911
- initializeDialog: function () {
1912
-
1913
- this.on( 'open_dialog', function () {
1914
- this.filter.search = '';
1915
- this.filterWidgets( this.filter );
1916
- }, this );
1917
-
1918
- this.on( 'open_dialog_complete', function () {
1919
- // Clear the search and re-filter the widgets when we open the dialog
1920
- this.$( '.so-sidebar-search' ).val( '' ).focus();
1921
- this.balanceWidgetHeights();
1922
- } );
1923
-
1924
- // We'll implement a custom tab click handler
1925
- this.on( 'tab_click', this.tabClickHandler, this );
1926
- },
1927
-
1928
- render: function () {
1929
- // Render the dialog and attach it to the builder interface
1930
- this.renderDialog( this.parseDialogContent( $( '#siteorigin-panels-dialog-widgets' ).html(), {} ) );
1931
-
1932
- // Add all the widgets
1933
- _.each( panelsOptions.widgets, function ( widget ) {
1934
- var $w = $( this.widgetTemplate( {
1935
- title: widget.title,
1936
- description: widget.description
1937
- } ) );
1938
-
1939
- if ( _.isUndefined( widget.icon ) ) {
1940
- widget.icon = 'dashicons dashicons-admin-generic';
1941
- }
1942
-
1943
- $( '<span class="widget-icon" />' ).addClass( widget.icon ).prependTo( $w.find( '.widget-type-wrapper' ) );
1944
-
1945
- $w.data( 'class', widget.class ).appendTo( this.$( '.widget-type-list' ) );
1946
- }, this );
1947
-
1948
- // Add the sidebar tabs
1949
- var tabs = this.$( '.so-sidebar-tabs' );
1950
- _.each( panelsOptions.widget_dialog_tabs, function ( tab, key ) {
1951
- $( this.dialogTabTemplate( {'title': tab.title, 'tab': key} ) ).data( {
1952
- 'message': tab.message,
1953
- 'filter': tab.filter
1954
- } ).appendTo( tabs );
1955
- }, this );
1956
-
1957
- // We'll be using tabs, so initialize them
1958
- this.initTabs();
1959
-
1960
- var thisDialog = this;
1961
- $( window ).resize( function () {
1962
- thisDialog.balanceWidgetHeights();
1963
- } );
1964
- },
1965
-
1966
- /**
1967
- * Handle a tab being clicked
1968
- */
1969
- tabClickHandler: function ( $t ) {
1970
- // Get the filter from the tab, and filter the widgets
1971
- this.filter = $t.parent().data( 'filter' );
1972
- this.filter.search = this.$( '.so-sidebar-search' ).val();
1973
-
1974
- var message = $t.parent().data( 'message' );
1975
- if ( _.isEmpty( message ) ) {
1976
- message = '';
1977
- }
1978
-
1979
- this.$( '.so-toolbar .so-status' ).html( message );
1980
-
1981
- this.filterWidgets( this.filter );
1982
-
1983
- return false;
1984
- },
1985
-
1986
- /**
1987
- * Handle changes to the search value
1988
- */
1989
- searchHandler: function ( e ) {
1990
- if( e.which === 13 ) {
1991
- var visibleWidgets = this.$( '.widget-type-list .widget-type:visible' );
1992
- if( visibleWidgets.length === 1 ) {
1993
- visibleWidgets.click();
1994
- }
1995
- }
1996
- else {
1997
- this.filter.search = $( e.target ).val().trim();
1998
- this.filterWidgets( this.filter );
1999
- }
2000
- },
2001
-
2002
- /**
2003
- * Filter the widgets that we're displaying
2004
- * @param filter
2005
- */
2006
- filterWidgets: function ( filter ) {
2007
- if ( _.isUndefined( filter ) ) {
2008
- filter = {};
2009
- }
2010
-
2011
- if ( _.isUndefined( filter.groups ) ) {
2012
- filter.groups = '';
2013
- }
2014
-
2015
- this.$( '.widget-type-list .widget-type' ).each( function () {
2016
- var $$ = $( this ), showWidget;
2017
- var widgetClass = $$.data( 'class' );
2018
-
2019
- var widgetData = (
2020
- ! _.isUndefined( panelsOptions.widgets[widgetClass] )
2021
- ) ? panelsOptions.widgets[widgetClass] : null;
2022
-
2023
- if ( _.isEmpty( filter.groups ) ) {
2024
- // This filter doesn't specify groups, so show all
2025
- showWidget = true;
2026
- } else if ( widgetData !== null && ! _.isEmpty( _.intersection( filter.groups, panelsOptions.widgets[widgetClass].groups ) ) ) {
2027
- // This widget is in the filter group
2028
- showWidget = true;
2029
- } else {
2030
- // This widget is not in the filter group
2031
- showWidget = false;
2032
- }
2033
-
2034
- // This can probably be done with a more intelligent operator
2035
- if ( showWidget ) {
2036
-
2037
- if ( ! _.isUndefined( filter.search ) && filter.search !== '' ) {
2038
- // Check if the widget title contains the search term
2039
- if ( widgetData.title.toLowerCase().indexOf( filter.search.toLowerCase() ) === - 1 ) {
2040
- showWidget = false;
2041
- }
2042
- }
2043
-
2044
- }
2045
-
2046
- if ( showWidget ) {
2047
- $$.show();
2048
- } else {
2049
- $$.hide();
2050
- }
2051
- } );
2052
-
2053
- // Balance the tags after filtering
2054
- this.balanceWidgetHeights();
2055
- },
2056
-
2057
- /**
2058
- * Add the widget to the current builder
2059
- *
2060
- * @param e
2061
- */
2062
- widgetClickHandler: function ( e ) {
2063
- // Add the history entry
2064
- this.builder.trigger('before_user_adds_widget');
2065
- this.builder.addHistoryEntry( 'widget_added' );
2066
-
2067
- var $w = $( e.currentTarget );
2068
-
2069
- var widget = new panels.model.widget( {
2070
- class: $w.data( 'class' )
2071
- } );
2072
-
2073
- // Add the widget to the cell model
2074
- widget.cell = this.builder.getActiveCell();
2075
- widget.cell.get('widgets').add( widget );
2076
-
2077
- this.closeDialog();
2078
- this.builder.model.refreshPanelsData();
2079
-
2080
- this.builder.trigger('after_user_adds_widget', widget);
2081
- },
2082
-
2083
- /**
2084
- * Balance widgets in a given row so they have enqual height.
2085
- * @param e
2086
- */
2087
- balanceWidgetHeights: function ( e ) {
2088
- var widgetRows = [[]];
2089
- var previousWidget = null;
2090
-
2091
- // Work out how many widgets there are per row
2092
- var perRow = Math.round( this.$( '.widget-type' ).parent().width() / this.$( '.widget-type' ).width() );
2093
-
2094
- // Add clears to create balanced rows
2095
- this.$( '.widget-type' )
2096
- .css( 'clear', 'none' )
2097
- .filter( ':visible' )
2098
- .each( function ( i, el ) {
2099
- if ( i % perRow === 0 && i !== 0 ) {
2100
- $( el ).css( 'clear', 'both' );
2101
- }
2102
- } );
2103
-
2104
- // Group the widgets into rows
2105
- this.$( '.widget-type-wrapper' )
2106
- .css( 'height', 'auto' )
2107
- .filter( ':visible' )
2108
- .each( function ( i, el ) {
2109
- var $el = $( el );
2110
- if ( previousWidget !== null && previousWidget.position().top !== $el.position().top ) {
2111
- widgetRows[widgetRows.length] = [];
2112
- }
2113
- previousWidget = $el;
2114
- widgetRows[widgetRows.length - 1].push( $el );
2115
- } );
2116
-
2117
- // Balance the height of the widgets within the row.
2118
- _.each( widgetRows, function ( row, i ) {
2119
- var maxHeight = _.max( row.map( function ( el ) {
2120
- return el.height();
2121
- } ) );
2122
- // Set the height of each widget in the row
2123
- _.each( row, function ( el ) {
2124
- el.height( maxHeight );
2125
- } );
2126
-
2127
- } );
2128
- }
2129
- } );
2130
 
2131
- },{}],11:[function(require,module,exports){
2132
- module.exports = {
2133
  /**
2134
- * Check if we have copy paste available.
2135
- * @returns {boolean|*}
2136
  */
2137
- canCopyPaste: function(){
2138
- return typeof(Storage) !== "undefined" && panelsOptions.user;
 
 
 
 
 
 
 
2139
  },
2140
 
2141
  /**
2142
- * Set the model that we're going to store in the clipboard
2143
  */
2144
- setModel: function( model ){
2145
- if( ! this.canCopyPaste() ) {
2146
- return false;
2147
- }
2148
-
2149
- var serial = panels.helpers.serialize.serialize( model );
2150
- if( model instanceof panels.model.row ) {
2151
- serial.thingType = 'row-model';
2152
- } else if( model instanceof panels.model.widget ) {
2153
- serial.thingType = 'widget-model';
2154
- }
2155
 
2156
- // Store this in local storage
2157
- localStorage[ 'panels_clipboard_' + panelsOptions.user ] = JSON.stringify( serial );
2158
- return true;
2159
  },
2160
 
2161
  /**
2162
- * Check if the current model stored in the clipboard is the expected type
 
 
2163
  */
2164
- isModel: function( expected ){
2165
- if( ! this.canCopyPaste() ) {
2166
- return false;
2167
- }
 
 
2168
 
2169
- var clipboardObject = localStorage[ 'panels_clipboard_' + panelsOptions.user ];
2170
- if( clipboardObject !== undefined ) {
2171
- clipboardObject = JSON.parse(clipboardObject);
2172
- return clipboardObject.thingType && clipboardObject.thingType === expected;
 
 
 
 
 
 
 
 
 
 
 
 
2173
  }
2174
 
2175
- return false;
2176
  },
2177
 
2178
  /**
2179
- * Get the model currently stored in the clipboard
2180
  */
2181
- getModel: function( expected ){
2182
- if( ! this.canCopyPaste() ) {
2183
- return null;
2184
- }
2185
 
2186
- var clipboardObject = localStorage[ 'panels_clipboard_' + panelsOptions.user ];
2187
- if( clipboardObject !== undefined ) {
2188
- clipboardObject = JSON.parse( clipboardObject );
2189
- if( clipboardObject.thingType && clipboardObject.thingType === expected ) {
2190
- return panels.helpers.serialize.unserialize( clipboardObject, clipboardObject.thingType, null );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2191
  }
 
 
 
 
 
 
 
 
 
2192
  }
2193
 
2194
- return null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2195
  },
2196
- };
2197
 
2198
- },{}],12:[function(require,module,exports){
2199
- module.exports = {
2200
  /**
2201
- * Lock window scrolling for the main overlay
 
 
2202
  */
2203
- lock: function () {
2204
- if ( jQuery( 'body' ).css( 'overflow' ) === 'hidden' ) {
2205
- return;
 
 
 
 
 
 
 
 
 
2206
  }
2207
 
2208
- // lock scroll position, but retain settings for later
2209
- var scrollPosition = [
2210
- self.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
2211
- self.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
2212
- ];
 
 
 
 
 
2213
 
2214
- jQuery( 'body' )
2215
- .data( {
2216
- 'scroll-position': scrollPosition
2217
- } )
2218
- .css( 'overflow', 'hidden' );
2219
 
2220
- if( ! _.isUndefined( scrollPosition ) ) {
2221
- window.scrollTo( scrollPosition[0], scrollPosition[1] );
 
 
 
 
2222
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2223
  },
2224
 
2225
  /**
2226
- * Unlock window scrolling
 
2227
  */
2228
- unlock: function () {
2229
- if ( jQuery( 'body' ).css( 'overflow' ) !== 'hidden' ) {
2230
- return;
2231
- }
 
 
2232
 
2233
- // Check that there are no more dialogs or a live editor
2234
- if ( ! jQuery( '.so-panels-dialog-wrapper' ).is( ':visible' ) && ! jQuery( '.so-panels-live-editor' ).is( ':visible' ) ) {
2235
- jQuery( 'body' ).css( 'overflow', 'visible' );
2236
- var scrollPosition = jQuery( 'body' ).data( 'scroll-position' );
2237
 
2238
- if( ! _.isUndefined( scrollPosition ) ) {
2239
- window.scrollTo( scrollPosition[0], scrollPosition[1] );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2240
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2241
  }
2242
  },
2243
- };
2244
 
2245
- },{}],13:[function(require,module,exports){
2246
- /*
2247
- This is a modified version of https://github.com/underdogio/backbone-serialize/
2248
- */
2249
-
2250
- /* global Backbone, module, panels */
2251
-
2252
- module.exports = {
2253
- serialize: function( thing ){
2254
- var val;
2255
-
2256
- if( thing instanceof Backbone.Model ) {
2257
- var retObj = {};
2258
- for ( var key in thing.attributes ) {
2259
- if (thing.attributes.hasOwnProperty( key ) ) {
2260
- // Skip these to avoid recursion
2261
- if( key === 'builder' || key === 'collection' ) { continue; }
2262
-
2263
- // If the value is a Model or a Collection, then serialize them as well
2264
- val = thing.attributes[key];
2265
- if ( val instanceof Backbone.Model || val instanceof Backbone.Collection ) {
2266
- retObj[key] = this.serialize( val );
2267
- } else {
2268
- // Otherwise, save the original value
2269
- retObj[key] = val;
2270
- }
2271
- }
2272
- }
2273
- return retObj;
2274
- }
2275
- else if( thing instanceof Backbone.Collection ) {
2276
- // Walk over all of our models
2277
- var retArr = [];
2278
-
2279
- for ( var i = 0; i < thing.models.length; i++ ) {
2280
- // If the model is serializable, then serialize it
2281
- val = thing.models[i];
2282
-
2283
- if ( val instanceof Backbone.Model || val instanceof Backbone.Collection ) {
2284
- retArr.push( this.serialize( val ) );
2285
- } else {
2286
- // Otherwise (it is an object), return it in its current form
2287
- retArr.push( val );
2288
- }
2289
- }
2290
-
2291
- // Return the serialized models
2292
- return retArr;
2293
- }
2294
- },
2295
-
2296
- unserialize: function( thing, thingType, parent ) {
2297
- var retObj;
2298
-
2299
- switch( thingType ) {
2300
- case 'row-model' :
2301
- retObj = new panels.model.row();
2302
- retObj.builder = parent;
2303
- var atts = { style: thing.style };
2304
- if ( thing.hasOwnProperty( 'label' ) ) {
2305
- atts.label = thing.label;
2306
- }
2307
- if ( thing.hasOwnProperty( 'color_label' ) ) {
2308
- atts.color_label = thing.color_label;
2309
- }
2310
- retObj.set( atts );
2311
- retObj.setCells( this.unserialize( thing.cells, 'cell-collection', retObj ) );
2312
- break;
2313
-
2314
- case 'cell-model' :
2315
- retObj = new panels.model.cell();
2316
- retObj.row = parent;
2317
- retObj.set( 'weight', thing.weight );
2318
- retObj.set( 'style', thing.style );
2319
- retObj.set( 'widgets', this.unserialize( thing.widgets, 'widget-collection', retObj ) );
2320
- break;
2321
-
2322
- case 'widget-model' :
2323
- retObj = new panels.model.widget();
2324
- retObj.cell = parent;
2325
- for ( var key in thing ) {
2326
- if ( thing.hasOwnProperty( key ) ) {
2327
- retObj.set( key, thing[key] );
2328
- }
2329
- }
2330
- retObj.set( 'widget_id', panels.helpers.utils.generateUUID() );
2331
- break;
2332
-
2333
- case 'cell-collection':
2334
- retObj = new panels.collection.cells();
2335
- for( var i = 0; i < thing.length; i++ ) {
2336
- retObj.push( this.unserialize( thing[i], 'cell-model', parent ) );
2337
- }
2338
- break;
2339
-
2340
- case 'widget-collection':
2341
- retObj = new panels.collection.widgets();
2342
- for( var i = 0; i < thing.length; i++ ) {
2343
- retObj.push( this.unserialize( thing[i], 'widget-model', parent ) );
2344
- }
2345
- break;
2346
-
2347
- default:
2348
- console.log( 'Unknown Thing - ' + thingType );
2349
- break;
2350
- }
2351
-
2352
- return retObj;
2353
- }
2354
- };
2355
 
2356
- },{}],14:[function(require,module,exports){
2357
- module.exports = {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2358
 
2359
- generateUUID: function(){
2360
- var d = new Date().getTime();
2361
- if( window.performance && typeof window.performance.now === "function" ){
2362
- d += performance.now(); //use high-precision timer if available
 
 
2363
  }
2364
- var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace( /[xy]/g, function(c) {
2365
- var r = (d + Math.random()*16)%16 | 0;
2366
- d = Math.floor(d/16);
2367
- return ( c == 'x' ? r : (r&0x3|0x8) ).toString(16);
2368
- } );
2369
- return uuid;
2370
  },
2371
 
2372
- processTemplate: function ( s ) {
2373
- if ( _.isUndefined( s ) || _.isNull( s ) ) {
2374
- return '';
 
 
 
 
 
 
 
 
 
 
 
2375
  }
2376
- s = s.replace( /{{%/g, '<%' );
2377
- s = s.replace( /%}}/g, '%>' );
2378
- s = s.trim();
2379
- return s;
2380
  },
2381
 
2382
- // From this SO post: http://stackoverflow.com/questions/6139107/programmatically-select-text-in-a-contenteditable-html-element
2383
- selectElementContents: function( element ) {
2384
- var range = document.createRange();
2385
- range.selectNodeContents( element );
2386
- var sel = window.getSelection();
2387
- sel.removeAllRanges();
2388
- sel.addRange( range );
2389
- },
2390
 
2391
- }
 
2392
 
2393
- },{}],15:[function(require,module,exports){
2394
- /* global _, jQuery, panels */
2395
-
2396
- var panels = window.panels, $ = jQuery;
2397
-
2398
- module.exports = function ( config, force ) {
2399
-
2400
- return this.each( function () {
2401
- var $$ = jQuery( this );
2402
-
2403
- if ( $$.data( 'soPanelsBuilderWidgetInitialized' ) && ! force ) {
2404
- return;
2405
- }
2406
- var widgetId = $$.closest( 'form' ).find( '.widget-id' ).val();
2407
-
2408
- // Create a config for this specific widget
2409
- var thisConfig = $.extend(true, {}, config);
2410
-
2411
- // Exit if this isn't a real widget
2412
- if ( ! _.isUndefined( widgetId ) && widgetId.indexOf( '__i__' ) > - 1 ) {
2413
- return;
2414
- }
2415
-
2416
- // Create the main builder model
2417
- var builderModel = new panels.model.builder();
2418
-
2419
- // Now for the view to display the builder
2420
- var builderView = new panels.view.builder( {
2421
- model: builderModel,
2422
- config: thisConfig
2423
- } );
2424
-
2425
- // Save panels data when we close the dialog, if we're in a dialog
2426
- var dialog = $$.closest( '.so-panels-dialog-wrapper' ).data( 'view' );
2427
- if ( ! _.isUndefined( dialog ) ) {
2428
- dialog.on( 'close_dialog', function () {
2429
- builderModel.refreshPanelsData();
2430
- } );
2431
-
2432
- dialog.on( 'open_dialog_complete', function () {
2433
- // Make sure the new layout widget is always properly setup
2434
- builderView.trigger( 'builder_resize' );
2435
- } );
2436
-
2437
- dialog.model.on( 'destroy', function () {
2438
- // Destroy the builder
2439
- builderModel.emptyRows().destroy();
2440
- } );
2441
-
2442
- // Set the parent for all the sub dialogs
2443
- builderView.setDialogParents( panelsOptions.loc.layout_widget, dialog );
2444
- }
2445
-
2446
- // Basic setup for the builder
2447
- var isWidget = Boolean( $$.closest( '.widget-content' ).length );
2448
- builderView
2449
- .render()
2450
- .attach( {
2451
- container: $$,
2452
- dialog: isWidget || $$.data('mode') === 'dialog',
2453
- type: $$.data( 'type' )
2454
- } )
2455
- .setDataField( $$.find( 'input.panels-data' ) );
2456
-
2457
- if ( isWidget || $$.data('mode') === 'dialog' ) {
2458
- // Set up the dialog opening
2459
- builderView.setDialogParents( panelsOptions.loc.layout_widget, builderView.dialog );
2460
- $$.find( '.siteorigin-panels-display-builder' ).click( function ( e ) {
2461
- e.preventDefault();
2462
- builderView.dialog.openDialog();
2463
- } );
2464
- } else {
2465
- // Remove the dialog opener button, this is already being displayed in a page builder dialog.
2466
- $$.find( '.siteorigin-panels-display-builder' ).parent().remove();
2467
- }
2468
-
2469
- // Trigger a global jQuery event after we've setup the builder view
2470
- $( document ).trigger( 'panels_setup', builderView );
2471
-
2472
- $$.data( 'soPanelsBuilderWidgetInitialized', true );
2473
- } );
2474
- };
2475
 
2476
- },{}],16:[function(require,module,exports){
2477
- /**
2478
- * Everything we need for SiteOrigin Page Builder.
2479
- *
2480
- * @copyright Greg Priday 2013 - 2016 - <https://siteorigin.com/>
2481
- * @license GPL 3.0 http://www.gnu.org/licenses/gpl.html
2482
- */
2483
-
2484
- /* global Backbone, _, jQuery, tinyMCE, panelsOptions, plupload, confirm, console, require */
2485
-
2486
- var panels = {};
2487
-
2488
- // Store everything globally
2489
- window.panels = panels;
2490
- window.siteoriginPanels = panels;
2491
-
2492
- // Helpers
2493
- panels.helpers = {};
2494
- panels.helpers.clipboard = require( './helpers/clipboard' );
2495
- panels.helpers.utils = require( './helpers/utils' );
2496
- panels.helpers.serialize = require( './helpers/serialize' );
2497
- panels.helpers.pageScroll = require( './helpers/page-scroll' );
2498
-
2499
- // The models
2500
- panels.model = {};
2501
- panels.model.widget = require( './model/widget' );
2502
- panels.model.cell = require( './model/cell' );
2503
- panels.model.row = require( './model/row' );
2504
- panels.model.builder = require( './model/builder' );
2505
- panels.model.historyEntry = require( './model/history-entry' );
2506
-
2507
- // The collections
2508
- panels.collection = {};
2509
- panels.collection.widgets = require( './collection/widgets' );
2510
- panels.collection.cells = require( './collection/cells' );
2511
- panels.collection.rows = require( './collection/rows' );
2512
- panels.collection.historyEntries = require( './collection/history-entries' );
2513
-
2514
- // The views
2515
- panels.view = {};
2516
- panels.view.widget = require( './view/widget' );
2517
- panels.view.cell = require( './view/cell' );
2518
- panels.view.row = require( './view/row' );
2519
- panels.view.builder = require( './view/builder' );
2520
- panels.view.dialog = require( './view/dialog' );
2521
- panels.view.styles = require( './view/styles' );
2522
- panels.view.liveEditor = require( './view/live-editor' );
2523
-
2524
- // The dialogs
2525
- panels.dialog = {};
2526
- panels.dialog.builder = require( './dialog/builder' );
2527
- panels.dialog.widgets = require( './dialog/widgets' );
2528
- panels.dialog.widget = require( './dialog/widget' );
2529
- panels.dialog.prebuilt = require( './dialog/prebuilt' );
2530
- panels.dialog.row = require( './dialog/row' );
2531
- panels.dialog.history = require( './dialog/history' );
2532
-
2533
- // The utils
2534
- panels.utils = {};
2535
- panels.utils.menu = require( './utils/menu' );
2536
-
2537
- // jQuery Plugins
2538
- jQuery.fn.soPanelsSetupBuilderWidget = require( './jquery/setup-builder-widget' );
2539
-
2540
-
2541
- // Set up Page Builder if we're on the main interface
2542
- jQuery( function ( $ ) {
2543
-
2544
- var container,
2545
- field,
2546
- form,
2547
- builderConfig;
2548
-
2549
- var $panelsMetabox = $( '#siteorigin-panels-metabox' );
2550
- form = $( 'form#post' );
2551
- if ( $panelsMetabox.length && form.length ) {
2552
- // This is usually the case when we're in the post edit interface
2553
- container = $panelsMetabox;
2554
- field = $panelsMetabox.find( '.siteorigin-panels-data-field' );
2555
-
2556
- builderConfig = {
2557
- editorType: 'tinyMCE',
2558
- postId: $( '#post_ID' ).val(),
2559
- editorId: '#content',
2560
- builderType: $panelsMetabox.data( 'builder-type' ),
2561
- builderSupports: $panelsMetabox.data( 'builder-supports' ),
2562
- loadOnAttach: panelsOptions.loadOnAttach && $( '#auto_draft' ).val() == 1,
2563
- loadLiveEditor: $panelsMetabox.data('live-editor') == 1,
2564
- liveEditorPreview: container.data('preview-url')
2565
- };
2566
- }
2567
- else if ( $( '.siteorigin-panels-builder-form' ).length ) {
2568
- // We're dealing with another interface like the custom home page interface
2569
- var $$ = $( '.siteorigin-panels-builder-form' );
2570
-
2571
- container = $$.find( '.siteorigin-panels-builder-container' );
2572
- field = $$.find( 'input[name="panels_data"]' );
2573
- form = $$;
2574
-
2575
- builderConfig = {
2576
- editorType: 'standalone',
2577
- postId: $$.data( 'post-id' ),
2578
- editorId: '#post_content',
2579
- builderType: $$.data( 'type' ),
2580
- builderSupports: $$.data( 'builder-supports' ),
2581
- loadLiveEditor: false,
2582
- liveEditorPreview: $$.data( 'preview-url' )
2583
- };
2584
- }
2585
-
2586
- if ( ! _.isUndefined( container ) ) {
2587
- // If we have a container, then set up the main builder
2588
- var panels = window.siteoriginPanels;
2589
-
2590
- // Create the main builder model
2591
- var builderModel = new panels.model.builder();
2592
-
2593
- // Now for the view to display the builder
2594
- var builderView = new panels.view.builder( {
2595
- model: builderModel,
2596
- config: builderConfig
2597
- } );
2598
-
2599
- // Trigger an event before the panels setup to allow adding listeners for various builder events which are
2600
- // triggered during initial setup.
2601
- $(document).trigger('before_panels_setup', builderView);
2602
-
2603
- // Set up the builder view
2604
- builderView
2605
- .render()
2606
- .attach( {
2607
- container: container
2608
- } )
2609
- .setDataField( field )
2610
- .attachToEditor();
2611
-
2612
- // When the form is submitted, update the panels data
2613
- form.submit( function () {
2614
- // Refresh the data
2615
- builderModel.refreshPanelsData();
2616
- } );
2617
-
2618
- container.removeClass( 'so-panels-loading' );
2619
-
2620
- // Trigger a global jQuery event after we've setup the builder view. Everything is accessible form there
2621
- $( document ).trigger( 'panels_setup', builderView, window.panels );
2622
- }
2623
-
2624
- // Setup new widgets when they're added in the standard widget interface
2625
- $( document ).on( 'widget-added', function ( e, widget ) {
2626
- $( widget ).find( '.siteorigin-page-builder-widget' ).soPanelsSetupBuilderWidget();
2627
- } );
2628
-
2629
- // Setup existing widgets on the page (for the widgets interface)
2630
- if ( ! $( 'body' ).hasClass( 'wp-customizer' ) ) {
2631
- $( function () {
2632
- $( '.siteorigin-page-builder-widget' ).soPanelsSetupBuilderWidget();
2633
- } );
2634
- }
2635
-
2636
- // A global escape handler
2637
- $(window).on('keyup', function(e){
2638
- // [Esc] to close
2639
- if ( e.which === 27 ) {
2640
- // Trigger a click on the last visible Page Builder window
2641
- $( '.so-panels-dialog-wrapper, .so-panels-live-editor' ).filter(':visible')
2642
- .last().find('.so-title-bar .so-close, .live-editor-close').click();
2643
- }
2644
- });
2645
- } );
2646
 
2647
- },{"./collection/cells":1,"./collection/history-entries":2,"./collection/rows":3,"./collection/widgets":4,"./dialog/builder":5,"./dialog/history":6,"./dialog/prebuilt":7,"./dialog/row":8,"./dialog/widget":9,"./dialog/widgets":10,"./helpers/clipboard":11,"./helpers/page-scroll":12,"./helpers/serialize":13,"./helpers/utils":14,"./jquery/setup-builder-widget":15,"./model/builder":17,"./model/cell":18,"./model/history-entry":19,"./model/row":20,"./model/widget":21,"./utils/menu":22,"./view/builder":23,"./view/cell":24,"./view/dialog":25,"./view/live-editor":26,"./view/row":27,"./view/styles":28,"./view/widget":29}],17:[function(require,module,exports){
2648
- module.exports = Backbone.Model.extend({
2649
- layoutPosition: {
2650
- BEFORE: 'before',
2651
- AFTER: 'after',
2652
- REPLACE: 'replace',
2653
- },
2654
 
2655
- rows: {},
 
2656
 
2657
- defaults: {
2658
- 'data': {
2659
- 'widgets': [],
2660
- 'grids': [],
2661
- 'grid_cells': []
2662
- }
2663
- },
2664
 
2665
- initialize: function () {
2666
- // These are the main rows in the interface
2667
- this.set( 'rows', new panels.collection.rows() );
2668
  },
2669
 
 
 
 
 
 
 
 
2670
  /**
2671
- * Add a new row to this builder.
2672
- *
2673
- * @param attrs
2674
- * @param cells
2675
- * @param options
2676
  */
2677
- addRow: function (attrs, cells, options) {
2678
- options = _.extend({
2679
- noAnimate: false
2680
- }, options);
2681
-
2682
- var cellCollection = new panels.collection.cells(cells);
2683
 
2684
- attrs = _.extend({
2685
- collection: this.get('rows'),
2686
- cells: cellCollection,
2687
- }, attrs);
2688
 
2689
- // Create the actual row
2690
- var row = new panels.model.row(attrs);
2691
- row.builder = this;
 
 
 
 
2692
 
2693
- this.get('rows').add( row, options );
 
 
 
2694
 
2695
- return row;
2696
- },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2697
 
2698
  /**
2699
- * Load the panels data into the builder
2700
  *
2701
- * @param data Object the layout and widgets data to load.
2702
- * @param position string Where to place the new layout. Allowed options are 'before', 'after'. Anything else will
2703
- * cause the new layout to replace the old one.
2704
  */
2705
- loadPanelsData: function ( data, position ) {
2706
- try {
2707
- if ( position === this.layoutPosition.BEFORE ) {
2708
- data = this.concatPanelsData( data, this.getPanelsData() );
2709
- } else if ( position === this.layoutPosition.AFTER ) {
2710
- data = this.concatPanelsData( this.getPanelsData(), data );
2711
- }
2712
 
2713
- // Start by destroying any rows that currently exist. This will in turn destroy cells, widgets and all the associated views
2714
- this.emptyRows();
 
 
 
 
 
 
 
2715
 
2716
- // This will empty out the current rows and reload the builder data.
2717
- this.set( 'data', JSON.parse( JSON.stringify( data ) ), {silent: true} );
2718
 
2719
- var cit = 0;
2720
- var rows = [];
 
 
2721
 
2722
- if ( _.isUndefined( data.grid_cells ) ) {
2723
- this.trigger( 'load_panels_data' );
2724
- return;
 
 
 
 
 
 
 
 
 
2725
  }
 
 
 
 
2726
 
2727
- var gi;
2728
- for ( var ci = 0; ci < data.grid_cells.length; ci ++ ) {
2729
- gi = parseInt( data.grid_cells[ci].grid );
2730
- if ( _.isUndefined( rows[gi] ) ) {
2731
- rows[gi] = [];
2732
- }
2733
 
2734
- rows[gi].push( data.grid_cells[ci] );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2735
  }
 
 
2736
 
2737
- var builderModel = this;
2738
- _.each( rows, function ( row, i ) {
2739
- var rowAttrs = {};
 
 
 
 
2740
 
2741
- if ( ! _.isUndefined( data.grids[i].style ) ) {
2742
- rowAttrs.style = data.grids[i].style;
2743
- }
2744
 
2745
- if ( ! _.isUndefined( data.grids[i].ratio) ) {
2746
- rowAttrs.ratio = data.grids[i].ratio;
2747
- }
 
 
 
 
2748
 
2749
- if ( ! _.isUndefined( data.grids[i].ratio_direction) ) {
2750
- rowAttrs.ratio_direction = data.grids[i].ratio_direction
2751
- }
 
 
 
 
 
2752
 
2753
- if ( ! _.isUndefined( data.grids[i].color_label) ) {
2754
- rowAttrs.color_label = data.grids[i].color_label;
2755
- }
2756
 
2757
- if ( ! _.isUndefined( data.grids[i].label) ) {
2758
- rowAttrs.label = data.grids[i].label;
2759
- }
2760
- // This will create and add the row model and its cells
2761
- builderModel.addRow(rowAttrs, row, {noAnimate: true} );
2762
- } );
 
 
 
2763
 
 
 
2764
 
2765
- if ( _.isUndefined( data.widgets ) ) {
2766
- return;
 
 
 
 
 
 
 
 
 
2767
  }
2768
 
2769
- // Add the widgets
2770
- _.each( data.widgets, function ( widgetData ) {
2771
- var panels_info = null;
2772
- if ( ! _.isUndefined( widgetData.panels_info ) ) {
2773
- panels_info = widgetData.panels_info;
2774
- delete widgetData.panels_info;
2775
- } else {
2776
- panels_info = widgetData.info;
2777
- delete widgetData.info;
2778
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2779
 
2780
- var row = builderModel.get('rows').at( parseInt( panels_info.grid ) );
2781
- var cell = row.get('cells').at( parseInt( panels_info.cell ) );
 
 
 
2782
 
2783
- var newWidget = new panels.model.widget( {
2784
- class: panels_info.class,
2785
- values: widgetData
2786
- } );
2787
 
2788
- if ( ! _.isUndefined( panels_info.style ) ) {
2789
- newWidget.set( 'style', panels_info.style );
2790
  }
2791
 
2792
- if ( ! _.isUndefined( panels_info.read_only ) ) {
2793
- newWidget.set( 'read_only', panels_info.read_only );
2794
- }
2795
- if ( ! _.isUndefined( panels_info.widget_id ) ) {
2796
- newWidget.set( 'widget_id', panels_info.widget_id );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2797
  }
2798
- else {
2799
- newWidget.set( 'widget_id', panels.helpers.utils.generateUUID() );
2800
  }
2801
 
2802
- if ( ! _.isUndefined( panels_info.label ) ) {
2803
- newWidget.set( 'label', panels_info.label );
2804
- }
 
2805
 
2806
- newWidget.cell = cell;
2807
- cell.get('widgets').add( newWidget, { noAnimate: true } );
2808
- } );
2809
 
2810
- this.trigger( 'load_panels_data' );
 
 
 
 
 
 
 
 
2811
  }
2812
- catch ( err ) {
2813
- console.log( 'Error loading data: ' + err.message );
2814
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2815
  }
 
 
 
 
 
 
 
 
 
 
 
2816
  },
2817
 
2818
  /**
2819
- * Concatenate the second set of Page Builder data to the first. There is some validation of input, but for the most
2820
- * part it's up to the caller to ensure the Page Builder data is well formed.
2821
  */
2822
- concatPanelsData: function ( panelsDataA, panelsDataB ) {
 
 
 
 
 
 
 
 
2823
 
2824
- if ( _.isUndefined( panelsDataB ) || _.isUndefined( panelsDataB.grids ) || _.isEmpty( panelsDataB.grids ) ||
2825
- _.isUndefined( panelsDataB.grid_cells ) || _.isEmpty( panelsDataB.grid_cells ) ) {
2826
- return panelsDataA;
2827
- }
2828
 
2829
- if ( _.isUndefined( panelsDataA ) || _.isUndefined( panelsDataA.grids ) || _.isEmpty( panelsDataA.grids ) ) {
2830
- return panelsDataB;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2831
  }
2832
 
2833
- var gridsBOffset = panelsDataA.grids.length;
2834
- var widgetsBOffset = ! _.isUndefined( panelsDataA.widgets ) ? panelsDataA.widgets.length : 0;
2835
- var newPanelsData = {grids: [], 'grid_cells': [], 'widgets': []};
2836
 
2837
- // Concatenate grids (rows)
2838
- newPanelsData.grids = panelsDataA.grids.concat( panelsDataB.grids );
 
2839
 
2840
- // Create a copy of panelsDataA grid_cells and widgets
2841
- if ( ! _.isUndefined( panelsDataA.grid_cells ) ) {
2842
- newPanelsData.grid_cells = panelsDataA.grid_cells.slice();
 
 
 
 
 
2843
  }
2844
- if ( ! _.isUndefined( panelsDataA.widgets ) ) {
2845
- newPanelsData.widgets = panelsDataA.widgets.slice();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2846
  }
2847
 
2848
- var i;
2849
- // Concatenate grid cells (row columns)
2850
- for ( i = 0; i < panelsDataB.grid_cells.length; i ++ ) {
2851
- var gridCellB = panelsDataB.grid_cells[i];
2852
- gridCellB.grid = parseInt( gridCellB.grid ) + gridsBOffset;
2853
- newPanelsData.grid_cells.push( gridCellB );
 
 
 
 
 
 
2854
  }
2855
 
2856
- // Concatenate widgets
2857
- if ( ! _.isUndefined( panelsDataB.widgets ) ) {
2858
- for ( i = 0; i < panelsDataB.widgets.length; i ++ ) {
2859
- var widgetB = panelsDataB.widgets[i];
2860
- widgetB.panels_info.grid = parseInt( widgetB.panels_info.grid ) + gridsBOffset;
2861
- widgetB.panels_info.id = parseInt( widgetB.panels_info.id ) + widgetsBOffset;
2862
- newPanelsData.widgets.push( widgetB );
 
 
2863
  }
 
 
2864
  }
2865
 
2866
- return newPanelsData;
 
 
2867
  },
2868
 
2869
  /**
2870
- * Convert the content of the builder into a object that represents the page builder data
2871
  */
2872
- getPanelsData: function () {
 
2873
 
2874
- var builder = this;
2875
 
2876
- var data = {
2877
- 'widgets': [],
2878
- 'grids': [],
2879
- 'grid_cells': []
2880
- };
2881
- var widgetId = 0;
2882
 
2883
- this.get('rows').each( function ( row, ri ) {
 
 
 
2884
 
2885
- row.get('cells').each( function ( cell, ci ) {
 
 
2886
 
2887
- cell.get('widgets').each( function ( widget, wi ) {
2888
- // Add the data for the widget, including the panels_info field.
2889
- var panels_info = {
2890
- class: widget.get( 'class' ),
2891
- raw: widget.get( 'raw' ),
2892
- grid: ri,
2893
- cell: ci,
2894
- // Strictly this should be an index
2895
- id: widgetId ++,
2896
- widget_id: widget.get( 'widget_id' ),
2897
- style: widget.get( 'style' ),
2898
- label: widget.get( 'label' ),
2899
- };
2900
-
2901
- if( _.isEmpty( panels_info.widget_id ) ) {
2902
- panels_info.widget_id = panels.helpers.utils.generateUUID();
2903
- }
2904
 
2905
- var values = _.extend( _.clone( widget.get( 'values' ) ), {
2906
- panels_info: panels_info
2907
- } );
2908
- data.widgets.push( values );
2909
- } );
2910
 
2911
- // Add the cell info
2912
- data.grid_cells.push( {
2913
- grid: ri,
2914
- index: ci,
2915
- weight: cell.get( 'weight' ),
2916
- style: cell.get( 'style' ),
2917
- } );
2918
 
2919
- } );
 
 
 
 
 
 
2920
 
2921
- data.grids.push( {
2922
- cells: row.get('cells').length,
2923
- style: row.get( 'style' ),
2924
- ratio: row.get('ratio'),
2925
- ratio_direction: row.get('ratio_direction'),
2926
- color_label: row.get( 'color_label' ),
2927
- label: row.get( 'label' ),
2928
- } );
2929
 
2930
- } );
 
2931
 
2932
- return data;
 
 
 
 
 
 
2933
 
 
2934
  },
2935
 
2936
  /**
2937
- * This will check all the current entries and refresh the panels data
2938
  */
2939
- refreshPanelsData: function ( args ) {
2940
- args = _.extend( {
2941
- silent: false
2942
- }, args );
2943
 
2944
- var oldData = this.get( 'data' );
2945
- var newData = this.getPanelsData();
2946
- this.set( 'data', newData, {silent: true} );
2947
 
2948
- if ( ! args.silent && JSON.stringify( newData ) !== JSON.stringify( oldData ) ) {
2949
- // The default change event doesn't trigger on deep changes, so we'll trigger our own
2950
- this.trigger( 'change' );
2951
- this.trigger( 'change:data' );
2952
- this.trigger( 'refresh_panels_data', newData, args );
 
 
 
 
 
 
 
 
2953
  }
2954
  },
2955
 
2956
- /**
2957
- * Empty all the rows and the cells/widgets they contain.
2958
- */
2959
- emptyRows: function () {
2960
- _.invoke( this.get('rows').toArray(), 'destroy' );
2961
- this.get('rows').reset();
2962
 
2963
- return this;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2964
  },
2965
 
2966
- isValidLayoutPosition: function ( position ) {
2967
- return position === this.layoutPosition.BEFORE ||
2968
- position === this.layoutPosition.AFTER ||
2969
- position === this.layoutPosition.REPLACE;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2970
  },
2971
 
2972
  /**
2973
- * Convert HTML into Panels Data
2974
- * @param html
2975
  */
2976
- getPanelsDataFromHtml: function( html, editorClass ){
2977
- var thisModel = this;
2978
- var $html = jQuery( '<div id="wrapper">' + html + '</div>' );
 
2979
 
2980
- if( $html.find('.panel-layout .panel-grid').length ) {
2981
- // This looks like Page Builder html, lets try parse it
2982
- var panels_data = {
2983
- grids: [],
2984
- grid_cells: [],
2985
- widgets: [],
2986
- };
2987
 
2988
- // The Regex object that'll match SiteOrigin widgets
2989
- var re = new RegExp( panelsOptions.siteoriginWidgetRegex , "i" );
2990
- var decodeEntities = (function() {
2991
- // this prevents any overhead from creating the object each time
2992
- var element = document.createElement('div');
 
2993
 
2994
- function decodeHTMLEntities (str) {
2995
- if(str && typeof str === 'string') {
2996
- // strip script/html tags
2997
- str = str.replace(/<script[^>]*>([\S\s]*?)<\/script>/gmi, '');
2998
- str = str.replace(/<\/?\w(?:[^"'>]|"[^"]*"|'[^']*')*>/gmi, '');
2999
- element.innerHTML = str;
3000
- str = element.textContent;
3001
- element.textContent = '';
3002
- }
3003
 
3004
- return str;
3005
- }
3006
 
3007
- return decodeHTMLEntities;
3008
- })();
 
 
 
 
 
 
 
3009
 
3010
- // Remove all wrapping divs from a widget to get its html
3011
- var getTextWidgetContents = function( $el ){
3012
- var $divs = $el.find( 'div' );
3013
- if( ! $divs.length ) {
3014
- return $el.html();
3015
- }
 
 
 
 
3016
 
3017
- var i;
3018
- for( i = 0; i < $divs.length - 1; i++ ) {
3019
- if( jQuery.trim( $divs.eq(i).text() ) != jQuery.trim( $divs.eq(i+1).text() ) ) {
3020
- break;
3021
- }
 
 
 
3022
  }
 
 
3023
 
3024
- var title = $divs.eq( i ).find( '.widget-title:header' ),
3025
- titleText = '';
3026
 
3027
- if( title.length ) {
3028
- titleText = title.html();
3029
- title.remove();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3030
  }
 
 
3031
 
3032
- return {
3033
- title: titleText,
3034
- text: $divs.eq(i).html(),
3035
- };
3036
- };
3037
 
3038
- var $layout = $html.find( '.panel-layout' ).eq(0);
3039
- var filterNestedLayout = function( i, el ){
3040
- return jQuery( el ).closest( '.panel-layout' ).is( $layout );
3041
- };
 
 
 
 
 
3042
 
3043
- $html.find('> .panel-layout > .panel-grid').filter( filterNestedLayout ).each( function( ri, el ){
3044
- var $row = jQuery( el ),
3045
- $cells = $row.find( '.panel-grid-cell' ).filter( filterNestedLayout );
3046
 
3047
- panels_data.grids.push( {
3048
- cells: $cells.length,
3049
- style: $row.data( 'style' ),
3050
- ratio: $row.data( 'ratio' ),
3051
- ratio_direction: $row.data( 'ratio-direction' ),
3052
- color_label: $row.data( 'color-label' ),
3053
- label: $row.data( 'label' ),
3054
- } );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3055
 
3056
- $cells.each( function( ci, el ){
3057
- var $cell = jQuery( el ),
3058
- $widgets = $cell.find( '.so-panel' ).filter( filterNestedLayout );
 
 
 
 
 
3059
 
3060
- panels_data.grid_cells.push( {
3061
- grid: ri,
3062
- weight: ! _.isUndefined( $cell.data( 'weight' ) ) ? parseFloat( $cell.data( 'weight' ) ) : 1,
3063
- style: $cell.data( 'style' ),
3064
- } );
3065
 
3066
- $widgets.each( function( wi, el ){
3067
- var $widget = jQuery(el),
3068
- widgetContent = $widget.find('.panel-widget-style').length ? $widget.find('.panel-widget-style').html() : $widget.html(),
3069
- panels_info = {
3070
- grid: ri,
3071
- cell: ci,
3072
- style: $widget.data( 'style' ),
3073
- raw: false,
3074
- label: $widget.data( 'label' )
3075
- };
3076
 
3077
- widgetContent = widgetContent.trim();
 
 
3078
 
3079
- // Check if this is a SiteOrigin Widget
3080
- var match = re.exec( widgetContent );
3081
- if( ! _.isNull( match ) && widgetContent.replace( re, '' ).trim() === '' ) {
3082
- try {
3083
- var classMatch = /class="(.*?)"/.exec( match[3] ),
3084
- dataInput = jQuery( match[5] ),
3085
- data = JSON.parse( decodeEntities( dataInput.val( ) ) ),
3086
- newWidget = data.instance;
 
 
3087
 
3088
- panels_info.class = classMatch[1].replace( /\\\\+/g, '\\' );
3089
- panels_info.raw = false;
3090
 
3091
- newWidget.panels_info = panels_info;
3092
- panels_data.widgets.push( newWidget );
3093
- }
3094
- catch ( err ) {
3095
- // There was a problem, so treat this as a standard editor widget
3096
- panels_info.class = editorClass;
3097
- panels_data.widgets.push( _.extend( getTextWidgetContents( $widget ), {
3098
- filter: "1",
3099
- type: "visual",
3100
- panels_info: panels_info
3101
- } ) );
3102
- }
3103
 
3104
- // Continue
3105
- return true;
3106
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3107
  else if( widgetContent.indexOf( 'panel-layout' ) !== -1 ) {
3108
  // Check if this is a layout widget
3109
  var $widgetContent = jQuery( '<div>' + widgetContent + '</div>' );
@@ -3115,4190 +3115,4192 @@ module.exports = Backbone.Model.extend({
3115
  panels_info: panels_info
3116
  } );
3117
 
3118
- // continue
3119
- return true;
3120
- }
3121
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3122
 
3123
- // This is a standard editor class widget
3124
- panels_info.class = editorClass;
3125
- panels_data.widgets.push( _.extend( getTextWidgetContents( $widget ), {
3126
- filter: "1",
3127
- type: "visual",
3128
- panels_info: panels_info
3129
- } ) );
3130
- return true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3131
  } );
 
 
 
 
3132
  } );
 
 
 
 
 
 
 
3133
  } );
 
3134
 
3135
- // Remove all the Page Builder content
3136
- $html.find('.panel-layout').remove();
3137
- $html.find('style[data-panels-style-for-post]').remove();
 
 
 
 
3138
 
3139
- // If there's anything left, add it to an editor widget at the end of panels_data
3140
- if( $html.html().replace(/^\s+|\s+$/gm,'').length ) {
3141
- panels_data.grids.push( {
3142
- cells: 1,
3143
- style: {},
3144
- } );
3145
- panels_data.grid_cells.push( {
3146
- grid: panels_data.grids.length - 1,
3147
- weight: 1,
3148
- } );
3149
- panels_data.widgets.push( {
3150
- filter: "1",
3151
- text: $html.html().replace(/^\s+|\s+$/gm,''),
3152
- title: "",
3153
- type: "visual",
3154
- panels_info: {
3155
- class: editorClass,
3156
- raw: false,
3157
- grid: panels_data.grids.length - 1,
3158
- cell: 0
3159
- }
3160
- } );
3161
- }
3162
 
3163
- return panels_data;
3164
- }
3165
- else {
3166
- // This is probably just old school post content
3167
- return {
3168
- grid_cells: [ { grid: 0, weight: 1 } ],
3169
- grids: [ { cells: 1 } ],
3170
- widgets: [
3171
- {
3172
- filter: "1",
3173
- text: html,
3174
- title: "",
3175
- type: "visual",
3176
- panels_info: {
3177
- class: editorClass,
3178
- raw: false,
3179
- grid: 0,
3180
- cell: 0
3181
- }
3182
- }
3183
- ]
3184
- };
3185
- }
3186
  }
3187
  } );
3188
 
3189
- },{}],18:[function(require,module,exports){
3190
- module.exports = Backbone.Model.extend( {
3191
- /* A collection of widgets */
3192
- widgets: {},
3193
 
3194
- /* The row this model belongs to */
3195
- row: null,
3196
 
3197
- defaults: {
3198
- weight: 0,
3199
- style: {}
 
 
3200
  },
3201
 
3202
- indexes: null,
 
3203
 
3204
  /**
3205
- * Set up the cell model
3206
  */
3207
  initialize: function () {
3208
- this.set( 'widgets', new panels.collection.widgets() );
3209
- this.on( 'destroy', this.onDestroy, this );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3210
  },
3211
 
3212
  /**
3213
- * Triggered when we destroy a cell
 
 
3214
  */
3215
- onDestroy: function () {
3216
- // Destroy all the widgets
3217
- _.invoke( this.get('widgets').toArray(), 'destroy' );
3218
- this.get('widgets').reset();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3219
  },
3220
 
3221
  /**
3222
- * Create a clone of the cell, along with all its widgets
3223
  */
3224
- clone: function ( row, cloneOptions ) {
3225
- if ( _.isUndefined( row ) ) {
3226
- row = this.row;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3227
  }
3228
- cloneOptions = _.extend( {cloneWidgets: true}, cloneOptions );
3229
 
3230
- var clone = new this.constructor( this.attributes );
3231
- clone.set( 'collection', row.get('cells'), {silent: true} );
3232
- clone.row = row;
3233
 
3234
- if ( cloneOptions.cloneWidgets ) {
3235
- // Now we're going add all the widgets that belong to this, to the clone
3236
- this.get('widgets').each( function ( widget ) {
3237
- clone.get('widgets').add( widget.clone( clone, cloneOptions ), {silent: true} );
3238
- } );
3239
  }
3240
 
3241
- return clone;
3242
- }
 
 
 
 
 
3243
 
3244
- } );
 
 
3245
 
3246
- },{}],19:[function(require,module,exports){
3247
- module.exports = Backbone.Model.extend( {
3248
- defaults: {
3249
- text: '',
3250
- data: '',
3251
- time: null,
3252
- count: 1
3253
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3254
  } );
3255
 
3256
- },{}],20:[function(require,module,exports){
3257
- module.exports = Backbone.Model.extend( {
3258
- /* The builder model */
3259
- builder: null,
3260
 
3261
- defaults: {
3262
- style: {}
3263
- },
3264
 
3265
- indexes: null,
3266
 
3267
- /**
3268
- * Initialize the row model
3269
- */
3270
  initialize: function () {
3271
- if ( _.isEmpty(this.get('cells') ) ) {
3272
- this.set('cells', new panels.collection.cells());
3273
- }
3274
- else {
3275
- // Make sure that the cells have this row set as their parent
3276
- this.get('cells').each( function( cell ){
3277
- cell.row = this;
3278
- }.bind( this ) );
3279
- }
3280
- this.on( 'destroy', this.onDestroy, this );
3281
- },
3282
 
 
 
3283
  /**
3284
- * Add cells to the model row
3285
  *
3286
- * @param newCells the updated collection of cell models
 
 
3287
  */
3288
- setCells: function ( newCells ) {
3289
- var currentCells = this.get('cells') || new panels.collection.cells();
3290
- var cellsToRemove = [];
3291
-
3292
- currentCells.each(function (cell, i) {
3293
- var newCell = newCells.at(i);
3294
- if(newCell) {
3295
- cell.set('weight', newCell.get('weight'));
3296
- } else {
3297
- var newParentCell = currentCells.at( newCells.length - 1 );
3298
 
3299
- // First move all the widgets to the new cell
3300
- var widgetsToMove = cell.get('widgets').models.slice();
3301
- for ( var j = 0; j < widgetsToMove.length; j++ ) {
3302
- widgetsToMove[j].moveToCell( newParentCell, { silent: false } );
3303
- }
3304
 
3305
- cellsToRemove.push(cell);
3306
- }
3307
- });
3308
 
3309
- _.each(cellsToRemove, function(cell) {
3310
- currentCells.remove(cell);
3311
- });
3312
 
3313
- if( newCells.length > currentCells.length) {
3314
- _.each(newCells.slice(currentCells.length, newCells.length), function (newCell) {
3315
- // TODO: make sure row and collection is set correctly when cell is created then we can just add new cells
3316
- newCell.set({collection: currentCells});
3317
- newCell.row = this;
3318
- currentCells.add(newCell);
3319
- }.bind(this));
3320
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3321
 
3322
- // Rescale the cells when we add or remove
3323
- this.reweightCells();
3324
  },
3325
 
3326
  /**
3327
- * Make sure that all the cell weights add up to 1
 
 
3328
  */
3329
- reweightCells: function () {
3330
- var totalWeight = 0;
3331
- var cells = this.get('cells');
3332
- cells.each( function ( cell ) {
3333
- totalWeight += cell.get( 'weight' );
3334
- } );
3335
-
3336
- cells.each( function ( cell ) {
3337
- cell.set( 'weight', cell.get( 'weight' ) / totalWeight );
3338
- } );
3339
-
3340
- // This is for the row view to hook into and resize
3341
- this.trigger( 'reweight_cells' );
3342
  },
3343
 
3344
  /**
3345
- * Triggered when the model is destroyed
3346
  */
3347
- onDestroy: function () {
3348
- // Also destroy all the cells
3349
- _.invoke( this.get('cells').toArray(), 'destroy' );
3350
- this.get('cells').reset();
3351
  },
3352
 
3353
  /**
3354
- * Create a clone of the row, along with all its cells
3355
- *
3356
- * @param {panels.model.builder} builder The builder model to attach this to.
3357
- *
3358
- * @return {panels.model.row} The cloned row.
3359
  */
3360
- clone: function ( builder ) {
3361
- if ( _.isUndefined( builder ) ) {
3362
- builder = this.builder;
3363
- }
3364
 
3365
- var clone = new this.constructor( this.attributes );
3366
- clone.set( 'collection', builder.get('rows'), {silent: true} );
3367
- clone.builder = builder;
3368
 
3369
- var cellClones = new panels.collection.cells();
3370
- this.get('cells').each( function ( cell ) {
3371
- cellClones.add( cell.clone( clone ), {silent: true} );
 
3372
  } );
3373
 
3374
- clone.set( 'cells', cellClones );
 
 
 
 
 
 
 
 
3375
 
3376
- return clone;
3377
- }
3378
- } );
 
3379
 
3380
- },{}],21:[function(require,module,exports){
3381
- /**
3382
- * Model for an instance of a widget
3383
- */
3384
- module.exports = Backbone.Model.extend( {
3385
-
3386
- cell: null,
3387
-
3388
- defaults: {
3389
- // The PHP Class of the widget
3390
- class: null,
3391
-
3392
- // Is this class missing? Missing widgets are a special case.
3393
- missing: false,
3394
-
3395
- // The values of the widget
3396
- values: {},
3397
-
3398
- // Have the current values been passed through the widgets update function
3399
- raw: false,
3400
-
3401
- // Visual style fields
3402
- style: {},
3403
-
3404
- read_only: false,
3405
- widget_id: '',
3406
- },
3407
-
3408
- indexes: null,
3409
-
3410
- initialize: function () {
3411
- var widgetClass = this.get( 'class' );
3412
- if ( _.isUndefined( panelsOptions.widgets[widgetClass] ) || ! panelsOptions.widgets[widgetClass].installed ) {
3413
- this.set( 'missing', true );
3414
- }
3415
- },
3416
-
3417
- /**
3418
- * @param field
3419
- * @returns {*}
3420
- */
3421
- getWidgetField: function ( field ) {
3422
- if ( _.isUndefined( panelsOptions.widgets[this.get( 'class' )] ) ) {
3423
- if ( field === 'title' || field === 'description' ) {
3424
- return panelsOptions.loc.missing_widget[field];
3425
- } else {
3426
- return '';
3427
- }
3428
- } else if ( this.has( 'label' ) && ! _.isEmpty( this.get( 'label' ) ) ) {
3429
- // Use the label instead of the actual widget title
3430
- return this.get( 'label' );
3431
- } else {
3432
- return panelsOptions.widgets[ this.get( 'class' ) ][ field ];
3433
- }
3434
- },
3435
-
3436
- /**
3437
- * Move this widget model to a new cell. Called by the views.
3438
- *
3439
- * @param panels.model.cell newCell
3440
- * @param object options The options passed to the
3441
- *
3442
- * @return boolean Indicating if the widget was moved into a different cell
3443
- */
3444
- moveToCell: function ( newCell, options, at ) {
3445
- options = _.extend( {
3446
- silent: true,
3447
- }, options );
3448
-
3449
- this.cell = newCell;
3450
- this.collection.remove( this, options );
3451
- newCell.get('widgets').add( this, _.extend( {
3452
- at: at
3453
- }, options ) );
3454
-
3455
- // This should be used by views to reposition everything.
3456
- this.trigger( 'move_to_cell', newCell, at );
3457
-
3458
- return this;
3459
- },
3460
-
3461
- /**
3462
- * This is basically a wrapper for set that checks if we need to trigger a change
3463
- */
3464
- setValues: function ( values ) {
3465
- var hasChanged = false;
3466
- if ( JSON.stringify( values ) !== JSON.stringify( this.get( 'values' ) ) ) {
3467
- hasChanged = true;
3468
- }
3469
-
3470
- this.set( 'values', values, {silent: true} );
3471
-
3472
- if ( hasChanged ) {
3473
- // We'll trigger our own change events.
3474
- // NB: Must include the model being changed (i.e. `this`) as a workaround for a bug in Backbone 1.2.3
3475
- this.trigger( 'change', this );
3476
- this.trigger( 'change:values' );
3477
- }
3478
- },
3479
-
3480
- /**
3481
- * Create a clone of this widget attached to the given cell.
3482
- *
3483
- * @param {panels.model.cell} cell The cell model we're attaching this widget clone to.
3484
- * @returns {panels.model.widget}
3485
- */
3486
- clone: function ( cell, options ) {
3487
- if ( _.isUndefined( cell ) ) {
3488
- cell = this.cell;
3489
- }
3490
-
3491
- var clone = new this.constructor( this.attributes );
3492
-
3493
- // Create a deep clone of the original values
3494
- var cloneValues = JSON.parse( JSON.stringify( this.get( 'values' ) ) );
3495
-
3496
- // We want to exclude any fields that start with _ from the clone. Assuming these are internal.
3497
- var cleanClone = function ( vals ) {
3498
- _.each( vals, function ( el, i ) {
3499
- if ( _.isString( i ) && i[0] === '_' ) {
3500
- delete vals[i];
3501
- }
3502
- else if ( _.isObject( vals[i] ) ) {
3503
- cleanClone( vals[i] );
3504
- }
3505
- } );
3506
-
3507
- return vals;
3508
- };
3509
- cloneValues = cleanClone( cloneValues );
3510
-
3511
- if ( this.get( 'class' ) === "SiteOrigin_Panels_Widgets_Layout" ) {
3512
- // Special case of this being a layout widget, it needs a new ID
3513
- cloneValues.builder_id = Math.random().toString( 36 ).substr( 2 );
3514
- }
3515
-
3516
- clone.set( 'widget_id', '' );
3517
- clone.set( 'values', cloneValues, {silent: true} );
3518
- clone.set( 'collection', cell.get('widgets'), {silent: true} );
3519
- clone.cell = cell;
3520
-
3521
- // This is used to force a form reload later on
3522
- clone.isDuplicate = true;
3523
-
3524
- return clone;
3525
- },
3526
-
3527
- /**
3528
- * Gets the value that makes most sense as the title.
3529
- */
3530
- getTitle: function () {
3531
- var widgetData = panelsOptions.widgets[this.get( 'class' )];
3532
-
3533
- if ( _.isUndefined( widgetData ) ) {
3534
- return this.get( 'class' ).replace( /_/g, ' ' );
3535
- }
3536
- else if ( ! _.isUndefined( widgetData.panels_title ) ) {
3537
- // This means that the widget has told us which field it wants us to use as a title
3538
- if ( widgetData.panels_title === false ) {
3539
- return panelsOptions.widgets[this.get( 'class' )].description;
3540
- }
3541
- }
3542
-
3543
- var values = this.get( 'values' );
3544
-
3545
- // Create a list of fields to check for a title
3546
- var titleFields = ['title', 'text'];
3547
-
3548
- for ( var k in values ) {
3549
- if(k.charAt(0) === '_' || k === 'so_sidebar_emulator_id' || k === 'option_name'){
3550
- // Skip Widgets Bundle supporting fields
3551
- continue;
3552
- }
3553
- if ( values.hasOwnProperty( k ) ) {
3554
- titleFields.push( k );
3555
- }
3556
- }
3557
-
3558
- titleFields = _.uniq( titleFields );
3559
-
3560
- for ( var i in titleFields ) {
3561
- if (
3562
- ! _.isUndefined( values[titleFields[i]] ) &&
3563
- _.isString( values[titleFields[i]] ) &&
3564
- values[titleFields[i]] !== '' &&
3565
- values[titleFields[i]] !== 'on' &&
3566
- titleFields[i][0] !== '_' && ! jQuery.isNumeric( values[titleFields[i]] )
3567
- ) {
3568
- var title = values[titleFields[i]];
3569
- title = title.replace( /<\/?[^>]+(>|$)/g, "" );
3570
- var parts = title.split( " " );
3571
- parts = parts.slice( 0, 20 );
3572
- return parts.join( ' ' );
3573
- }
3574
- }
3575
-
3576
- // If we still have nothing, then just return the widget description
3577
- return this.getWidgetField( 'description' );
3578
- }
3579
-
3580
- } );
3581
 
3582
- },{}],22:[function(require,module,exports){
3583
- var panels = window.panels, $ = jQuery;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3584
 
3585
- module.exports = Backbone.View.extend( {
3586
- wrapperTemplate: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-context-menu' ).html() ) ),
3587
- sectionTemplate: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-context-menu-section' ).html() ) ),
3588
 
3589
- contexts: [],
3590
- active: false,
 
 
 
 
 
 
 
 
 
3591
 
3592
- events: {
3593
- 'keyup .so-search-wrapper input': 'searchKeyUp'
3594
- },
 
 
 
3595
 
3596
- /**
3597
- * Intialize the context menu
3598
- */
3599
- initialize: function () {
3600
- this.listenContextMenu();
3601
- this.render();
3602
- this.attach();
3603
- },
3604
 
3605
- /**
3606
- * Listen for the right click context menu
3607
- */
3608
- listenContextMenu: function () {
3609
- var thisView = this;
3610
 
3611
- $( window ).on( 'contextmenu', function ( e ) {
3612
- if ( thisView.active && ! thisView.isOverEl( thisView.$el, e ) ) {
3613
- thisView.closeMenu();
3614
- thisView.active = false;
3615
  e.preventDefault();
3616
- return false;
3617
- }
 
 
 
3618
 
3619
- if ( thisView.active ) {
3620
- // Lets not double up on the context menu
3621
- return true;
3622
- }
3623
 
3624
- // Other components should listen to activate_context
3625
- thisView.active = false;
3626
- thisView.trigger( 'activate_context', e, thisView );
3627
 
3628
- if ( thisView.active ) {
3629
- // We don't want the default event to happen.
3630
- e.preventDefault();
3631
 
3632
- thisView.openMenu( {
3633
- left: e.pageX,
3634
- top: e.pageY
3635
- } );
3636
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3637
  } );
3638
- },
3639
 
3640
- render: function () {
3641
- this.setElement( this.wrapperTemplate() );
3642
- },
3643
 
3644
- attach: function () {
3645
- this.$el.appendTo( 'body' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3646
  },
3647
 
3648
  /**
3649
- * Display the actual context menu.
3650
- *
3651
- * @param position
3652
  */
3653
- openMenu: function ( position ) {
3654
- this.trigger( 'open_menu' );
 
 
 
 
 
 
 
 
 
3655
 
3656
- // Start listening for situations when we should close the menu
3657
- $( window ).on( 'keyup', {menu: this}, this.keyboardListen );
3658
- $( window ).on( 'click', {menu: this}, this.clickOutsideListen );
 
 
3659
 
3660
- // Set the maximum height of the menu
3661
- this.$el.css( 'max-height', $( window ).height() - 20 );
3662
 
3663
- // Correct the left position
3664
- if ( position.left + this.$el.outerWidth() + 10 >= $( window ).width() ) {
3665
- position.left = $( window ).width() - this.$el.outerWidth() - 10;
 
3666
  }
3667
- if ( position.left <= 0 ) {
3668
- position.left = 10;
 
3669
  }
3670
-
3671
- // Check top position
3672
- if ( position.top + this.$el.outerHeight() - $( window ).scrollTop() + 10 >= $( window ).height() ) {
3673
- position.top = $( window ).height() + $( window ).scrollTop() - this.$el.outerHeight() - 10;
3674
  }
3675
- if ( position.left <= 0 ) {
3676
- position.left = 10;
 
 
 
3677
  }
3678
 
3679
- // position the contextual menu
3680
- this.$el.css( {
3681
- left: position.left + 1,
3682
- top: position.top + 1
3683
- } ).show();
3684
- this.$( '.so-search-wrapper input' ).focus();
3685
- },
3686
 
3687
- closeMenu: function () {
3688
- this.trigger( 'close_menu' );
 
3689
 
3690
- // Stop listening for situations when we should close the menu
3691
- $( window ).off( 'keyup', this.keyboardListen );
3692
- $( window ).off( 'click', this.clickOutsideListen );
3693
 
3694
- this.active = false;
3695
- this.$el.empty().hide();
 
 
 
 
 
 
3696
  },
3697
 
3698
  /**
3699
- * Keyboard events handler
3700
  */
3701
- keyboardListen: function ( e ) {
3702
- var menu = e.data.menu;
 
3703
 
3704
- switch ( e.which ) {
3705
- case 27:
3706
- menu.closeMenu();
3707
- break;
 
 
 
 
 
 
 
 
 
 
3708
  }
 
3709
  },
3710
 
3711
  /**
3712
- * Listen for a click outside the menu to close it.
3713
- * @param e
3714
  */
3715
- clickOutsideListen: function ( e ) {
3716
- var menu = e.data.menu;
3717
- if ( e.which !== 3 && menu.$el.is( ':visible' ) && ! menu.isOverEl( menu.$el, e ) ) {
3718
- menu.closeMenu();
3719
  }
 
 
 
3720
  },
3721
 
3722
  /**
3723
- * Add a new section to the contextual menu.
3724
  *
3725
- * @param settings
3726
- * @param items
3727
- * @param callback
3728
  */
3729
- addSection: function ( id, settings, items, callback ) {
3730
- var thisView = this;
3731
- settings = _.extend( {
3732
- display: 5,
3733
- defaultDisplay: false,
3734
- search: true,
3735
-
3736
- // All the labels
3737
- sectionTitle: '',
3738
- searchPlaceholder: '',
3739
-
3740
- // This is the key to be used in items for the title. Makes it easier to list objects
3741
- titleKey: 'title'
3742
- }, settings );
3743
 
3744
- // Create the new section
3745
- var section = $( this.sectionTemplate( {
3746
- settings: settings,
3747
- items: items
3748
- } ) ).attr( 'id', 'panels-menu-section-' + id );
3749
- this.$el.append( section );
3750
 
3751
- section.find( '.so-item:not(.so-confirm)' ).click( function () {
3752
- var $$ = $( this );
3753
- callback( $$.data( 'key' ) );
3754
- thisView.closeMenu();
3755
  } );
3756
 
3757
- section.find( '.so-item.so-confirm' ).click( function () {
3758
- var $$ = $( this );
 
3759
 
3760
- if ( $$.hasClass( 'so-confirming' ) ) {
3761
- callback( $$.data( 'key' ) );
3762
- thisView.closeMenu();
3763
- return;
3764
- }
 
3765
 
3766
- $$
3767
- .data( 'original-text', $$.html() )
3768
- .addClass( 'so-confirming' )
3769
- .html( '<span class="dashicons dashicons-yes"></span> ' + panelsOptions.loc.dropdown_confirm );
 
 
 
 
 
3770
 
3771
- setTimeout( function () {
3772
- $$.removeClass( 'so-confirming' );
3773
- $$.html( $$.data( 'original-text' ) );
3774
- }, 2500 );
3775
- } );
3776
 
3777
- section.data( 'settings', settings ).find( '.so-search-wrapper input' ).trigger( 'keyup' );
 
 
3778
 
3779
- this.active = true;
 
 
 
 
3780
  },
3781
 
3782
  /**
3783
- * Check if a section exists in the current menu.
3784
- *
3785
- * @param id
3786
- * @returns {boolean}
3787
  */
3788
- hasSection: function( id ){
3789
- return this.$el.find( '#panels-menu-section-' + id ).length > 0;
 
 
 
 
 
 
 
 
 
 
3790
  },
3791
 
3792
  /**
3793
- * Handle searching inside a section.
3794
  *
3795
  * @param e
3796
- * @returns {boolean}
3797
  */
3798
- searchKeyUp: function ( e ) {
3799
- var
3800
- $$ = $( e.currentTarget ),
3801
- section = $$.closest( '.so-section' ),
3802
- settings = section.data( 'settings' );
 
 
 
 
 
 
 
 
 
 
 
 
 
3803
 
3804
- if ( e.which === 38 || e.which === 40 ) {
3805
- // First, lets check if this is an up, down or enter press
3806
- var
3807
- items = section.find( 'ul li:visible' ),
3808
- activeItem = items.filter( '.so-active' ).eq( 0 );
3809
 
3810
- if ( activeItem.length ) {
3811
- items.removeClass( 'so-active' );
3812
 
3813
- var activeIndex = items.index( activeItem );
 
 
 
3814
 
3815
- if ( e.which === 38 ) {
3816
- if ( activeIndex - 1 < 0 ) {
3817
- activeItem = items.last();
3818
- } else {
3819
- activeItem = items.eq( activeIndex - 1 );
3820
- }
3821
- }
3822
- else if ( e.which === 40 ) {
3823
- if ( activeIndex + 1 >= items.length ) {
3824
- activeItem = items.first();
3825
- } else {
3826
- activeItem = items.eq( activeIndex + 1 );
3827
- }
3828
- }
3829
- }
3830
- else if ( e.which === 38 ) {
3831
- activeItem = items.last();
3832
- }
3833
- else if ( e.which === 40 ) {
3834
- activeItem = items.first();
3835
- }
3836
 
3837
- activeItem.addClass( 'so-active' );
3838
- return false;
3839
- }
3840
- if ( e.which === 13 ) {
3841
- if ( section.find( 'ul li:visible' ).length === 1 ) {
3842
- // We'll treat a single visible item as active when enter is clicked
3843
- section.find( 'ul li:visible' ).trigger( 'click' );
3844
- return false;
3845
- }
3846
- section.find( 'ul li.so-active:visible' ).trigger( 'click' );
3847
- return false;
3848
  }
3849
 
3850
- if ( $$.val() === '' ) {
3851
- // We'll display the defaultDisplay items
3852
- if ( settings.defaultDisplay ) {
3853
- section.find( '.so-item' ).hide();
3854
- for ( var i = 0; i < settings.defaultDisplay.length; i ++ ) {
3855
- section.find( '.so-item[data-key="' + settings.defaultDisplay[i] + '"]' ).show();
3856
- }
3857
- } else {
3858
- // We'll just display all the items
3859
- section.find( '.so-item' ).show();
3860
- }
3861
- } else {
3862
- section.find( '.so-item' ).hide().each( function () {
3863
- var item = $( this );
3864
- if ( item.html().toLowerCase().indexOf( $$.val().toLowerCase() ) !== - 1 ) {
3865
- item.show();
3866
- }
3867
- } );
3868
  }
3869
 
3870
- // Now, we'll only show the first settings.display visible items
3871
- section.find( '.so-item:visible:gt(' + (
3872
- settings.display - 1
3873
- ) + ')' ).hide();
3874
 
 
 
 
3875
 
3876
- if ( section.find( '.so-item:visible' ).length === 0 && $$.val() !== '' ) {
3877
- section.find( '.so-no-results' ).show();
3878
- } else {
3879
- section.find( '.so-no-results' ).hide();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3880
  }
 
 
 
3881
  },
3882
 
3883
  /**
3884
- * Check if the given mouse event is over the element
3885
- * @param el
3886
- * @param event
3887
  */
3888
- isOverEl: function ( el, event ) {
3889
- var elPos = [
3890
- [el.offset().left, el.offset().top],
3891
- [el.offset().left + el.outerWidth(), el.offset().top + el.outerHeight()]
3892
- ];
3893
-
3894
- // Return if this event is over the given element
3895
- return (
3896
- event.pageX >= elPos[0][0] && event.pageX <= elPos[1][0] &&
3897
- event.pageY >= elPos[0][1] && event.pageY <= elPos[1][1]
3898
- );
3899
  }
3900
 
3901
  } );
3902
 
3903
- },{}],23:[function(require,module,exports){
3904
- var panels = window.panels, $ = jQuery;
3905
-
3906
- module.exports = Backbone.View.extend( {
3907
-
3908
- // Config options
3909
- config: {},
3910
-
3911
- template: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-builder' ).html() ) ),
3912
- dialogs: {},
3913
- rowsSortable: null,
3914
- dataField: false,
3915
- currentData: '',
3916
-
3917
- attachedToEditor: false,
3918
- attachedVisible: false,
3919
- liveEditor: undefined,
3920
- menu: false,
3921
-
3922
- activeCell: null,
3923
-
3924
- events: {
3925
- 'click .so-tool-button.so-widget-add': 'displayAddWidgetDialog',
3926
- 'click .so-tool-button.so-row-add': 'displayAddRowDialog',
3927
- 'click .so-tool-button.so-prebuilt-add': 'displayAddPrebuiltDialog',
3928
- 'click .so-tool-button.so-history': 'displayHistoryDialog',
3929
- 'click .so-tool-button.so-live-editor': 'displayLiveEditor'
3930
- },
3931
-
3932
- /* A row collection */
3933
- rows: null,
3934
-
3935
- /**
3936
- * Initialize the builder
3937
- */
3938
- initialize: function ( options ) {
3939
- var builder = this;
3940
-
3941
- this.config = _.extend( {
3942
- loadLiveEditor: false,
3943
- builderSupports: {}
3944
- }, options.config );
3945
-
3946
- // These are the actions that a user can perform in the builder
3947
- this.config.builderSupports = _.extend( {
3948
- addRow: true,
3949
- editRow: true,
3950
- deleteRow: true,
3951
- moveRow: true,
3952
- addWidget: true,
3953
- editWidget: true,
3954
- deleteWidget: true,
3955
- moveWidget: true,
3956
- prebuilt: true,
3957
- history: true,
3958
- liveEditor: true,
3959
- revertToEditor: true
3960
- }, this.config.builderSupports );
3961
-
3962
- // Automatically load the live editor as soon as it's ready
3963
- if ( options.config.loadLiveEditor ) {
3964
- this.on( 'builder_live_editor_added', function () {
3965
- this.displayLiveEditor();
3966
- } );
3967
- }
3968
-
3969
- // Now lets create all the dialog boxes that the main builder interface uses
3970
- this.dialogs = {
3971
- widgets: new panels.dialog.widgets(),
3972
- row: new panels.dialog.row(),
3973
- prebuilt: new panels.dialog.prebuilt()
3974
- };
3975
-
3976
- // Set the builder for each dialog and render it.
3977
- _.each( this.dialogs, function ( p, i, d ) {
3978
- d[ i ].setBuilder( builder );
3979
- } );
3980
-
3981
- this.dialogs.row.setRowDialogType( 'create' );
3982
-
3983
- // This handles a new row being added to the collection - we'll display it in the interface
3984
- this.listenTo( this.model.get( 'rows' ), 'add', this.onAddRow );
3985
-
3986
- // Reflow the entire builder when ever the
3987
- $( window ).resize( function ( e ) {
3988
- if ( e.target === window ) {
3989
- builder.trigger( 'builder_resize' );
3990
- }
3991
- } );
3992
-
3993
- // When the data changes in the model, store it in the field
3994
- this.listenTo( this.model, 'change:data load_panels_data', this.storeModelData );
3995
- this.listenTo( this.model, 'change:data load_panels_data', this.toggleWelcomeDisplay );
3996
-
3997
- // Handle a content change
3998
- this.on( 'content_change', this.handleContentChange, this );
3999
- this.on( 'display_builder', this.handleDisplayBuilder, this );
4000
- this.on( 'hide_builder', this.handleHideBuilder, this );
4001
- this.on( 'builder_rendered builder_resize', this.handleBuilderSizing, this );
4002
-
4003
- this.on( 'display_builder', this.wrapEditorExpandAdjust, this );
4004
-
4005
- // Create the context menu for this builder
4006
- this.menu = new panels.utils.menu( {} );
4007
- this.listenTo( this.menu, 'activate_context', this.activateContextMenu )
4008
-
4009
- if ( this.config.loadOnAttach ) {
4010
- this.on( 'builder_attached_to_editor', function () {
4011
- this.displayAttachedBuilder( { confirm: false } );
4012
- }, this );
4013
- }
4014
-
4015
- return this;
4016
- },
4017
-
4018
- /**
4019
- * Render the builder interface.
4020
- *
4021
- * @return {panels.view.builder}
4022
- */
4023
- render: function () {
4024
- // this.$el.html( this.template() );
4025
- this.setElement( this.template() );
4026
- this.$el
4027
- .attr( 'id', 'siteorigin-panels-builder-' + this.cid )
4028
- .addClass( 'so-builder-container' );
4029
-
4030
- this.trigger( 'builder_rendered' );
4031
-
4032
- return this;
4033
- },
4034
-
4035
- /**
4036
- * Attach the builder to the given container
4037
- *
4038
- * @param container
4039
- * @returns {panels.view.builder}
4040
- */
4041
- attach: function ( options ) {
4042
-
4043
- options = _.extend( {
4044
- container: false,
4045
- dialog: false
4046
- }, options );
4047
-
4048
- if ( options.dialog ) {
4049
- // We're going to add this to a dialog
4050
- this.dialog = new panels.dialog.builder();
4051
- this.dialog.builder = this;
4052
- } else {
4053
- // Attach this in the standard way
4054
- this.$el.appendTo( options.container );
4055
- this.metabox = options.container.closest( '.postbox' );
4056
- this.initSortable();
4057
- this.trigger( 'attached_to_container', options.container );
4058
- }
4059
-
4060
- this.trigger( 'builder_attached' );
4061
-
4062
- // Add support for components we have
4063
-
4064
- if ( this.supports( 'liveEditor' ) ) {
4065
- this.addLiveEditor();
4066
- }
4067
- if ( this.supports( 'history' ) ) {
4068
- this.addHistoryBrowser();
4069
- }
4070
-
4071
- // Hide toolbar buttons we don't support
4072
- var toolbar = this.$( '.so-builder-toolbar' );
4073
- var welcomeMessageContainer = this.$( '.so-panels-welcome-message' );
4074
- var welcomeMessage = panelsOptions.loc.welcomeMessage;
4075
-
4076
- var supportedItems = [];
4077
-
4078
- if ( !this.supports( 'addWidget' ) ) {
4079
- toolbar.find( '.so-widget-add' ).hide();
4080
- } else {
4081
- supportedItems.push( welcomeMessage.addWidgetButton );
4082
- }
4083
- if ( !this.supports( 'addRow' ) ) {
4084
- toolbar.find( '.so-row-add' ).hide();
4085
- } else {
4086
- supportedItems.push( welcomeMessage.addRowButton );
4087
- }
4088
- if ( !this.supports( 'prebuilt' ) ) {
4089
- toolbar.find( '.so-prebuilt-add' ).hide();
4090
- } else {
4091
- supportedItems.push( welcomeMessage.addPrebuiltButton );
4092
- }
4093
-
4094
- var msg = '';
4095
- if ( supportedItems.length === 3 ) {
4096
- msg = welcomeMessage.threeEnabled;
4097
- } else if ( supportedItems.length === 2 ) {
4098
- msg = welcomeMessage.twoEnabled;
4099
- } else if ( supportedItems.length === 1 ) {
4100
- msg = welcomeMessage.oneEnabled;
4101
- } else if ( supportedItems.length === 0 ) {
4102
- msg = welcomeMessage.addingDisabled;
4103
- }
4104
-
4105
- var resTemplate = _.template( panels.helpers.utils.processTemplate( msg ) );
4106
- var msgHTML = resTemplate( { items: supportedItems } ) + ' ' + welcomeMessage.docsMessage;
4107
- welcomeMessageContainer.find( '.so-message-wrapper' ).html( msgHTML );
4108
-
4109
- return this;
4110
- },
4111
-
4112
- /**
4113
- * This will move the Page Builder meta box into the editor if we're in the post/page edit interface.
4114
- *
4115
- * @returns {panels.view.builder}
4116
- */
4117
- attachToEditor: function () {
4118
- if ( this.config.editorType !== 'tinyMCE' ) {
4119
- return this;
4120
- }
4121
-
4122
- this.attachedToEditor = true;
4123
- var metabox = this.metabox;
4124
- var thisView = this;
4125
-
4126
- // Handle switching between the page builder and other tabs
4127
- $( '#wp-content-wrap .wp-editor-tabs' )
4128
- .find( '.wp-switch-editor' )
4129
- .click( function ( e ) {
4130
- e.preventDefault();
4131
- $( '#wp-content-editor-container' ).show();
4132
-
4133
- // metabox.hide();
4134
- $( '#wp-content-wrap' ).removeClass( 'panels-active' );
4135
- $( '#content-resize-handle' ).show();
4136
-
4137
- // Make sure the word count is visible
4138
- thisView.trigger( 'hide_builder' );
4139
- } ).end()
4140
- .append(
4141
- $( '<button type="button" id="content-panels" class="hide-if-no-js wp-switch-editor switch-panels">' + metabox.find( '.hndle span' ).html() + '</button>' )
4142
- .click( function ( e ) {
4143
- if ( thisView.displayAttachedBuilder( { confirm: true } ) ) {
4144
- e.preventDefault();
4145
- }
4146
- } )
4147
- );
4148
-
4149
- // Switch back to the standard editor
4150
- if ( this.supports( 'revertToEditor' ) ) {
4151
- metabox.find( '.so-switch-to-standard' ).click( function ( e ) {
4152
- e.preventDefault();
4153
-
4154
- if ( !confirm( panelsOptions.loc.confirm_stop_builder ) ) {
4155
- return;
4156
- }
4157
-
4158
- // User is switching to the standard visual editor
4159
- thisView.addHistoryEntry( 'back_to_editor' );
4160
- thisView.model.loadPanelsData( false );
4161
-
4162
- // Switch back to the standard editor
4163
- $( '#wp-content-wrap' ).show();
4164
- metabox.hide();
4165
-
4166
- // Resize to trigger reflow of WordPress editor stuff
4167
- $( window ).resize();
4168
-
4169
- thisView.attachedVisible = false;
4170
- thisView.trigger( 'hide_builder' );
4171
- } ).show();
4172
- }
4173
-
4174
- // Move the panels box into a tab of the content editor
4175
- metabox.insertAfter( '#wp-content-wrap' ).hide().addClass( 'attached-to-editor' );
4176
-
4177
- // Switch to the Page Builder interface as soon as we load the page if there are widgets or the normal editor
4178
- // isn't supported.
4179
- var data = this.model.get( 'data' );
4180
- if ( !_.isEmpty( data.widgets ) || !_.isEmpty( data.grids ) || !this.supports( 'revertToEditor' ) ) {
4181
- this.displayAttachedBuilder( { confirm: false } );
4182
- }
4183
-
4184
- // We will also make this sticky if its attached to an editor.
4185
- var stickToolbar = function () {
4186
- var toolbar = thisView.$( '.so-builder-toolbar' );
4187
-
4188
- if ( thisView.$el.hasClass( 'so-display-narrow' ) ) {
4189
- // In this case, we don't want to stick the toolbar.
4190
- toolbar.css( {
4191
- top: 0,
4192
- left: 0,
4193
- width: '100%',
4194
- position: 'absolute'
4195
- } );
4196
- thisView.$el.css( 'padding-top', toolbar.outerHeight() );
4197
- return;
4198
- }
4199
-
4200
- var newTop = $( window ).scrollTop() - thisView.$el.offset().top;
4201
-
4202
- if ( $( '#wpadminbar' ).css( 'position' ) === 'fixed' ) {
4203
- newTop += $( '#wpadminbar' ).outerHeight();
4204
- }
4205
-
4206
- var limits = {
4207
- top: 0,
4208
- bottom: thisView.$el.outerHeight() - toolbar.outerHeight() + 20
4209
- };
4210
-
4211
- if ( newTop > limits.top && newTop < limits.bottom ) {
4212
- if ( toolbar.css( 'position' ) !== 'fixed' ) {
4213
- // The toolbar needs to stick to the top, over the interface
4214
- toolbar.css( {
4215
- top: $( '#wpadminbar' ).outerHeight(),
4216
- left: thisView.$el.offset().left,
4217
- width: thisView.$el.outerWidth(),
4218
- position: 'fixed'
4219
- } );
4220
- }
4221
- } else {
4222
- // The toolbar needs to be at the top or bottom of the interface
4223
- toolbar.css( {
4224
- top: Math.min( Math.max( newTop, 0 ), thisView.$el.outerHeight() - toolbar.outerHeight() + 20 ),
4225
- left: 0,
4226
- width: '100%',
4227
- position: 'absolute'
4228
- } );
4229
- }
4230
-
4231
- thisView.$el.css( 'padding-top', toolbar.outerHeight() );
4232
- };
4233
-
4234
- this.on( 'builder_resize', stickToolbar, this );
4235
- $( document ).scroll( stickToolbar );
4236
- stickToolbar();
4237
-
4238
- this.trigger( 'builder_attached_to_editor' );
4239
-
4240
- return this;
4241
- },
4242
-
4243
- /**
4244
- * Display the builder interface when attached to a WordPress editor
4245
- */
4246
- displayAttachedBuilder: function ( options ) {
4247
- options = _.extend( {
4248
- confirm: true
4249
- }, options );
4250
-
4251
- // Switch to the Page Builder interface
4252
-
4253
- if ( options.confirm ) {
4254
- var editor = typeof tinyMCE !== 'undefined' ? tinyMCE.get( 'content' ) : false;
4255
- var editorContent = ( editor && _.isFunction( editor.getContent ) ) ? editor.getContent() : $( 'textarea#content' ).val();
4256
-
4257
- if ( editorContent !== '' && !confirm( panelsOptions.loc.confirm_use_builder ) ) {
4258
- return false;
4259
- }
4260
- }
4261
-
4262
- // Hide the standard content editor
4263
- $( '#wp-content-wrap' ).hide();
4264
-
4265
-
4266
- $( '#editor-expand-toggle' ).on( 'change.editor-expand', function () {
4267
- if ( !$( this ).prop( 'checked' ) ) {
4268
- $( '#wp-content-wrap' ).hide();
4269
- }
4270
- } );
4271
-
4272
- // Show page builder and the inside div
4273
- this.metabox.show().find( '> .inside' ).show();
4274
-
4275
- // Triggers full refresh
4276
- $( window ).resize();
4277
- $( document ).scroll();
4278
-
4279
- // Make sure the word count is visible
4280
- this.attachedVisible = true;
4281
- this.trigger( 'display_builder' );
4282
-
4283
- return true;
4284
- },
4285
-
4286
- /**
4287
- * Initialize the row sortables
4288
- */
4289
- initSortable: function () {
4290
- if ( !this.supports( 'moveRow' ) ) {
4291
- return this;
4292
- }
4293
-
4294
- var builderView = this;
4295
- var builderID = builderView.$el.attr( 'id' );
4296
-
4297
- // Create the sortable for the rows
4298
- this.rowsSortable = this.$( '.so-rows-container' ).sortable( {
4299
- appendTo: '#wpwrap',
4300
- items: '.so-row-container',
4301
- handle: '.so-row-move',
4302
- // For the block editor, where it's possible to have multiple Page Builder blocks on a page.
4303
- // Also specify builderID when not in the block editor to prevent being able to drop rows from builder in a dialog
4304
- // into builder on the page under the dialog.
4305
- connectWith: '#' + builderID + '.so-rows-container,.block-editor .so-rows-container',
4306
- axis: 'y',
4307
- tolerance: 'pointer',
4308
- scroll: false,
4309
- remove: function ( e, ui ) {
4310
- builderView.model.get( 'rows' ).remove(
4311
- $( ui.item ).data( 'view' ).model,
4312
- { silent: true }
4313
- );
4314
- builderView.model.refreshPanelsData();
4315
- },
4316
- receive: function ( e, ui ) {
4317
- builderView.model.get( 'rows' ).add(
4318
- $( ui.item ).data( 'view' ).model,
4319
- { silent: true, at: $( ui.item ).index() }
4320
- );
4321
- builderView.model.refreshPanelsData();
4322
- },
4323
- stop: function ( e, ui ) {
4324
- var $$ = $( ui.item ),
4325
- row = $$.data( 'view' ),
4326
- rows = builderView.model.get( 'rows' );
4327
-
4328
- // If this hasn't already been removed and added to a different builder.
4329
- if ( rows.get( row.model ) ) {
4330
- builderView.addHistoryEntry( 'row_moved' );
4331
-
4332
- rows.remove( row.model, {
4333
- 'silent': true
4334
- } );
4335
- rows.add( row.model, {
4336
- 'silent': true,
4337
- 'at': $$.index()
4338
- } );
4339
-
4340
- row.trigger( 'move', $$.index() );
4341
-
4342
- builderView.model.refreshPanelsData();
4343
- }
4344
- }
4345
- } );
4346
-
4347
- return this;
4348
- },
4349
-
4350
- /**
4351
- * Refresh the row sortable
4352
- */
4353
- refreshSortable: function () {
4354
- // Refresh the sortable to account for the new row
4355
- if ( !_.isNull( this.rowsSortable ) ) {
4356
- this.rowsSortable.sortable( 'refresh' );
4357
- }
4358
- },
4359
-
4360
- /**
4361
- * Set the field that's used to store the data
4362
- * @param field
4363
- * @param options
4364
- */
4365
- setDataField: function ( field, options ) {
4366
- options = _.extend( {
4367
- load: true
4368
- }, options );
4369
-
4370
- this.dataField = field;
4371
- this.dataField.data( 'builder', this );
4372
-
4373
- if ( options.load && field.val() !== '' ) {
4374
- var data = this.dataField.val();
4375
- try {
4376
- data = JSON.parse( data );
4377
- }
4378
- catch ( err ) {
4379
- console.log( "Failed to parse Page Builder layout data from supplied data field." );
4380
- data = {};
4381
- }
4382
-
4383
- this.setData( data );
4384
- }
4385
-
4386
- return this;
4387
- },
4388
-
4389
- /**
4390
- * Set the current panels data to be used.
4391
- *
4392
- * @param data
4393
- */
4394
- setData: function( data ) {
4395
- this.model.loadPanelsData( data );
4396
- this.currentData = data;
4397
- this.toggleWelcomeDisplay();
4398
- },
4399
-
4400
- /**
4401
- * Get the current panels data.
4402
- *
4403
- */
4404
- getData: function() {
4405
- return this.model.get( 'data' );
4406
- },
4407
-
4408
- /**
4409
- * Store the model data in the data html field set in this.setDataField.
4410
- */
4411
- storeModelData: function () {
4412
- var data = JSON.stringify( this.model.get( 'data' ) );
4413
-
4414
- if ( $( this.dataField ).val() !== data ) {
4415
- // If the data is different, set it and trigger a content_change event
4416
- $( this.dataField ).val( data );
4417
- $( this.dataField ).trigger( 'change' );
4418
- this.trigger( 'content_change' );
4419
- }
4420
- },
4421
-
4422
- /**
4423
- * HAndle the visual side of adding a new row to the builder.
4424
- *
4425
- * @param row
4426
- * @param collection
4427
- * @param options
4428
- */
4429
- onAddRow: function ( row, collection, options ) {
4430
- options = _.extend( { noAnimate: false }, options );
4431
- // Create a view for the row
4432
- var rowView = new panels.view.row( { model: row } );
4433
- rowView.builder = this;
4434
- rowView.render();
4435
-
4436
- // Attach the row elements to this builder
4437
- if ( _.isUndefined( options.at ) || collection.length <= 1 ) {
4438
- // Insert this at the end of the widgets container
4439
- rowView.$el.appendTo( this.$( '.so-rows-container' ) );
4440
- } else {
4441
- // We need to insert this at a specific position
4442
- rowView.$el.insertAfter(
4443
- this.$( '.so-rows-container .so-row-container' ).eq( options.at - 1 )
4444
- );
4445
- }
4446
-
4447
- if ( options.noAnimate === false ) {
4448
- rowView.visualCreate();
4449
- }
4450
-
4451
- this.refreshSortable();
4452
- rowView.resize();
4453
- this.trigger( 'row_added' );
4454
- },
4455
-
4456
- /**
4457
- * Display the dialog to add a new widget.
4458
- *
4459
- * @returns {boolean}
4460
- */
4461
- displayAddWidgetDialog: function () {
4462
- this.dialogs.widgets.openDialog();
4463
- },
4464
-
4465
- /**
4466
- * Display the dialog to add a new row.
4467
- */
4468
- displayAddRowDialog: function () {
4469
- var row = new panels.model.row();
4470
- var cells = new panels.collection.cells( [ { weight: 0.5 }, { weight: 0.5 } ] );
4471
- cells.each( function ( cell ) {
4472
- cell.row = row;
4473
- } );
4474
- row.set( 'cells', cells );
4475
- row.builder = this.model;
4476
-
4477
- this.dialogs.row.setRowModel( row );
4478
- this.dialogs.row.openDialog();
4479
- },
4480
-
4481
- /**
4482
- * Display the dialog to add prebuilt layouts.
4483
- *
4484
- * @returns {boolean}
4485
- */
4486
- displayAddPrebuiltDialog: function () {
4487
- this.dialogs.prebuilt.openDialog();
4488
- },
4489
-
4490
- /**
4491
- * Display the history dialog.
4492
- *
4493
- * @returns {boolean}
4494
- */
4495
- displayHistoryDialog: function () {
4496
- this.dialogs.history.openDialog();
4497
- },
4498
-
4499
- /**
4500
- * Handle pasting a row into the builder.
4501
- */
4502
- pasteRowHandler: function () {
4503
- var pastedModel = panels.helpers.clipboard.getModel( 'row-model' );
4504
-
4505
- if ( !_.isEmpty( pastedModel ) && pastedModel instanceof panels.model.row ) {
4506
- this.addHistoryEntry( 'row_pasted' );
4507
- pastedModel.builder = this.model;
4508
- this.model.get( 'rows' ).add( pastedModel, {
4509
- at: this.model.get( 'rows' ).indexOf( this.model ) + 1
4510
- } );
4511
- this.model.refreshPanelsData();
4512
- }
4513
- },
4514
-
4515
- /**
4516
- * Get the model for the currently selected cell
4517
- */
4518
- getActiveCell: function ( options ) {
4519
- options = _.extend( {
4520
- createCell: true,
4521
- }, options );
4522
-
4523
- if ( !this.model.get( 'rows' ).length ) {
4524
- // There aren't any rows yet
4525
- if ( options.createCell ) {
4526
- // Create a row with a single cell
4527
- this.model.addRow( {}, [ { weight: 1 } ], { noAnimate: true } );
4528
- } else {
4529
- return null;
4530
- }
4531
- }
4532
-
4533
- // Make sure the active cell isn't empty, and it's in a row that exists
4534
- var activeCell = this.activeCell;
4535
- if ( _.isEmpty( activeCell ) || this.model.get( 'rows' ).indexOf( activeCell.model.row ) === -1 ) {
4536
- return this.model.get( 'rows' ).last().get( 'cells' ).first();
4537
- } else {
4538
- return activeCell.model;
4539
- }
4540
- },
4541
-
4542
- /**
4543
- * Add a live editor to the builder
4544
- *
4545
- * @returns {panels.view.builder}
4546
- */
4547
- addLiveEditor: function () {
4548
- if ( _.isEmpty( this.config.liveEditorPreview ) ) {
4549
- return this;
4550
- }
4551
-
4552
- // Create the live editor and set the builder to this.
4553
- this.liveEditor = new panels.view.liveEditor( {
4554
- builder: this,
4555
- previewUrl: this.config.liveEditorPreview
4556
- } );
4557
-
4558
- // Display the live editor button in the toolbar
4559
- if ( this.liveEditor.hasPreviewUrl() ) {
4560
- this.$( '.so-builder-toolbar .so-live-editor' ).show();
4561
- }
4562
-
4563
- this.trigger( 'builder_live_editor_added' );
4564
-
4565
- return this;
4566
- },
4567
-
4568
- /**
4569
- * Show the current live editor
4570
- */
4571
- displayLiveEditor: function () {
4572
- if ( _.isUndefined( this.liveEditor ) ) {
4573
- return;
4574
- }
4575
-
4576
- this.liveEditor.open();
4577
- },
4578
-
4579
- /**
4580
- * Add the history browser.
4581
- *
4582
- * @return {panels.view.builder}
4583
- */
4584
- addHistoryBrowser: function () {
4585
- if ( _.isEmpty( this.config.liveEditorPreview ) ) {
4586
- return this;
4587
- }
4588
-
4589
- this.dialogs.history = new panels.dialog.history();
4590
- this.dialogs.history.builder = this;
4591
- this.dialogs.history.entries.builder = this.model;
4592
-
4593
- // Set the revert entry
4594
- this.dialogs.history.setRevertEntry( this.model );
4595
-
4596
- // Display the live editor button in the toolbar
4597
- this.$( '.so-builder-toolbar .so-history' ).show();
4598
- },
4599
-
4600
- /**
4601
- * Add an entry.
4602
- *
4603
- * @param text
4604
- * @param data
4605
- */
4606
- addHistoryEntry: function ( text, data ) {
4607
- if ( _.isUndefined( data ) ) {
4608
- data = null;
4609
- }
4610
-
4611
- if ( !_.isUndefined( this.dialogs.history ) ) {
4612
- this.dialogs.history.entries.addEntry( text, data );
4613
- }
4614
- },
4615
-
4616
- supports: function ( thing ) {
4617
-
4618
- if ( thing === 'rowAction' ) {
4619
- // Check if this supports any row action
4620
- return this.supports( 'addRow' ) || this.supports( 'editRow' ) || this.supports( 'deleteRow' );
4621
- } else if ( thing === 'widgetAction' ) {
4622
- // Check if this supports any widget action
4623
- return this.supports( 'addWidget' ) || this.supports( 'editWidget' ) || this.supports( 'deleteWidget' );
4624
- }
4625
-
4626
- return _.isUndefined( this.config.builderSupports[ thing ] ) ? false : this.config.builderSupports[ thing ];
4627
- },
4628
-
4629
- /**
4630
- * Handle a change of the content
4631
- */
4632
- handleContentChange: function () {
4633
-
4634
- // Make sure we actually need to copy content.
4635
- if ( panelsOptions.copy_content && this.attachedToEditor && this.$el.is( ':visible' ) ) {
4636
-
4637
- var panelsData = this.model.getPanelsData();
4638
- if ( !_.isEmpty( panelsData.widgets ) ) {
4639
- // We're going to create a copy of page builder content into the post content
4640
- $.post(
4641
- panelsOptions.ajaxurl,
4642
- {
4643
- action: 'so_panels_builder_content',
4644
- panels_data: JSON.stringify( panelsData ),
4645
- post_id: this.config.postId
4646
- },
4647
- function ( content ) {
4648
- if ( content !== '' ) {
4649
- this.updateEditorContent( content );
4650
- }
4651
- }.bind( this )
4652
- );
4653
- }
4654
- }
4655
- },
4656
-
4657
- /**
4658
- * Update editor content with the given content.
4659
- *
4660
- * @param content
4661
- */
4662
- updateEditorContent: function ( content ) {
4663
- // Switch back to the standard editor
4664
- if ( this.config.editorType !== 'tinyMCE' || typeof tinyMCE === 'undefined' || _.isNull( tinyMCE.get( "content" ) ) ) {
4665
- var $editor = $( this.config.editorId );
4666
- $editor.val( content ).trigger( 'change' ).trigger( 'keyup' );
4667
- } else {
4668
- var contentEd = tinyMCE.get( "content" );
4669
-
4670
- contentEd.setContent( content );
4671
-
4672
- contentEd.fire( 'change' );
4673
- contentEd.fire( 'keyup' );
4674
- }
4675
-
4676
- this.triggerYoastSeoChange();
4677
- },
4678
-
4679
- /**
4680
- * Trigger a change on Yoast SEO
4681
- */
4682
- triggerYoastSeoChange: function () {
4683
- if ( $( '#yoast_wpseo_focuskw_text_input' ).length ) {
4684
- var element = document.getElementById( 'yoast_wpseo_focuskw_text_input' ), event;
4685
-
4686
- if ( document.createEvent ) {
4687
- event = document.createEvent( "HTMLEvents" );
4688
- event.initEvent( "keyup", true, true );
4689
- } else {
4690
- event = document.createEventObject();
4691
- event.eventType = "keyup";
4692
- }
4693
-
4694
- event.eventName = "keyup";
4695
-
4696
- if ( document.createEvent ) {
4697
- element.dispatchEvent( event );
4698
- } else {
4699
- element.fireEvent( "on" + event.eventType, event );
4700
- }
4701
- }
4702
- },
4703
-
4704
- /**
4705
- * Handle displaying the builder
4706
- */
4707
- handleDisplayBuilder: function () {
4708
- var editor = typeof tinyMCE !== 'undefined' ? tinyMCE.get( 'content' ) : false;
4709
- var editorContent = ( editor && _.isFunction( editor.getContent ) ) ? editor.getContent() : $( 'textarea#content' ).val();
4710
-
4711
- if (
4712
- (
4713
- _.isEmpty( this.model.get( 'data' ) ) ||
4714
- ( _.isEmpty( this.model.get( 'data' ).widgets ) && _.isEmpty( this.model.get( 'data' ).grids ) )
4715
- ) &&
4716
- editorContent !== ''
4717
- ) {
4718
- var editorClass = panelsOptions.text_widget;
4719
- // There is a small chance a theme will have removed this, so check
4720
- if ( _.isEmpty( editorClass ) ) {
4721
- return;
4722
- }
4723
-
4724
- // Create the existing page content in a single widget
4725
- this.model.loadPanelsData( this.model.getPanelsDataFromHtml( editorContent, editorClass ) );
4726
- this.model.trigger( 'change' );
4727
- this.model.trigger( 'change:data' );
4728
- }
4729
-
4730
- $( '#post-status-info' ).addClass( 'for-siteorigin-panels' );
4731
- },
4732
-
4733
- handleHideBuilder: function () {
4734
- $( '#post-status-info' ).show().removeClass( 'for-siteorigin-panels' );
4735
- },
4736
-
4737
- wrapEditorExpandAdjust: function () {
4738
- try {
4739
- var events = ( $.hasData( window ) && $._data( window ) ).events.scroll,
4740
- event;
4741
-
4742
- for ( var i = 0; i < events.length; i++ ) {
4743
- if ( events[ i ].namespace === 'editor-expand' ) {
4744
- event = events[ i ];
4745
-
4746
- // Wrap the call
4747
- $( window ).unbind( 'scroll', event.handler );
4748
- $( window ).bind( 'scroll', function ( e ) {
4749
- if ( !this.attachedVisible ) {
4750
- event.handler( e );
4751
- }
4752
- }.bind( this ) );
4753
-
4754
- break;
4755
- }
4756
- }
4757
- }
4758
- catch ( e ) {
4759
- // We tried, we failed
4760
- return;
4761
- }
4762
- },
4763
-
4764
- /**
4765
- * Either add or remove the narrow class
4766
- * @returns {exports}
4767
- */
4768
- handleBuilderSizing: function () {
4769
- var width = this.$el.width();
4770
-
4771
- if ( !width ) {
4772
- return this;
4773
- }
4774
-
4775
- if ( width < 575 ) {
4776
- this.$el.addClass( 'so-display-narrow' );
4777
- } else {
4778
- this.$el.removeClass( 'so-display-narrow' );
4779
- }
4780
-
4781
- return this;
4782
- },
4783
-
4784
- /**
4785
- * Set the parent dialog for all the dialogs in this builder.
4786
- *
4787
- * @param text
4788
- * @param dialog
4789
- */
4790
- setDialogParents: function ( text, dialog ) {
4791
- _.each( this.dialogs, function ( p, i, d ) {
4792
- d[ i ].setParent( text, dialog );
4793
- } );
4794
-
4795
- // For any future dialogs
4796
- this.on( 'add_dialog', function ( newDialog ) {
4797
- newDialog.setParent( text, dialog );
4798
- }, this );
4799
- },
4800
-
4801
- /**
4802
- * This shows or hides the welcome display depending on whether there are any rows in the collection.
4803
- */
4804
- toggleWelcomeDisplay: function () {
4805
- if ( !this.model.get( 'rows' ).isEmpty() ) {
4806
- this.$( '.so-panels-welcome-message' ).hide();
4807
- } else {
4808
- this.$( '.so-panels-welcome-message' ).show();
4809
- }
4810
- },
4811
-
4812
- /**
4813
- * Activate the contextual menu
4814
- * @param e
4815
- * @param menu
4816
- */
4817
- activateContextMenu: function ( e, menu ) {
4818
- var builder = this;
4819
-
4820
- // Only run this if the event target is a descendant of this builder's DOM element.
4821
- if ( $.contains( builder.$el.get( 0 ), e.target ) ) {
4822
- // Get the element we're currently hovering over
4823
- var over = $( [] )
4824
- .add( builder.$( '.so-panels-welcome-message:visible' ) )
4825
- .add( builder.$( '.so-rows-container > .so-row-container' ) )
4826
- .add( builder.$( '.so-cells > .cell' ) )
4827
- .add( builder.$( '.cell-wrapper > .so-widget' ) )
4828
- .filter( function ( i ) {
4829
- return menu.isOverEl( $( this ), e );
4830
- } );
4831
-
4832
- var activeView = over.last().data( 'view' );
4833
- if ( activeView !== undefined && activeView.buildContextualMenu !== undefined ) {
4834
- // We'll pass this to the current active view so it can populate the contextual menu
4835
- activeView.buildContextualMenu( e, menu );
4836
- }
4837
- else if ( over.last().hasClass( 'so-panels-welcome-message' ) ) {
4838
- // The user opened the contextual menu on the welcome message
4839
- this.buildContextualMenu( e, menu );
4840
- }
4841
- }
4842
- },
4843
-
4844
- /**
4845
- * Build the contextual menu for the main builder - before any content has been added.
4846
- */
4847
- buildContextualMenu: function ( e, menu ) {
4848
- var actions = {};
4849
-
4850
- if ( this.supports( 'addRow' ) ) {
4851
- actions.add_row = { title: panelsOptions.loc.contextual.add_row };
4852
- }
4853
-
4854
- if ( panels.helpers.clipboard.canCopyPaste() ) {
4855
- if ( panels.helpers.clipboard.isModel( 'row-model' ) && this.supports( 'addRow' ) ) {
4856
- actions.paste_row = { title: panelsOptions.loc.contextual.row_paste };
4857
- }
4858
- }
4859
-
4860
- if ( !_.isEmpty( actions ) ) {
4861
- menu.addSection(
4862
- 'builder-actions',
4863
- {
4864
- sectionTitle: panelsOptions.loc.contextual.row_actions,
4865
- search: false,
4866
- },
4867
- actions,
4868
- function ( c ) {
4869
- switch ( c ) {
4870
- case 'add_row':
4871
- this.displayAddRowDialog();
4872
- break;
4873
-
4874
- case 'paste_row':
4875
- this.pasteRowHandler();
4876
- break;
4877
- }
4878
- }.bind( this )
4879
- );
4880
- }
4881
- },
4882
- } );
4883
-
4884
- },{}],24:[function(require,module,exports){
4885
- var panels = window.panels, $ = jQuery;
4886
-
4887
- module.exports = Backbone.View.extend( {
4888
- template: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-builder-cell' ).html() ) ),
4889
- events: {
4890
- 'click .cell-wrapper': 'handleCellClick'
4891
- },
4892
-
4893
- /* The row view that this cell is a part of */
4894
- row: null,
4895
- widgetSortable: null,
4896
-
4897
- initialize: function () {
4898
- this.listenTo(this.model.get('widgets'), 'add', this.onAddWidget );
4899
- },
4900
-
4901
- /**
4902
- * Render the actual cell
4903
- */
4904
- render: function () {
4905
- var templateArgs = {
4906
- weight: this.model.get( 'weight' ),
4907
- totalWeight: this.row.model.get('cells').totalWeight()
4908
- };
4909
-
4910
- this.setElement( this.template( templateArgs ) );
4911
- this.$el.data( 'view', this );
4912
-
4913
- // Now lets render any widgets that are currently in the row
4914
- var thisView = this;
4915
- this.model.get('widgets').each( function ( widget ) {
4916
- var widgetView = new panels.view.widget( {model: widget} );
4917
- widgetView.cell = thisView;
4918
- widgetView.render();
4919
-
4920
- widgetView.$el.appendTo( thisView.$( '.widgets-container' ) );
4921
- } );
4922
-
4923
- this.initSortable();
4924
- this.initResizable();
4925
-
4926
- return this;
4927
- },
4928
-
4929
- /**
4930
- * Initialize the widget sortable
4931
- */
4932
- initSortable: function () {
4933
- if( ! this.row.builder.supports( 'moveWidget' ) ) {
4934
- return this;
4935
- }
4936
-
4937
- var cellView = this;
4938
- var builder = cellView.row.builder;
4939
-
4940
- // Go up the view hierarchy until we find the ID attribute
4941
- var builderID = builder.$el.attr( 'id' );
4942
- var builderModel = builder.model;
4943
-
4944
- // Create a widget sortable that's connected with all other cells
4945
- this.widgetSortable = this.$( '.widgets-container' ).sortable( {
4946
- placeholder: "so-widget-sortable-highlight",
4947
- connectWith: '#' + builderID + ' .so-cells .cell .widgets-container,.block-editor .so-cells .cell .widgets-container',
4948
- tolerance: 'pointer',
4949
- scroll: false,
4950
- over: function ( e, ui ) {
4951
- // This will make all the rows in the current builder resize
4952
- cellView.row.builder.trigger( 'widget_sortable_move' );
4953
- },
4954
- remove: function ( e, ui ) {
4955
- cellView.model.get( 'widgets' ).remove(
4956
- $( ui.item ).data( 'view' ).model,
4957
- { silent: true }
4958
- );
4959
- builderModel.refreshPanelsData();
4960
- },
4961
- receive: function ( e, ui ) {
4962
- var widgetModel = $( ui.item ).data( 'view' ).model;
4963
- widgetModel.cell = cellView.model;
4964
- cellView.model.get( 'widgets' ).add(
4965
- widgetModel,
4966
- { silent: true, at: $( ui.item ).index() }
4967
- );
4968
- builderModel.refreshPanelsData();
4969
- },
4970
- stop: function ( e, ui ) {
4971
- var $$ = $( ui.item ),
4972
- widget = $$.data( 'view' ),
4973
- targetCell = $$.closest( '.cell' ).data( 'view' );
4974
-
4975
-
4976
- // If this hasn't already been removed and added to a different builder.
4977
- if ( cellView.model.get( 'widgets' ).get( widget.model ) ) {
4978
-
4979
- cellView.row.builder.addHistoryEntry( 'widget_moved' );
4980
-
4981
- // Move the model and the view to the new cell
4982
- widget.model.moveToCell( targetCell.model, {}, $$.index() );
4983
- widget.cell = targetCell;
4984
-
4985
- builderModel.refreshPanelsData();
4986
- }
4987
- },
4988
- helper: function ( e, el ) {
4989
- var helper = el.clone()
4990
- .css( {
4991
- 'width': el.outerWidth(),
4992
- 'z-index': 10000,
4993
- 'position': 'fixed'
4994
- } )
4995
- .addClass( 'widget-being-dragged' ).appendTo( 'body' );
4996
-
4997
- // Center the helper to the mouse cursor.
4998
- if ( el.outerWidth() > 720 ) {
4999
- helper.animate( {
5000
- 'margin-left': e.pageX - el.offset().left - (
5001
- 480 / 2
5002
- ),
5003
- 'width': 480
5004
- }, 'fast' );
5005
- }
5006
-
5007
- return helper;
5008
- }
5009
- } );
5010
-
5011
- return this;
5012
- },
5013
-
5014
- /**
5015
- * Refresh the widget sortable when a new widget is added
5016
- */
5017
- refreshSortable: function () {
5018
- if ( ! _.isNull( this.widgetSortable ) ) {
5019
- this.widgetSortable.sortable( 'refresh' );
5020
- }
5021
- },
5022
-
5023
- /**
5024
- * This will make the cell resizble
5025
- */
5026
- initResizable: function () {
5027
- if( ! this.row.builder.supports( 'editRow' ) ) {
5028
- return this;
5029
- }
5030
-
5031
- // var neighbor = this.$el.previous().data('view');
5032
- var handle = this.$( '.resize-handle' ).css( 'position', 'absolute' );
5033
- var container = this.row.$el;
5034
- var cellView = this;
5035
-
5036
- // The view of the cell to the left is stored when dragging starts.
5037
- var previousCell;
5038
-
5039
- handle.draggable( {
5040
- axis: 'x',
5041
- containment: container,
5042
- start: function ( e, ui ) {
5043
- // Set the containment to the cell parent
5044
- previousCell = cellView.$el.prev().data( 'view' );
5045
- if ( _.isUndefined( previousCell ) ) {
5046
- return;
5047
- }
5048
-
5049
- // Create the clone for the current cell
5050
- var newCellClone = cellView.$el.clone().appendTo( ui.helper ).css( {
5051
- position: 'absolute',
5052
- top: '0',
5053
- width: cellView.$el.outerWidth(),
5054
- left: 5,
5055
- height: cellView.$el.outerHeight()
5056
- } );
5057
- newCellClone.find( '.resize-handle' ).remove();
5058
-
5059
- // Create the clone for the previous cell
5060
- var prevCellClone = previousCell.$el.clone().appendTo( ui.helper ).css( {
5061
- position: 'absolute',
5062
- top: '0',
5063
- width: previousCell.$el.outerWidth(),
5064
- right: 5,
5065
- height: previousCell.$el.outerHeight()
5066
- } );
5067
- prevCellClone.find( '.resize-handle' ).remove();
5068
-
5069
- $( this ).data( {
5070
- 'newCellClone': newCellClone,
5071
- 'prevCellClone': prevCellClone
5072
- } );
5073
- },
5074
- drag: function ( e, ui ) {
5075
- // Calculate the new cell and previous cell widths as a percent
5076
- var containerWidth = cellView.row.$el.width() + 10;
5077
- var ncw = cellView.model.get( 'weight' ) - (
5078
- (
5079
- ui.position.left + handle.outerWidth() / 2
5080
- ) / containerWidth
5081
- );
5082
- var pcw = previousCell.model.get( 'weight' ) + (
5083
- (
5084
- ui.position.left + handle.outerWidth() / 2
5085
- ) / containerWidth
5086
- );
5087
-
5088
- $( this ).data( 'newCellClone' ).css( 'width', containerWidth * ncw )
5089
- .find( '.preview-cell-weight' ).html( Math.round( ncw * 1000 ) / 10 );
5090
-
5091
- $( this ).data( 'prevCellClone' ).css( 'width', containerWidth * pcw )
5092
- .find( '.preview-cell-weight' ).html( Math.round( pcw * 1000 ) / 10 );
5093
- },
5094
- stop: function ( e, ui ) {
5095
- // Remove the clones
5096
- $( this ).data( 'newCellClone' ).remove();
5097
- $( this ).data( 'prevCellClone' ).remove();
5098
-
5099
- var containerWidth = cellView.row.$el.width() + 10;
5100
- var ncw = cellView.model.get( 'weight' ) - (
5101
- (
5102
- ui.position.left + handle.outerWidth() / 2
5103
- ) / containerWidth
5104
- );
5105
- var pcw = previousCell.model.get( 'weight' ) + (
5106
- (
5107
- ui.position.left + handle.outerWidth() / 2
5108
- ) / containerWidth
5109
- );
5110
-
5111
- if ( ncw > 0.02 && pcw > 0.02 ) {
5112
- cellView.row.builder.addHistoryEntry( 'cell_resized' );
5113
- cellView.model.set( 'weight', ncw );
5114
- previousCell.model.set( 'weight', pcw );
5115
- cellView.row.resize();
5116
- }
5117
-
5118
- ui.helper.css( 'left', - handle.outerWidth() / 2 );
5119
-
5120
- // Refresh the panels data
5121
- cellView.row.builder.model.refreshPanelsData();
5122
- }
5123
- } );
5124
-
5125
- return this;
5126
- },
5127
-
5128
- /**
5129
- * This is triggered when ever a widget is added to the row collection.
5130
- *
5131
- * @param widget
5132
- */
5133
- onAddWidget: function ( widget, collection, options ) {
5134
- options = _.extend( {noAnimate: false}, options );
5135
-
5136
- // Create the view for the widget
5137
- var view = new panels.view.widget( {
5138
- model: widget
5139
- } );
5140
- view.cell = this;
5141
-
5142
- if ( _.isUndefined( widget.isDuplicate ) ) {
5143
- widget.isDuplicate = false;
5144
- }
5145
-
5146
- // Render and load the form if this is a duplicate
5147
- view.render( {
5148
- 'loadForm': widget.isDuplicate
5149
- } );
5150
-
5151
- if ( _.isUndefined( options.at ) || collection.length <= 1 ) {
5152
- // Insert this at the end of the widgets container
5153
- view.$el.appendTo( this.$( '.widgets-container' ) );
5154
- } else {
5155
- // We need to insert this at a specific position
5156
- view.$el.insertAfter(
5157
- this.$( '.widgets-container .so-widget' ).eq( options.at - 1 )
5158
- );
5159
- }
5160
-
5161
- if ( options.noAnimate === false ) {
5162
- // We need an animation
5163
- view.visualCreate();
5164
- }
5165
-
5166
- this.refreshSortable();
5167
- this.row.resize();
5168
- this.row.builder.trigger( 'widget_added', view );
5169
- },
5170
-
5171
- /**
5172
- * Handle this cell being clicked on
5173
- *
5174
- * @param e
5175
- * @returns {boolean}
5176
- */
5177
- handleCellClick: function ( e ) {
5178
- // Remove all existing selected cell indication for this builder
5179
- this.row.builder.$el.find( '.so-cells .cell' ).removeClass( 'cell-selected' );
5180
-
5181
- if( this.row.builder.activeCell === this && ! this.model.get('widgets').length ) {
5182
- // This is a click on an empty cell
5183
- this.row.builder.activeCell = null;
5184
- }
5185
- else {
5186
- this.$el.addClass( 'cell-selected' );
5187
- this.row.builder.activeCell = this;
5188
- }
5189
- },
5190
-
5191
- /**
5192
- * Insert a widget from the clipboard
5193
- */
5194
- pasteHandler: function(){
5195
- var pastedModel = panels.helpers.clipboard.getModel( 'widget-model' );
5196
- if( ! _.isEmpty( pastedModel ) && pastedModel instanceof panels.model.widget ) {
5197
- this.row.builder.addHistoryEntry( 'widget_pasted' );
5198
- pastedModel.cell = this.model;
5199
- this.model.get('widgets').add( pastedModel );
5200
- this.row.builder.model.refreshPanelsData();
5201
- }
5202
- },
5203
-
5204
- /**
5205
- * Build up the contextual menu for a cell
5206
- *
5207
- * @param e
5208
- * @param menu
5209
- */
5210
- buildContextualMenu: function ( e, menu ) {
5211
- var thisView = this;
5212
-
5213
- if( ! menu.hasSection( 'add-widget-below' ) ) {
5214
- menu.addSection(
5215
- 'add-widget-cell',
5216
- {
5217
- sectionTitle: panelsOptions.loc.contextual.add_widget_cell,
5218
- searchPlaceholder: panelsOptions.loc.contextual.search_widgets,
5219
- defaultDisplay: panelsOptions.contextual.default_widgets
5220
- },
5221
- panelsOptions.widgets,
5222
- function ( c ) {
5223
- thisView.row.builder.trigger('before_user_adds_widget')
5224
- thisView.row.builder.addHistoryEntry( 'widget_added' );
5225
-
5226
- var widget = new panels.model.widget( {
5227
- class: c
5228
- } );
5229
-
5230
- // Add the widget to the cell model
5231
- widget.cell = thisView.model;
5232
- widget.cell.get('widgets').add( widget );
5233
-
5234
- thisView.row.builder.model.refreshPanelsData();
5235
- thisView.row.builder.trigger('after_user_adds_widget', widget);
5236
- }
5237
- );
5238
- }
5239
-
5240
- var actions = {};
5241
- if ( this.row.builder.supports('addWidget') && panels.helpers.clipboard.isModel( 'widget-model' ) ) {
5242
- actions.paste = {title: panelsOptions.loc.contextual.cell_paste_widget};
5243
- }
5244
-
5245
- if( ! _.isEmpty( actions ) ) {
5246
- menu.addSection(
5247
- 'cell-actions',
5248
- {
5249
- sectionTitle: panelsOptions.loc.contextual.cell_actions,
5250
- search: false,
5251
- },
5252
- actions,
5253
- function ( c ) {
5254
- switch ( c ) {
5255
- case 'paste':
5256
- this.pasteHandler();
5257
- break;
5258
- }
5259
-
5260
- this.row.builder.model.refreshPanelsData();
5261
- }.bind( this )
5262
- );
5263
- }
5264
-
5265
- // Add the contextual menu for the parent row
5266
- this.row.buildContextualMenu( e, menu );
5267
- }
5268
- } );
5269
-
5270
- },{}],25:[function(require,module,exports){
5271
- var panels = window.panels, $ = jQuery;
5272
-
5273
- module.exports = Backbone.View.extend( {
5274
- dialogTemplate: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-dialog' ).html() ) ),
5275
- dialogTabTemplate: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-dialog-tab' ).html() ) ),
5276
-
5277
- tabbed: false,
5278
- rendered: false,
5279
- builder: false,
5280
- className: 'so-panels-dialog-wrapper',
5281
- dialogClass: '',
5282
- dialogIcon: '',
5283
- parentDialog: false,
5284
- dialogOpen: false,
5285
- editableLabel: false,
5286
-
5287
- events: {
5288
- 'click .so-close': 'closeDialog',
5289
- 'click .so-nav.so-previous': 'navToPrevious',
5290
- 'click .so-nav.so-next': 'navToNext',
5291
- },
5292
-
5293
- initialize: function () {
5294
- // The first time this dialog is opened, render it
5295
- this.once( 'open_dialog', this.render );
5296
- this.once( 'open_dialog', this.attach );
5297
- this.once( 'open_dialog', this.setDialogClass );
5298
-
5299
- this.trigger( 'initialize_dialog', this );
5300
-
5301
- if ( ! _.isUndefined( this.initializeDialog ) ) {
5302
- this.initializeDialog();
5303
- }
5304
-
5305
- _.bindAll( this, 'initSidebars', 'hasSidebar', 'onResize', 'toggleLeftSideBar', 'toggleRightSideBar' );
5306
- },
5307
-
5308
- /**
5309
- * Returns the next dialog in the sequence. Should be overwritten by a child dialog.
5310
- * @returns {null}
5311
- */
5312
- getNextDialog: function () {
5313
- return null;
5314
- },
5315
-
5316
- /**
5317
- * Returns the previous dialog in this sequence. Should be overwritten by child dialog.
5318
- * @returns {null}
5319
- */
5320
- getPrevDialog: function () {
5321
- return null;
5322
- },
5323
-
5324
- /**
5325
- * Adds a dialog class to uniquely identify this dialog type
5326
- */
5327
- setDialogClass: function () {
5328
- if ( this.dialogClass !== '' ) {
5329
- this.$( '.so-panels-dialog' ).addClass( this.dialogClass );
5330
- }
5331
- },
5332
-
5333
- /**
5334
- * Set the builder that controls this dialog.
5335
- * @param {panels.view.builder} builder
5336
- */
5337
- setBuilder: function ( builder ) {
5338
- this.builder = builder;
5339
-
5340
- // Trigger an add dialog event on the builder so it can modify the dialog in any way
5341
- builder.trigger( 'add_dialog', this, this.builder );
5342
-
5343
- return this;
5344
- },
5345
-
5346
- /**
5347
- * Attach the dialog to the window
5348
- */
5349
- attach: function () {
5350
- this.$el.appendTo( 'body' );
5351
-
5352
- return this;
5353
- },
5354
-
5355
- /**
5356
- * Converts an HTML representation of the dialog into arguments for a dialog box
5357
- * @param html HTML for the dialog
5358
- * @param args Arguments passed to the template
5359
- * @returns {}
5360
- */
5361
- parseDialogContent: function ( html, args ) {
5362
- // Add a CID
5363
- args = _.extend( {cid: this.cid}, args );
5364
-
5365
-
5366
- var c = $( (
5367
- _.template( panels.helpers.utils.processTemplate( html ) )
5368
- )( args ) );
5369
- var r = {
5370
- title: c.find( '.title' ).html(),
5371
- buttons: c.find( '.buttons' ).html(),
5372
- content: c.find( '.content' ).html()
5373
- };
5374
-
5375
- if ( c.has( '.left-sidebar' ) ) {
5376
- r.left_sidebar = c.find( '.left-sidebar' ).html();
5377
- }
5378
-
5379
- if ( c.has( '.right-sidebar' ) ) {
5380
- r.right_sidebar = c.find( '.right-sidebar' ).html();
5381
- }
5382
-
5383
- return r;
5384
-
5385
- },
5386
-
5387
- /**
5388
- * Render the dialog and initialize the tabs
5389
- *
5390
- * @param attributes
5391
- * @returns {panels.view.dialog}
5392
- */
5393
- renderDialog: function ( attributes ) {
5394
- attributes = _.extend( {
5395
- editableLabel: this.editableLabel,
5396
- dialogIcon: this.dialogIcon,
5397
- }, attributes );
5398
-
5399
- this.$el.html( this.dialogTemplate( attributes ) ).hide();
5400
- this.$el.data( 'view', this );
5401
- this.$el.addClass( 'so-panels-dialog-wrapper' );
5402
-
5403
- if ( this.parentDialog !== false ) {
5404
- // Add a link to the parent dialog as a sort of crumbtrail.
5405
- var dialogParent = $( '<h3 class="so-parent-link"></h3>' ).html( this.parentDialog.text + '<div class="so-separator"></div>' );
5406
- dialogParent.click( function ( e ) {
5407
- e.preventDefault();
5408
- this.closeDialog();
5409
- this.parentDialog.dialog.openDialog();
5410
- }.bind(this) );
5411
- this.$( '.so-title-bar .so-title' ).before( dialogParent );
5412
- }
5413
-
5414
- if( this.$( '.so-title-bar .so-title-editable' ).length ) {
5415
- // Added here because .so-edit-title is only available after the template has been rendered.
5416
- this.initEditableLabel();
5417
- }
5418
-
5419
- setTimeout( this.initSidebars, 1 );
5420
-
5421
- return this;
5422
- },
5423
-
5424
- initSidebars: function () {
5425
- var $leftButton = this.$( '.so-show-left-sidebar' ).hide();
5426
- var $rightButton = this.$( '.so-show-right-sidebar' ).hide();
5427
- var hasLeftSidebar = this.hasSidebar( 'left' );
5428
- var hasRightSidebar = this.hasSidebar( 'right' );
5429
- // Set up resize handling
5430
- if ( hasLeftSidebar || hasRightSidebar ) {
5431
- $( window ).on( 'resize', this.onResize );
5432
- if ( hasLeftSidebar ) {
5433
- $leftButton.show();
5434
- $leftButton.on( 'click', this.toggleLeftSideBar );
5435
- }
5436
- if ( hasRightSidebar ) {
5437
- $rightButton.show();
5438
- $rightButton.on( 'click', this.toggleRightSideBar );
5439
- }
5440
- }
5441
-
5442
- this.onResize();
5443
- },
5444
-
5445
- /**
5446
- * Initialize the sidebar tabs
5447
- */
5448
- initTabs: function () {
5449
- var tabs = this.$( '.so-sidebar-tabs li a' );
5450
-
5451
- if ( tabs.length === 0 ) {
5452
- return this;
5453
- }
5454
-
5455
- var thisDialog = this;
5456
- tabs.click( function ( e ) {
5457
- e.preventDefault();
5458
- var $$ = $( this );
5459
-
5460
- thisDialog.$( '.so-sidebar-tabs li' ).removeClass( 'tab-active' );
5461
- thisDialog.$( '.so-content .so-content-tabs > *' ).hide();
5462
-
5463
- $$.parent().addClass( 'tab-active' );
5464
-
5465
- var url = $$.attr( 'href' );
5466
- if ( ! _.isUndefined( url ) && url.charAt( 0 ) === '#' ) {
5467
- // Display the new tab
5468
- var tabName = url.split( '#' )[1];
5469
- thisDialog.$( '.so-content .so-content-tabs .tab-' + tabName ).show();
5470
- }
5471
-
5472
- // This lets other dialogs implement their own custom handlers
5473
- thisDialog.trigger( 'tab_click', $$ );
5474
-
5475
- } );
5476
-
5477
- // Trigger a click on the first tab
5478
- this.$( '.so-sidebar-tabs li a' ).first().click();
5479
- return this;
5480
- },
5481
-
5482
- initToolbar: function () {
5483
- // Trigger simplified click event for elements marked as toolbar buttons.
5484
- var buttons = this.$( '.so-toolbar .so-buttons .so-toolbar-button' );
5485
- buttons.click( function ( e ) {
5486
- e.preventDefault();
5487
-
5488
- this.trigger( 'button_click', $( e.currentTarget ) );
5489
- }.bind( this ) );
5490
-
5491
- // Handle showing and hiding the dropdown list items
5492
- var $dropdowns = this.$( '.so-toolbar .so-buttons .so-dropdown-button' );
5493
- $dropdowns.click( function ( e ) {
5494
- e.preventDefault();
5495
- var $dropdownButton = $( e.currentTarget );
5496
- var $dropdownList = $dropdownButton.siblings( '.so-dropdown-links-wrapper' );
5497
- if ( $dropdownList.is( '.hidden' ) ) {
5498
- $dropdownList.removeClass( 'hidden' );
5499
- } else {
5500
- $dropdownList.addClass( 'hidden' );
5501
- }
5502
-
5503
- }.bind( this ) );
5504
-
5505
- // Hide dropdown list on click anywhere, unless it's a dropdown option which requires confirmation in it's
5506
- // unconfirmed state.
5507
- $( 'html' ).click( function ( e ) {
5508
- this.$( '.so-dropdown-links-wrapper' ).not( '.hidden' ).each( function ( index, el ) {
5509
- var $dropdownList = $( el );
5510
- var $trgt = $( e.target );
5511
- if ( $trgt.length === 0 || !(
5512
- (
5513
- $trgt.is('.so-needs-confirm') && !$trgt.is('.so-confirmed')
5514
- ) || $trgt.is('.so-dropdown-button')
5515
- ) ) {
5516
- $dropdownList.addClass('hidden');
5517
- }
5518
- } );
5519
- }.bind( this ) );
5520
- },
5521
-
5522
- /**
5523
- * Initialize the editable dialog title
5524
- */
5525
- initEditableLabel: function(){
5526
- var $editElt = this.$( '.so-title-bar .so-title-editable' );
5527
-
5528
- $editElt.keypress( function ( event ) {
5529
- var enterPressed = event.type === 'keypress' && event.keyCode === 13;
5530
- if ( enterPressed ) {
5531
- // Need to make sure tab focus is on another element, otherwise pressing enter multiple times refocuses
5532
- // the element and allows newlines.
5533
- var tabbables = $( ':tabbable' );
5534
- var curTabIndex = tabbables.index( $editElt );
5535
- tabbables.eq( curTabIndex + 1 ).focus();
5536
- // After the above, we're somehow left with the first letter of text selected,
5537
- // so this removes the selection.
5538
- window.getSelection().removeAllRanges();
5539
- }
5540
- return !enterPressed;
5541
- } ).blur( function () {
5542
- var newValue = $editElt.text().replace( /^\s+|\s+$/gm, '' );
5543
- var oldValue = $editElt.data( 'original-value' ).replace( /^\s+|\s+$/gm, '' );
5544
- if ( newValue !== oldValue ) {
5545
- $editElt.text( newValue );
5546
- this.trigger( 'edit_label', newValue );
5547
- }
5548
-
5549
- }.bind( this ) );
5550
-
5551
- $editElt.focus( function() {
5552
- $editElt.data( 'original-value', $editElt.text() );
5553
- panels.helpers.utils.selectElementContents( this );
5554
- } );
5555
- },
5556
-
5557
- /**
5558
- * Quickly setup the dialog by opening and closing it.
5559
- */
5560
- setupDialog: function () {
5561
- this.openDialog();
5562
- this.closeDialog();
5563
- },
5564
-
5565
- /**
5566
- * Refresh the next and previous buttons.
5567
- */
5568
- refreshDialogNav: function () {
5569
- this.$( '.so-title-bar .so-nav' ).show().removeClass( 'so-disabled' );
5570
-
5571
- // Lets also hide the next and previous if we don't have a next and previous dialog
5572
- var nextDialog = this.getNextDialog();
5573
- var nextButton = this.$( '.so-title-bar .so-next' );
5574
-
5575
- var prevDialog = this.getPrevDialog();
5576
- var prevButton = this.$( '.so-title-bar .so-previous' );
5577
-
5578
- if ( nextDialog === null ) {
5579
- nextButton.hide();
5580
- }
5581
- else if ( nextDialog === false ) {
5582
- nextButton.addClass( 'so-disabled' );
5583
- }
5584
-
5585
- if ( prevDialog === null ) {
5586
- prevButton.hide();
5587
- }
5588
- else if ( prevDialog === false ) {
5589
- prevButton.addClass( 'so-disabled' );
5590
- }
5591
- },
5592
-
5593
- /**
5594
- * Open the dialog
5595
- */
5596
- openDialog: function ( options ) {
5597
- options = _.extend( {
5598
- silent: false
5599
- }, options );
5600
-
5601
- if ( ! options.silent ) {
5602
- this.trigger( 'open_dialog' );
5603
- }
5604
-
5605
- this.dialogOpen = true;
5606
-
5607
- this.refreshDialogNav();
5608
-
5609
- // Stop scrolling for the main body
5610
- panels.helpers.pageScroll.lock();
5611
-
5612
- this.onResize();
5613
-
5614
- this.$el.show();
5615
-
5616
- if ( ! options.silent ) {
5617
- // This triggers once everything is visible
5618
- this.trigger( 'open_dialog_complete' );
5619
- this.builder.trigger( 'open_dialog', this );
5620
- $( document ).trigger( 'open_dialog', this );
5621
- }
5622
- },
5623
-
5624
- /**
5625
- * Close the dialog
5626
- *
5627
- * @param e
5628
- * @returns {boolean}
5629
- */
5630
- closeDialog: function ( options ) {
5631
- options = _.extend( {
5632
- silent: false
5633
- }, options );
5634
-
5635
- if ( ! options.silent ) {
5636
- this.trigger( 'close_dialog' );
5637
- }
5638
-
5639
- this.dialogOpen = false;
5640
-
5641
- this.$el.hide();
5642
- panels.helpers.pageScroll.unlock();
5643
-
5644
- if ( ! options.silent ) {
5645
- // This triggers once everything is hidden
5646
- this.trigger( 'close_dialog_complete' );
5647
- this.builder.trigger( 'close_dialog', this );
5648
- }
5649
- },
5650
-
5651
- /**
5652
- * Navigate to the previous dialog
5653
- */
5654
- navToPrevious: function () {
5655
- this.closeDialog();
5656
-
5657
- var prev = this.getPrevDialog();
5658
- if ( prev !== null && prev !== false ) {
5659
- prev.openDialog();
5660
- }
5661
- },
5662
-
5663
- /**
5664
- * Navigate to the next dialog
5665
- */
5666
- navToNext: function () {
5667
- this.closeDialog();
5668
-
5669
- var next = this.getNextDialog();
5670
- if ( next !== null && next !== false ) {
5671
- next.openDialog();
5672
- }
5673
- },
5674
-
5675
- /**
5676
- * Get the values from the form and convert them into a data array
5677
- */
5678
- getFormValues: function ( formSelector ) {
5679
- if ( _.isUndefined( formSelector ) ) {
5680
- formSelector = '.so-content';
5681
- }
5682
-
5683
- var $f = this.$( formSelector );
5684
-
5685
- var data = {}, parts;
5686
-
5687
- // Find all the named fields in the form
5688
- $f.find( '[name]' ).each( function () {
5689
- var $$ = $( this );
5690
-
5691
- try {
5692
-
5693
- var name = /([A-Za-z_]+)\[(.*)\]/.exec( $$.attr( 'name' ) );
5694
- if ( _.isEmpty( name ) ) {
5695
- return true;
5696
- }
5697
-
5698
- // Create an array with the parts of the name
5699
- if ( _.isUndefined( name[2] ) ) {
5700
- parts = $$.attr( 'name' );
5701
- } else {
5702
- parts = name[2].split( '][' );
5703
- parts.unshift( name[1] );
5704
- }
5705
-
5706
- parts = parts.map( function ( e ) {
5707
- if ( ! isNaN( parseFloat( e ) ) && isFinite( e ) ) {
5708
- return parseInt( e );
5709
- } else {
5710
- return e;
5711
- }
5712
- } );
5713
-
5714
- var sub = data;
5715
- var fieldValue = null;
5716
-
5717
- var fieldType = (
5718
- _.isString( $$.attr( 'type' ) ) ? $$.attr( 'type' ).toLowerCase() : false
5719
- );
5720
-
5721
- // First we need to get the value from the field
5722
- if ( fieldType === 'checkbox' ) {
5723
- if ( $$.is( ':checked' ) ) {
5724
- fieldValue = $$.val() !== '' ? $$.val() : true;
5725
- } else {
5726
- fieldValue = null;
5727
- }
5728
- }
5729
- else if ( fieldType === 'radio' ) {
5730
- if ( $$.is( ':checked' ) ) {
5731
- fieldValue = $$.val();
5732
- } else {
5733
- //skip over unchecked radios
5734
- return;
5735
- }
5736
- }
5737
- else if ( $$.prop( 'tagName' ) === 'SELECT' ) {
5738
- var selected = $$.find( 'option:selected' );
5739
-
5740
- if ( selected.length === 1 ) {
5741
- fieldValue = $$.find( 'option:selected' ).val();
5742
- }
5743
- else if ( selected.length > 1 ) {
5744
- // This is a mutli-select field
5745
- fieldValue = _.map( $$.find( 'option:selected' ), function ( n, i ) {
5746
- return $( n ).val();
5747
- } );
5748
- }
5749
-
5750
- } else {
5751
- // This is a fallback that will work for most fields
5752
- fieldValue = $$.val();
5753
- }
5754
-
5755
- // Now, we need to filter this value if necessary
5756
- if ( ! _.isUndefined( $$.data( 'panels-filter' ) ) ) {
5757
- switch ( $$.data( 'panels-filter' ) ) {
5758
- case 'json_parse':
5759
- // Attempt to parse the JSON value of this field
5760
- try {
5761
- fieldValue = JSON.parse( fieldValue );
5762
- }
5763
- catch ( err ) {
5764
- fieldValue = '';
5765
- }
5766
- break;
5767
- }
5768
- }
5769
-
5770
- // Now convert this into an array
5771
- if ( fieldValue !== null ) {
5772
- for ( var i = 0; i < parts.length; i ++ ) {
5773
- if ( i === parts.length - 1 ) {
5774
- if ( parts[i] === '' ) {
5775
- // This needs to be an array
5776
- sub.push( fieldValue );
5777
- } else {
5778
- sub[parts[i]] = fieldValue;
5779
- }
5780
- } else {
5781
- if ( _.isUndefined( sub[parts[i]] ) ) {
5782
- if ( parts[i + 1] === '' ) {
5783
- sub[parts[i]] = [];
5784
- } else {
5785
- sub[parts[i]] = {};
5786
- }
5787
- }
5788
- sub = sub[parts[i]];
5789
- }
5790
- }
5791
- }
5792
- }
5793
- catch ( error ) {
5794
- // Ignore this error, just log the message for debugging
5795
- console.log( 'Field [' + $$.attr('name') + '] could not be processed and was skipped - ' + error.message );
5796
- }
5797
-
5798
- } ); // End of each through input fields
5799
-
5800
- return data;
5801
- },
5802
-
5803
- /**
5804
- * Set a status message for the dialog
5805
- */
5806
- setStatusMessage: function ( message, loading, error ) {
5807
- var msg = error ? '<span class="dashicons dashicons-warning"></span>' + message : message;
5808
- this.$( '.so-toolbar .so-status' ).html( msg );
5809
- if ( ! _.isUndefined( loading ) && loading ) {
5810
- this.$( '.so-toolbar .so-status' ).addClass( 'so-panels-loading' );
5811
- } else {
5812
- this.$( '.so-toolbar .so-status' ).removeClass( 'so-panels-loading' );
5813
- }
5814
- },
5815
-
5816
- /**
5817
- * Set the parent after.
5818
- */
5819
- setParent: function ( text, dialog ) {
5820
- this.parentDialog = {
5821
- text: text,
5822
- dialog: dialog
5823
- };
5824
- },
5825
-
5826
- onResize: function () {
5827
- var mediaQuery = window.matchMedia( '(max-width: 980px)' );
5828
- var sides = [ 'left', 'right' ];
5829
-
5830
- sides.forEach( function ( side ) {
5831
- var $sideBar = this.$( '.so-' + side + '-sidebar' );
5832
- var $showSideBarButton = this.$( '.so-show-' + side + '-sidebar' );
5833
- if ( this.hasSidebar( side ) ) {
5834
- $showSideBarButton.hide();
5835
- if ( mediaQuery.matches ) {
5836
- $showSideBarButton.show();
5837
- $showSideBarButton.closest( '.so-title-bar' ).addClass( 'so-has-' + side + '-button' );
5838
- $sideBar.hide();
5839
- $sideBar.closest( '.so-panels-dialog' ).removeClass( 'so-panels-dialog-has-' + side + '-sidebar' );
5840
- } else {
5841
- $showSideBarButton.hide();
5842
- $showSideBarButton.closest( '.so-title-bar' ).removeClass( 'so-has-' + side + '-button' );
5843
- $sideBar.show();
5844
- $sideBar.closest( '.so-panels-dialog' ).addClass( 'so-panels-dialog-has-' + side + '-sidebar' );
5845
- }
5846
- } else {
5847
- $sideBar.hide();
5848
- $showSideBarButton.hide();
5849
- }
5850
- }.bind( this ) );
5851
- },
5852
-
5853
- hasSidebar: function ( side ) {
5854
- return this.$( '.so-' + side + '-sidebar' ).children().length > 0;
5855
- },
5856
-
5857
- toggleLeftSideBar: function () {
5858
- this.toggleSidebar( 'left' );
5859
- },
5860
-
5861
- toggleRightSideBar: function () {
5862
- this.toggleSidebar( 'right' );
5863
- },
5864
-
5865
- toggleSidebar: function ( side ) {
5866
- var sidebar = this.$( '.so-' + side + '-sidebar' );
5867
-
5868
- if ( sidebar.is( ':visible' ) ) {
5869
- sidebar.hide();
5870
- } else {
5871
- sidebar.show();
5872
- }
5873
- },
5874
-
5875
- } );
5876
-
5877
- },{}],26:[function(require,module,exports){
5878
- var panels = window.panels, $ = jQuery;
5879
-
5880
- module.exports = Backbone.View.extend( {
5881
- template: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-live-editor' ).html() ) ),
5882
-
5883
- previewScrollTop: 0,
5884
- loadTimes: [],
5885
- previewFrameId: 1,
5886
-
5887
- previewUrl: null,
5888
- previewIframe: null,
5889
-
5890
- events: {
5891
- 'click .live-editor-close': 'close',
5892
- 'click .live-editor-save': 'closeAndSave',
5893
- 'click .live-editor-collapse': 'collapse',
5894
- 'click .live-editor-mode': 'mobileToggle'
5895
- },
5896
-
5897
- initialize: function ( options ) {
5898
- options = _.extend( {
5899
- builder: false,
5900
- previewUrl: false,
5901
- }, options );
5902
-
5903
- if( _.isEmpty( options.previewUrl ) ) {
5904
- options.previewUrl = panelsOptions.ajaxurl + "&action=so_panels_live_editor_preview";
5905
- }
5906
-
5907
- this.builder = options.builder;
5908
- this.previewUrl = options.previewUrl;
5909
-
5910
- this.listenTo( this.builder.model, 'refresh_panels_data', this.handleRefreshData );
5911
- this.listenTo( this.builder.model, 'load_panels_data', this.handleLoadData );
5912
- },
5913
-
5914
- /**
5915
- * Render the live editor
5916
- */
5917
- render: function () {
5918
- this.setElement( this.template() );
5919
- this.$el.hide();
5920
-
5921
- if ( $( '#submitdiv #save-post' ).length > 0 ) {
5922
- var $saveButton = this.$el.find( '.live-editor-save' );
5923
- $saveButton.text( $saveButton.data( 'save' ) );
5924
- }
5925
-
5926
- var isMouseDown = false;
5927
- $( document )
5928
- .mousedown( function () {
5929
- isMouseDown = true;
5930
- } )
5931
- .mouseup( function () {
5932
- isMouseDown = false;
5933
- } );
5934
-
5935
- // Handle highlighting the relevant widget in the live editor preview
5936
- var liveEditorView = this;
5937
- this.$el.on( 'mouseenter', '.so-widget-wrapper', function () {
5938
- var $$ = $( this ),
5939
- previewWidget = $$.data( 'live-editor-preview-widget' );
5940
-
5941
- if ( ! isMouseDown && previewWidget !== undefined && previewWidget.length && ! liveEditorView.$( '.so-preview-overlay' ).is( ':visible' ) ) {
5942
- liveEditorView.highlightElement( previewWidget );
5943
- liveEditorView.scrollToElement( previewWidget );
5944
- }
5945
- } );
5946
-
5947
- this.$el.on( 'mouseleave', '.so-widget-wrapper', function () {
5948
- this.resetHighlights();
5949
- }.bind(this) );
5950
-
5951
- this.listenTo( this.builder, 'open_dialog', function () {
5952
- this.resetHighlights();
5953
- } );
5954
-
5955
- return this;
5956
- },
5957
-
5958
- /**
5959
- * Attach the live editor to the document
5960
- */
5961
- attach: function () {
5962
- this.$el.appendTo( 'body' );
5963
- },
5964
-
5965
- /**
5966
- * Display the live editor
5967
- */
5968
- open: function () {
5969
- if ( this.$el.html() === '' ) {
5970
- this.render();
5971
- }
5972
- if ( this.$el.closest( 'body' ).length === 0 ) {
5973
- this.attach();
5974
- }
5975
-
5976
- // Disable page scrolling
5977
- panels.helpers.pageScroll.lock();
5978
-
5979
- if ( this.$el.is( ':visible' ) ) {
5980
- return this;
5981
- }
5982
-
5983
- // Refresh the preview display
5984
- this.$el.show();
5985
- this.refreshPreview( this.builder.model.getPanelsData() );
5986
-
5987
- // Move the builder view into the Live Editor
5988
- this.originalContainer = this.builder.$el.parent();
5989
- this.builder.$el.appendTo( this.$( '.so-live-editor-builder' ) );
5990
- this.builder.$( '.so-tool-button.so-live-editor' ).hide();
5991
- this.builder.trigger( 'builder_resize' );
5992
-
5993
-
5994
- if( $('#original_post_status' ).val() === 'auto-draft' && ! this.autoSaved ) {
5995
- // The live editor requires a saved draft post, so we'll create one for auto-draft posts
5996
- var thisView = this;
5997
-
5998
- if ( wp.autosave ) {
5999
- // Set a temporary post title so the autosave triggers properly
6000
- if( $('#title[name="post_title"]' ).val() === '' ) {
6001
- $('#title[name="post_title"]' ).val( panelsOptions.loc.draft ).trigger('keydown');
6002
- }
6003
-
6004
- $( document ).one( 'heartbeat-tick.autosave', function(){
6005
- thisView.autoSaved = true;
6006
- thisView.refreshPreview( thisView.builder.model.getPanelsData() );
6007
- } );
6008
- wp.autosave.server.triggerSave();
6009
- }
6010
- }
6011
- },
6012
-
6013
- /**
6014
- * Close the Live Editor
6015
- */
6016
- close: function () {
6017
- if ( ! this.$el.is( ':visible' ) ) {
6018
- return this;
6019
- }
6020
-
6021
- this.$el.hide();
6022
- panels.helpers.pageScroll.unlock();
6023
-
6024
- // Move the builder back to its original container
6025
- this.builder.$el.appendTo( this.originalContainer );
6026
- this.builder.$( '.so-tool-button.so-live-editor' ).show();
6027
- this.builder.trigger( 'builder_resize' );
6028
- },
6029
-
6030
- /**
6031
- * Close the Live Editor and save the post.
6032
- */
6033
- closeAndSave: function(){
6034
- this.close();
6035
- // Finds the submit input for saving without publishing draft posts.
6036
- $('#submitdiv input[type="submit"][name="save"]').click();
6037
- },
6038
-
6039
- /**
6040
- * Collapse the live editor
6041
- */
6042
- collapse: function () {
6043
- this.$el.toggleClass( 'so-collapsed' );
6044
- },
6045
-
6046
- /**
6047
- * Create an overlay in the preview.
6048
- *
6049
- * @param over
6050
- * @return {*|Object} The item we're hovering over.
6051
- */
6052
- highlightElement: function ( over ) {
6053
- if( ! _.isUndefined( this.resetHighlightTimeout ) ) {
6054
- clearTimeout( this.resetHighlightTimeout );
6055
- }
6056
-
6057
- // Remove any old overlays
6058
-
6059
- var body = this.previewIframe.contents().find( 'body' );
6060
- body.find( '.panel-grid .panel-grid-cell .so-panel' )
6061
- .filter( function () {
6062
- // Filter to only include non nested
6063
- return $( this ).parents( '.so-panel' ).length === 0;
6064
- } )
6065
- .not( over )
6066
- .addClass( 'so-panels-faded' );
6067
-
6068
- over.removeClass( 'so-panels-faded' ).addClass( 'so-panels-highlighted' );
6069
- },
6070
-
6071
- /**
6072
- * Reset highlights in the live preview
6073
- */
6074
- resetHighlights: function() {
6075
-
6076
- var body = this.previewIframe.contents().find( 'body' );
6077
- this.resetHighlightTimeout = setTimeout( function(){
6078
- body.find( '.panel-grid .panel-grid-cell .so-panel' )
6079
- .removeClass( 'so-panels-faded so-panels-highlighted' );
6080
- }, 100 );
6081
- },
6082
-
6083
- /**
6084
- * Scroll over an element in the live preview
6085
- * @param over
6086
- */
6087
- scrollToElement: function( over ) {
6088
- var contentWindow = this.$( '.so-preview iframe' )[0].contentWindow;
6089
- contentWindow.liveEditorScrollTo( over );
6090
- },
6091
-
6092
- handleRefreshData: function ( newData ) {
6093
- if ( ! this.$el.is( ':visible' ) ) {
6094
- return this;
6095
- }
6096
-
6097
- this.refreshPreview( newData );
6098
- },
6099
-
6100
- handleLoadData: function () {
6101
- if ( ! this.$el.is( ':visible' ) ) {
6102
- return this;
6103
- }
6104
-
6105
- this.refreshPreview( this.builder.model.getPanelsData() );
6106
- },
6107
-
6108
- /**
6109
- * Refresh the Live Editor preview.
6110
- * @returns {exports}
6111
- */
6112
- refreshPreview: function ( data ) {
6113
- var loadTimePrediction = this.loadTimes.length ?
6114
- _.reduce( this.loadTimes, function ( memo, num ) {
6115
- return memo + num;
6116
- }, 0 ) / this.loadTimes.length : 1000;
6117
-
6118
- // Store the last preview iframe position
6119
- if( ! _.isNull( this.previewIframe ) ) {
6120
- if ( ! this.$( '.so-preview-overlay' ).is( ':visible' ) ) {
6121
- this.previewScrollTop = this.previewIframe.contents().scrollTop();
6122
- }
6123
- }
6124
-
6125
- // Add a loading bar
6126
- this.$( '.so-preview-overlay' ).show();
6127
- this.$( '.so-preview-overlay .so-loading-bar' )
6128
- .clearQueue()
6129
- .css( 'width', '0%' )
6130
- .animate( {width: '100%'}, parseInt( loadTimePrediction ) + 100 );
6131
-
6132
-
6133
- this.postToIframe(
6134
- {
6135
- live_editor_panels_data: JSON.stringify( data ),
6136
- live_editor_post_ID: this.builder.config.postId
6137
- },
6138
- this.previewUrl,
6139
- this.$('.so-preview')
6140
- );
6141
-
6142
- this.previewIframe.data( 'load-start', new Date().getTime() );
6143
- },
6144
-
6145
- /**
6146
- * Use a temporary form to post data to an iframe.
6147
- *
6148
- * @param data The data to send
6149
- * @param url The preview URL
6150
- * @param target The target iframe
6151
- */
6152
- postToIframe: function( data, url, target ){
6153
- // Store the old preview
6154
-
6155
- if( ! _.isNull( this.previewIframe ) ) {
6156
- this.previewIframe.remove();
6157
- }
6158
-
6159
- var iframeId = 'siteorigin-panels-live-preview-' + this.previewFrameId;
6160
-
6161
- // Remove the old preview frame
6162
- this.previewIframe = $('<iframe src="javascript:false;" />')
6163
- .attr( {
6164
- 'id' : iframeId,
6165
- 'name' : iframeId,
6166
- } )
6167
- .appendTo( target );
6168
-
6169
- this.setupPreviewFrame( this.previewIframe );
6170
-
6171
- // We can use a normal POST form submit
6172
- var tempForm = $('<form id="soPostToPreviewFrame" method="post" />')
6173
- .attr( {
6174
- id: iframeId,
6175
- target: this.previewIframe.attr('id'),
6176
- action: url
6177
- } )
6178
- .appendTo( 'body' );
6179
-
6180
- $.each( data, function( name, value ){
6181
- $('<input type="hidden" />')
6182
- .attr( {
6183
- name: name,
6184
- value: value
6185
- } )
6186
- .appendTo( tempForm );
6187
- } );
6188
-
6189
- tempForm
6190
- .submit()
6191
- .remove();
6192
-
6193
- this.previewFrameId++;
6194
-
6195
- return this.previewIframe;
6196
- },
6197
-
6198
- /**
6199
- * Do all the basic setup for the preview Iframe element
6200
- * @param iframe
6201
- */
6202
- setupPreviewFrame: function( iframe ){
6203
- var thisView = this;
6204
- iframe
6205
- .data( 'iframeready', false )
6206
- .on( 'iframeready', function () {
6207
- var $$ = $( this ),
6208
- $iframeContents = $$.contents();
6209
-
6210
- if( $$.data( 'iframeready' ) ) {
6211
- // Skip this if the iframeready function has already run
6212
- return;
6213
- }
6214
-
6215
- $$.data( 'iframeready', true );
6216
-
6217
- if ( $$.data( 'load-start' ) !== undefined ) {
6218
- thisView.loadTimes.unshift( new Date().getTime() - $$.data( 'load-start' ) );
6219
-
6220
- if ( ! _.isEmpty( thisView.loadTimes ) ) {
6221
- thisView.loadTimes = thisView.loadTimes.slice( 0, 4 );
6222
- }
6223
- }
6224
-
6225
- setTimeout( function(){
6226
- // Scroll to the correct position
6227
- $iframeContents.scrollTop( thisView.previewScrollTop );
6228
- thisView.$( '.so-preview-overlay' ).hide();
6229
- }, 100 );
6230
-
6231
-
6232
- // Lets find all the first level grids. This is to account for the Page Builder layout widget.
6233
- var layoutWrapper = $iframeContents.find( '#pl-' + thisView.builder.config.postId );
6234
- layoutWrapper.find( '.panel-grid .panel-grid-cell .so-panel' )
6235
- .filter( function () {
6236
- // Filter to only include non nested
6237
- return $( this ).closest( '.panel-layout' ).is( layoutWrapper );
6238
- } )
6239
- .each( function ( i, el ) {
6240
- var $$ = $( el );
6241
- var widgetEdit = thisView.$( '.so-live-editor-builder .so-widget-wrapper' ).eq( $$.data( 'index' ) );
6242
- widgetEdit.data( 'live-editor-preview-widget', $$ );
6243
-
6244
- $$
6245
- .css( {
6246
- 'cursor': 'pointer'
6247
- } )
6248
- .mouseenter( function () {
6249
- widgetEdit.parent().addClass( 'so-hovered' );
6250
- thisView.highlightElement( $$ );
6251
- } )
6252
- .mouseleave( function () {
6253
- widgetEdit.parent().removeClass( 'so-hovered' );
6254
- thisView.resetHighlights();
6255
- } )
6256
- .click( function ( e ) {
6257
- e.preventDefault();
6258
- // When we click a widget, send that click to the form
6259
- widgetEdit.find( '.title h4' ).click();
6260
- } );
6261
- } );
6262
-
6263
- // Prevent default clicks inside the preview iframe
6264
- $iframeContents.find( "a" ).css( {'pointer-events': 'none'} ).click( function ( e ) {
6265
- e.preventDefault();
6266
- } );
6267
-
6268
- } )
6269
- .on( 'load', function(){
6270
- var $$ = $( this );
6271
- if( ! $$.data( 'iframeready' ) ) {
6272
- $$.trigger('iframeready');
6273
- }
6274
- } );
6275
- },
6276
-
6277
- /**
6278
- * Return true if the live editor has a valid preview URL.
6279
- * @return {boolean}
6280
- */
6281
- hasPreviewUrl: function () {
6282
- return this.$( 'form.live-editor-form' ).attr( 'action' ) !== '';
6283
- },
6284
-
6285
- /**
6286
- * Toggle the size of the preview iframe to simulate mobile devices.
6287
- * @param e
6288
- */
6289
- mobileToggle: function( e ){
6290
- var button = $( e.currentTarget );
6291
- this.$('.live-editor-mode' ).not( button ).removeClass('so-active');
6292
- button.addClass( 'so-active' );
6293
-
6294
- this.$el
6295
- .removeClass( 'live-editor-desktop-mode live-editor-tablet-mode live-editor-mobile-mode' )
6296
- .addClass( 'live-editor-' + button.data( 'mode' ) + '-mode' );
6297
-
6298
- }
6299
- } );
6300
-
6301
- },{}],27:[function(require,module,exports){
6302
- var panels = window.panels, $ = jQuery;
6303
-
6304
- module.exports = Backbone.View.extend( {
6305
- template: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-builder-row' ).html() ) ),
6306
-
6307
- events: {
6308
- 'click .so-row-settings': 'editSettingsHandler',
6309
- 'click .so-row-duplicate': 'duplicateHandler',
6310
- 'click .so-row-delete': 'confirmedDeleteHandler',
6311
- 'click .so-row-color': 'rowColorChangeHandler',
6312
- },
6313
-
6314
- builder: null,
6315
- dialog: null,
6316
-
6317
- /**
6318
- * Initialize the row view
6319
- */
6320
- initialize: function () {
6321
-
6322
- var rowCells = this.model.get('cells');
6323
- this.listenTo(rowCells, 'add', this.handleCellAdd );
6324
- this.listenTo(rowCells, 'remove', this.handleCellRemove );
6325
-
6326
- this.listenTo( this.model, 'reweight_cells', this.resize );
6327
- this.listenTo( this.model, 'destroy', this.onModelDestroy );
6328
-
6329
- var thisView = this;
6330
- rowCells.each( function ( cell ) {
6331
- thisView.listenTo( cell.get('widgets'), 'add', thisView.resize );
6332
- } );
6333
-
6334
- // When ever a new cell is added, listen to it for new widgets
6335
- rowCells.on( 'add', function ( cell ) {
6336
- thisView.listenTo( cell.get('widgets'), 'add', thisView.resize );
6337
- }, this );
6338
-
6339
- this.listenTo( this.model, 'change:label', this.onLabelChange );
6340
- },
6341
-
6342
- /**
6343
- * Render the row.
6344
- *
6345
- * @returns {panels.view.row}
6346
- */
6347
- render: function () {
6348
- var rowColorLabel = this.model.has( 'color_label' ) ? this.model.get( 'color_label' ) : 1;
6349
- var rowLabel = this.model.has( 'label' ) ? this.model.get( 'label' ) : '';
6350
- this.setElement( this.template( { rowColorLabel: rowColorLabel, rowLabel: rowLabel } ) );
6351
- this.$el.data( 'view', this );
6352
-
6353
- // Create views for the cells in this row
6354
- var thisView = this;
6355
- this.model.get('cells').each( function ( cell ) {
6356
- var cellView = new panels.view.cell( {
6357
- model: cell
6358
- } );
6359
- cellView.row = thisView;
6360
- cellView.render();
6361
- cellView.$el.appendTo( thisView.$( '.so-cells' ) );
6362
- } );
6363
-
6364
- // Remove any unsupported actions
6365
- if( ! this.builder.supports( 'rowAction' ) ) {
6366
- this.$('.so-row-toolbar .so-dropdown-wrapper' ).remove();
6367
- this.$el.addClass('so-row-no-actions');
6368
- }
6369
- else {
6370
- if( ! this.builder.supports( 'editRow' ) ) {
6371
- this.$('.so-row-toolbar .so-dropdown-links-wrapper .so-row-settings' ).parent().remove();
6372
- this.$el.addClass('so-row-no-edit');
6373
- }
6374
- if( ! this.builder.supports( 'addRow' ) ) {
6375
- this.$('.so-row-toolbar .so-dropdown-links-wrapper .so-row-duplicate' ).parent().remove();
6376
- this.$el.addClass('so-row-no-duplicate');
6377
- }
6378
- if( ! this.builder.supports( 'deleteRow' ) ) {
6379
- this.$('.so-row-toolbar .so-dropdown-links-wrapper .so-row-delete' ).parent().remove();
6380
- this.$el.addClass('so-row-no-delete');
6381
- }
6382
- }
6383
- if( ! this.builder.supports( 'moveRow' ) ) {
6384
- this.$('.so-row-toolbar .so-row-move' ).remove();
6385
- this.$el.addClass('so-row-no-move');
6386
- }
6387
- if( !$.trim( this.$('.so-row-toolbar').html() ).length ) {
6388
- this.$('.so-row-toolbar' ).remove();
6389
- }
6390
-
6391
- // Resize the rows when ever the widget sortable moves
6392
- this.listenTo( this.builder, 'widget_sortable_move', this.resize );
6393
- this.listenTo( this.builder, 'builder_resize', this.resize );
6394
-
6395
- this.resize();
6396
-
6397
- return this;
6398
- },
6399
-
6400
- /**
6401
- * Give a visual indication of the creation of this row
6402
- */
6403
- visualCreate: function () {
6404
- this.$el.hide().fadeIn( 'fast' );
6405
- },
6406
-
6407
- /**
6408
- * Visually resize the row so that all cell heights are the same and the widths so that they balance to 100%
6409
- *
6410
- * @param e
6411
- */
6412
- resize: function ( e ) {
6413
- // Don't resize this
6414
- if ( ! this.$el.is( ':visible' ) ) {
6415
- return;
6416
- }
6417
-
6418
- // Reset everything to have an automatic height
6419
- this.$( '.so-cells .cell-wrapper' ).css( 'min-height', 0 );
6420
- this.$( '.so-cells .resize-handle' ).css( 'height', 0 );
6421
-
6422
- // We'll tie the values to the row view, to prevent issue with values going to different rows
6423
- var height = 0;
6424
- this.$( '.so-cells .cell' ).each( function () {
6425
- height = Math.max(
6426
- height,
6427
- $( this ).height()
6428
- );
6429
-
6430
- $( this ).css(
6431
- 'width',
6432
- ( $( this ).data( 'view' ).model.get( 'weight' ) * 100) + "%"
6433
- );
6434
- } );
6435
-
6436
- // Resize all the grids and cell wrappers
6437
- this.$( '.so-cells .cell-wrapper' ).css( 'min-height', Math.max( height, 63 ) );
6438
- this.$( '.so-cells .resize-handle' ).css( 'height', this.$( '.so-cells .cell-wrapper' ).outerHeight() );
6439
- },
6440
-
6441
- /**
6442
- * Remove the view from the dom.
6443
- */
6444
- onModelDestroy: function () {
6445
- this.remove();
6446
- },
6447
-
6448
- /**
6449
- * Fade out the view and destroy the model
6450
- */
6451
- visualDestroyModel: function () {
6452
- this.builder.addHistoryEntry( 'row_deleted' );
6453
- var thisView = this;
6454
- this.$el.fadeOut( 'normal', function () {
6455
- thisView.model.destroy();
6456
- thisView.builder.model.refreshPanelsData();
6457
- } );
6458
- },
6459
-
6460
- onLabelChange: function( model, text ) {
6461
- if ( this.$('.so-row-label').length == 0 ) {
6462
- this.$( '.so-row-toolbar' ).prepend( '<h3 class="so-row-label">' + text + '</h3>' );
6463
- } else {
6464
- this.$('.so-row-label').text( text );
6465
- }
6466
- },
6467
-
6468
- /**
6469
- * Duplicate this row.
6470
- *
6471
- * @return {boolean}
6472
- */
6473
- duplicateHandler: function () {
6474
- this.builder.addHistoryEntry( 'row_duplicated' );
6475
-
6476
- var duplicateRow = this.model.clone( this.builder.model );
6477
-
6478
- this.builder.model.get('rows').add( duplicateRow, {
6479
- at: this.builder.model.get('rows').indexOf( this.model ) + 1
6480
- } );
6481
-
6482
- this.builder.model.refreshPanelsData();
6483
- },
6484
-
6485
- /**
6486
- * Copy the row to a localStorage
6487
- */
6488
- copyHandler: function(){
6489
- panels.helpers.clipboard.setModel( this.model );
6490
- },
6491
-
6492
- /**
6493
- * Create a new row and insert it
6494
- */
6495
- pasteHandler: function(){
6496
- var pastedModel = panels.helpers.clipboard.getModel( 'row-model' );
6497
-
6498
- if( ! _.isEmpty( pastedModel ) && pastedModel instanceof panels.model.row ) {
6499
- this.builder.addHistoryEntry( 'row_pasted' );
6500
- pastedModel.builder = this.builder.model;
6501
- this.builder.model.get('rows').add( pastedModel, {
6502
- at: this.builder.model.get('rows').indexOf( this.model ) + 1
6503
- } );
6504
- this.builder.model.refreshPanelsData();
6505
- }
6506
- },
6507
-
6508
- /**
6509
- * Handles deleting the row with a confirmation.
6510
- */
6511
- confirmedDeleteHandler: function ( e ) {
6512
- var $$ = $( e.target );
6513
-
6514
- // The user clicked on the dashicon
6515
- if ( $$.hasClass( 'dashicons' ) ) {
6516
- $$ = $$.parent();
6517
- }
6518
-
6519
- if ( $$.hasClass( 'so-confirmed' ) ) {
6520
- this.visualDestroyModel();
6521
- } else {
6522
- var originalText = $$.html();
6523
-
6524
- $$.addClass( 'so-confirmed' ).html(
6525
- '<span class="dashicons dashicons-yes"></span>' + panelsOptions.loc.dropdown_confirm
6526
- );
6527
-
6528
- setTimeout( function () {
6529
- $$.removeClass( 'so-confirmed' ).html( originalText );
6530
- }, 2500 );
6531
- }
6532
- },
6533
-
6534
- /**
6535
- * Handle displaying the settings dialog
6536
- */
6537
- editSettingsHandler: function () {
6538
- if ( ! this.builder.supports( 'editRow' ) ) {
6539
- return;
6540
- }
6541
- // Lets open up an instance of the settings dialog
6542
- if ( this.dialog === null ) {
6543
- // Create the dialog
6544
- this.dialog = new panels.dialog.row();
6545
- this.dialog.setBuilder( this.builder ).setRowModel( this.model );
6546
- this.dialog.rowView = this;
6547
- }
6548
-
6549
- this.dialog.openDialog();
6550
-
6551
- return this;
6552
- },
6553
-
6554
- /**
6555
- * Handle deleting this entire row.
6556
- */
6557
- deleteHandler: function () {
6558
- this.model.destroy();
6559
- return this;
6560
- },
6561
-
6562
- /**
6563
- * Change the row background color.
6564
- */
6565
- rowColorChangeHandler: function ( event ) {
6566
- this.$( '.so-row-color' ).removeClass( 'so-row-color-selected' );
6567
- var clickedColorElem = $( event.target );
6568
- var newColorLabel = clickedColorElem.data( 'color-label' );
6569
- var oldColorLabel = this.model.has( 'color_label' ) ? this.model.get( 'color_label' ) : 1;
6570
- clickedColorElem.addClass( 'so-row-color-selected' );
6571
- this.$el.removeClass( 'so-row-color-' + oldColorLabel );
6572
- this.$el.addClass( 'so-row-color-' + newColorLabel );
6573
- this.model.set( 'color_label', newColorLabel );
6574
- },
6575
-
6576
- /**
6577
- * Handle a new cell being added to this row view. For now we'll assume the new cell is always last
6578
- */
6579
- handleCellAdd: function ( cell ) {
6580
- var cellView = new panels.view.cell( {
6581
- model: cell
6582
- } );
6583
- cellView.row = this;
6584
- cellView.render();
6585
- cellView.$el.appendTo( this.$( '.so-cells' ) );
6586
- },
6587
-
6588
- /**
6589
- * Handle a cell being removed from this row view
6590
- */
6591
- handleCellRemove: function ( cell ) {
6592
- // Find the view that ties in to the cell we're removing
6593
- this.$( '.so-cells > .cell' ).each( function () {
6594
- var view = $( this ).data( 'view' );
6595
- if ( _.isUndefined( view ) ) {
6596
- return;
6597
- }
6598
-
6599
- if ( view.model.cid === cell.cid ) {
6600
- // Remove this view
6601
- view.remove();
6602
- }
6603
- } );
6604
- },
6605
-
6606
- /**
6607
- * Build up the contextual menu for a row
6608
- *
6609
- * @param e
6610
- * @param menu
6611
- */
6612
- buildContextualMenu: function ( e, menu ) {
6613
- var options = [];
6614
- for ( var i = 1; i < 5; i ++ ) {
6615
- options.push( {
6616
- title: i + ' ' + panelsOptions.loc.contextual.column
6617
- } );
6618
- }
6619
-
6620
- if( this.builder.supports( 'addRow' ) ) {
6621
- menu.addSection(
6622
- 'add-row',
6623
- {
6624
- sectionTitle: panelsOptions.loc.contextual.add_row,
6625
- search: false
6626
- },
6627
- options,
6628
- function ( c ) {
6629
- this.builder.addHistoryEntry( 'row_added' );
6630
-
6631
- var columns = Number( c ) + 1;
6632
- var weights = [];
6633
- for ( var i = 0; i < columns; i ++ ) {
6634
- weights.push( {weight: 100 / columns } );
6635
- }
6636
-
6637
- // Create the actual row
6638
- var newRow = new panels.model.row( {
6639
- collection: this.collection
6640
- } );
6641
-
6642
- var cells = new panels.collection.cells(weights);
6643
- cells.each(function (cell) {
6644
- cell.row = newRow;
6645
- });
6646
- newRow.setCells(cells);
6647
- newRow.builder = this.builder.model;
6648
-
6649
- this.builder.model.get('rows').add( newRow, {
6650
- at: this.builder.model.get('rows').indexOf( this.model ) + 1
6651
- } );
6652
-
6653
- this.builder.model.refreshPanelsData();
6654
- }.bind( this )
6655
- );
6656
- }
6657
-
6658
- var actions = {};
6659
-
6660
- if( this.builder.supports( 'editRow' ) ) {
6661
- actions.edit = { title: panelsOptions.loc.contextual.row_edit };
6662
- }
6663
-
6664
- // Copy and paste functions
6665
- if ( panels.helpers.clipboard.canCopyPaste() ) {
6666
- actions.copy = { title: panelsOptions.loc.contextual.row_copy };
6667
- if ( this.builder.supports( 'addRow' ) && panels.helpers.clipboard.isModel( 'row-model' ) ) {
6668
- actions.paste = { title: panelsOptions.loc.contextual.row_paste };
6669
- }
6670
- }
6671
-
6672
- if( this.builder.supports( 'addRow' ) ) {
6673
- actions.duplicate = { title: panelsOptions.loc.contextual.row_duplicate };
6674
- }
6675
-
6676
- if( this.builder.supports( 'deleteRow' ) ) {
6677
- actions.delete = { title: panelsOptions.loc.contextual.row_delete, confirm: true };
6678
- }
6679
-
6680
- if( ! _.isEmpty( actions ) ) {
6681
- menu.addSection(
6682
- 'row-actions',
6683
- {
6684
- sectionTitle: panelsOptions.loc.contextual.row_actions,
6685
- search: false,
6686
- },
6687
- actions,
6688
- function ( c ) {
6689
- switch ( c ) {
6690
- case 'edit':
6691
- this.editSettingsHandler();
6692
- break;
6693
- case 'copy':
6694
- this.copyHandler();
6695
- break;
6696
- case 'paste':
6697
- this.pasteHandler();
6698
- break;
6699
- case 'duplicate':
6700
- this.duplicateHandler();
6701
- break;
6702
- case 'delete':
6703
- this.visualDestroyModel();
6704
- break;
6705
- }
6706
- }.bind( this )
6707
- );
6708
- }
6709
- },
6710
- } );
6711
-
6712
- },{}],28:[function(require,module,exports){
6713
- var panels = window.panels, $ = jQuery;
6714
-
6715
- module.exports = Backbone.View.extend( {
6716
-
6717
- stylesLoaded: false,
6718
-
6719
- initialize: function () {
6720
-
6721
- },
6722
-
6723
- /**
6724
- * Render the visual styles object.
6725
- *
6726
- * @param stylesType
6727
- * @param postId
6728
- * @param args
6729
- */
6730
- render: function ( stylesType, postId, args ) {
6731
- if ( _.isUndefined( stylesType ) ) {
6732
- return;
6733
- }
6734
-
6735
- // Add in the default args
6736
- args = _.extend( {
6737
- builderType: '',
6738
- dialog: null
6739
- }, args );
6740
-
6741
- this.$el.addClass( 'so-visual-styles so-' + stylesType + '-styles so-panels-loading' );
6742
-
6743
- var postArgs = {
6744
- builderType: args.builderType
6745
- };
6746
-
6747
- if ( stylesType === 'cell') {
6748
- postArgs.index = args.index;
6749
- }
6750
-
6751
- // Load the form
6752
- $.post(
6753
- panelsOptions.ajaxurl,
6754
- {
6755
- action: 'so_panels_style_form',
6756
- type: stylesType,
6757
- style: this.model.get( 'style' ),
6758
- args: JSON.stringify( postArgs ),
6759
- postId: postId
6760
- },
6761
- null,
6762
- 'html'
6763
- ).done( function ( response ) {
6764
- this.$el.html( response );
6765
- this.setupFields();
6766
- this.stylesLoaded = true;
6767
- this.trigger( 'styles_loaded', !_.isEmpty( response ) );
6768
- if ( !_.isNull( args.dialog ) ) {
6769
- args.dialog.trigger( 'styles_loaded', !_.isEmpty( response ) );
6770
- }
6771
- }.bind( this ) )
6772
- .fail( function ( error ) {
6773
- var html;
6774
- if ( error && error.responseText ) {
6775
- html = error.responseText;
6776
- } else {
6777
- html = panelsOptions.forms.loadingFailed;
6778
- }
6779
-
6780
- this.$el.html( html );
6781
- }.bind( this ) )
6782
- .always( function () {
6783
- this.$el.removeClass( 'so-panels-loading' );
6784
- }.bind( this ) );
6785
-
6786
- return this;
6787
- },
6788
-
6789
- /**
6790
- * Attach the style view to the DOM.
6791
- *
6792
- * @param wrapper
6793
- */
6794
- attach: function ( wrapper ) {
6795
- wrapper.append( this.$el );
6796
- },
6797
-
6798
- /**
6799
- * Detach the styles view from the DOM
6800
- */
6801
- detach: function () {
6802
- this.$el.detach();
6803
- },
6804
-
6805
- /**
6806
- * Setup all the fields
6807
- */
6808
- setupFields: function () {
6809
-
6810
- // Set up the sections as collapsible
6811
- this.$( '.style-section-wrapper' ).each( function () {
6812
- var $s = $( this );
6813
-
6814
- $s.find( '.style-section-head' ).click( function ( e ) {
6815
- e.preventDefault();
6816
- $s.find( '.style-section-fields' ).slideToggle( 'fast' );
6817
- } );
6818
- } );
6819
-
6820
- // Set up the color fields
6821
- if ( ! _.isUndefined( $.fn.wpColorPicker ) ) {
6822
- if ( _.isObject( panelsOptions.wpColorPickerOptions.palettes ) && ! $.isArray( panelsOptions.wpColorPickerOptions.palettes ) ) {
6823
- panelsOptions.wpColorPickerOptions.palettes = $.map( panelsOptions.wpColorPickerOptions.palettes, function ( el ) {
6824
- return el;
6825
- } );
6826
- }
6827
- this.$( '.so-wp-color-field' ).wpColorPicker( panelsOptions.wpColorPickerOptions );
6828
- }
6829
-
6830
- // Set up the image select fields
6831
- this.$( '.style-field-image' ).each( function () {
6832
- var frame = null;
6833
- var $s = $( this );
6834
-
6835
- $s.find( '.so-image-selector' ).click( function ( e ) {
6836
- e.preventDefault();
6837
-
6838
- if ( frame === null ) {
6839
- // Create the media frame.
6840
- frame = wp.media( {
6841
- // Set the title of the modal.
6842
- title: 'choose',
6843
-
6844
- // Tell the modal to show only images.
6845
- library: {
6846
- type: 'image'
6847
- },
6848
-
6849
- // Customize the submit button.
6850
- button: {
6851
- // Set the text of the button.
6852
- text: 'Done',
6853
- close: true
6854
- }
6855
- } );
6856
-
6857
- frame.on( 'select', function () {
6858
- var attachment = frame.state().get( 'selection' ).first().attributes;
6859
-
6860
- var url = attachment.url;
6861
- if ( ! _.isUndefined( attachment.sizes ) ) {
6862
- try {
6863
- url = attachment.sizes.thumbnail.url;
6864
- }
6865
- catch ( e ) {
6866
- // We'll use the full image instead
6867
- url = attachment.sizes.full.url;
6868
- }
6869
- }
6870
- $s.find( '.current-image' ).css( 'background-image', 'url(' + url + ')' );
6871
-
6872
- // Store the ID
6873
- $s.find( '.so-image-selector > input' ).val( attachment.id );
6874
-
6875
- $s.find( '.remove-image' ).removeClass( 'hidden' );
6876
- } );
6877
- }
6878
-
6879
- frame.open();
6880
-
6881
- } );
6882
-
6883
- // Handle clicking on remove
6884
- $s.find( '.remove-image' ).click( function ( e ) {
6885
- e.preventDefault();
6886
- $s.find( '.current-image' ).css( 'background-image', 'none' );
6887
- $s.find( '.so-image-selector > input' ).val( '' );
6888
- $s.find( '.remove-image' ).addClass( 'hidden' );
6889
- } );
6890
- } );
6891
-
6892
- // Set up all the measurement fields
6893
- this.$( '.style-field-measurement' ).each( function () {
6894
- var $$ = $( this );
6895
-
6896
- var text = $$.find( 'input[type="text"]' );
6897
- var unit = $$.find( 'select' );
6898
- var hidden = $$.find( 'input[type="hidden"]' );
6899
-
6900
- text.focus( function(){
6901
- $(this).select();
6902
- } );
6903
-
6904
- /**
6905
- * Load value into the visible input fields.
6906
- * @param value
6907
- */
6908
- var loadValue = function( value ) {
6909
- if( value === '' ) {
6910
- return;
6911
- }
6912
-
6913
- var re = /(?:([0-9\.,\-]+)(.*))+/;
6914
- var valueList = hidden.val().split( ' ' );
6915
- var valueListValue = [];
6916
- for ( var i in valueList ) {
6917
- var match = re.exec( valueList[i] );
6918
- if ( ! _.isNull( match ) && ! _.isUndefined( match[1] ) && ! _.isUndefined( match[2] ) ) {
6919
- valueListValue.push( match[1] );
6920
- unit.val( match[2] );
6921
- }
6922
- }
6923
-
6924
- if( text.length === 1 ) {
6925
- // This is a single input text field
6926
- text.val( valueListValue.join( ' ' ) );
6927
- }
6928
- else {
6929
- // We're dealing with a multiple field
6930
- if( valueListValue.length === 1 ) {
6931
- valueListValue = [ valueListValue[0], valueListValue[0], valueListValue[0], valueListValue[0] ];
6932
- }
6933
- else if( valueListValue.length === 2 ) {
6934
- valueListValue = [ valueListValue[0], valueListValue[1], valueListValue[0], valueListValue[1] ];
6935
- }
6936
- else if( valueListValue.length === 3 ) {
6937
- valueListValue = [ valueListValue[0], valueListValue[1], valueListValue[2], valueListValue[1] ];
6938
- }
6939
-
6940
- // Store this in the visible fields
6941
- text.each( function( i, el ) {
6942
- $( el ).val( valueListValue[i] );
6943
- } );
6944
- }
6945
- };
6946
- loadValue( hidden.val() );
6947
-
6948
- /**
6949
- * Set value of the hidden field based on inputs
6950
- */
6951
- var setValue = function( e ){
6952
- var i;
6953
-
6954
- if( text.length === 1 ) {
6955
- // We're dealing with a single measurement
6956
- var fullString = text
6957
- .val()
6958
- .split( ' ' )
6959
- .filter( function ( value ) {
6960
- return value !== '';
6961
- } )
6962
- .map( function ( value ) {
6963
- return value + unit.val();
6964
- } )
6965
- .join( ' ' );
6966
- hidden.val( fullString );
6967
- }
6968
- else {
6969
- var target = $( e.target ),
6970
- valueList = [],
6971
- emptyIndex = [],
6972
- fullIndex = [];
6973
-
6974
- text.each( function( i, el ) {
6975
- var value = $( el ).val( ) !== '' ? parseFloat( $( el ).val( ) ) : null;
6976
- valueList.push( value );
6977
-
6978
- if( value === null ) {
6979
- emptyIndex.push( i );
6980
- }
6981
- else {
6982
- fullIndex.push( i );
6983
- }
6984
- } );
6985
-
6986
- if( emptyIndex.length === 3 && fullIndex[0] === text.index( target ) ) {
6987
- text.val( target.val() );
6988
- valueList = [ target.val(), target.val(), target.val(), target.val() ];
6989
- }
6990
-
6991
- if( JSON.stringify( valueList ) === JSON.stringify( [ null, null, null, null ] ) ) {
6992
- hidden.val('');
6993
- }
6994
- else {
6995
- hidden.val( valueList.map( function( k ){
6996
- return ( k === null ? 0 : k ) + unit.val();
6997
- } ).join( ' ' ) );
6998
- }
6999
- }
7000
- };
7001
-
7002
- // Set the value when ever anything changes
7003
- text.change( setValue );
7004
- unit.change( setValue );
7005
- } );
7006
- }
7007
-
7008
- } );
7009
-
7010
- },{}],29:[function(require,module,exports){
7011
- var panels = window.panels, $ = jQuery;
7012
-
7013
- module.exports = Backbone.View.extend( {
7014
- template: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-builder-widget' ).html() ) ),
7015
-
7016
- // The cell view that this widget belongs to
7017
- cell: null,
7018
-
7019
- // The edit dialog
7020
- dialog: null,
7021
-
7022
- events: {
7023
- 'click .widget-edit': 'editHandler',
7024
- 'click .title h4': 'editHandler',
7025
- 'click .actions .widget-duplicate': 'duplicateHandler',
7026
- 'click .actions .widget-delete': 'deleteHandler'
7027
- },
7028
-
7029
- /**
7030
- * Initialize the widget
7031
- */
7032
- initialize: function () {
7033
- this.listenTo(this.model, 'destroy', this.onModelDestroy);
7034
- this.listenTo(this.model, 'change:values', this.onModelChange);
7035
- this.listenTo(this.model, 'change:label', this.onLabelChange);
7036
- },
7037
-
7038
- /**
7039
- * Render the widget
7040
- */
7041
- render: function ( options ) {
7042
- options = _.extend( {'loadForm': false}, options );
7043
-
7044
- this.setElement( this.template( {
7045
- title: this.model.getWidgetField( 'title' ),
7046
- description: this.model.getTitle(),
7047
- widget_class: this.model.attributes.class
7048
- } ) );
7049
-
7050
- this.$el.data( 'view', this );
7051
-
7052
- // Remove any unsupported actions
7053
- if( ! this.cell.row.builder.supports( 'editWidget' ) || this.model.get( 'read_only' ) ) {
7054
- this.$( '.actions .widget-edit' ).remove();
7055
- this.$el.addClass('so-widget-no-edit');
7056
- }
7057
- if( ! this.cell.row.builder.supports( 'addWidget' ) ) {
7058
- this.$( '.actions .widget-duplicate' ).remove();
7059
- this.$el.addClass('so-widget-no-duplicate');
7060
- }
7061
- if( ! this.cell.row.builder.supports( 'deleteWidget' ) ) {
7062
- this.$( '.actions .widget-delete' ).remove();
7063
- this.$el.addClass('so-widget-no-delete');
7064
- }
7065
- if( ! this.cell.row.builder.supports( 'moveWidget' ) ) {
7066
- this.$el.addClass('so-widget-no-move');
7067
- }
7068
- if( !$.trim( this.$('.actions').html() ).length ) {
7069
- this.$( '.actions' ).remove();
7070
- }
7071
-
7072
- if( this.model.get( 'read_only' ) ) {
7073
- this.$el.addClass('so-widget-read-only');
7074
- }
7075
-
7076
- if ( _.size( this.model.get( 'values' ) ) === 0 || options.loadForm ) {
7077
- // If this widget doesn't have a value, create a form and save it
7078
- var dialog = this.getEditDialog();
7079
-
7080
- // Save the widget as soon as the form is loaded
7081
- dialog.once( 'form_loaded', dialog.saveWidget, dialog );
7082
-
7083
- // Setup the dialog to load the form
7084
- dialog.setupDialog();
7085
- }
7086
-
7087
- // Add the global builder listeners
7088
- this.listenTo(this.cell.row.builder, 'after_user_adds_widget', this.afterUserAddsWidgetHandler);
7089
-
7090
- return this;
7091
- },
7092
-
7093
- /**
7094
- * Display an animation that implies creation using a visual animation
7095
- */
7096
- visualCreate: function () {
7097
- this.$el.hide().fadeIn( 'fast' );
7098
- },
7099
-
7100
- /**
7101
- * Get the dialog view of the form that edits this widget
7102
- *
7103
- * @returns {null}
7104
- */
7105
- getEditDialog: function () {
7106
- if ( this.dialog === null ) {
7107
- this.dialog = new panels.dialog.widget( {
7108
- model: this.model
7109
- } );
7110
- this.dialog.setBuilder( this.cell.row.builder );
7111
-
7112
- // Store the widget view
7113
- this.dialog.widgetView = this;
7114
- }
7115
- return this.dialog;
7116
- },
7117
-
7118
- /**
7119
- * Handle clicking on edit widget.
7120
- */
7121
- editHandler: function () {
7122
- // Create a new dialog for editing this
7123
- if ( ! this.cell.row.builder.supports( 'editWidget' ) || this.model.get( 'read_only' ) ) {
7124
- return this;
7125
- }
7126
-
7127
- this.getEditDialog().openDialog();
7128
- return this;
7129
- },
7130
-
7131
- /**
7132
- * Handle clicking on duplicate.
7133
- *
7134
- * @returns {boolean}
7135
- */
7136
- duplicateHandler: function () {
7137
- // Add the history entry
7138
- this.cell.row.builder.addHistoryEntry( 'widget_duplicated' );
7139
-
7140
- // Create the new widget and connect it to the widget collection for the current row
7141
- var newWidget = this.model.clone( this.model.cell );
7142
-
7143
- this.cell.model.get('widgets').add( newWidget, {
7144
- // Add this after the existing model
7145
- at: this.model.collection.indexOf( this.model ) + 1
7146
- } );
7147
-
7148
- this.cell.row.builder.model.refreshPanelsData();
7149
- return this;
7150
- },
7151
-
7152
- /**
7153
- * Copy the row to a cookie based clipboard
7154
- */
7155
- copyHandler: function(){
7156
- panels.helpers.clipboard.setModel( this.model );
7157
- },
7158
-
7159
- /**
7160
- * Handle clicking on delete.
7161
- *
7162
- * @returns {boolean}
7163
- */
7164
- deleteHandler: function () {
7165
- this.visualDestroyModel();
7166
- return this;
7167
- },
7168
-
7169
- onModelChange: function () {
7170
- // Update the description when ever the model changes
7171
- this.$( '.description' ).html( this.model.getTitle() );
7172
- },
7173
-
7174
- onLabelChange: function( model ) {
7175
- this.$( '.title > h4' ).text( model.getWidgetField( 'title' ) );
7176
- },
7177
-
7178
- /**
7179
- * When the model is destroyed, fade it out
7180
- */
7181
- onModelDestroy: function () {
7182
- this.remove();
7183
- },
7184
-
7185
- /**
7186
- * Visually destroy a model
7187
- */
7188
- visualDestroyModel: function () {
7189
- // Add the history entry
7190
- this.cell.row.builder.addHistoryEntry( 'widget_deleted' );
7191
-
7192
- this.$el.fadeOut( 'fast', function () {
7193
- this.cell.row.resize();
7194
- this.model.destroy();
7195
- this.cell.row.builder.model.refreshPanelsData();
7196
- this.remove();
7197
- }.bind(this) );
7198
-
7199
- return this;
7200
- },
7201
-
7202
- /**
7203
- * Build up the contextual menu for a widget
7204
- *
7205
- * @param e
7206
- * @param menu
7207
- */
7208
- buildContextualMenu: function ( e, menu ) {
7209
- if( this.cell.row.builder.supports( 'addWidget' ) ) {
7210
- menu.addSection(
7211
- 'add-widget-below',
7212
- {
7213
- sectionTitle: panelsOptions.loc.contextual.add_widget_below,
7214
- searchPlaceholder: panelsOptions.loc.contextual.search_widgets,
7215
- defaultDisplay: panelsOptions.contextual.default_widgets
7216
- },
7217
- panelsOptions.widgets,
7218
- function ( c ) {
7219
- this.cell.row.builder.trigger('before_user_adds_widget');
7220
- this.cell.row.builder.addHistoryEntry( 'widget_added' );
7221
-
7222
- var widget = new panels.model.widget( {
7223
- class: c
7224
- } );
7225
- widget.cell = this.cell.model;
7226
-
7227
- // Insert the new widget below
7228
- this.cell.model.get('widgets').add( widget, {
7229
- // Add this after the existing model
7230
- at: this.model.collection.indexOf( this.model ) + 1
7231
- } );
7232
-
7233
- this.cell.row.builder.model.refreshPanelsData();
7234
-
7235
- this.cell.row.builder.trigger('after_user_adds_widget', widget);
7236
- }.bind( this )
7237
- );
7238
- }
7239
-
7240
- var actions = {};
7241
-
7242
- if( this.cell.row.builder.supports( 'editWidget' ) && ! this.model.get( 'read_only' ) ) {
7243
- actions.edit = { title: panelsOptions.loc.contextual.widget_edit };
7244
- }
7245
-
7246
- // Copy and paste functions
7247
- if ( panels.helpers.clipboard.canCopyPaste() ) {
7248
- actions.copy = {title: panelsOptions.loc.contextual.widget_copy};
7249
- }
7250
-
7251
- if( this.cell.row.builder.supports( 'addWidget' ) ) {
7252
- actions.duplicate = { title: panelsOptions.loc.contextual.widget_duplicate };
7253
- }
7254
-
7255
- if( this.cell.row.builder.supports( 'deleteWidget' ) ) {
7256
- actions.delete = { title: panelsOptions.loc.contextual.widget_delete, confirm: true };
7257
- }
7258
-
7259
- if( ! _.isEmpty( actions ) ) {
7260
- menu.addSection(
7261
- 'widget-actions',
7262
- {
7263
- sectionTitle: panelsOptions.loc.contextual.widget_actions,
7264
- search: false,
7265
- },
7266
- actions,
7267
- function ( c ) {
7268
- switch ( c ) {
7269
- case 'edit':
7270
- this.editHandler();
7271
- break;
7272
- case 'copy':
7273
- this.copyHandler();
7274
- break;
7275
- case 'duplicate':
7276
- this.duplicateHandler();
7277
- break;
7278
- case 'delete':
7279
- this.visualDestroyModel();
7280
- break;
7281
- }
7282
- }.bind( this )
7283
- );
7284
- }
7285
-
7286
- // Lets also add the contextual menu for the entire row
7287
- this.cell.buildContextualMenu( e, menu );
7288
- },
7289
-
7290
- /**
7291
- * Handler for any action after the user adds a new widget.
7292
- * @param widget
7293
- */
7294
- afterUserAddsWidgetHandler: function( widget ) {
7295
- if( this.model === widget && panelsOptions.instant_open ) {
7296
- setTimeout(this.editHandler.bind(this), 350);
7297
- }
7298
- }
7299
-
7300
- } );
7301
-
7302
  },{}],30:[function(require,module,exports){
7303
  var $ = jQuery;
7304
 
@@ -7405,47 +7407,47 @@ var mediaWidget = {
7405
  module.exports = mediaWidget;
7406
 
7407
  },{}],33:[function(require,module,exports){
7408
- var $ = jQuery;
7409
-
7410
- var textWidget = {
7411
- addWidget: function( idBase, widgetContainer, widgetId ) {
7412
- var component = wp.textWidgets;
7413
-
7414
- var options = {};
7415
- var visualField = widgetContainer.find( '.visual' );
7416
- // 'visual' field and syncContainer were introduced together in 4.8.1
7417
- if ( visualField.length > 0 ) {
7418
- // If 'visual' field has no value it's a legacy text widget.
7419
- if ( ! visualField.val() ) {
7420
- return null;
7421
- }
7422
-
7423
- var fieldContainer = $( '<div></div>' );
7424
- var syncContainer = widgetContainer.find( '.widget-content:first' );
7425
- syncContainer.before( fieldContainer );
7426
-
7427
- options = {
7428
- el: fieldContainer,
7429
- syncContainer: syncContainer,
7430
- };
7431
- } else {
7432
- options = { el: widgetContainer };
7433
- }
7434
-
7435
- var widgetControl = new component.TextWidgetControl( options );
7436
- var wpEditor = wp.oldEditor ? wp.oldEditor : wp.editor;
7437
- if ( wpEditor && wpEditor.hasOwnProperty( 'autop' ) ) {
7438
- wp.editor.autop = wpEditor.autop;
7439
- wp.editor.removep = wpEditor.removep;
7440
- wp.editor.initialize = wpEditor.initialize
7441
- }
7442
-
7443
- widgetControl.initializeEditor();
7444
-
7445
- return widgetControl;
7446
- }
7447
- };
7448
-
7449
- module.exports = textWidget;
7450
 
7451
  },{}]},{},[16]);
383
  } );
384
 
385
  },{}],7:[function(require,module,exports){
386
+ var panels = window.panels, $ = jQuery;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
387
 
388
+ module.exports = panels.view.dialog.extend( {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
389
 
390
+ directoryTemplate: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-directory-items' ).html() ) ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
391
 
392
+ builder: null,
393
+ dialogClass: 'so-panels-dialog-prebuilt-layouts',
394
+ dialogIcon: 'layouts',
395
+
396
+ layoutCache: {},
397
+ currentTab: false,
398
+ directoryPage: 1,
399
+
400
+ events: {
401
+ 'click .so-close': 'closeDialog',
402
+ 'click .so-sidebar-tabs li a': 'tabClickHandler',
403
+ 'click .so-content .layout': 'layoutClickHandler',
404
+ 'keyup .so-sidebar-search': 'searchHandler',
405
+
406
+ // The directory items
407
+ 'click .so-screenshot, .so-title': 'directoryItemClickHandler'
408
+ },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
409
 
 
 
410
  /**
411
+ * Initialize the prebuilt dialog.
 
412
  */
413
+ initializeDialog: function () {
414
+ var thisView = this;
415
+
416
+ this.on( 'open_dialog', function () {
417
+ thisView.$( '.so-sidebar-tabs li a' ).first().click();
418
+ thisView.$( '.so-status' ).removeClass( 'so-panels-loading' );
419
+ } );
420
+
421
+ this.on( 'button_click', this.toolbarButtonClick, this );
422
  },
423
 
424
  /**
425
+ * Render the prebuilt layouts dialog
426
  */
427
+ render: function () {
428
+ this.renderDialog( this.parseDialogContent( $( '#siteorigin-panels-dialog-prebuilt' ).html(), {} ) );
 
 
 
 
 
 
 
 
 
429
 
430
+ this.initToolbar();
 
 
431
  },
432
 
433
  /**
434
+ *
435
+ * @param e
436
+ * @return {boolean}
437
  */
438
+ tabClickHandler: function ( e ) {
439
+ e.preventDefault();
440
+ // Reset selected item state when changing tabs
441
+ this.selectedLayoutItem = null;
442
+ this.uploadedLayout = null;
443
+ this.updateButtonState( false );
444
 
445
+ this.$( '.so-sidebar-tabs li' ).removeClass( 'tab-active' );
446
+
447
+ var $$ = $( e.target );
448
+ var tab = $$.attr( 'href' ).split( '#' )[1];
449
+ $$.parent().addClass( 'tab-active' );
450
+
451
+ var thisView = this;
452
+
453
+ // Empty everything
454
+ this.$( '.so-content' ).empty();
455
+
456
+ thisView.currentTab = tab;
457
+ if ( tab == 'import' ) {
458
+ this.displayImportExport();
459
+ } else {
460
+ this.displayLayoutDirectory( '', 1, tab );
461
  }
462
 
463
+ thisView.$( '.so-sidebar-search' ).val( '' );
464
  },
465
 
466
  /**
467
+ * Display and setup the import/export form
468
  */
469
+ displayImportExport: function () {
470
+ var c = this.$( '.so-content' ).empty().removeClass( 'so-panels-loading' );
471
+ c.html( $( '#siteorigin-panels-dialog-prebuilt-importexport' ).html() );
 
472
 
473
+ var thisView = this;
474
+ var uploadUi = thisView.$( '.import-upload-ui' );
475
+
476
+ // Create the uploader
477
+ var uploader = new plupload.Uploader( {
478
+ runtimes: 'html5,silverlight,flash,html4',
479
+
480
+ browse_button: uploadUi.find( '.file-browse-button' ).get( 0 ),
481
+ container: uploadUi.get( 0 ),
482
+ drop_element: uploadUi.find( '.drag-upload-area' ).get( 0 ),
483
+
484
+ file_data_name: 'panels_import_data',
485
+ multiple_queues: false,
486
+ max_file_size: panelsOptions.plupload.max_file_size,
487
+ url: panelsOptions.plupload.url,
488
+ flash_swf_url: panelsOptions.plupload.flash_swf_url,
489
+ silverlight_xap_url: panelsOptions.plupload.silverlight_xap_url,
490
+ filters: [
491
+ {title: panelsOptions.plupload.filter_title, extensions: 'json'}
492
+ ],
493
+
494
+ multipart_params: {
495
+ action: 'so_panels_import_layout'
496
+ },
497
+
498
+ init: {
499
+ PostInit: function ( uploader ) {
500
+ if ( uploader.features.dragdrop ) {
501
+ uploadUi.addClass( 'has-drag-drop' );
502
+ }
503
+ uploadUi.find( '.progress-precent' ).css( 'width', '0%' );
504
+ },
505
+ FilesAdded: function ( uploader ) {
506
+ uploadUi.find( '.file-browse-button' ).blur();
507
+ uploadUi.find( '.drag-upload-area' ).removeClass( 'file-dragover' );
508
+ uploadUi.find( '.progress-bar' ).fadeIn( 'fast' );
509
+ thisView.$( '.js-so-selected-file' ).text( panelsOptions.loc.prebuilt_loading );
510
+ uploader.start();
511
+ },
512
+ UploadProgress: function ( uploader, file ) {
513
+ uploadUi.find( '.progress-precent' ).css( 'width', file.percent + '%' );
514
+ },
515
+ FileUploaded: function ( uploader, file, response ) {
516
+ var layout = JSON.parse( response.response );
517
+ if ( ! _.isUndefined( layout.widgets ) ) {
518
+
519
+ thisView.uploadedLayout = layout;
520
+ uploadUi.find( '.progress-bar' ).hide();
521
+ thisView.$( '.js-so-selected-file' ).text(
522
+ panelsOptions.loc.ready_to_insert.replace( '%s', file.name )
523
+ );
524
+ thisView.updateButtonState( true );
525
+ } else {
526
+ alert( panelsOptions.plupload.error_message );
527
+ }
528
+ },
529
+ Error: function () {
530
+ alert( panelsOptions.plupload.error_message );
531
+ }
532
  }
533
+ } );
534
+ uploader.init();
535
+
536
+ if ( /Edge\/\d./i.test(navigator.userAgent) ){
537
+ // A very dirty fix for a Microsoft Edge issue.
538
+ // TODO find a more elegant fix if Edge gains market share
539
+ setTimeout( function(){
540
+ uploader.refresh();
541
+ }, 250 );
542
  }
543
 
544
+ // This is
545
+ uploadUi.find( '.drag-upload-area' )
546
+ .on( 'dragover', function () {
547
+ $( this ).addClass( 'file-dragover' );
548
+ } )
549
+ .on( 'dragleave', function () {
550
+ $( this ).removeClass( 'file-dragover' );
551
+ } );
552
+
553
+ // Handle exporting the file
554
+ c.find( '.so-export' ).submit( function ( e ) {
555
+ var $$ = $( this );
556
+ var panelsData = thisView.builder.model.getPanelsData();
557
+ var postName = $('input[name="post_title"]').val();
558
+ if ( ! postName ) {
559
+ postName = $('input[name="post_ID"]').val();
560
+ }
561
+ panelsData.name = postName;
562
+ $$.find( 'input[name="panels_export_data"]' ).val( JSON.stringify( panelsData ) );
563
+ } );
564
+
565
  },
 
566
 
 
 
567
  /**
568
+ * Display the layout directory tab.
569
+ *
570
+ * @param query
571
  */
572
+ displayLayoutDirectory: function ( search, page, type ) {
573
+ var thisView = this;
574
+ var c = this.$( '.so-content' ).empty().addClass( 'so-panels-loading' );
575
+
576
+ if ( search === undefined ) {
577
+ search = '';
578
+ }
579
+ if ( page === undefined ) {
580
+ page = 1;
581
+ }
582
+ if ( type === undefined ) {
583
+ type = 'directory-siteorigin';
584
  }
585
 
586
+ if ( type.match('^directory-') && ! panelsOptions.directory_enabled ) {
587
+ // Display the button to enable the prebuilt layout
588
+ c.removeClass( 'so-panels-loading' ).html( $( '#siteorigin-panels-directory-enable' ).html() );
589
+ c.find( '.so-panels-enable-directory' ).click( function ( e ) {
590
+ e.preventDefault();
591
+ // Sent the query to enable the directory, then enable the directory
592
+ $.get(
593
+ panelsOptions.ajaxurl,
594
+ {action: 'so_panels_directory_enable'},
595
+ function () {
596
 
597
+ }
598
+ );
 
 
 
599
 
600
+ // Enable the layout directory
601
+ panelsOptions.directory_enabled = true;
602
+ c.addClass( 'so-panels-loading' );
603
+ thisView.displayLayoutDirectory( search, page, type );
604
+ } );
605
+ return;
606
  }
607
+
608
+ // Get all the items for the current query
609
+ $.get(
610
+ panelsOptions.ajaxurl,
611
+ {
612
+ action: 'so_panels_layouts_query',
613
+ search: search,
614
+ page: page,
615
+ type: type,
616
+ },
617
+ function ( data ) {
618
+ // Skip this if we're no longer viewing the layout directory
619
+ if ( thisView.currentTab !== type ) {
620
+ return;
621
+ }
622
+
623
+ // Add the directory items
624
+ c.removeClass( 'so-panels-loading' ).html( thisView.directoryTemplate( data ) );
625
+
626
+ // Lets setup the next and previous buttons
627
+ var prev = c.find( '.so-previous' ), next = c.find( '.so-next' );
628
+
629
+ if ( page <= 1 ) {
630
+ prev.addClass( 'button-disabled' );
631
+ } else {
632
+ prev.click( function ( e ) {
633
+ e.preventDefault();
634
+ thisView.displayLayoutDirectory( search, page - 1, thisView.currentTab );
635
+ } );
636
+ }
637
+
638
+ if ( page === data.max_num_pages || data.max_num_pages === 0 ) {
639
+ next.addClass( 'button-disabled' );
640
+ } else {
641
+ next.click( function ( e ) {
642
+ e.preventDefault();
643
+ thisView.displayLayoutDirectory( search, page + 1, thisView.currentTab );
644
+ } );
645
+ }
646
+
647
+ // Handle nice preloading of the screenshots
648
+ c.find( '.so-screenshot' ).each( function () {
649
+ var $$ = $( this ), $a = $$.find( '.so-screenshot-wrapper' );
650
+ $a.css( 'height', ( $a.width() / 4 * 3 ) + 'px' ).addClass( 'so-loading' );
651
+
652
+ if ( $$.data( 'src' ) !== '' ) {
653
+ // Set the initial height
654
+ var $img = $( '<img/>' ).attr( 'src', $$.data( 'src' ) ).load( function () {
655
+ $a.removeClass( 'so-loading' ).css( 'height', 'auto' );
656
+ $img.appendTo( $a ).hide().fadeIn( 'fast' );
657
+ } );
658
+ } else {
659
+ $( '<img/>' ).attr( 'src', panelsOptions.prebuiltDefaultScreenshot ).appendTo( $a ).hide().fadeIn( 'fast' );
660
+ }
661
+
662
+ } );
663
+
664
+ // Set the title
665
+ c.find( '.so-directory-browse' ).html( data.title );
666
+ },
667
+ 'json'
668
+ );
669
  },
670
 
671
  /**
672
+ * Set the selected state for the clicked layout directory item and remove previously selected item.
673
+ * Enable the toolbar buttons.
674
  */
675
+ directoryItemClickHandler: function ( e ) {
676
+ var $directoryItem = this.$( e.target ).closest( '.so-directory-item' );
677
+ this.$( '.so-directory-items' ).find( '.selected' ).removeClass( 'selected' );
678
+ $directoryItem.addClass( 'selected' );
679
+ this.selectedLayoutItem = {lid: $directoryItem.data( 'layout-id' ), type: $directoryItem.data( 'layout-type' )};
680
+ this.updateButtonState( true );
681
 
682
+ },
 
 
 
683
 
684
+ /**
685
+ * Load a particular layout into the builder.
686
+ *
687
+ * @param id
688
+ */
689
+ toolbarButtonClick: function ( $button ) {
690
+ if ( ! this.canAddLayout() ) {
691
+ return false;
692
+ }
693
+ var position = $button.data( 'value' );
694
+ if ( _.isUndefined( position ) ) {
695
+ return false;
696
+ }
697
+ this.updateButtonState( false );
698
+
699
+ if ( $button.hasClass( 'so-needs-confirm' ) && ! $button.hasClass( 'so-confirmed' ) ) {
700
+ this.updateButtonState( true );
701
+ if ( $button.hasClass( 'so-confirming' ) ) {
702
+ return;
703
  }
704
+ $button.addClass( 'so-confirming' );
705
+ var originalText = $button.html();
706
+ $button.html( '<span class="dashicons dashicons-yes"></span>' + $button.data( 'confirm' ) );
707
+ setTimeout( function () {
708
+ $button.removeClass( 'so-confirmed' ).html( originalText );
709
+ }, 2500 );
710
+ setTimeout( function () {
711
+ $button.removeClass( 'so-confirming' );
712
+ $button.addClass( 'so-confirmed' );
713
+ }, 200 );
714
+ return false;
715
+ }
716
+ this.addingLayout = true;
717
+ if ( this.currentTab === 'import' ) {
718
+ this.addLayoutToBuilder( this.uploadedLayout, position );
719
+ } else {
720
+ this.loadSelectedLayout().then( function ( layout ) {
721
+ this.addLayoutToBuilder( layout, position );
722
+ }.bind( this ) );
723
  }
724
  },
 
725
 
726
+ canAddLayout: function () {
727
+ return (
728
+ this.selectedLayoutItem || this.uploadedLayout
729
+ ) && ! this.addingLayout;
730
+ },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
731
 
732
+ /**
733
+ * Load the layout according to selectedLayoutItem.
734
+ */
735
+ loadSelectedLayout: function () {
736
+ this.setStatusMessage( panelsOptions.loc.prebuilt_loading, true );
737
+
738
+ var args = _.extend( this.selectedLayoutItem, {action: 'so_panels_get_layout'} );
739
+ var deferredLayout = new $.Deferred();
740
+
741
+ $.get(
742
+ panelsOptions.ajaxurl,
743
+ args,
744
+ function ( layout ) {
745
+ var msg = '';
746
+ if ( ! layout.success ) {
747
+ msg = layout.data.message;
748
+ deferredLayout.reject( layout.data );
749
+ } else {
750
+ deferredLayout.resolve( layout.data );
751
+ }
752
+ this.setStatusMessage( msg, false, ! layout.success );
753
+ this.updateButtonState( true );
754
+ }.bind( this )
755
+ );
756
+ return deferredLayout.promise();
757
+ },
758
 
759
+ /**
760
+ * Handle an update to the search
761
+ */
762
+ searchHandler: function ( e ) {
763
+ if ( e.keyCode === 13 ) {
764
+ this.displayLayoutDirectory( $( e.currentTarget ).val(), 1, this.currentTab );
765
  }
 
 
 
 
 
 
766
  },
767
 
768
+ /**
769
+ * Attempt to set the 'Insert' button's state according to the `enabled` argument, also checking whether the
770
+ * requirements for inserting a layout have valid values.
771
+ */
772
+ updateButtonState: function ( enabled ) {
773
+ enabled = enabled && (
774
+ this.selectedLayoutItem || this.uploadedLayout
775
+ );
776
+ var $button = this.$( '.so-import-layout' );
777
+ $button.prop( "disabled", ! enabled );
778
+ if ( enabled ) {
779
+ $button.removeClass( 'disabled' );
780
+ } else {
781
+ $button.addClass( 'disabled' );
782
  }
 
 
 
 
783
  },
784
 
785
+ addLayoutToBuilder: function ( layout, position ) {
786
+ this.builder.addHistoryEntry( 'prebuilt_loaded' );
787
+ this.builder.model.loadPanelsData( layout, position );
788
+ this.addingLayout = false;
789
+ this.closeDialog();
790
+ }
791
+ } );
 
792
 
793
+ },{}],8:[function(require,module,exports){
794
+ var panels = window.panels, $ = jQuery;
795
 
796
+ module.exports = panels.view.dialog.extend({
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
797
 
798
+ cellPreviewTemplate: _.template( panels.helpers.utils.processTemplate( $('#siteorigin-panels-dialog-row-cell-preview').html() ) ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
799
 
800
+ editableLabel: true,
 
 
 
 
 
 
801
 
802
+ events: {
803
+ 'click .so-close': 'closeDialog',
804
 
805
+ // Toolbar buttons
806
+ 'click .so-toolbar .so-save': 'saveHandler',
807
+ 'click .so-toolbar .so-insert': 'insertHandler',
808
+ 'click .so-toolbar .so-delete': 'deleteHandler',
809
+ 'click .so-toolbar .so-duplicate': 'duplicateHandler',
 
 
810
 
811
+ // Changing the row
812
+ 'change .row-set-form > *': 'setCellsFromForm',
813
+ 'click .row-set-form button.set-row': 'setCellsFromForm',
814
  },
815
 
816
+ rowView: null,
817
+ dialogIcon: 'add-row',
818
+ dialogClass: 'so-panels-dialog-row-edit',
819
+ styleType: 'row',
820
+
821
+ dialogType: 'edit',
822
+
823
  /**
824
+ * The current settings, not yet saved to the model
 
 
 
 
825
  */
826
+ row: {
827
+ // This will be a clone of cells collection.
828
+ cells: null,
829
+ // The style settings of the row
830
+ style: {}
831
+ },
832
 
833
+ cellStylesCache: [],
 
 
 
834
 
835
+ initializeDialog: function () {
836
+ this.on('open_dialog', function () {
837
+ if (!_.isUndefined(this.model) && !_.isEmpty(this.model.get('cells'))) {
838
+ this.setRowModel(this.model);
839
+ } else {
840
+ this.setRowModel(null);
841
+ }
842
 
843
+ this.regenerateRowPreview();
844
+ this.renderStyles();
845
+ this.openSelectedCellStyles();
846
+ }, this);
847
 
848
+ // This is the default row layout
849
+ this.row = {
850
+ cells: new panels.collection.cells([{weight: 0.5}, {weight: 0.5}]),
851
+ style: {}
852
+ };
853
+
854
+ // Refresh panels data after both dialog form components are loaded
855
+ this.dialogFormsLoaded = 0;
856
+ var thisView = this;
857
+ this.on('form_loaded styles_loaded', function () {
858
+ this.dialogFormsLoaded++;
859
+ if (this.dialogFormsLoaded === 2) {
860
+ thisView.updateModel({
861
+ refreshArgs: {
862
+ silent: true
863
+ }
864
+ });
865
+ }
866
+ });
867
+
868
+ this.on('close_dialog', this.closeHandler);
869
+
870
+ this.on( 'edit_label', function ( text ) {
871
+ // If text is set to default values, just clear it.
872
+ if ( text === panelsOptions.loc.row.add || text === panelsOptions.loc.row.edit ) {
873
+ text = '';
874
+ }
875
+ this.model.set( 'label', text );
876
+ if ( _.isEmpty( text ) ) {
877
+ var title = this.dialogType === 'create' ? panelsOptions.loc.row.add : panelsOptions.loc.row.edit;
878
+ this.$( '.so-title').text( title );
879
+ }
880
+ }.bind( this ) );
881
+ },
882
 
883
  /**
 
884
  *
885
+ * @param dialogType Either "edit" or "create"
 
 
886
  */
887
+ setRowDialogType: function (dialogType) {
888
+ this.dialogType = dialogType;
889
+ },
 
 
 
 
890
 
891
+ /**
892
+ * Render the new row dialog
893
+ */
894
+ render: function () {
895
+ var title = this.dialogType === 'create' ? panelsOptions.loc.row.add : panelsOptions.loc.row.edit;
896
+ this.renderDialog( this.parseDialogContent( $( '#siteorigin-panels-dialog-row' ).html(), {
897
+ title: title,
898
+ dialogType: this.dialogType
899
+ } ) );
900
 
901
+ var titleElt = this.$( '.so-title' );
 
902
 
903
+ if ( this.model.has( 'label' ) && ! _.isEmpty( this.model.get( 'label' ) ) ) {
904
+ titleElt.text( this.model.get( 'label' ) );
905
+ }
906
+ this.$( '.so-edit-title' ).val( titleElt.text() );
907
 
908
+ if (!this.builder.supports('addRow')) {
909
+ this.$('.so-buttons .so-duplicate').remove();
910
+ }
911
+ if (!this.builder.supports('deleteRow')) {
912
+ this.$('.so-buttons .so-delete').remove();
913
+ }
914
+
915
+ if (!_.isUndefined(this.model)) {
916
+ // Set the initial value of the
917
+ this.$( 'input[name="cells"].so-row-field' ).val( this.model.get( 'cells' ).length );
918
+ if ( this.model.has( 'ratio' ) ) {
919
+ this.$( 'select[name="ratio"].so-row-field' ).val( this.model.get( 'ratio' ) );
920
  }
921
+ if ( this.model.has( 'ratio_direction' ) ) {
922
+ this.$( 'select[name="ratio_direction"].so-row-field' ).val( this.model.get( 'ratio_direction' ) );
923
+ }
924
+ }
925
 
926
+ this.$('input.so-row-field').keyup(function () {
927
+ $(this).trigger('change');
928
+ });
 
 
 
929
 
930
+ return this;
931
+ },
932
+
933
+ renderStyles: function () {
934
+ if ( this.styles ) {
935
+ this.styles.off( 'styles_loaded' );
936
+ this.styles.remove();
937
+ }
938
+
939
+ // Now we need to attach the style window
940
+ this.styles = new panels.view.styles();
941
+ this.styles.model = this.model;
942
+ this.styles.render('row', this.builder.config.postId, {
943
+ builderType: this.builder.config.builderType,
944
+ dialog: this
945
+ });
946
+
947
+ var $rightSidebar = this.$('.so-sidebar.so-right-sidebar');
948
+ this.styles.attach( $rightSidebar );
949
+
950
+ // Handle the loading class
951
+ this.styles.on('styles_loaded', function (hasStyles) {
952
+ if ( ! hasStyles ) {
953
+ // If we don't have styles remove the view.
954
+ this.styles.remove();
955
+
956
+ // If the sidebar is empty, hide it.
957
+ if ( $rightSidebar.children().length === 0 ) {
958
+ $rightSidebar.closest('.so-panels-dialog').removeClass('so-panels-dialog-has-right-sidebar');
959
+ $rightSidebar.hide();
960
+ }
961
  }
962
+ }, this);
963
+ },
964
 
965
+ /**
966
+ * Set the row model we'll be using for this dialog.
967
+ *
968
+ * @param model
969
+ */
970
+ setRowModel: function (model) {
971
+ this.model = model;
972
 
973
+ if (_.isEmpty(this.model)) {
974
+ return this;
975
+ }
976
 
977
+ // Set the rows to be a copy of the model
978
+ this.row = {
979
+ cells: this.model.get('cells').clone(),
980
+ style: {},
981
+ ratio: this.model.get('ratio'),
982
+ ratio_direction: this.model.get('ratio_direction'),
983
+ };
984
 
985
+ // Set the initial value of the cell field.
986
+ this.$( 'input[name="cells"].so-row-field' ).val( this.model.get( 'cells' ).length );
987
+ if ( this.model.has( 'ratio' ) ) {
988
+ this.$( 'select[name="ratio"].so-row-field' ).val( this.model.get( 'ratio' ) );
989
+ }
990
+ if ( this.model.has( 'ratio_direction' ) ) {
991
+ this.$( 'select[name="ratio_direction"].so-row-field' ).val( this.model.get( 'ratio_direction' ) );
992
+ }
993
 
994
+ this.clearCellStylesCache();
 
 
995
 
996
+ return this;
997
+ },
998
+
999
+ /**
1000
+ * Regenerate the row preview and resizing interface.
1001
+ */
1002
+ regenerateRowPreview: function () {
1003
+ var thisDialog = this;
1004
+ var rowPreview = this.$('.row-preview');
1005
 
1006
+ // If no selected cell, select the first cell.
1007
+ var selectedIndex = this.getSelectedCellIndex();
1008
 
1009
+ rowPreview.empty();
1010
+
1011
+ var timeout;
1012
+
1013
+ // Represent the cells
1014
+ this.row.cells.each(function (cellModel, i) {
1015
+ var newCell = $(this.cellPreviewTemplate({weight: cellModel.get('weight')}));
1016
+ rowPreview.append(newCell);
1017
+
1018
+ if(i == selectedIndex) {
1019
+ newCell.find('.preview-cell-in').addClass('cell-selected');
1020
  }
1021
 
1022
+ var prevCell = newCell.prev();
1023
+ var handle;
1024
+
1025
+ if (prevCell.length) {
1026
+ handle = $('<div class="resize-handle"></div>');
1027
+ handle
1028
+ .appendTo(newCell)
1029
+ .dblclick(function () {
1030
+ var prevCellModel = thisDialog.row.cells.at(i - 1);
1031
+ var t = cellModel.get('weight') + prevCellModel.get('weight');
1032
+ cellModel.set('weight', t / 2);
1033
+ prevCellModel.set('weight', t / 2);
1034
+ thisDialog.scaleRowWidths();
1035
+ });
1036
+
1037
+ handle.draggable({
1038
+ axis: 'x',
1039
+ containment: rowPreview,
1040
+ start: function (e, ui) {
1041
+
1042
+ // Create the clone for the current cell
1043
+ var newCellClone = newCell.clone().appendTo(ui.helper).css({
1044
+ position: 'absolute',
1045
+ top: '0',
1046
+ width: newCell.outerWidth(),
1047
+ left: 6,
1048
+ height: newCell.outerHeight()
1049
+ });
1050
+ newCellClone.find('.resize-handle').remove();
1051
+
1052
+ // Create the clone for the previous cell
1053
+ var prevCellClone = prevCell.clone().appendTo(ui.helper).css({
1054
+ position: 'absolute',
1055
+ top: '0',
1056
+ width: prevCell.outerWidth(),
1057
+ right: 6,
1058
+ height: prevCell.outerHeight()
1059
+ });
1060
+ prevCellClone.find('.resize-handle').remove();
1061
+
1062
+ $(this).data({
1063
+ 'newCellClone': newCellClone,
1064
+ 'prevCellClone': prevCellClone
1065
+ });
1066
+
1067
+ // Hide the
1068
+ newCell.find('> .preview-cell-in').css('visibility', 'hidden');
1069
+ prevCell.find('> .preview-cell-in').css('visibility', 'hidden');
1070
+ },
1071
+ drag: function (e, ui) {
1072
+ // Calculate the new cell and previous cell widths as a percent
1073
+ var cellWeight = thisDialog.row.cells.at(i).get('weight');
1074
+ var prevCellWeight = thisDialog.row.cells.at(i - 1).get('weight');
1075
+ var ncw = cellWeight - (
1076
+ (
1077
+ ui.position.left + 6
1078
+ ) / rowPreview.width()
1079
+ );
1080
+ var pcw = prevCellWeight + (
1081
+ (
1082
+ ui.position.left + 6
1083
+ ) / rowPreview.width()
1084
+ );
1085
+
1086
+ var helperLeft = ui.helper.offset().left - rowPreview.offset().left - 6;
1087
+
1088
+ $(this).data('newCellClone').css('width', rowPreview.width() * ncw)
1089
+ .find('.preview-cell-weight').html(Math.round(ncw * 1000) / 10);
1090
+
1091
+ $(this).data('prevCellClone').css('width', rowPreview.width() * pcw)
1092
+ .find('.preview-cell-weight').html(Math.round(pcw * 1000) / 10);
1093
+ },
1094
+ stop: function (e, ui) {
1095
+ // Remove the clones
1096
+ $(this).data('newCellClone').remove();
1097
+ $(this).data('prevCellClone').remove();
1098
+
1099
+ // Reshow the main cells
1100
+ newCell.find('.preview-cell-in').css('visibility', 'visible');
1101
+ prevCell.find('.preview-cell-in').css('visibility', 'visible');
1102
+
1103
+ // Calculate the new cell weights
1104
+ var offset = ui.position.left + 6;
1105
+ var percent = offset / rowPreview.width();
1106
+
1107
+ // Ignore this if any of the cells are below 2% in width.
1108
+ var cellModel = thisDialog.row.cells.at(i);
1109
+ var prevCellModel = thisDialog.row.cells.at(i - 1);
1110
+ if (cellModel.get('weight') - percent > 0.02 && prevCellModel.get('weight') + percent > 0.02) {
1111
+ cellModel.set('weight', cellModel.get('weight') - percent);
1112
+ prevCellModel.set('weight', prevCellModel.get('weight') + percent);
1113
+ }
1114
 
1115
+ thisDialog.scaleRowWidths();
1116
+ ui.helper.css('left', -6);
1117
+ }
1118
+ });
1119
+ }
1120
 
1121
+ newCell.click(function (event) {
 
 
 
1122
 
1123
+ if ( ! ( $(event.target).is('.preview-cell') || $(event.target).is('.preview-cell-in') ) ) {
1124
+ return;
1125
  }
1126
 
1127
+ var cell = $(event.target);
1128
+ cell.closest('.row-preview').find('.preview-cell .preview-cell-in').removeClass('cell-selected');
1129
+ cell.addClass('cell-selected');
1130
+
1131
+ this.openSelectedCellStyles();
1132
+
1133
+ }.bind(this));
1134
+
1135
+ // Make this row weight click editable
1136
+ newCell.find('.preview-cell-weight').click(function (ci) {
1137
+
1138
+ // Disable the draggable while entering values
1139
+ thisDialog.$('.resize-handle').css('pointer-event', 'none').draggable('disable');
1140
+
1141
+ rowPreview.find('.preview-cell-weight').each(function () {
1142
+ var $$ = jQuery(this).hide();
1143
+ $('<input type="text" class="preview-cell-weight-input no-user-interacted" />')
1144
+ .val(parseFloat($$.html())).insertAfter($$)
1145
+ .focus(function () {
1146
+ clearTimeout(timeout);
1147
+ })
1148
+ .keyup(function (e) {
1149
+ if (e.keyCode !== 9) {
1150
+ // Only register the interaction if the user didn't press tab
1151
+ $(this).removeClass('no-user-interacted');
1152
+ }
1153
+
1154
+ // Enter is clicked
1155
+ if (e.keyCode === 13) {
1156
+ e.preventDefault();
1157
+ $(this).blur();
1158
+ }
1159
+ })
1160
+ .keydown(function (e) {
1161
+ if (e.keyCode === 9) {
1162
+ e.preventDefault();
1163
+
1164
+ // Tab will always cycle around the row inputs
1165
+ var inputs = rowPreview.find('.preview-cell-weight-input');
1166
+ var i = inputs.index($(this));
1167
+ if (i === inputs.length - 1) {
1168
+ inputs.eq(0).focus().select();
1169
+ } else {
1170
+ inputs.eq(i + 1).focus().select();
1171
+ }
1172
+ }
1173
+ })
1174
+ .blur(function () {
1175
+ rowPreview.find('.preview-cell-weight-input').each(function (i, el) {
1176
+ if (isNaN(parseFloat($(el).val()))) {
1177
+ $(el).val(Math.floor(thisDialog.row.cells.at(i).get('weight') * 1000) / 10);
1178
+ }
1179
+ });
1180
+
1181
+ timeout = setTimeout(function () {
1182
+ // If there are no weight inputs, then skip this
1183
+ if (rowPreview.find('.preview-cell-weight-input').length === 0) {
1184
+ return false;
1185
+ }
1186
+
1187
+ // Go through all the inputs
1188
+ var rowWeights = [],
1189
+ rowChanged = [],
1190
+ changedSum = 0,
1191
+ unchangedSum = 0;
1192
+
1193
+ rowPreview.find('.preview-cell-weight-input').each(function (i, el) {
1194
+ var val = parseFloat($(el).val());
1195
+ if (isNaN(val)) {
1196
+ val = 1 / thisDialog.row.cells.length;
1197
+ } else {
1198
+ val = Math.round(val * 10) / 1000;
1199
+ }
1200
+
1201
+ // Check within 3 decimal points
1202
+ var changed = !$(el).hasClass('no-user-interacted');
1203
+
1204
+ rowWeights.push(val);
1205
+ rowChanged.push(changed);
1206
+
1207
+ if (changed) {
1208
+ changedSum += val;
1209
+ } else {
1210
+ unchangedSum += val;
1211
+ }
1212
+ });
1213
+
1214
+ if (changedSum > 0 && unchangedSum > 0 && (
1215
+ 1 - changedSum
1216
+ ) > 0) {
1217
+ // Balance out the unchanged rows to occupy the weight left over by the changed sum
1218
+ for (var i = 0; i < rowWeights.length; i++) {
1219
+ if (!rowChanged[i]) {
1220
+ rowWeights[i] = (
1221
+ rowWeights[i] / unchangedSum
1222
+ ) * (
1223
+ 1 - changedSum
1224
+ );
1225
+ }
1226
+ }
1227
+ }
1228
+
1229
+ // Last check to ensure total weight is 1
1230
+ var sum = _.reduce(rowWeights, function (memo, num) {
1231
+ return memo + num;
1232
+ });
1233
+ rowWeights = rowWeights.map(function (w) {
1234
+ return w / sum;
1235
+ });
1236
+
1237
+ // Set the new cell weights and regenerate the preview.
1238
+ if (Math.min.apply(Math, rowWeights) > 0.01) {
1239
+ thisDialog.row.cells.each(function (cell, i) {
1240
+ cell.set('weight', rowWeights[i]);
1241
+ });
1242
+ }
1243
+
1244
+ // Now lets animate the cells into their new widths
1245
+ rowPreview.find('.preview-cell').each(function (i, el) {
1246
+ var cellWeight = thisDialog.row.cells.at(i).get('weight');
1247
+ $(el).animate({'width': Math.round(cellWeight * 1000) / 10 + "%"}, 250);
1248
+ $(el).find('.preview-cell-weight-input').val(Math.round(cellWeight * 1000) / 10);
1249
+ });
1250
+
1251
+ // So the draggable handle is not hidden.
1252
+ rowPreview.find('.preview-cell').css('overflow', 'visible');
1253
+ setTimeout(thisDialog.regenerateRowPreview.bind(thisDialog), 260);
1254
+
1255
+ }, 100);
1256
+ })
1257
+ .click(function () {
1258
+ $(this).select();
1259
+ });
1260
+ });
1261
+
1262
+ $(this).siblings('.preview-cell-weight-input').select();
1263
+
1264
+ });
1265
+
1266
+ }, this);
1267
+
1268
+ this.trigger('form_loaded', this);
1269
+ },
1270
+
1271
+ getSelectedCellIndex: function() {
1272
+ var selectedIndex = -1;
1273
+ this.$('.preview-cell .preview-cell-in').each(function(index, el) {
1274
+ if($(el).is('.cell-selected')) {
1275
+ selectedIndex = index;
1276
+ }
1277
+ });
1278
+ return selectedIndex;
1279
+ },
1280
+
1281
+ openSelectedCellStyles: function() {
1282
+ if (!_.isUndefined(this.cellStyles)) {
1283
+ if (this.cellStyles.stylesLoaded) {
1284
+ var style = {};
1285
+ try {
1286
+ style = this.getFormValues('.so-sidebar .so-visual-styles.so-cell-styles').style;
1287
  }
1288
+ catch (err) {
1289
+ console.log('Error retrieving cell styles - ' + err.message);
1290
  }
1291
 
1292
+ this.cellStyles.model.set('style', style);
1293
+ }
1294
+ this.cellStyles.detach();
1295
+ }
1296
 
1297
+ this.cellStyles = this.getSelectedCellStyles();
 
 
1298
 
1299
+ if ( this.cellStyles ) {
1300
+ var $rightSidebar = this.$( '.so-sidebar.so-right-sidebar' );
1301
+ this.cellStyles.attach( $rightSidebar );
1302
+ this.cellStyles.on( 'styles_loaded', function ( hasStyles ) {
1303
+ if ( hasStyles ) {
1304
+ $rightSidebar.closest('.so-panels-dialog').addClass('so-panels-dialog-has-right-sidebar');
1305
+ $rightSidebar.show();
1306
+ }
1307
+ } );
1308
  }
1309
+ },
 
1310
 
1311
+ getSelectedCellStyles: function () {
1312
+ var cellIndex = this.getSelectedCellIndex();
1313
+ if ( cellIndex > -1 ) {
1314
+ var cellStyles = this.cellStylesCache[cellIndex];
1315
+ if ( !cellStyles ) {
1316
+ cellStyles = new panels.view.styles();
1317
+ cellStyles.model = this.row.cells.at( cellIndex );
1318
+ cellStyles.render( 'cell', this.builder.config.postId, {
1319
+ builderType: this.builder.config.builderType,
1320
+ dialog: this,
1321
+ index: cellIndex,
1322
+ } );
1323
+ this.cellStylesCache[cellIndex] = cellStyles;
1324
+ }
1325
  }
1326
+
1327
+ return cellStyles;
1328
+ },
1329
+
1330
+ clearCellStylesCache: function () {
1331
+ // Call remove() on all cell styles to remove data, event listeners etc.
1332
+ this.cellStylesCache.forEach(function (cellStyles) {
1333
+ cellStyles.remove();
1334
+ cellStyles.off( 'styles_loaded' );
1335
+ });
1336
+ this.cellStylesCache = [];
1337
  },
1338
 
1339
  /**
1340
+ * Visually scale the row widths based on the cell weights
 
1341
  */
1342
+ scaleRowWidths: function () {
1343
+ var thisDialog = this;
1344
+ this.$('.row-preview .preview-cell').each(function (i, el) {
1345
+ var cell = thisDialog.row.cells.at(i);
1346
+ $(el)
1347
+ .css('width', cell.get('weight') * 100 + "%")
1348
+ .find('.preview-cell-weight').html(Math.round(cell.get('weight') * 1000) / 10);
1349
+ });
1350
+ },
1351
 
1352
+ /**
1353
+ * Get the weights from the
1354
+ */
1355
+ setCellsFromForm: function () {
1356
 
1357
+ try {
1358
+ var f = {
1359
+ 'cells': parseInt(this.$('.row-set-form input[name="cells"]').val()),
1360
+ 'ratio': parseFloat(this.$('.row-set-form select[name="ratio"]').val()),
1361
+ 'direction': this.$('.row-set-form select[name="ratio_direction"]').val()
1362
+ };
1363
+
1364
+ if (_.isNaN(f.cells)) {
1365
+ f.cells = 1;
1366
+ }
1367
+ if (isNaN(f.ratio)) {
1368
+ f.ratio = 1;
1369
+ }
1370
+ if (f.cells < 1) {
1371
+ f.cells = 1;
1372
+ this.$('.row-set-form input[name="cells"]').val(f.cells);
1373
+ }
1374
+ else if (f.cells > 12) {
1375
+ f.cells = 12;
1376
+ this.$('.row-set-form input[name="cells"]').val(f.cells);
1377
+ }
1378
+
1379
+ this.$('.row-set-form select[name="ratio"]').val(f.ratio);
1380
+
1381
+ var cells = [];
1382
+ var cellCountChanged = (
1383
+ this.row.cells.length !== f.cells
1384
+ );
1385
+
1386
+ // Now, lets create some cells
1387
+ var currentWeight = 1;
1388
+ for (var i = 0; i < f.cells; i++) {
1389
+ cells.push(currentWeight);
1390
+ currentWeight *= f.ratio;
1391
+ }
1392
+
1393
+ // Now lets make sure that the row weights add up to 1
1394
+
1395
+ var totalRowWeight = _.reduce(cells, function (memo, weight) {
1396
+ return memo + weight;
1397
+ });
1398
+ cells = _.map(cells, function (cell) {
1399
+ return cell / totalRowWeight;
1400
+ });
1401
+
1402
+ // Don't return cells that are too small
1403
+ cells = _.filter(cells, function (cell) {
1404
+ return cell > 0.01;
1405
+ });
1406
+
1407
+ if (f.direction === 'left') {
1408
+ cells = cells.reverse();
1409
+ }
1410
+
1411
+ // Discard deleted cells.
1412
+ this.row.cells = new panels.collection.cells(this.row.cells.first(cells.length));
1413
+
1414
+ _.each(cells, function (cellWeight, index) {
1415
+ var cell = this.row.cells.at(index);
1416
+ if (!cell) {
1417
+ cell = new panels.model.cell({weight: cellWeight, row: this.model});
1418
+ this.row.cells.add(cell);
1419
+ } else {
1420
+ cell.set('weight', cellWeight);
1421
+ }
1422
+ }.bind(this));
1423
+
1424
+ this.row.ratio = f.ratio;
1425
+ this.row.ratio_direction = f.direction;
1426
+
1427
+ if (cellCountChanged) {
1428
+ this.regenerateRowPreview();
1429
+ } else {
1430
+ var thisDialog = this;
1431
+
1432
+ // Now lets animate the cells into their new widths
1433
+ this.$('.preview-cell').each(function (i, el) {
1434
+ var cellWeight = thisDialog.row.cells.at(i).get('weight');
1435
+ $(el).animate({'width': Math.round(cellWeight * 1000) / 10 + "%"}, 250);
1436
+ $(el).find('.preview-cell-weight').html(Math.round(cellWeight * 1000) / 10);
1437
+ });
1438
+
1439
+ // So the draggable handle is not hidden.
1440
+ this.$('.preview-cell').css('overflow', 'visible');
1441
+
1442
+ setTimeout(thisDialog.regenerateRowPreview.bind(thisDialog), 260);
1443
+ }
1444
+ }
1445
+ catch (err) {
1446
+ console.log('Error setting cells - ' + err.message);
1447
  }
1448
 
 
 
 
1449
 
1450
+ // Remove the button primary class
1451
+ this.$('.row-set-form .so-button-row-set').removeClass('button-primary');
1452
+ },
1453
 
1454
+ /**
1455
+ * Handle a click on the dialog left bar tab
1456
+ */
1457
+ tabClickHandler: function ($t) {
1458
+ if ($t.attr('href') === '#row-layout') {
1459
+ this.$('.so-panels-dialog').addClass('so-panels-dialog-has-right-sidebar');
1460
+ } else {
1461
+ this.$('.so-panels-dialog').removeClass('so-panels-dialog-has-right-sidebar');
1462
  }
1463
+ },
1464
+
1465
+ /**
1466
+ * Update the current model with what we have in the dialog
1467
+ */
1468
+ updateModel: function (args) {
1469
+ args = _.extend({
1470
+ refresh: true,
1471
+ refreshArgs: null
1472
+ }, args);
1473
+
1474
+ // Set the cells
1475
+ if (!_.isEmpty(this.model)) {
1476
+ this.model.setCells( this.row.cells );
1477
+ this.model.set( 'ratio', this.row.ratio );
1478
+ this.model.set( 'ratio_direction', this.row.ratio_direction );
1479
  }
1480
 
1481
+ // Update the row styles if they've loaded
1482
+ if (!_.isUndefined(this.styles) && this.styles.stylesLoaded) {
1483
+ // This is an edit dialog, so there are styles
1484
+ var style = {};
1485
+ try {
1486
+ style = this.getFormValues('.so-sidebar .so-visual-styles.so-row-styles').style;
1487
+ }
1488
+ catch (err) {
1489
+ console.log('Error retrieving row styles - ' + err.message);
1490
+ }
1491
+
1492
+ this.model.set('style', style);
1493
  }
1494
 
1495
+ // Update the cell styles if any are showing.
1496
+ if (!_.isUndefined(this.cellStyles) && this.cellStyles.stylesLoaded) {
1497
+
1498
+ var style = {};
1499
+ try {
1500
+ style = this.getFormValues('.so-sidebar .so-visual-styles.so-cell-styles').style;
1501
+ }
1502
+ catch (err) {
1503
+ console.log('Error retrieving cell styles - ' + err.message);
1504
  }
1505
+
1506
+ this.cellStyles.model.set('style', style);
1507
  }
1508
 
1509
+ if (args.refresh) {
1510
+ this.builder.model.refreshPanelsData(args.refreshArgs);
1511
+ }
1512
  },
1513
 
1514
  /**
1515
+ * Insert the new row
1516
  */
1517
+ insertHandler: function () {
1518
+ this.builder.addHistoryEntry('row_added');
1519
 
1520
+ this.updateModel();
1521
 
1522
+ var activeCell = this.builder.getActiveCell({
1523
+ createCell: false,
1524
+ });
 
 
 
1525
 
1526
+ var options = {};
1527
+ if (activeCell !== null) {
1528
+ options.at = this.builder.model.get('rows').indexOf(activeCell.row) + 1;
1529
+ }
1530
 
1531
+ // Set up the model and add it to the builder
1532
+ this.model.collection = this.builder.model.get('rows');
1533
+ this.builder.model.get('rows').add(this.model, options);
1534
 
1535
+ this.closeDialog();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1536
 
1537
+ this.builder.model.refreshPanelsData();
 
 
 
 
1538
 
1539
+ return false;
1540
+ },
 
 
 
 
 
1541
 
1542
+ /**
1543
+ * We'll just save this model and close the dialog
1544
+ */
1545
+ saveHandler: function () {
1546
+ this.builder.addHistoryEntry('row_edited');
1547
+ this.updateModel();
1548
+ this.closeDialog();
1549
 
1550
+ this.builder.model.refreshPanelsData();
 
 
 
 
 
 
 
1551
 
1552
+ return false;
1553
+ },
1554
 
1555
+ /**
1556
+ * The user clicks delete, so trigger deletion on the row model
1557
+ */
1558
+ deleteHandler: function () {
1559
+ // Trigger a destroy on the model that will happen with a visual indication to the user
1560
+ this.rowView.visualDestroyModel();
1561
+ this.closeDialog({silent: true});
1562
 
1563
+ return false;
1564
  },
1565
 
1566
  /**
1567
+ * Duplicate this row
1568
  */
1569
+ duplicateHandler: function () {
1570
+ this.builder.addHistoryEntry('row_duplicated');
 
 
1571
 
1572
+ var duplicateRow = this.model.clone(this.builder.model);
 
 
1573
 
1574
+ this.builder.model.get('rows').add( duplicateRow, {
1575
+ at: this.builder.model.get('rows').indexOf(this.model) + 1
1576
+ } );
1577
+
1578
+ this.closeDialog({silent: true});
1579
+
1580
+ return false;
1581
+ },
1582
+
1583
+ closeHandler: function() {
1584
+ this.clearCellStylesCache();
1585
+ if( ! _.isUndefined(this.cellStyles) ) {
1586
+ this.cellStyles = undefined;
1587
  }
1588
  },
1589
 
1590
+ });
 
 
 
 
 
1591
 
1592
+ },{}],9:[function(require,module,exports){
1593
+ var panels = window.panels, $ = jQuery;
1594
+ var jsWidget = require( '../view/widgets/js-widget' );
1595
+
1596
+ module.exports = panels.view.dialog.extend( {
1597
+
1598
+ builder: null,
1599
+ sidebarWidgetTemplate: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-dialog-widget-sidebar-widget' ).html() ) ),
1600
+
1601
+ dialogClass: 'so-panels-dialog-edit-widget',
1602
+ dialogIcon: 'add-widget',
1603
+
1604
+ widgetView: false,
1605
+ savingWidget: false,
1606
+ editableLabel: true,
1607
+
1608
+ events: {
1609
+ 'click .so-close': 'saveHandler',
1610
+ 'click .so-nav.so-previous': 'navToPrevious',
1611
+ 'click .so-nav.so-next': 'navToNext',
1612
+
1613
+ // Action handlers
1614
+ 'click .so-toolbar .so-delete': 'deleteHandler',
1615
+ 'click .so-toolbar .so-duplicate': 'duplicateHandler'
1616
  },
1617
 
1618
+ initializeDialog: function () {
1619
+ var thisView = this;
1620
+ this.listenTo( this.model, 'change:values', this.handleChangeValues );
1621
+ this.listenTo( this.model, 'destroy', this.remove );
1622
+
1623
+ // Refresh panels data after both dialog form components are loaded
1624
+ this.dialogFormsLoaded = 0;
1625
+ this.on( 'form_loaded styles_loaded', function () {
1626
+ this.dialogFormsLoaded ++;
1627
+ if ( this.dialogFormsLoaded === 2 ) {
1628
+ thisView.updateModel( {
1629
+ refreshArgs: {
1630
+ silent: true
1631
+ }
1632
+ } );
1633
+ }
1634
+ } );
1635
+
1636
+ this.on( 'edit_label', function ( text ) {
1637
+ // If text is set to default value, just clear it.
1638
+ if ( text === panelsOptions.widgets[ this.model.get( 'class' ) ][ 'title' ] ) {
1639
+ text = '';
1640
+ }
1641
+ this.model.set( 'label', text );
1642
+ if ( _.isEmpty( text ) ) {
1643
+ this.$( '.so-title' ).text( this.model.getWidgetField( 'title' ) );
1644
+ }
1645
+ }.bind( this ) );
1646
  },
1647
 
1648
  /**
1649
+ * Render the widget dialog.
 
1650
  */
1651
+ render: function () {
1652
+ // Render the dialog and attach it to the builder interface
1653
+ this.renderDialog( this.parseDialogContent( $( '#siteorigin-panels-dialog-widget' ).html(), {} ) );
1654
+ this.loadForm();
1655
 
1656
+ var title = this.model.getWidgetField( 'title' );
1657
+ this.$( '.so-title .widget-name' ).html( title );
1658
+ this.$( '.so-edit-title' ).val( title );
 
 
 
 
1659
 
1660
+ if( ! this.builder.supports( 'addWidget' ) ) {
1661
+ this.$( '.so-buttons .so-duplicate' ).remove();
1662
+ }
1663
+ if( ! this.builder.supports( 'deleteWidget' ) ) {
1664
+ this.$( '.so-buttons .so-delete' ).remove();
1665
+ }
1666
 
1667
+ // Now we need to attach the style window
1668
+ this.styles = new panels.view.styles();
1669
+ this.styles.model = this.model;
1670
+ this.styles.render( 'widget', this.builder.config.postId, {
1671
+ builderType: this.builder.config.builderType,
1672
+ dialog: this
1673
+ } );
 
 
1674
 
1675
+ var $rightSidebar = this.$( '.so-sidebar.so-right-sidebar' );
1676
+ this.styles.attach( $rightSidebar );
1677
 
1678
+ // Handle the loading class
1679
+ this.styles.on( 'styles_loaded', function ( hasStyles ) {
1680
+ // If we don't have styles remove the empty sidebar.
1681
+ if ( ! hasStyles ) {
1682
+ $rightSidebar.closest( '.so-panels-dialog' ).removeClass( 'so-panels-dialog-has-right-sidebar' );
1683
+ $rightSidebar.remove();
1684
+ }
1685
+ }, this );
1686
+ },
1687
 
1688
+ /**
1689
+ * Get the previous widget editing dialog by looking at the dom.
1690
+ * @returns {*}
1691
+ */
1692
+ getPrevDialog: function () {
1693
+ var widgets = this.builder.$( '.so-cells .cell .so-widget' );
1694
+ if ( widgets.length <= 1 ) {
1695
+ return false;
1696
+ }
1697
+ var currentIndex = widgets.index( this.widgetView.$el );
1698
 
1699
+ if ( currentIndex === 0 ) {
1700
+ return false;
1701
+ } else {
1702
+ var widgetView;
1703
+ do {
1704
+ widgetView = widgets.eq( --currentIndex ).data( 'view' );
1705
+ if ( ! _.isUndefined( widgetView ) && ! widgetView.model.get( 'read_only' ) ) {
1706
+ return widgetView.getEditDialog();
1707
  }
1708
+ } while( ! _.isUndefined( widgetView ) && currentIndex > 0 );
1709
+ }
1710
 
1711
+ return false;
1712
+ },
1713
 
1714
+ /**
1715
+ * Get the next widget editing dialog by looking at the dom.
1716
+ * @returns {*}
1717
+ */
1718
+ getNextDialog: function () {
1719
+ var widgets = this.builder.$( '.so-cells .cell .so-widget' );
1720
+ if ( widgets.length <= 1 ) {
1721
+ return false;
1722
+ }
1723
+
1724
+ var currentIndex = widgets.index( this.widgetView.$el );
1725
+
1726
+ if ( currentIndex === widgets.length - 1 ) {
1727
+ return false;
1728
+ } else {
1729
+ var widgetView;
1730
+ do {
1731
+ widgetView = widgets.eq( ++currentIndex ).data( 'view' );
1732
+ if ( ! _.isUndefined( widgetView ) && ! widgetView.model.get( 'read_only' ) ) {
1733
+ return widgetView.getEditDialog();
1734
  }
1735
+ } while( ! _.isUndefined( widgetView ) );
1736
+ }
1737
 
1738
+ return false;
1739
+ },
 
 
 
1740
 
1741
+ /**
1742
+ * Load the widget form from the server.
1743
+ * This is called when rendering the dialog for the first time.
1744
+ */
1745
+ loadForm: function () {
1746
+ // don't load the form if this dialog hasn't been rendered yet
1747
+ if ( ! this.$( '> *' ).length ) {
1748
+ return;
1749
+ }
1750
 
1751
+ this.$( '.so-content' ).addClass( 'so-panels-loading' );
 
 
1752
 
1753
+ var data = {
1754
+ 'action': 'so_panels_widget_form',
1755
+ 'widget': this.model.get( 'class' ),
1756
+ 'instance': JSON.stringify( this.model.get( 'values' ) ),
1757
+ 'raw': this.model.get( 'raw' )
1758
+ };
1759
+
1760
+ var $soContent = this.$( '.so-content' );
1761
+
1762
+ $.post( panelsOptions.ajaxurl, data, null, 'html' )
1763
+ .done( function ( result ) {
1764
+ // Add in the CID of the widget model
1765
+ var html = result.replace( /{\$id}/g, this.model.cid );
1766
+
1767
+ // Load this content into the form
1768
+ $soContent
1769
+ .removeClass( 'so-panels-loading' )
1770
+ .html( html );
1771
+
1772
+ // Trigger all the necessary events
1773
+ this.trigger( 'form_loaded', this );
1774
+
1775
+ // For legacy compatibility, trigger a panelsopen event
1776
+ this.$( '.panel-dialog' ).trigger( 'panelsopen' );
1777
+
1778
+ // If the main dialog is closed from this point on, save the widget content
1779
+ this.on( 'close_dialog', this.updateModel, this );
1780
+
1781
+ var widgetContent = $soContent.find( '> .widget-content' );
1782
+ // If there's a widget content wrapper, this is one of the new widgets in WP 4.8 which need some special
1783
+ // handling in JS.
1784
+ if ( widgetContent.length > 0 ) {
1785
+ jsWidget.addWidget( $soContent, this.model.widget_id );
1786
+ }
1787
+
1788
+ }.bind( this ) )
1789
+ .fail( function ( error ) {
1790
+ var html;
1791
+ if ( error && error.responseText ) {
1792
+ html = error.responseText;
1793
+ } else {
1794
+ html = panelsOptions.forms.loadingFailed;
1795
+ }
1796
+
1797
+ $soContent
1798
+ .removeClass( 'so-panels-loading' )
1799
+ .html( html );
1800
+ } );
1801
+ },
1802
 
1803
+ /**
1804
+ * Save the widget from the form to the model
1805
+ */
1806
+ updateModel: function ( args ) {
1807
+ args = _.extend( {
1808
+ refresh: true,
1809
+ refreshArgs: null
1810
+ }, args );
1811
 
1812
+ // Get the values from the form and assign the new values to the model
1813
+ this.savingWidget = true;
 
 
 
1814
 
1815
+ if ( ! this.model.get( 'missing' ) ) {
1816
+ // Only get the values for non missing widgets.
1817
+ var values = this.getFormValues();
1818
+ if ( _.isUndefined( values.widgets ) ) {
1819
+ values = {};
1820
+ } else {
1821
+ values = values.widgets;
1822
+ values = values[Object.keys( values )[0]];
1823
+ }
 
1824
 
1825
+ this.model.setValues( values );
1826
+ this.model.set( 'raw', true ); // We've saved from the widget form, so this is now raw
1827
+ }
1828
 
1829
+ if ( this.styles.stylesLoaded ) {
1830
+ // If the styles view has loaded
1831
+ var style = {};
1832
+ try {
1833
+ style = this.getFormValues( '.so-sidebar .so-visual-styles' ).style;
1834
+ }
1835
+ catch ( e ) {
1836
+ }
1837
+ this.model.set( 'style', style );
1838
+ }
1839
 
1840
+ this.savingWidget = false;
 
1841
 
1842
+ if ( args.refresh ) {
1843
+ this.builder.model.refreshPanelsData( args.refreshArgs );
1844
+ }
1845
+ },
 
 
 
 
 
 
 
 
1846
 
1847
+ /**
1848
+ *
1849
+ */
1850
+ handleChangeValues: function () {
1851
+ if ( ! this.savingWidget ) {
1852
+ // Reload the form when we've changed the model and we're not currently saving from the form
1853
+ this.loadForm();
1854
+ }
1855
+ },
1856
+
1857
+ /**
1858
+ * Save a history entry for this widget. Called when the dialog is closed.
1859
+ */
1860
+ saveHandler: function () {
1861
+ this.builder.addHistoryEntry( 'widget_edited' );
1862
+ this.closeDialog();
1863
+ },
1864
+
1865
+ /**
1866
+ * When the user clicks delete.
1867
+ *
1868
+ * @returns {boolean}
1869
+ */
1870
+ deleteHandler: function () {
1871
+ this.widgetView.visualDestroyModel();
1872
+ this.closeDialog( {silent: true} );
1873
+ this.builder.model.refreshPanelsData();
1874
+
1875
+ return false;
1876
+ },
1877
+
1878
+ duplicateHandler: function () {
1879
+ // Call the widget duplicate handler directly
1880
+ this.widgetView.duplicateHandler();
1881
+
1882
+ this.closeDialog( {silent: true} );
1883
+ this.builder.model.refreshPanelsData();
1884
+
1885
+ return false;
1886
+ }
1887
+
1888
+ } );
1889
+
1890
+ },{"../view/widgets/js-widget":31}],10:[function(require,module,exports){
1891
+ var panels = window.panels, $ = jQuery;
1892
+
1893
+ module.exports = panels.view.dialog.extend( {
1894
+
1895
+ builder: null,
1896
+ widgetTemplate: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-dialog-widgets-widget' ).html() ) ),
1897
+ filter: {},
1898
+
1899
+ dialogClass: 'so-panels-dialog-add-widget',
1900
+ dialogIcon: 'add-widget',
1901
+
1902
+ events: {
1903
+ 'click .so-close': 'closeDialog',
1904
+ 'click .widget-type': 'widgetClickHandler',
1905
+ 'keyup .so-sidebar-search': 'searchHandler'
1906
+ },
1907
+
1908
+ /**
1909
+ * Initialize the widget adding dialog
1910
+ */
1911
+ initializeDialog: function () {
1912
+
1913
+ this.on( 'open_dialog', function () {
1914
+ this.filter.search = '';
1915
+ this.filterWidgets( this.filter );
1916
+ }, this );
1917
+
1918
+ this.on( 'open_dialog_complete', function () {
1919
+ // Clear the search and re-filter the widgets when we open the dialog
1920
+ this.$( '.so-sidebar-search' ).val( '' ).focus();
1921
+ this.balanceWidgetHeights();
1922
+ } );
1923
+
1924
+ // We'll implement a custom tab click handler
1925
+ this.on( 'tab_click', this.tabClickHandler, this );
1926
+ },
1927
+
1928
+ render: function () {
1929
+ // Render the dialog and attach it to the builder interface
1930
+ this.renderDialog( this.parseDialogContent( $( '#siteorigin-panels-dialog-widgets' ).html(), {} ) );
1931
+
1932
+ // Add all the widgets
1933
+ _.each( panelsOptions.widgets, function ( widget ) {
1934
+ var $w = $( this.widgetTemplate( {
1935
+ title: widget.title,
1936
+ description: widget.description
1937
+ } ) );
1938
+
1939
+ if ( _.isUndefined( widget.icon ) ) {
1940
+ widget.icon = 'dashicons dashicons-admin-generic';
1941
+ }
1942
+
1943
+ $( '<span class="widget-icon" />' ).addClass( widget.icon ).prependTo( $w.find( '.widget-type-wrapper' ) );
1944
+
1945
+ $w.data( 'class', widget.class ).appendTo( this.$( '.widget-type-list' ) );
1946
+ }, this );
1947
+
1948
+ // Add the sidebar tabs
1949
+ var tabs = this.$( '.so-sidebar-tabs' );
1950
+ _.each( panelsOptions.widget_dialog_tabs, function ( tab, key ) {
1951
+ $( this.dialogTabTemplate( {'title': tab.title, 'tab': key} ) ).data( {
1952
+ 'message': tab.message,
1953
+ 'filter': tab.filter
1954
+ } ).appendTo( tabs );
1955
+ }, this );
1956
+
1957
+ // We'll be using tabs, so initialize them
1958
+ this.initTabs();
1959
+
1960
+ var thisDialog = this;
1961
+ $( window ).resize( function () {
1962
+ thisDialog.balanceWidgetHeights();
1963
+ } );
1964
+ },
1965
+
1966
+ /**
1967
+ * Handle a tab being clicked
1968
+ */
1969
+ tabClickHandler: function ( $t ) {
1970
+ // Get the filter from the tab, and filter the widgets
1971
+ this.filter = $t.parent().data( 'filter' );
1972
+ this.filter.search = this.$( '.so-sidebar-search' ).val();
1973
+
1974
+ var message = $t.parent().data( 'message' );
1975
+ if ( _.isEmpty( message ) ) {
1976
+ message = '';
1977
+ }
1978
+
1979
+ this.$( '.so-toolbar .so-status' ).html( message );
1980
+
1981
+ this.filterWidgets( this.filter );
1982
+
1983
+ return false;
1984
+ },
1985
+
1986
+ /**
1987
+ * Handle changes to the search value
1988
+ */
1989
+ searchHandler: function ( e ) {
1990
+ if( e.which === 13 ) {
1991
+ var visibleWidgets = this.$( '.widget-type-list .widget-type:visible' );
1992
+ if( visibleWidgets.length === 1 ) {
1993
+ visibleWidgets.click();
1994
+ }
1995
+ }
1996
+ else {
1997
+ this.filter.search = $( e.target ).val().trim();
1998
+ this.filterWidgets( this.filter );
1999
+ }
2000
+ },
2001
+
2002
+ /**
2003
+ * Filter the widgets that we're displaying
2004
+ * @param filter
2005
+ */
2006
+ filterWidgets: function ( filter ) {
2007
+ if ( _.isUndefined( filter ) ) {
2008
+ filter = {};
2009
+ }
2010
+
2011
+ if ( _.isUndefined( filter.groups ) ) {
2012
+ filter.groups = '';
2013
+ }
2014
+
2015
+ this.$( '.widget-type-list .widget-type' ).each( function () {
2016
+ var $$ = $( this ), showWidget;
2017
+ var widgetClass = $$.data( 'class' );
2018
+
2019
+ var widgetData = (
2020
+ ! _.isUndefined( panelsOptions.widgets[widgetClass] )
2021
+ ) ? panelsOptions.widgets[widgetClass] : null;
2022
+
2023
+ if ( _.isEmpty( filter.groups ) ) {
2024
+ // This filter doesn't specify groups, so show all
2025
+ showWidget = true;
2026
+ } else if ( widgetData !== null && ! _.isEmpty( _.intersection( filter.groups, panelsOptions.widgets[widgetClass].groups ) ) ) {
2027
+ // This widget is in the filter group
2028
+ showWidget = true;
2029
+ } else {
2030
+ // This widget is not in the filter group
2031
+ showWidget = false;
2032
+ }
2033
+
2034
+ // This can probably be done with a more intelligent operator
2035
+ if ( showWidget ) {
2036
+
2037
+ if ( ! _.isUndefined( filter.search ) && filter.search !== '' ) {
2038
+ // Check if the widget title contains the search term
2039
+ if ( widgetData.title.toLowerCase().indexOf( filter.search.toLowerCase() ) === - 1 ) {
2040
+ showWidget = false;
2041
+ }
2042
+ }
2043
+
2044
+ }
2045
+
2046
+ if ( showWidget ) {
2047
+ $$.show();
2048
+ } else {
2049
+ $$.hide();
2050
+ }
2051
+ } );
2052
+
2053
+ // Balance the tags after filtering
2054
+ this.balanceWidgetHeights();
2055
+ },
2056
+
2057
+ /**
2058
+ * Add the widget to the current builder
2059
+ *
2060
+ * @param e
2061
+ */
2062
+ widgetClickHandler: function ( e ) {
2063
+ // Add the history entry
2064
+ this.builder.trigger('before_user_adds_widget');
2065
+ this.builder.addHistoryEntry( 'widget_added' );
2066
+
2067
+ var $w = $( e.currentTarget );
2068
+
2069
+ var widget = new panels.model.widget( {
2070
+ class: $w.data( 'class' )
2071
+ } );
2072
+
2073
+ // Add the widget to the cell model
2074
+ widget.cell = this.builder.getActiveCell();
2075
+ widget.cell.get('widgets').add( widget );
2076
+
2077
+ this.closeDialog();
2078
+ this.builder.model.refreshPanelsData();
2079
+
2080
+ this.builder.trigger('after_user_adds_widget', widget);
2081
+ },
2082
+
2083
+ /**
2084
+ * Balance widgets in a given row so they have enqual height.
2085
+ * @param e
2086
+ */
2087
+ balanceWidgetHeights: function ( e ) {
2088
+ var widgetRows = [[]];
2089
+ var previousWidget = null;
2090
+
2091
+ // Work out how many widgets there are per row
2092
+ var perRow = Math.round( this.$( '.widget-type' ).parent().width() / this.$( '.widget-type' ).width() );
2093
+
2094
+ // Add clears to create balanced rows
2095
+ this.$( '.widget-type' )
2096
+ .css( 'clear', 'none' )
2097
+ .filter( ':visible' )
2098
+ .each( function ( i, el ) {
2099
+ if ( i % perRow === 0 && i !== 0 ) {
2100
+ $( el ).css( 'clear', 'both' );
2101
+ }
2102
+ } );
2103
+
2104
+ // Group the widgets into rows
2105
+ this.$( '.widget-type-wrapper' )
2106
+ .css( 'height', 'auto' )
2107
+ .filter( ':visible' )
2108
+ .each( function ( i, el ) {
2109
+ var $el = $( el );
2110
+ if ( previousWidget !== null && previousWidget.position().top !== $el.position().top ) {
2111
+ widgetRows[widgetRows.length] = [];
2112
+ }
2113
+ previousWidget = $el;
2114
+ widgetRows[widgetRows.length - 1].push( $el );
2115
+ } );
2116
+
2117
+ // Balance the height of the widgets within the row.
2118
+ _.each( widgetRows, function ( row, i ) {
2119
+ var maxHeight = _.max( row.map( function ( el ) {
2120
+ return el.height();
2121
+ } ) );
2122
+ // Set the height of each widget in the row
2123
+ _.each( row, function ( el ) {
2124
+ el.height( maxHeight );
2125
+ } );
2126
+
2127
+ } );
2128
+ }
2129
+ } );
2130
+
2131
+ },{}],11:[function(require,module,exports){
2132
+ module.exports = {
2133
+ /**
2134
+ * Check if we have copy paste available.
2135
+ * @returns {boolean|*}
2136
+ */
2137
+ canCopyPaste: function(){
2138
+ return typeof(Storage) !== "undefined" && panelsOptions.user;
2139
+ },
2140
+
2141
+ /**
2142
+ * Set the model that we're going to store in the clipboard
2143
+ */
2144
+ setModel: function( model ){
2145
+ if( ! this.canCopyPaste() ) {
2146
+ return false;
2147
+ }
2148
+
2149
+ var serial = panels.helpers.serialize.serialize( model );
2150
+ if( model instanceof panels.model.row ) {
2151
+ serial.thingType = 'row-model';
2152
+ } else if( model instanceof panels.model.widget ) {
2153
+ serial.thingType = 'widget-model';
2154
+ }
2155
+
2156
+ // Store this in local storage
2157
+ localStorage[ 'panels_clipboard_' + panelsOptions.user ] = JSON.stringify( serial );
2158
+ return true;
2159
+ },
2160
+
2161
+ /**
2162
+ * Check if the current model stored in the clipboard is the expected type
2163
+ */
2164
+ isModel: function( expected ){
2165
+ if( ! this.canCopyPaste() ) {
2166
+ return false;
2167
+ }
2168
+
2169
+ var clipboardObject = localStorage[ 'panels_clipboard_' + panelsOptions.user ];
2170
+ if( clipboardObject !== undefined ) {
2171
+ clipboardObject = JSON.parse(clipboardObject);
2172
+ return clipboardObject.thingType && clipboardObject.thingType === expected;
2173
+ }
2174
+
2175
+ return false;
2176
+ },
2177
+
2178
+ /**
2179
+ * Get the model currently stored in the clipboard
2180
+ */
2181
+ getModel: function( expected ){
2182
+ if( ! this.canCopyPaste() ) {
2183
+ return null;
2184
+ }
2185
+
2186
+ var clipboardObject = localStorage[ 'panels_clipboard_' + panelsOptions.user ];
2187
+ if( clipboardObject !== undefined ) {
2188
+ clipboardObject = JSON.parse( clipboardObject );
2189
+ if( clipboardObject.thingType && clipboardObject.thingType === expected ) {
2190
+ return panels.helpers.serialize.unserialize( clipboardObject, clipboardObject.thingType, null );
2191
+ }
2192
+ }
2193
+
2194
+ return null;
2195
+ },
2196
+ };
2197
+
2198
+ },{}],12:[function(require,module,exports){
2199
+ module.exports = {
2200
+ /**
2201
+ * Lock window scrolling for the main overlay
2202
+ */
2203
+ lock: function () {
2204
+ if ( jQuery( 'body' ).css( 'overflow' ) === 'hidden' ) {
2205
+ return;
2206
+ }
2207
+
2208
+ // lock scroll position, but retain settings for later
2209
+ var scrollPosition = [
2210
+ self.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
2211
+ self.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
2212
+ ];
2213
+
2214
+ jQuery( 'body' )
2215
+ .data( {
2216
+ 'scroll-position': scrollPosition
2217
+ } )
2218
+ .css( 'overflow', 'hidden' );
2219
+
2220
+ if( ! _.isUndefined( scrollPosition ) ) {
2221
+ window.scrollTo( scrollPosition[0], scrollPosition[1] );
2222
+ }
2223
+ },
2224
+
2225
+ /**
2226
+ * Unlock window scrolling
2227
+ */
2228
+ unlock: function () {
2229
+ if ( jQuery( 'body' ).css( 'overflow' ) !== 'hidden' ) {
2230
+ return;
2231
+ }
2232
+
2233
+ // Check that there are no more dialogs or a live editor
2234
+ if ( ! jQuery( '.so-panels-dialog-wrapper' ).is( ':visible' ) && ! jQuery( '.so-panels-live-editor' ).is( ':visible' ) ) {
2235
+ jQuery( 'body' ).css( 'overflow', 'visible' );
2236
+ var scrollPosition = jQuery( 'body' ).data( 'scroll-position' );
2237
+
2238
+ if( ! _.isUndefined( scrollPosition ) ) {
2239
+ window.scrollTo( scrollPosition[0], scrollPosition[1] );
2240
+ }
2241
+ }
2242
+ },
2243
+ };
2244
+
2245
+ },{}],13:[function(require,module,exports){
2246
+ /*
2247
+ This is a modified version of https://github.com/underdogio/backbone-serialize/
2248
+ */
2249
+
2250
+ /* global Backbone, module, panels */
2251
+
2252
+ module.exports = {
2253
+ serialize: function( thing ){
2254
+ var val;
2255
+
2256
+ if( thing instanceof Backbone.Model ) {
2257
+ var retObj = {};
2258
+ for ( var key in thing.attributes ) {
2259
+ if (thing.attributes.hasOwnProperty( key ) ) {
2260
+ // Skip these to avoid recursion
2261
+ if( key === 'builder' || key === 'collection' ) { continue; }
2262
+
2263
+ // If the value is a Model or a Collection, then serialize them as well
2264
+ val = thing.attributes[key];
2265
+ if ( val instanceof Backbone.Model || val instanceof Backbone.Collection ) {
2266
+ retObj[key] = this.serialize( val );
2267
+ } else {
2268
+ // Otherwise, save the original value
2269
+ retObj[key] = val;
2270
+ }
2271
+ }
2272
+ }
2273
+ return retObj;
2274
+ }
2275
+ else if( thing instanceof Backbone.Collection ) {
2276
+ // Walk over all of our models
2277
+ var retArr = [];
2278
+
2279
+ for ( var i = 0; i < thing.models.length; i++ ) {
2280
+ // If the model is serializable, then serialize it
2281
+ val = thing.models[i];
2282
+
2283
+ if ( val instanceof Backbone.Model || val instanceof Backbone.Collection ) {
2284
+ retArr.push( this.serialize( val ) );
2285
+ } else {
2286
+ // Otherwise (it is an object), return it in its current form
2287
+ retArr.push( val );
2288
+ }
2289
+ }
2290
+
2291
+ // Return the serialized models
2292
+ return retArr;
2293
+ }
2294
+ },
2295
+
2296
+ unserialize: function( thing, thingType, parent ) {
2297
+ var retObj;
2298
+
2299
+ switch( thingType ) {
2300
+ case 'row-model' :
2301
+ retObj = new panels.model.row();
2302
+ retObj.builder = parent;
2303
+ var atts = { style: thing.style };
2304
+ if ( thing.hasOwnProperty( 'label' ) ) {
2305
+ atts.label = thing.label;
2306
+ }
2307
+ if ( thing.hasOwnProperty( 'color_label' ) ) {
2308
+ atts.color_label = thing.color_label;
2309
+ }
2310
+ retObj.set( atts );
2311
+ retObj.setCells( this.unserialize( thing.cells, 'cell-collection', retObj ) );
2312
+ break;
2313
+
2314
+ case 'cell-model' :
2315
+ retObj = new panels.model.cell();
2316
+ retObj.row = parent;
2317
+ retObj.set( 'weight', thing.weight );
2318
+ retObj.set( 'style', thing.style );
2319
+ retObj.set( 'widgets', this.unserialize( thing.widgets, 'widget-collection', retObj ) );
2320
+ break;
2321
+
2322
+ case 'widget-model' :
2323
+ retObj = new panels.model.widget();
2324
+ retObj.cell = parent;
2325
+ for ( var key in thing ) {
2326
+ if ( thing.hasOwnProperty( key ) ) {
2327
+ retObj.set( key, thing[key] );
2328
+ }
2329
+ }
2330
+ retObj.set( 'widget_id', panels.helpers.utils.generateUUID() );
2331
+ break;
2332
+
2333
+ case 'cell-collection':
2334
+ retObj = new panels.collection.cells();
2335
+ for( var i = 0; i < thing.length; i++ ) {
2336
+ retObj.push( this.unserialize( thing[i], 'cell-model', parent ) );
2337
+ }
2338
+ break;
2339
+
2340
+ case 'widget-collection':
2341
+ retObj = new panels.collection.widgets();
2342
+ for( var i = 0; i < thing.length; i++ ) {
2343
+ retObj.push( this.unserialize( thing[i], 'widget-model', parent ) );
2344
+ }
2345
+ break;
2346
+
2347
+ default:
2348
+ console.log( 'Unknown Thing - ' + thingType );
2349
+ break;
2350
+ }
2351
+
2352
+ return retObj;
2353
+ }
2354
+ };
2355
+
2356
+ },{}],14:[function(require,module,exports){
2357
+ module.exports = {
2358
+
2359
+ generateUUID: function(){
2360
+ var d = new Date().getTime();
2361
+ if( window.performance && typeof window.performance.now === "function" ){
2362
+ d += performance.now(); //use high-precision timer if available
2363
+ }
2364
+ var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace( /[xy]/g, function(c) {
2365
+ var r = (d + Math.random()*16)%16 | 0;
2366
+ d = Math.floor(d/16);
2367
+ return ( c == 'x' ? r : (r&0x3|0x8) ).toString(16);
2368
+ } );
2369
+ return uuid;
2370
+ },
2371
+
2372
+ processTemplate: function ( s ) {
2373
+ if ( _.isUndefined( s ) || _.isNull( s ) ) {
2374
+ return '';
2375
+ }
2376
+ s = s.replace( /{{%/g, '<%' );
2377
+ s = s.replace( /%}}/g, '%>' );
2378
+ s = s.trim();
2379
+ return s;
2380
+ },
2381
+
2382
+ // From this SO post: http://stackoverflow.com/questions/6139107/programmatically-select-text-in-a-contenteditable-html-element
2383
+ selectElementContents: function( element ) {
2384
+ var range = document.createRange();
2385
+ range.selectNodeContents( element );
2386
+ var sel = window.getSelection();
2387
+ sel.removeAllRanges();
2388
+ sel.addRange( range );
2389
+ },
2390
+
2391
+ }
2392
+
2393
+ },{}],15:[function(require,module,exports){
2394
+ /* global _, jQuery, panels */
2395
+
2396
+ var panels = window.panels, $ = jQuery;
2397
+
2398
+ module.exports = function ( config, force ) {
2399
+
2400
+ return this.each( function () {
2401
+ var $$ = jQuery( this );
2402
+
2403
+ if ( $$.data( 'soPanelsBuilderWidgetInitialized' ) && ! force ) {
2404
+ return;
2405
+ }
2406
+ var widgetId = $$.closest( 'form' ).find( '.widget-id' ).val();
2407
+
2408
+ // Create a config for this specific widget
2409
+ var thisConfig = $.extend(true, {}, config);
2410
+
2411
+ // Exit if this isn't a real widget
2412
+ if ( ! _.isUndefined( widgetId ) && widgetId.indexOf( '__i__' ) > - 1 ) {
2413
+ return;
2414
+ }
2415
+
2416
+ // Create the main builder model
2417
+ var builderModel = new panels.model.builder();
2418
+
2419
+ // Now for the view to display the builder
2420
+ var builderView = new panels.view.builder( {
2421
+ model: builderModel,
2422
+ config: thisConfig
2423
+ } );
2424
+
2425
+ // Save panels data when we close the dialog, if we're in a dialog
2426
+ var dialog = $$.closest( '.so-panels-dialog-wrapper' ).data( 'view' );
2427
+ if ( ! _.isUndefined( dialog ) ) {
2428
+ dialog.on( 'close_dialog', function () {
2429
+ builderModel.refreshPanelsData();
2430
+ } );
2431
+
2432
+ dialog.on( 'open_dialog_complete', function () {
2433
+ // Make sure the new layout widget is always properly setup
2434
+ builderView.trigger( 'builder_resize' );
2435
+ } );
2436
+
2437
+ dialog.model.on( 'destroy', function () {
2438
+ // Destroy the builder
2439
+ builderModel.emptyRows().destroy();
2440
+ } );
2441
+
2442
+ // Set the parent for all the sub dialogs
2443
+ builderView.setDialogParents( panelsOptions.loc.layout_widget, dialog );
2444
+ }
2445
+
2446
+ // Basic setup for the builder
2447
+ var isWidget = Boolean( $$.closest( '.widget-content' ).length );
2448
+ builderView
2449
+ .render()
2450
+ .attach( {
2451
+ container: $$,
2452
+ dialog: isWidget || $$.data('mode') === 'dialog',
2453
+ type: $$.data( 'type' )
2454
+ } )
2455
+ .setDataField( $$.find( 'input.panels-data' ) );
2456
+
2457
+ if ( isWidget || $$.data('mode') === 'dialog' ) {
2458
+ // Set up the dialog opening
2459
+ builderView.setDialogParents( panelsOptions.loc.layout_widget, builderView.dialog );
2460
+ $$.find( '.siteorigin-panels-display-builder' ).click( function ( e ) {
2461
+ e.preventDefault();
2462
+ builderView.dialog.openDialog();
2463
+ } );
2464
+ } else {
2465
+ // Remove the dialog opener button, this is already being displayed in a page builder dialog.
2466
+ $$.find( '.siteorigin-panels-display-builder' ).parent().remove();
2467
+ }
2468
+
2469
+ // Trigger a global jQuery event after we've setup the builder view
2470
+ $( document ).trigger( 'panels_setup', builderView );
2471
+
2472
+ $$.data( 'soPanelsBuilderWidgetInitialized', true );
2473
+ } );
2474
+ };
2475
+
2476
+ },{}],16:[function(require,module,exports){
2477
+ /**
2478
+ * Everything we need for SiteOrigin Page Builder.
2479
+ *
2480
+ * @copyright Greg Priday 2013 - 2016 - <https://siteorigin.com/>
2481
+ * @license GPL 3.0 http://www.gnu.org/licenses/gpl.html
2482
+ */
2483
+
2484
+ /* global Backbone, _, jQuery, tinyMCE, panelsOptions, plupload, confirm, console, require */
2485
+
2486
+ var panels = {};
2487
+
2488
+ // Store everything globally
2489
+ window.panels = panels;
2490
+ window.siteoriginPanels = panels;
2491
+
2492
+ // Helpers
2493
+ panels.helpers = {};
2494
+ panels.helpers.clipboard = require( './helpers/clipboard' );
2495
+ panels.helpers.utils = require( './helpers/utils' );
2496
+ panels.helpers.serialize = require( './helpers/serialize' );
2497
+ panels.helpers.pageScroll = require( './helpers/page-scroll' );
2498
+
2499
+ // The models
2500
+ panels.model = {};
2501
+ panels.model.widget = require( './model/widget' );
2502
+ panels.model.cell = require( './model/cell' );
2503
+ panels.model.row = require( './model/row' );
2504
+ panels.model.builder = require( './model/builder' );
2505
+ panels.model.historyEntry = require( './model/history-entry' );
2506
+
2507
+ // The collections
2508
+ panels.collection = {};
2509
+ panels.collection.widgets = require( './collection/widgets' );
2510
+ panels.collection.cells = require( './collection/cells' );
2511
+ panels.collection.rows = require( './collection/rows' );
2512
+ panels.collection.historyEntries = require( './collection/history-entries' );
2513
+
2514
+ // The views
2515
+ panels.view = {};
2516
+ panels.view.widget = require( './view/widget' );
2517
+ panels.view.cell = require( './view/cell' );
2518
+ panels.view.row = require( './view/row' );
2519
+ panels.view.builder = require( './view/builder' );
2520
+ panels.view.dialog = require( './view/dialog' );
2521
+ panels.view.styles = require( './view/styles' );
2522
+ panels.view.liveEditor = require( './view/live-editor' );
2523
+
2524
+ // The dialogs
2525
+ panels.dialog = {};
2526
+ panels.dialog.builder = require( './dialog/builder' );
2527
+ panels.dialog.widgets = require( './dialog/widgets' );
2528
+ panels.dialog.widget = require( './dialog/widget' );
2529
+ panels.dialog.prebuilt = require( './dialog/prebuilt' );
2530
+ panels.dialog.row = require( './dialog/row' );
2531
+ panels.dialog.history = require( './dialog/history' );
2532
+
2533
+ // The utils
2534
+ panels.utils = {};
2535
+ panels.utils.menu = require( './utils/menu' );
2536
+
2537
+ // jQuery Plugins
2538
+ jQuery.fn.soPanelsSetupBuilderWidget = require( './jquery/setup-builder-widget' );
2539
+
2540
+
2541
+ // Set up Page Builder if we're on the main interface
2542
+ jQuery( function ( $ ) {
2543
+
2544
+ var container,
2545
+ field,
2546
+ form,
2547
+ builderConfig;
2548
+
2549
+ var $panelsMetabox = $( '#siteorigin-panels-metabox' );
2550
+ form = $( 'form#post' );
2551
+ if ( $panelsMetabox.length && form.length ) {
2552
+ // This is usually the case when we're in the post edit interface
2553
+ container = $panelsMetabox;
2554
+ field = $panelsMetabox.find( '.siteorigin-panels-data-field' );
2555
+
2556
+ builderConfig = {
2557
+ editorType: 'tinyMCE',
2558
+ postId: $( '#post_ID' ).val(),
2559
+ editorId: '#content',
2560
+ builderType: $panelsMetabox.data( 'builder-type' ),
2561
+ builderSupports: $panelsMetabox.data( 'builder-supports' ),
2562
+ loadOnAttach: panelsOptions.loadOnAttach && $( '#auto_draft' ).val() == 1,
2563
+ loadLiveEditor: $panelsMetabox.data('live-editor') == 1,
2564
+ liveEditorPreview: container.data('preview-url')
2565
+ };
2566
+ }
2567
+ else if ( $( '.siteorigin-panels-builder-form' ).length ) {
2568
+ // We're dealing with another interface like the custom home page interface
2569
+ var $$ = $( '.siteorigin-panels-builder-form' );
2570
+
2571
+ container = $$.find( '.siteorigin-panels-builder-container' );
2572
+ field = $$.find( 'input[name="panels_data"]' );
2573
+ form = $$;
2574
+
2575
+ builderConfig = {
2576
+ editorType: 'standalone',
2577
+ postId: $$.data( 'post-id' ),
2578
+ editorId: '#post_content',
2579
+ builderType: $$.data( 'type' ),
2580
+ builderSupports: $$.data( 'builder-supports' ),
2581
+ loadLiveEditor: false,
2582
+ liveEditorPreview: $$.data( 'preview-url' )
2583
+ };
2584
+ }
2585
+
2586
+ if ( ! _.isUndefined( container ) ) {
2587
+ // If we have a container, then set up the main builder
2588
+ var panels = window.siteoriginPanels;
2589
+
2590
+ // Create the main builder model
2591
+ var builderModel = new panels.model.builder();
2592
+
2593
+ // Now for the view to display the builder
2594
+ var builderView = new panels.view.builder( {
2595
+ model: builderModel,
2596
+ config: builderConfig
2597
+ } );
2598
+
2599
+ // Trigger an event before the panels setup to allow adding listeners for various builder events which are
2600
+ // triggered during initial setup.
2601
+ $(document).trigger('before_panels_setup', builderView);
2602
+
2603
+ // Set up the builder view
2604
+ builderView
2605
+ .render()
2606
+ .attach( {
2607
+ container: container
2608
+ } )
2609
+ .setDataField( field )
2610
+ .attachToEditor();
2611
+
2612
+ // When the form is submitted, update the panels data
2613
+ form.submit( function () {
2614
+ // Refresh the data
2615
+ builderModel.refreshPanelsData();
2616
+ } );
2617
+
2618
+ container.removeClass( 'so-panels-loading' );
2619
+
2620
+ // Trigger a global jQuery event after we've setup the builder view. Everything is accessible form there
2621
+ $( document ).trigger( 'panels_setup', builderView, window.panels );
2622
+ }
2623
+
2624
+ // Setup new widgets when they're added in the standard widget interface
2625
+ $( document ).on( 'widget-added', function ( e, widget ) {
2626
+ $( widget ).find( '.siteorigin-page-builder-widget' ).soPanelsSetupBuilderWidget();
2627
+ } );
2628
+
2629
+ // Setup existing widgets on the page (for the widgets interface)
2630
+ if ( ! $( 'body' ).hasClass( 'wp-customizer' ) ) {
2631
+ $( function () {
2632
+ $( '.siteorigin-page-builder-widget' ).soPanelsSetupBuilderWidget();
2633
+ } );
2634
+ }
2635
+
2636
+ // A global escape handler
2637
+ $(window).on('keyup', function(e){
2638
+ // [Esc] to close
2639
+ if ( e.which === 27 ) {
2640
+ // Trigger a click on the last visible Page Builder window
2641
+ $( '.so-panels-dialog-wrapper, .so-panels-live-editor' ).filter(':visible')
2642
+ .last().find('.so-title-bar .so-close, .live-editor-close').click();
2643
+ }
2644
+ });
2645
+ } );
2646
+
2647
+ },{"./collection/cells":1,"./collection/history-entries":2,"./collection/rows":3,"./collection/widgets":4,"./dialog/builder":5,"./dialog/history":6,"./dialog/prebuilt":7,"./dialog/row":8,"./dialog/widget":9,"./dialog/widgets":10,"./helpers/clipboard":11,"./helpers/page-scroll":12,"./helpers/serialize":13,"./helpers/utils":14,"./jquery/setup-builder-widget":15,"./model/builder":17,"./model/cell":18,"./model/history-entry":19,"./model/row":20,"./model/widget":21,"./utils/menu":22,"./view/builder":23,"./view/cell":24,"./view/dialog":25,"./view/live-editor":26,"./view/row":27,"./view/styles":28,"./view/widget":29}],17:[function(require,module,exports){
2648
+ module.exports = Backbone.Model.extend({
2649
+ layoutPosition: {
2650
+ BEFORE: 'before',
2651
+ AFTER: 'after',
2652
+ REPLACE: 'replace',
2653
+ },
2654
+
2655
+ rows: {},
2656
+
2657
+ defaults: {
2658
+ 'data': {
2659
+ 'widgets': [],
2660
+ 'grids': [],
2661
+ 'grid_cells': []
2662
+ }
2663
+ },
2664
+
2665
+ initialize: function () {
2666
+ // These are the main rows in the interface
2667
+ this.set( 'rows', new panels.collection.rows() );
2668
+ },
2669
+
2670
+ /**
2671
+ * Add a new row to this builder.
2672
+ *
2673
+ * @param attrs
2674
+ * @param cells
2675
+ * @param options
2676
+ */
2677
+ addRow: function (attrs, cells, options) {
2678
+ options = _.extend({
2679
+ noAnimate: false
2680
+ }, options);
2681
+
2682
+ var cellCollection = new panels.collection.cells(cells);
2683
+
2684
+ attrs = _.extend({
2685
+ collection: this.get('rows'),
2686
+ cells: cellCollection,
2687
+ }, attrs);
2688
+
2689
+ // Create the actual row
2690
+ var row = new panels.model.row(attrs);
2691
+ row.builder = this;
2692
+
2693
+ this.get('rows').add( row, options );
2694
+
2695
+ return row;
2696
+ },
2697
+
2698
+ /**
2699
+ * Load the panels data into the builder
2700
+ *
2701
+ * @param data Object the layout and widgets data to load.
2702
+ * @param position string Where to place the new layout. Allowed options are 'before', 'after'. Anything else will
2703
+ * cause the new layout to replace the old one.
2704
+ */
2705
+ loadPanelsData: function ( data, position ) {
2706
+ try {
2707
+ if ( position === this.layoutPosition.BEFORE ) {
2708
+ data = this.concatPanelsData( data, this.getPanelsData() );
2709
+ } else if ( position === this.layoutPosition.AFTER ) {
2710
+ data = this.concatPanelsData( this.getPanelsData(), data );
2711
+ }
2712
+
2713
+ // Start by destroying any rows that currently exist. This will in turn destroy cells, widgets and all the associated views
2714
+ this.emptyRows();
2715
+
2716
+ // This will empty out the current rows and reload the builder data.
2717
+ this.set( 'data', JSON.parse( JSON.stringify( data ) ), {silent: true} );
2718
+
2719
+ var cit = 0;
2720
+ var rows = [];
2721
+
2722
+ if ( _.isUndefined( data.grid_cells ) ) {
2723
+ this.trigger( 'load_panels_data' );
2724
+ return;
2725
+ }
2726
+
2727
+ var gi;
2728
+ for ( var ci = 0; ci < data.grid_cells.length; ci ++ ) {
2729
+ gi = parseInt( data.grid_cells[ci].grid );
2730
+ if ( _.isUndefined( rows[gi] ) ) {
2731
+ rows[gi] = [];
2732
+ }
2733
+
2734
+ rows[gi].push( data.grid_cells[ci] );
2735
+ }
2736
+
2737
+ var builderModel = this;
2738
+ _.each( rows, function ( row, i ) {
2739
+ var rowAttrs = {};
2740
+
2741
+ if ( ! _.isUndefined( data.grids[i].style ) ) {
2742
+ rowAttrs.style = data.grids[i].style;
2743
+ }
2744
+
2745
+ if ( ! _.isUndefined( data.grids[i].ratio) ) {
2746
+ rowAttrs.ratio = data.grids[i].ratio;
2747
+ }
2748
+
2749
+ if ( ! _.isUndefined( data.grids[i].ratio_direction) ) {
2750
+ rowAttrs.ratio_direction = data.grids[i].ratio_direction
2751
+ }
2752
+
2753
+ if ( ! _.isUndefined( data.grids[i].color_label) ) {
2754
+ rowAttrs.color_label = data.grids[i].color_label;
2755
+ }
2756
+
2757
+ if ( ! _.isUndefined( data.grids[i].label) ) {
2758
+ rowAttrs.label = data.grids[i].label;
2759
+ }
2760
+ // This will create and add the row model and its cells
2761
+ builderModel.addRow(rowAttrs, row, {noAnimate: true} );
2762
+ } );
2763
+
2764
+
2765
+ if ( _.isUndefined( data.widgets ) ) {
2766
+ return;
2767
+ }
2768
+
2769
+ // Add the widgets
2770
+ _.each( data.widgets, function ( widgetData ) {
2771
+ var panels_info = null;
2772
+ if ( ! _.isUndefined( widgetData.panels_info ) ) {
2773
+ panels_info = widgetData.panels_info;
2774
+ delete widgetData.panels_info;
2775
+ } else {
2776
+ panels_info = widgetData.info;
2777
+ delete widgetData.info;
2778
+ }
2779
+
2780
+ var row = builderModel.get('rows').at( parseInt( panels_info.grid ) );
2781
+ var cell = row.get('cells').at( parseInt( panels_info.cell ) );
2782
+
2783
+ var newWidget = new panels.model.widget( {
2784
+ class: panels_info.class,
2785
+ values: widgetData
2786
+ } );
2787
+
2788
+ if ( ! _.isUndefined( panels_info.style ) ) {
2789
+ newWidget.set( 'style', panels_info.style );
2790
+ }
2791
+
2792
+ if ( ! _.isUndefined( panels_info.read_only ) ) {
2793
+ newWidget.set( 'read_only', panels_info.read_only );
2794
+ }
2795
+ if ( ! _.isUndefined( panels_info.widget_id ) ) {
2796
+ newWidget.set( 'widget_id', panels_info.widget_id );
2797
+ }
2798
+ else {
2799
+ newWidget.set( 'widget_id', panels.helpers.utils.generateUUID() );
2800
+ }
2801
+
2802
+ if ( ! _.isUndefined( panels_info.label ) ) {
2803
+ newWidget.set( 'label', panels_info.label );
2804
+ }
2805
+
2806
+ newWidget.cell = cell;
2807
+ cell.get('widgets').add( newWidget, { noAnimate: true } );
2808
+ } );
2809
+
2810
+ this.trigger( 'load_panels_data' );
2811
+ }
2812
+ catch ( err ) {
2813
+ console.log( 'Error loading data: ' + err.message );
2814
+
2815
+ }
2816
+ },
2817
+
2818
+ /**
2819
+ * Concatenate the second set of Page Builder data to the first. There is some validation of input, but for the most
2820
+ * part it's up to the caller to ensure the Page Builder data is well formed.
2821
+ */
2822
+ concatPanelsData: function ( panelsDataA, panelsDataB ) {
2823
+
2824
+ if ( _.isUndefined( panelsDataB ) || _.isUndefined( panelsDataB.grids ) || _.isEmpty( panelsDataB.grids ) ||
2825
+ _.isUndefined( panelsDataB.grid_cells ) || _.isEmpty( panelsDataB.grid_cells ) ) {
2826
+ return panelsDataA;
2827
+ }
2828
+
2829
+ if ( _.isUndefined( panelsDataA ) || _.isUndefined( panelsDataA.grids ) || _.isEmpty( panelsDataA.grids ) ) {
2830
+ return panelsDataB;
2831
+ }
2832
+
2833
+ var gridsBOffset = panelsDataA.grids.length;
2834
+ var widgetsBOffset = ! _.isUndefined( panelsDataA.widgets ) ? panelsDataA.widgets.length : 0;
2835
+ var newPanelsData = {grids: [], 'grid_cells': [], 'widgets': []};
2836
+
2837
+ // Concatenate grids (rows)
2838
+ newPanelsData.grids = panelsDataA.grids.concat( panelsDataB.grids );
2839
+
2840
+ // Create a copy of panelsDataA grid_cells and widgets
2841
+ if ( ! _.isUndefined( panelsDataA.grid_cells ) ) {
2842
+ newPanelsData.grid_cells = panelsDataA.grid_cells.slice();
2843
+ }
2844
+ if ( ! _.isUndefined( panelsDataA.widgets ) ) {
2845
+ newPanelsData.widgets = panelsDataA.widgets.slice();
2846
+ }
2847
+
2848
+ var i;
2849
+ // Concatenate grid cells (row columns)
2850
+ for ( i = 0; i < panelsDataB.grid_cells.length; i ++ ) {
2851
+ var gridCellB = panelsDataB.grid_cells[i];
2852
+ gridCellB.grid = parseInt( gridCellB.grid ) + gridsBOffset;
2853
+ newPanelsData.grid_cells.push( gridCellB );
2854
+ }
2855
+
2856
+ // Concatenate widgets
2857
+ if ( ! _.isUndefined( panelsDataB.widgets ) ) {
2858
+ for ( i = 0; i < panelsDataB.widgets.length; i ++ ) {
2859
+ var widgetB = panelsDataB.widgets[i];
2860
+ widgetB.panels_info.grid = parseInt( widgetB.panels_info.grid ) + gridsBOffset;
2861
+ widgetB.panels_info.id = parseInt( widgetB.panels_info.id ) + widgetsBOffset;
2862
+ newPanelsData.widgets.push( widgetB );
2863
+ }
2864
+ }
2865
+
2866
+ return newPanelsData;
2867
+ },
2868
+
2869
+ /**
2870
+ * Convert the content of the builder into a object that represents the page builder data
2871
+ */
2872
+ getPanelsData: function () {
2873
+
2874
+ var builder = this;
2875
+
2876
+ var data = {
2877
+ 'widgets': [],
2878
+ 'grids': [],
2879
+ 'grid_cells': []
2880
+ };
2881
+ var widgetId = 0;
2882
+
2883
+ this.get('rows').each( function ( row, ri ) {
2884
+
2885
+ row.get('cells').each( function ( cell, ci ) {
2886
+
2887
+ cell.get('widgets').each( function ( widget, wi ) {
2888
+ // Add the data for the widget, including the panels_info field.
2889
+ var panels_info = {
2890
+ class: widget.get( 'class' ),
2891
+ raw: widget.get( 'raw' ),
2892
+ grid: ri,
2893
+ cell: ci,
2894
+ // Strictly this should be an index
2895
+ id: widgetId ++,
2896
+ widget_id: widget.get( 'widget_id' ),
2897
+ style: widget.get( 'style' ),
2898
+ label: widget.get( 'label' ),
2899
+ };
2900
+
2901
+ if( _.isEmpty( panels_info.widget_id ) ) {
2902
+ panels_info.widget_id = panels.helpers.utils.generateUUID();
2903
+ }
2904
+
2905
+ var values = _.extend( _.clone( widget.get( 'values' ) ), {
2906
+ panels_info: panels_info
2907
+ } );
2908
+ data.widgets.push( values );
2909
+ } );
2910
+
2911
+ // Add the cell info
2912
+ data.grid_cells.push( {
2913
+ grid: ri,
2914
+ index: ci,
2915
+ weight: cell.get( 'weight' ),
2916
+ style: cell.get( 'style' ),
2917
+ } );
2918
+
2919
+ } );
2920
+
2921
+ data.grids.push( {
2922
+ cells: row.get('cells').length,
2923
+ style: row.get( 'style' ),
2924
+ ratio: row.get('ratio'),
2925
+ ratio_direction: row.get('ratio_direction'),
2926
+ color_label: row.get( 'color_label' ),
2927
+ label: row.get( 'label' ),
2928
+ } );
2929
+
2930
+ } );
2931
+
2932
+ return data;
2933
+
2934
+ },
2935
+
2936
+ /**
2937
+ * This will check all the current entries and refresh the panels data
2938
+ */
2939
+ refreshPanelsData: function ( args ) {
2940
+ args = _.extend( {
2941
+ silent: false
2942
+ }, args );
2943
+
2944
+ var oldData = this.get( 'data' );
2945
+ var newData = this.getPanelsData();
2946
+ this.set( 'data', newData, {silent: true} );
2947
+
2948
+ if ( ! args.silent && JSON.stringify( newData ) !== JSON.stringify( oldData ) ) {
2949
+ // The default change event doesn't trigger on deep changes, so we'll trigger our own
2950
+ this.trigger( 'change' );
2951
+ this.trigger( 'change:data' );
2952
+ this.trigger( 'refresh_panels_data', newData, args );
2953
+ }
2954
+ },
2955
+
2956
+ /**
2957
+ * Empty all the rows and the cells/widgets they contain.
2958
+ */
2959
+ emptyRows: function () {
2960
+ _.invoke( this.get('rows').toArray(), 'destroy' );
2961
+ this.get('rows').reset();
2962
+
2963
+ return this;
2964
+ },
2965
+
2966
+ isValidLayoutPosition: function ( position ) {
2967
+ return position === this.layoutPosition.BEFORE ||
2968
+ position === this.layoutPosition.AFTER ||
2969
+ position === this.layoutPosition.REPLACE;
2970
+ },
2971
+
2972
+ /**
2973
+ * Convert HTML into Panels Data
2974
+ * @param html
2975
+ */
2976
+ getPanelsDataFromHtml: function( html, editorClass ){
2977
+ var thisModel = this;
2978
+ var $html = jQuery( '<div id="wrapper">' + html + '</div>' );
2979
+
2980
+ if( $html.find('.panel-layout .panel-grid').length ) {
2981
+ // This looks like Page Builder html, lets try parse it
2982
+ var panels_data = {
2983
+ grids: [],
2984
+ grid_cells: [],
2985
+ widgets: [],
2986
+ };
2987
+
2988
+ // The Regex object that'll match SiteOrigin widgets
2989
+ var re = new RegExp( panelsOptions.siteoriginWidgetRegex , "i" );
2990
+ var decodeEntities = (function() {
2991
+ // this prevents any overhead from creating the object each time
2992
+ var element = document.createElement('div');
2993
+
2994
+ function decodeHTMLEntities (str) {
2995
+ if(str && typeof str === 'string') {
2996
+ // strip script/html tags
2997
+ str = str.replace(/<script[^>]*>([\S\s]*?)<\/script>/gmi, '');
2998
+ str = str.replace(/<\/?\w(?:[^"'>]|"[^"]*"|'[^']*')*>/gmi, '');
2999
+ element.innerHTML = str;
3000
+ str = element.textContent;
3001
+ element.textContent = '';
3002
+ }
3003
+
3004
+ return str;
3005
+ }
3006
+
3007
+ return decodeHTMLEntities;
3008
+ })();
3009
+
3010
+ // Remove all wrapping divs from a widget to get its html
3011
+ var getTextWidgetContents = function( $el ){
3012
+ var $divs = $el.find( 'div' );
3013
+ if( ! $divs.length ) {
3014
+ return $el.html();
3015
+ }
3016
+
3017
+ var i;
3018
+ for( i = 0; i < $divs.length - 1; i++ ) {
3019
+ if( jQuery.trim( $divs.eq(i).text() ) != jQuery.trim( $divs.eq(i+1).text() ) ) {
3020
+ break;
3021
+ }
3022
+ }
3023
+
3024
+ var title = $divs.eq( i ).find( '.widget-title:header' ),
3025
+ titleText = '';
3026
+
3027
+ if( title.length ) {
3028
+ titleText = title.html();
3029
+ title.remove();
3030
+ }
3031
+
3032
+ return {
3033
+ title: titleText,
3034
+ text: $divs.eq(i).html(),
3035
+ };
3036
+ };
3037
+
3038
+ var $layout = $html.find( '.panel-layout' ).eq(0);
3039
+ var filterNestedLayout = function( i, el ){
3040
+ return jQuery( el ).closest( '.panel-layout' ).is( $layout );
3041
+ };
3042
+
3043
+ $html.find('> .panel-layout > .panel-grid').filter( filterNestedLayout ).each( function( ri, el ){
3044
+ var $row = jQuery( el ),
3045
+ $cells = $row.find( '.panel-grid-cell' ).filter( filterNestedLayout );
3046
+
3047
+ panels_data.grids.push( {
3048
+ cells: $cells.length,
3049
+ style: $row.data( 'style' ),
3050
+ ratio: $row.data( 'ratio' ),
3051
+ ratio_direction: $row.data( 'ratio-direction' ),
3052
+ color_label: $row.data( 'color-label' ),
3053
+ label: $row.data( 'label' ),
3054
+ } );
3055
+
3056
+ $cells.each( function( ci, el ){
3057
+ var $cell = jQuery( el ),
3058
+ $widgets = $cell.find( '.so-panel' ).filter( filterNestedLayout );
3059
+
3060
+ panels_data.grid_cells.push( {
3061
+ grid: ri,
3062
+ weight: ! _.isUndefined( $cell.data( 'weight' ) ) ? parseFloat( $cell.data( 'weight' ) ) : 1,
3063
+ style: $cell.data( 'style' ),
3064
+ } );
3065
+
3066
+ $widgets.each( function( wi, el ){
3067
+ var $widget = jQuery(el),
3068
+ widgetContent = $widget.find('.panel-widget-style').length ? $widget.find('.panel-widget-style').html() : $widget.html(),
3069
+ panels_info = {
3070
+ grid: ri,
3071
+ cell: ci,
3072
+ style: $widget.data( 'style' ),
3073
+ raw: false,
3074
+ label: $widget.data( 'label' )
3075
+ };
3076
+
3077
+ widgetContent = widgetContent.trim();
3078
+
3079
+ // Check if this is a SiteOrigin Widget
3080
+ var match = re.exec( widgetContent );
3081
+ if( ! _.isNull( match ) && widgetContent.replace( re, '' ).trim() === '' ) {
3082
+ try {
3083
+ var classMatch = /class="(.*?)"/.exec( match[3] ),
3084
+ dataInput = jQuery( match[5] ),
3085
+ data = JSON.parse( decodeEntities( dataInput.val( ) ) ),
3086
+ newWidget = data.instance;
3087
+
3088
+ panels_info.class = classMatch[1].replace( /\\\\+/g, '\\' );
3089
+ panels_info.raw = false;
3090
+
3091
+ newWidget.panels_info = panels_info;
3092
+ panels_data.widgets.push( newWidget );
3093
+ }
3094
+ catch ( err ) {
3095
+ // There was a problem, so treat this as a standard editor widget
3096
+ panels_info.class = editorClass;
3097
+ panels_data.widgets.push( _.extend( getTextWidgetContents( $widget ), {
3098
+ filter: "1",
3099
+ type: "visual",
3100
+ panels_info: panels_info
3101
+ } ) );
3102
+ }
3103
+
3104
+ // Continue
3105
+ return true;
3106
+ }
3107
  else if( widgetContent.indexOf( 'panel-layout' ) !== -1 ) {
3108
  // Check if this is a layout widget
3109
  var $widgetContent = jQuery( '<div>' + widgetContent + '</div>' );
3115
  panels_info: panels_info
3116
  } );
3117
 
3118
+ // continue
3119
+ return true;
3120
+ }
3121
+ }
3122
+
3123
+ // This is a standard editor class widget
3124
+ panels_info.class = editorClass;
3125
+ panels_data.widgets.push( _.extend( getTextWidgetContents( $widget ), {
3126
+ filter: "1",
3127
+ type: "visual",
3128
+ panels_info: panels_info
3129
+ } ) );
3130
+ return true;
3131
+ } );
3132
+ } );
3133
+ } );
3134
+
3135
+ // Remove all the Page Builder content
3136
+ $html.find('.panel-layout').remove();
3137
+ $html.find('style[data-panels-style-for-post]').remove();
3138
+
3139
+ // If there's anything left, add it to an editor widget at the end of panels_data
3140
+ if( $html.html().replace(/^\s+|\s+$/gm,'').length ) {
3141
+ panels_data.grids.push( {
3142
+ cells: 1,
3143
+ style: {},
3144
+ } );
3145
+ panels_data.grid_cells.push( {
3146
+ grid: panels_data.grids.length - 1,
3147
+ weight: 1,
3148
+ } );
3149
+ panels_data.widgets.push( {
3150
+ filter: "1",
3151
+ text: $html.html().replace(/^\s+|\s+$/gm,''),
3152
+ title: "",
3153
+ type: "visual",
3154
+ panels_info: {
3155
+ class: editorClass,
3156
+ raw: false,
3157
+ grid: panels_data.grids.length - 1,
3158
+ cell: 0
3159
+ }
3160
+ } );
3161
+ }
3162
+
3163
+ return panels_data;
3164
+ }
3165
+ else {
3166
+ // This is probably just old school post content
3167
+ return {
3168
+ grid_cells: [ { grid: 0, weight: 1 } ],
3169
+ grids: [ { cells: 1 } ],
3170
+ widgets: [
3171
+ {
3172
+ filter: "1",
3173
+ text: html,
3174
+ title: "",
3175
+ type: "visual",
3176
+ panels_info: {
3177
+ class: editorClass,
3178
+ raw: false,
3179
+ grid: 0,
3180
+ cell: 0
3181
+ }
3182
+ }
3183
+ ]
3184
+ };
3185
+ }
3186
+ }
3187
+ } );
3188
+
3189
+ },{}],18:[function(require,module,exports){
3190
+ module.exports = Backbone.Model.extend( {
3191
+ /* A collection of widgets */
3192
+ widgets: {},
3193
+
3194
+ /* The row this model belongs to */
3195
+ row: null,
3196
+
3197
+ defaults: {
3198
+ weight: 0,
3199
+ style: {}
3200
+ },
3201
+
3202
+ indexes: null,
3203
+
3204
+ /**
3205
+ * Set up the cell model
3206
+ */
3207
+ initialize: function () {
3208
+ this.set( 'widgets', new panels.collection.widgets() );
3209
+ this.on( 'destroy', this.onDestroy, this );
3210
+ },
3211
+
3212
+ /**
3213
+ * Triggered when we destroy a cell
3214
+ */
3215
+ onDestroy: function () {
3216
+ // Destroy all the widgets
3217
+ _.invoke( this.get('widgets').toArray(), 'destroy' );
3218
+ this.get('widgets').reset();
3219
+ },
3220
+
3221
+ /**
3222
+ * Create a clone of the cell, along with all its widgets
3223
+ */
3224
+ clone: function ( row, cloneOptions ) {
3225
+ if ( _.isUndefined( row ) ) {
3226
+ row = this.row;
3227
+ }
3228
+ cloneOptions = _.extend( {cloneWidgets: true}, cloneOptions );
3229
+
3230
+ var clone = new this.constructor( this.attributes );
3231
+ clone.set( 'collection', row.get('cells'), {silent: true} );
3232
+ clone.row = row;
3233
+
3234
+ if ( cloneOptions.cloneWidgets ) {
3235
+ // Now we're going add all the widgets that belong to this, to the clone
3236
+ this.get('widgets').each( function ( widget ) {
3237
+ clone.get('widgets').add( widget.clone( clone, cloneOptions ), {silent: true} );
3238
+ } );
3239
+ }
3240
+
3241
+ return clone;
3242
+ }
3243
+
3244
+ } );
3245
+
3246
+ },{}],19:[function(require,module,exports){
3247
+ module.exports = Backbone.Model.extend( {
3248
+ defaults: {
3249
+ text: '',
3250
+ data: '',
3251
+ time: null,
3252
+ count: 1
3253
+ }
3254
+ } );
3255
+
3256
+ },{}],20:[function(require,module,exports){
3257
+ module.exports = Backbone.Model.extend( {
3258
+ /* The builder model */
3259
+ builder: null,
3260
+
3261
+ defaults: {
3262
+ style: {}
3263
+ },
3264
+
3265
+ indexes: null,
3266
+
3267
+ /**
3268
+ * Initialize the row model
3269
+ */
3270
+ initialize: function () {
3271
+ if ( _.isEmpty(this.get('cells') ) ) {
3272
+ this.set('cells', new panels.collection.cells());
3273
+ }
3274
+ else {
3275
+ // Make sure that the cells have this row set as their parent
3276
+ this.get('cells').each( function( cell ){
3277
+ cell.row = this;
3278
+ }.bind( this ) );
3279
+ }
3280
+ this.on( 'destroy', this.onDestroy, this );
3281
+ },
3282
+
3283
+ /**
3284
+ * Add cells to the model row
3285
+ *
3286
+ * @param newCells the updated collection of cell models
3287
+ */
3288
+ setCells: function ( newCells ) {
3289
+ var currentCells = this.get('cells') || new panels.collection.cells();
3290
+ var cellsToRemove = [];
3291
+
3292
+ currentCells.each(function (cell, i) {
3293
+ var newCell = newCells.at(i);
3294
+ if(newCell) {
3295
+ cell.set('weight', newCell.get('weight'));
3296
+ } else {
3297
+ var newParentCell = currentCells.at( newCells.length - 1 );
3298
+
3299
+ // First move all the widgets to the new cell
3300
+ var widgetsToMove = cell.get('widgets').models.slice();
3301
+ for ( var j = 0; j < widgetsToMove.length; j++ ) {
3302
+ widgetsToMove[j].moveToCell( newParentCell, { silent: false } );
3303
+ }
3304
+
3305
+ cellsToRemove.push(cell);
3306
+ }
3307
+ });
3308
+
3309
+ _.each(cellsToRemove, function(cell) {
3310
+ currentCells.remove(cell);
3311
+ });
3312
+
3313
+ if( newCells.length > currentCells.length) {
3314
+ _.each(newCells.slice(currentCells.length, newCells.length), function (newCell) {
3315
+ // TODO: make sure row and collection is set correctly when cell is created then we can just add new cells
3316
+ newCell.set({collection: currentCells});
3317
+ newCell.row = this;
3318
+ currentCells.add(newCell);
3319
+ }.bind(this));
3320
+ }
3321
+
3322
+ // Rescale the cells when we add or remove
3323
+ this.reweightCells();
3324
+ },
3325
+
3326
+ /**
3327
+ * Make sure that all the cell weights add up to 1
3328
+ */
3329
+ reweightCells: function () {
3330
+ var totalWeight = 0;
3331
+ var cells = this.get('cells');
3332
+ cells.each( function ( cell ) {
3333
+ totalWeight += cell.get( 'weight' );
3334
+ } );
3335
+
3336
+ cells.each( function ( cell ) {
3337
+ cell.set( 'weight', cell.get( 'weight' ) / totalWeight );
3338
+ } );
3339
+
3340
+ // This is for the row view to hook into and resize
3341
+ this.trigger( 'reweight_cells' );
3342
+ },
3343
+
3344
+ /**
3345
+ * Triggered when the model is destroyed
3346
+ */
3347
+ onDestroy: function () {
3348
+ // Also destroy all the cells
3349
+ _.invoke( this.get('cells').toArray(), 'destroy' );
3350
+ this.get('cells').reset();
3351
+ },
3352
+
3353
+ /**
3354
+ * Create a clone of the row, along with all its cells
3355
+ *
3356
+ * @param {panels.model.builder} builder The builder model to attach this to.
3357
+ *
3358
+ * @return {panels.model.row} The cloned row.
3359
+ */
3360
+ clone: function ( builder ) {
3361
+ if ( _.isUndefined( builder ) ) {
3362
+ builder = this.builder;
3363
+ }
3364
+
3365
+ var clone = new this.constructor( this.attributes );
3366
+ clone.set( 'collection', builder.get('rows'), {silent: true} );
3367
+ clone.builder = builder;
3368
+
3369
+ var cellClones = new panels.collection.cells();
3370
+ this.get('cells').each( function ( cell ) {
3371
+ cellClones.add( cell.clone( clone ), {silent: true} );
3372
+ } );
3373
+
3374
+ clone.set( 'cells', cellClones );
3375
+
3376
+ return clone;
3377
+ }
3378
+ } );
3379
+
3380
+ },{}],21:[function(require,module,exports){
3381
+ /**
3382
+ * Model for an instance of a widget
3383
+ */
3384
+ module.exports = Backbone.Model.extend( {
3385
+
3386
+ cell: null,
3387
+
3388
+ defaults: {
3389
+ // The PHP Class of the widget
3390
+ class: null,
3391
+
3392
+ // Is this class missing? Missing widgets are a special case.
3393
+ missing: false,
3394
+
3395
+ // The values of the widget
3396
+ values: {},
3397
+
3398
+ // Have the current values been passed through the widgets update function
3399
+ raw: false,
3400
+
3401
+ // Visual style fields
3402
+ style: {},
3403
+
3404
+ read_only: false,
3405
+ widget_id: '',
3406
+ },
3407
+
3408
+ indexes: null,
3409
+
3410
+ initialize: function () {
3411
+ var widgetClass = this.get( 'class' );
3412
+ if ( _.isUndefined( panelsOptions.widgets[widgetClass] ) || ! panelsOptions.widgets[widgetClass].installed ) {
3413
+ this.set( 'missing', true );
3414
+ }
3415
+ },
3416
+
3417
+ /**
3418
+ * @param field
3419
+ * @returns {*}
3420
+ */
3421
+ getWidgetField: function ( field ) {
3422
+ if ( _.isUndefined( panelsOptions.widgets[this.get( 'class' )] ) ) {
3423
+ if ( field === 'title' || field === 'description' ) {
3424
+ return panelsOptions.loc.missing_widget[field];
3425
+ } else {
3426
+ return '';
3427
+ }
3428
+ } else if ( this.has( 'label' ) && ! _.isEmpty( this.get( 'label' ) ) ) {
3429
+ // Use the label instead of the actual widget title
3430
+ return this.get( 'label' );
3431
+ } else {
3432
+ return panelsOptions.widgets[ this.get( 'class' ) ][ field ];
3433
+ }
3434
+ },
3435
+
3436
+ /**
3437
+ * Move this widget model to a new cell. Called by the views.
3438
+ *
3439
+ * @param panels.model.cell newCell
3440
+ * @param object options The options passed to the
3441
+ *
3442
+ * @return boolean Indicating if the widget was moved into a different cell
3443
+ */
3444
+ moveToCell: function ( newCell, options, at ) {
3445
+ options = _.extend( {
3446
+ silent: true,
3447
+ }, options );
3448
+
3449
+ this.cell = newCell;
3450
+ this.collection.remove( this, options );
3451
+ newCell.get('widgets').add( this, _.extend( {
3452
+ at: at
3453
+ }, options ) );
3454
+
3455
+ // This should be used by views to reposition everything.
3456
+ this.trigger( 'move_to_cell', newCell, at );
3457
+
3458
+ return this;
3459
+ },
3460
+
3461
+ /**
3462
+ * This is basically a wrapper for set that checks if we need to trigger a change
3463
+ */
3464
+ setValues: function ( values ) {
3465
+ var hasChanged = false;
3466
+ if ( JSON.stringify( values ) !== JSON.stringify( this.get( 'values' ) ) ) {
3467
+ hasChanged = true;
3468
+ }
3469
+
3470
+ this.set( 'values', values, {silent: true} );
3471
+
3472
+ if ( hasChanged ) {
3473
+ // We'll trigger our own change events.
3474
+ // NB: Must include the model being changed (i.e. `this`) as a workaround for a bug in Backbone 1.2.3
3475
+ this.trigger( 'change', this );
3476
+ this.trigger( 'change:values' );
3477
+ }
3478
+ },
3479
+
3480
+ /**
3481
+ * Create a clone of this widget attached to the given cell.
3482
+ *
3483
+ * @param {panels.model.cell} cell The cell model we're attaching this widget clone to.
3484
+ * @returns {panels.model.widget}
3485
+ */
3486
+ clone: function ( cell, options ) {
3487
+ if ( _.isUndefined( cell ) ) {
3488
+ cell = this.cell;
3489
+ }
3490
+
3491
+ var clone = new this.constructor( this.attributes );
3492
+
3493
+ // Create a deep clone of the original values
3494
+ var cloneValues = JSON.parse( JSON.stringify( this.get( 'values' ) ) );
3495
+
3496
+ // We want to exclude any fields that start with _ from the clone. Assuming these are internal.
3497
+ var cleanClone = function ( vals ) {
3498
+ _.each( vals, function ( el, i ) {
3499
+ if ( _.isString( i ) && i[0] === '_' ) {
3500
+ delete vals[i];
3501
+ }
3502
+ else if ( _.isObject( vals[i] ) ) {
3503
+ cleanClone( vals[i] );
3504
+ }
3505
+ } );
3506
+
3507
+ return vals;
3508
+ };
3509
+ cloneValues = cleanClone( cloneValues );
3510
+
3511
+ if ( this.get( 'class' ) === "SiteOrigin_Panels_Widgets_Layout" ) {
3512
+ // Special case of this being a layout widget, it needs a new ID
3513
+ cloneValues.builder_id = Math.random().toString( 36 ).substr( 2 );
3514
+ }
3515
+
3516
+ clone.set( 'widget_id', '' );
3517
+ clone.set( 'values', cloneValues, {silent: true} );
3518
+ clone.set( 'collection', cell.get('widgets'), {silent: true} );
3519
+ clone.cell = cell;
3520
+
3521
+ // This is used to force a form reload later on
3522
+ clone.isDuplicate = true;
3523
+
3524
+ return clone;
3525
+ },
3526
+
3527
+ /**
3528
+ * Gets the value that makes most sense as the title.
3529
+ */
3530
+ getTitle: function () {
3531
+ var widgetData = panelsOptions.widgets[this.get( 'class' )];
3532
+
3533
+ if ( _.isUndefined( widgetData ) ) {
3534
+ return this.get( 'class' ).replace( /_/g, ' ' );
3535
+ }
3536
+ else if ( ! _.isUndefined( widgetData.panels_title ) ) {
3537
+ // This means that the widget has told us which field it wants us to use as a title
3538
+ if ( widgetData.panels_title === false ) {
3539
+ return panelsOptions.widgets[this.get( 'class' )].description;
3540
+ }
3541
+ }
3542
+
3543
+ var values = this.get( 'values' );
3544
+
3545
+ // Create a list of fields to check for a title
3546
+ var titleFields = ['title', 'text'];
3547
+
3548
+ for ( var k in values ) {
3549
+ if(k.charAt(0) === '_' || k === 'so_sidebar_emulator_id' || k === 'option_name'){
3550
+ // Skip Widgets Bundle supporting fields
3551
+ continue;
3552
+ }
3553
+ if ( values.hasOwnProperty( k ) ) {
3554
+ titleFields.push( k );
3555
+ }
3556
+ }
3557
+
3558
+ titleFields = _.uniq( titleFields );
3559
+
3560
+ for ( var i in titleFields ) {
3561
+ if (
3562
+ ! _.isUndefined( values[titleFields[i]] ) &&
3563
+ _.isString( values[titleFields[i]] ) &&
3564
+ values[titleFields[i]] !== '' &&
3565
+ values[titleFields[i]] !== 'on' &&
3566
+ titleFields[i][0] !== '_' && ! jQuery.isNumeric( values[titleFields[i]] )
3567
+ ) {
3568
+ var title = values[titleFields[i]];
3569
+ title = title.replace( /<\/?[^>]+(>|$)/g, "" );
3570
+ var parts = title.split( " " );
3571
+ parts = parts.slice( 0, 20 );
3572
+ return parts.join( ' ' );
3573
+ }
3574
+ }
3575
+
3576
+ // If we still have nothing, then just return the widget description
3577
+ return this.getWidgetField( 'description' );
3578
+ }
3579
+
3580
+ } );
3581
+
3582
+ },{}],22:[function(require,module,exports){
3583
+ var panels = window.panels, $ = jQuery;
3584
+
3585
+ module.exports = Backbone.View.extend( {
3586
+ wrapperTemplate: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-context-menu' ).html() ) ),
3587
+ sectionTemplate: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-context-menu-section' ).html() ) ),
3588
+
3589
+ contexts: [],
3590
+ active: false,
3591
+
3592
+ events: {
3593
+ 'keyup .so-search-wrapper input': 'searchKeyUp'
3594
+ },
3595
+
3596
+ /**
3597
+ * Intialize the context menu
3598
+ */
3599
+ initialize: function () {
3600
+ this.listenContextMenu();
3601
+ this.render();
3602
+ this.attach();
3603
+ },
3604
+
3605
+ /**
3606
+ * Listen for the right click context menu
3607
+ */
3608
+ listenContextMenu: function () {
3609
+ var thisView = this;
3610
+
3611
+ $( window ).on( 'contextmenu', function ( e ) {
3612
+ if ( thisView.active && ! thisView.isOverEl( thisView.$el, e ) ) {
3613
+ thisView.closeMenu();
3614
+ thisView.active = false;
3615
+ e.preventDefault();
3616
+ return false;
3617
+ }
3618
+
3619
+ if ( thisView.active ) {
3620
+ // Lets not double up on the context menu
3621
+ return true;
3622
+ }
3623
+
3624
+ // Other components should listen to activate_context
3625
+ thisView.active = false;
3626
+ thisView.trigger( 'activate_context', e, thisView );
3627
+
3628
+ if ( thisView.active ) {
3629
+ // We don't want the default event to happen.
3630
+ e.preventDefault();
3631
+
3632
+ thisView.openMenu( {
3633
+ left: e.pageX,
3634
+ top: e.pageY
3635
+ } );
3636
+ }
3637
+ } );
3638
+ },
3639
+
3640
+ render: function () {
3641
+ this.setElement( this.wrapperTemplate() );
3642
+ },
3643
+
3644
+ attach: function () {
3645
+ this.$el.appendTo( 'body' );
3646
+ },
3647
+
3648
+ /**
3649
+ * Display the actual context menu.
3650
+ *
3651
+ * @param position
3652
+ */
3653
+ openMenu: function ( position ) {
3654
+ this.trigger( 'open_menu' );
3655
+
3656
+ // Start listening for situations when we should close the menu
3657
+ $( window ).on( 'keyup', {menu: this}, this.keyboardListen );
3658
+ $( window ).on( 'click', {menu: this}, this.clickOutsideListen );
3659
+
3660
+ // Set the maximum height of the menu
3661
+ this.$el.css( 'max-height', $( window ).height() - 20 );
3662
+
3663
+ // Correct the left position
3664
+ if ( position.left + this.$el.outerWidth() + 10 >= $( window ).width() ) {
3665
+ position.left = $( window ).width() - this.$el.outerWidth() - 10;
3666
+ }
3667
+ if ( position.left <= 0 ) {
3668
+ position.left = 10;
3669
+ }
3670
+
3671
+ // Check top position
3672
+ if ( position.top + this.$el.outerHeight() - $( window ).scrollTop() + 10 >= $( window ).height() ) {
3673
+ position.top = $( window ).height() + $( window ).scrollTop() - this.$el.outerHeight() - 10;
3674
+ }
3675
+ if ( position.left <= 0 ) {
3676
+ position.left = 10;
3677
+ }
3678
+
3679
+ // position the contextual menu
3680
+ this.$el.css( {
3681
+ left: position.left + 1,
3682
+ top: position.top + 1
3683
+ } ).show();
3684
+ this.$( '.so-search-wrapper input' ).focus();
3685
+ },
3686
+
3687
+ closeMenu: function () {
3688
+ this.trigger( 'close_menu' );
3689
+
3690
+ // Stop listening for situations when we should close the menu
3691
+ $( window ).off( 'keyup', this.keyboardListen );
3692
+ $( window ).off( 'click', this.clickOutsideListen );
3693
+
3694
+ this.active = false;
3695
+ this.$el.empty().hide();
3696
+ },
3697
+
3698
+ /**
3699
+ * Keyboard events handler
3700
+ */
3701
+ keyboardListen: function ( e ) {
3702
+ var menu = e.data.menu;
3703
+
3704
+ switch ( e.which ) {
3705
+ case 27:
3706
+ menu.closeMenu();
3707
+ break;
3708
+ }
3709
+ },
3710
+
3711
+ /**
3712
+ * Listen for a click outside the menu to close it.
3713
+ * @param e
3714
+ */
3715
+ clickOutsideListen: function ( e ) {
3716
+ var menu = e.data.menu;
3717
+ if ( e.which !== 3 && menu.$el.is( ':visible' ) && ! menu.isOverEl( menu.$el, e ) ) {
3718
+ menu.closeMenu();
3719
+ }
3720
+ },
3721
+
3722
+ /**
3723
+ * Add a new section to the contextual menu.
3724
+ *
3725
+ * @param settings
3726
+ * @param items
3727
+ * @param callback
3728
+ */
3729
+ addSection: function ( id, settings, items, callback ) {
3730
+ var thisView = this;
3731
+ settings = _.extend( {
3732
+ display: 5,
3733
+ defaultDisplay: false,
3734
+ search: true,
3735
+
3736
+ // All the labels
3737
+ sectionTitle: '',
3738
+ searchPlaceholder: '',
3739
+
3740
+ // This is the key to be used in items for the title. Makes it easier to list objects
3741
+ titleKey: 'title'
3742
+ }, settings );
3743
+
3744
+ // Create the new section
3745
+ var section = $( this.sectionTemplate( {
3746
+ settings: settings,
3747
+ items: items
3748
+ } ) ).attr( 'id', 'panels-menu-section-' + id );
3749
+ this.$el.append( section );
3750
+
3751
+ section.find( '.so-item:not(.so-confirm)' ).click( function () {
3752
+ var $$ = $( this );
3753
+ callback( $$.data( 'key' ) );
3754
+ thisView.closeMenu();
3755
+ } );
3756
+
3757
+ section.find( '.so-item.so-confirm' ).click( function () {
3758
+ var $$ = $( this );
3759
+
3760
+ if ( $$.hasClass( 'so-confirming' ) ) {
3761
+ callback( $$.data( 'key' ) );
3762
+ thisView.closeMenu();
3763
+ return;
3764
+ }
3765
+
3766
+ $$
3767
+ .data( 'original-text', $$.html() )
3768
+ .addClass( 'so-confirming' )
3769
+ .html( '<span class="dashicons dashicons-yes"></span> ' + panelsOptions.loc.dropdown_confirm );
3770
+
3771
+ setTimeout( function () {
3772
+ $$.removeClass( 'so-confirming' );
3773
+ $$.html( $$.data( 'original-text' ) );
3774
+ }, 2500 );
3775
+ } );
3776
+
3777
+ section.data( 'settings', settings ).find( '.so-search-wrapper input' ).trigger( 'keyup' );
3778
+
3779
+ this.active = true;
3780
+ },
3781
+
3782
+ /**
3783
+ * Check if a section exists in the current menu.
3784
+ *
3785
+ * @param id
3786
+ * @returns {boolean}
3787
+ */
3788
+ hasSection: function( id ){
3789
+ return this.$el.find( '#panels-menu-section-' + id ).length > 0;
3790
+ },
3791
+
3792
+ /**
3793
+ * Handle searching inside a section.
3794
+ *
3795
+ * @param e
3796
+ * @returns {boolean}
3797
+ */
3798
+ searchKeyUp: function ( e ) {
3799
+ var
3800
+ $$ = $( e.currentTarget ),
3801
+ section = $$.closest( '.so-section' ),
3802
+ settings = section.data( 'settings' );
3803
+
3804
+ if ( e.which === 38 || e.which === 40 ) {
3805
+ // First, lets check if this is an up, down or enter press
3806
+ var
3807
+ items = section.find( 'ul li:visible' ),
3808
+ activeItem = items.filter( '.so-active' ).eq( 0 );
3809
+
3810
+ if ( activeItem.length ) {
3811
+ items.removeClass( 'so-active' );
3812
+
3813
+ var activeIndex = items.index( activeItem );
3814
+
3815
+ if ( e.which === 38 ) {
3816
+ if ( activeIndex - 1 < 0 ) {
3817
+ activeItem = items.last();
3818
+ } else {
3819
+ activeItem = items.eq( activeIndex - 1 );
3820
+ }
3821
+ }
3822
+ else if ( e.which === 40 ) {
3823
+ if ( activeIndex + 1 >= items.length ) {
3824
+ activeItem = items.first();
3825
+ } else {
3826
+ activeItem = items.eq( activeIndex + 1 );
3827
+ }
3828
+ }
3829
+ }
3830
+ else if ( e.which === 38 ) {
3831
+ activeItem = items.last();
3832
+ }
3833
+ else if ( e.which === 40 ) {
3834
+ activeItem = items.first();
3835
+ }
3836
+
3837
+ activeItem.addClass( 'so-active' );
3838
+ return false;
3839
+ }
3840
+ if ( e.which === 13 ) {
3841
+ if ( section.find( 'ul li:visible' ).length === 1 ) {
3842
+ // We'll treat a single visible item as active when enter is clicked
3843
+ section.find( 'ul li:visible' ).trigger( 'click' );
3844
+ return false;
3845
+ }
3846
+ section.find( 'ul li.so-active:visible' ).trigger( 'click' );
3847
+ return false;
3848
+ }
3849
+
3850
+ if ( $$.val() === '' ) {
3851
+ // We'll display the defaultDisplay items
3852
+ if ( settings.defaultDisplay ) {
3853
+ section.find( '.so-item' ).hide();
3854
+ for ( var i = 0; i < settings.defaultDisplay.length; i ++ ) {
3855
+ section.find( '.so-item[data-key="' + settings.defaultDisplay[i] + '"]' ).show();
3856
+ }
3857
+ } else {
3858
+ // We'll just display all the items
3859
+ section.find( '.so-item' ).show();
3860
+ }
3861
+ } else {
3862
+ section.find( '.so-item' ).hide().each( function () {
3863
+ var item = $( this );
3864
+ if ( item.html().toLowerCase().indexOf( $$.val().toLowerCase() ) !== - 1 ) {
3865
+ item.show();
3866
+ }
3867
+ } );
3868
+ }
3869
+
3870
+ // Now, we'll only show the first settings.display visible items
3871
+ section.find( '.so-item:visible:gt(' + (
3872
+ settings.display - 1
3873
+ ) + ')' ).hide();
3874
+
3875
+
3876
+ if ( section.find( '.so-item:visible' ).length === 0 && $$.val() !== '' ) {
3877
+ section.find( '.so-no-results' ).show();
3878
+ } else {
3879
+ section.find( '.so-no-results' ).hide();
3880
+ }
3881
+ },
3882
+
3883
+ /**
3884
+ * Check if the given mouse event is over the element
3885
+ * @param el
3886
+ * @param event
3887
+ */
3888
+ isOverEl: function ( el, event ) {
3889
+ var elPos = [
3890
+ [el.offset().left, el.offset().top],
3891
+ [el.offset().left + el.outerWidth(), el.offset().top + el.outerHeight()]
3892
+ ];
3893
+
3894
+ // Return if this event is over the given element
3895
+ return (
3896
+ event.pageX >= elPos[0][0] && event.pageX <= elPos[1][0] &&
3897
+ event.pageY >= elPos[0][1] && event.pageY <= elPos[1][1]
3898
+ );
3899
+ }
3900
+
3901
+ } );
3902
+
3903
+ },{}],23:[function(require,module,exports){
3904
+ var panels = window.panels, $ = jQuery;
3905
+
3906
+ module.exports = Backbone.View.extend( {
3907
+
3908
+ // Config options
3909
+ config: {},
3910
+
3911
+ template: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-builder' ).html() ) ),
3912
+ dialogs: {},
3913
+ rowsSortable: null,
3914
+ dataField: false,
3915
+ currentData: '',
3916
+
3917
+ attachedToEditor: false,
3918
+ attachedVisible: false,
3919
+ liveEditor: undefined,
3920
+ menu: false,
3921
+
3922
+ activeCell: null,
3923
+
3924
+ events: {
3925
+ 'click .so-tool-button.so-widget-add': 'displayAddWidgetDialog',
3926
+ 'click .so-tool-button.so-row-add': 'displayAddRowDialog',
3927
+ 'click .so-tool-button.so-prebuilt-add': 'displayAddPrebuiltDialog',
3928
+ 'click .so-tool-button.so-history': 'displayHistoryDialog',
3929
+ 'click .so-tool-button.so-live-editor': 'displayLiveEditor'
3930
+ },
3931
+
3932
+ /* A row collection */
3933
+ rows: null,
3934
+
3935
+ /**
3936
+ * Initialize the builder
3937
+ */
3938
+ initialize: function ( options ) {
3939
+ var builder = this;
3940
+
3941
+ this.config = _.extend( {
3942
+ loadLiveEditor: false,
3943
+ builderSupports: {}
3944
+ }, options.config );
3945
+
3946
+ // These are the actions that a user can perform in the builder
3947
+ this.config.builderSupports = _.extend( {
3948
+ addRow: true,
3949
+ editRow: true,
3950
+ deleteRow: true,
3951
+ moveRow: true,
3952
+ addWidget: true,
3953
+ editWidget: true,
3954
+ deleteWidget: true,
3955
+ moveWidget: true,
3956
+ prebuilt: true,
3957
+ history: true,
3958
+ liveEditor: true,
3959
+ revertToEditor: true
3960
+ }, this.config.builderSupports );
3961
+
3962
+ // Automatically load the live editor as soon as it's ready
3963
+ if ( options.config.loadLiveEditor ) {
3964
+ this.on( 'builder_live_editor_added', function () {
3965
+ this.displayLiveEditor();
3966
+ } );
3967
+ }
3968
+
3969
+ // Now lets create all the dialog boxes that the main builder interface uses
3970
+ this.dialogs = {
3971
+ widgets: new panels.dialog.widgets(),
3972
+ row: new panels.dialog.row(),
3973
+ prebuilt: new panels.dialog.prebuilt()
3974
+ };
3975
+
3976
+ // Set the builder for each dialog and render it.
3977
+ _.each( this.dialogs, function ( p, i, d ) {
3978
+ d[ i ].setBuilder( builder );
3979
+ } );
3980
+
3981
+ this.dialogs.row.setRowDialogType( 'create' );
3982
+
3983
+ // This handles a new row being added to the collection - we'll display it in the interface
3984
+ this.listenTo( this.model.get( 'rows' ), 'add', this.onAddRow );
3985
+
3986
+ // Reflow the entire builder when ever the
3987
+ $( window ).resize( function ( e ) {
3988
+ if ( e.target === window ) {
3989
+ builder.trigger( 'builder_resize' );
3990
+ }
3991
+ } );
3992
+
3993
+ // When the data changes in the model, store it in the field
3994
+ this.listenTo( this.model, 'change:data load_panels_data', this.storeModelData );
3995
+ this.listenTo( this.model, 'change:data load_panels_data', this.toggleWelcomeDisplay );
3996
+
3997
+ // Handle a content change
3998
+ this.on( 'content_change', this.handleContentChange, this );
3999
+ this.on( 'display_builder', this.handleDisplayBuilder, this );
4000
+ this.on( 'hide_builder', this.handleHideBuilder, this );
4001
+ this.on( 'builder_rendered builder_resize', this.handleBuilderSizing, this );
4002
+
4003
+ this.on( 'display_builder', this.wrapEditorExpandAdjust, this );
4004
+
4005
+ // Create the context menu for this builder
4006
+ this.menu = new panels.utils.menu( {} );
4007
+ this.listenTo( this.menu, 'activate_context', this.activateContextMenu )
4008
+
4009
+ if ( this.config.loadOnAttach ) {
4010
+ this.on( 'builder_attached_to_editor', function () {
4011
+ this.displayAttachedBuilder( { confirm: false } );
4012
+ }, this );
4013
+ }
4014
+
4015
+ return this;
4016
+ },
4017
+
4018
+ /**
4019
+ * Render the builder interface.
4020
+ *
4021
+ * @return {panels.view.builder}
4022
+ */
4023
+ render: function () {
4024
+ // this.$el.html( this.template() );
4025
+ this.setElement( this.template() );
4026
+ this.$el
4027
+ .attr( 'id', 'siteorigin-panels-builder-' + this.cid )
4028
+ .addClass( 'so-builder-container' );
4029
+
4030
+ this.trigger( 'builder_rendered' );
4031
+
4032
+ return this;
4033
+ },
4034
+
4035
+ /**
4036
+ * Attach the builder to the given container
4037
+ *
4038
+ * @param container
4039
+ * @returns {panels.view.builder}
4040
+ */
4041
+ attach: function ( options ) {
4042
+
4043
+ options = _.extend( {
4044
+ container: false,
4045
+ dialog: false
4046
+ }, options );
4047
+
4048
+ if ( options.dialog ) {
4049
+ // We're going to add this to a dialog
4050
+ this.dialog = new panels.dialog.builder();
4051
+ this.dialog.builder = this;
4052
+ } else {
4053
+ // Attach this in the standard way
4054
+ this.$el.appendTo( options.container );
4055
+ this.metabox = options.container.closest( '.postbox' );
4056
+ this.initSortable();
4057
+ this.trigger( 'attached_to_container', options.container );
4058
+ }
4059
+
4060
+ this.trigger( 'builder_attached' );
4061
+
4062
+ // Add support for components we have
4063
+
4064
+ if ( this.supports( 'liveEditor' ) ) {
4065
+ this.addLiveEditor();
4066
+ }
4067
+ if ( this.supports( 'history' ) ) {
4068
+ this.addHistoryBrowser();
4069
+ }
4070
+
4071
+ // Hide toolbar buttons we don't support
4072
+ var toolbar = this.$( '.so-builder-toolbar' );
4073
+ var welcomeMessageContainer = this.$( '.so-panels-welcome-message' );
4074
+ var welcomeMessage = panelsOptions.loc.welcomeMessage;
4075
+
4076
+ var supportedItems = [];
4077
+
4078
+ if ( !this.supports( 'addWidget' ) ) {
4079
+ toolbar.find( '.so-widget-add' ).hide();
4080
+ } else {
4081
+ supportedItems.push( welcomeMessage.addWidgetButton );
4082
+ }
4083
+ if ( !this.supports( 'addRow' ) ) {
4084
+ toolbar.find( '.so-row-add' ).hide();
4085
+ } else {
4086
+ supportedItems.push( welcomeMessage.addRowButton );
4087
+ }
4088
+ if ( !this.supports( 'prebuilt' ) ) {
4089
+ toolbar.find( '.so-prebuilt-add' ).hide();
4090
+ } else {
4091
+ supportedItems.push( welcomeMessage.addPrebuiltButton );
4092
+ }
4093
+
4094
+ var msg = '';
4095
+ if ( supportedItems.length === 3 ) {
4096
+ msg = welcomeMessage.threeEnabled;
4097
+ } else if ( supportedItems.length === 2 ) {
4098
+ msg = welcomeMessage.twoEnabled;
4099
+ } else if ( supportedItems.length === 1 ) {
4100
+ msg = welcomeMessage.oneEnabled;
4101
+ } else if ( supportedItems.length === 0 ) {
4102
+ msg = welcomeMessage.addingDisabled;
4103
+ }
4104
+
4105
+ var resTemplate = _.template( panels.helpers.utils.processTemplate( msg ) );
4106
+ var msgHTML = resTemplate( { items: supportedItems } ) + ' ' + welcomeMessage.docsMessage;
4107
+ welcomeMessageContainer.find( '.so-message-wrapper' ).html( msgHTML );
4108
+
4109
+ return this;
4110
+ },
4111
+
4112
+ /**
4113
+ * This will move the Page Builder meta box into the editor if we're in the post/page edit interface.
4114
+ *
4115
+ * @returns {panels.view.builder}
4116
+ */
4117
+ attachToEditor: function () {
4118
+ if ( this.config.editorType !== 'tinyMCE' ) {
4119
+ return this;
4120
+ }
4121
+
4122
+ this.attachedToEditor = true;
4123
+ var metabox = this.metabox;
4124
+ var thisView = this;
4125
+
4126
+ // Handle switching between the page builder and other tabs
4127
+ $( '#wp-content-wrap .wp-editor-tabs' )
4128
+ .find( '.wp-switch-editor' )
4129
+ .click( function ( e ) {
4130
+ e.preventDefault();
4131
+ $( '#wp-content-editor-container' ).show();
4132
+
4133
+ // metabox.hide();
4134
+ $( '#wp-content-wrap' ).removeClass( 'panels-active' );
4135
+ $( '#content-resize-handle' ).show();
4136
+
4137
+ // Make sure the word count is visible
4138
+ thisView.trigger( 'hide_builder' );
4139
+ } ).end()
4140
+ .append(
4141
+ $( '<button type="button" id="content-panels" class="hide-if-no-js wp-switch-editor switch-panels">' + metabox.find( '.hndle span' ).html() + '</button>' )
4142
+ .click( function ( e ) {
4143
+ if ( thisView.displayAttachedBuilder( { confirm: true } ) ) {
4144
+ e.preventDefault();
4145
+ }
4146
+ } )
4147
+ );
4148
+
4149
+ // Switch back to the standard editor
4150
+ if ( this.supports( 'revertToEditor' ) ) {
4151
+ metabox.find( '.so-switch-to-standard' ).click( function ( e ) {
4152
+ e.preventDefault();
4153
+
4154
+ if ( !confirm( panelsOptions.loc.confirm_stop_builder ) ) {
4155
+ return;
4156
+ }
4157
+
4158
+ // User is switching to the standard visual editor
4159
+ thisView.addHistoryEntry( 'back_to_editor' );
4160
+ thisView.model.loadPanelsData( false );
4161
+
4162
+ // Switch back to the standard editor
4163
+ $( '#wp-content-wrap' ).show();
4164
+ metabox.hide();
4165
+
4166
+ // Resize to trigger reflow of WordPress editor stuff
4167
+ $( window ).resize();
4168
+
4169
+ thisView.attachedVisible = false;
4170
+ thisView.trigger( 'hide_builder' );
4171
+ } ).show();
4172
+ }
4173
+
4174
+ // Move the panels box into a tab of the content editor
4175
+ metabox.insertAfter( '#wp-content-wrap' ).hide().addClass( 'attached-to-editor' );
4176
+
4177
+ // Switch to the Page Builder interface as soon as we load the page if there are widgets or the normal editor
4178
+ // isn't supported.
4179
+ var data = this.model.get( 'data' );
4180
+ if ( !_.isEmpty( data.widgets ) || !_.isEmpty( data.grids ) || !this.supports( 'revertToEditor' ) ) {
4181
+ this.displayAttachedBuilder( { confirm: false } );
4182
+ }
4183
+
4184
+ // We will also make this sticky if its attached to an editor.
4185
+ var stickToolbar = function () {
4186
+ var toolbar = thisView.$( '.so-builder-toolbar' );
4187
+
4188
+ if ( thisView.$el.hasClass( 'so-display-narrow' ) ) {
4189
+ // In this case, we don't want to stick the toolbar.
4190
+ toolbar.css( {
4191
+ top: 0,
4192
+ left: 0,
4193
+ width: '100%',
4194
+ position: 'absolute'
4195
+ } );
4196
+ thisView.$el.css( 'padding-top', toolbar.outerHeight() );
4197
+ return;
4198
+ }
4199
+
4200
+ var newTop = $( window ).scrollTop() - thisView.$el.offset().top;
4201
+
4202
+ if ( $( '#wpadminbar' ).css( 'position' ) === 'fixed' ) {
4203
+ newTop += $( '#wpadminbar' ).outerHeight();
4204
+ }
4205
+
4206
+ var limits = {
4207
+ top: 0,
4208
+ bottom: thisView.$el.outerHeight() - toolbar.outerHeight() + 20
4209
+ };
4210
+
4211
+ if ( newTop > limits.top && newTop < limits.bottom ) {
4212
+ if ( toolbar.css( 'position' ) !== 'fixed' ) {
4213
+ // The toolbar needs to stick to the top, over the interface
4214
+ toolbar.css( {
4215
+ top: $( '#wpadminbar' ).outerHeight(),
4216
+ left: thisView.$el.offset().left,
4217
+ width: thisView.$el.outerWidth(),
4218
+ position: 'fixed'
4219
+ } );
4220
+ }
4221
+ } else {
4222
+ // The toolbar needs to be at the top or bottom of the interface
4223
+ toolbar.css( {
4224
+ top: Math.min( Math.max( newTop, 0 ), thisView.$el.outerHeight() - toolbar.outerHeight() + 20 ),
4225
+ left: 0,
4226
+ width: '100%',
4227
+ position: 'absolute'
4228
+ } );
4229
+ }
4230
+
4231
+ thisView.$el.css( 'padding-top', toolbar.outerHeight() );
4232
+ };
4233
+
4234
+ this.on( 'builder_resize', stickToolbar, this );
4235
+ $( document ).scroll( stickToolbar );
4236
+ stickToolbar();
4237
+
4238
+ this.trigger( 'builder_attached_to_editor' );
4239
+
4240
+ return this;
4241
+ },
4242
+
4243
+ /**
4244
+ * Display the builder interface when attached to a WordPress editor
4245
+ */
4246
+ displayAttachedBuilder: function ( options ) {
4247
+ options = _.extend( {
4248
+ confirm: true
4249
+ }, options );
4250
+
4251
+ // Switch to the Page Builder interface
4252
+
4253
+ if ( options.confirm ) {
4254
+ var editor = typeof tinyMCE !== 'undefined' ? tinyMCE.get( 'content' ) : false;
4255
+ var editorContent = ( editor && _.isFunction( editor.getContent ) ) ? editor.getContent() : $( 'textarea#content' ).val();
4256
+
4257
+ if ( editorContent !== '' && !confirm( panelsOptions.loc.confirm_use_builder ) ) {
4258
+ return false;
4259
+ }
4260
+ }
4261
+
4262
+ // Hide the standard content editor
4263
+ $( '#wp-content-wrap' ).hide();
4264
+
4265
+
4266
+ $( '#editor-expand-toggle' ).on( 'change.editor-expand', function () {
4267
+ if ( !$( this ).prop( 'checked' ) ) {
4268
+ $( '#wp-content-wrap' ).hide();
4269
+ }
4270
+ } );
4271
+
4272
+ // Show page builder and the inside div
4273
+ this.metabox.show().find( '> .inside' ).show();
4274
+
4275
+ // Triggers full refresh
4276
+ $( window ).resize();
4277
+ $( document ).scroll();
4278
+
4279
+ // Make sure the word count is visible
4280
+ this.attachedVisible = true;
4281
+ this.trigger( 'display_builder' );
4282
+
4283
+ return true;
4284
+ },
4285
+
4286
+ /**
4287
+ * Initialize the row sortables
4288
+ */
4289
+ initSortable: function () {
4290
+ if ( !this.supports( 'moveRow' ) ) {
4291
+ return this;
4292
+ }
4293
+
4294
+ var builderView = this;
4295
+ var builderID = builderView.$el.attr( 'id' );
4296
+
4297
+ // Create the sortable for the rows
4298
+ this.rowsSortable = this.$( '.so-rows-container' ).sortable( {
4299
+ appendTo: '#wpwrap',
4300
+ items: '.so-row-container',
4301
+ handle: '.so-row-move',
4302
+ // For the block editor, where it's possible to have multiple Page Builder blocks on a page.
4303
+ // Also specify builderID when not in the block editor to prevent being able to drop rows from builder in a dialog
4304
+ // into builder on the page under the dialog.
4305
+ connectWith: '#' + builderID + '.so-rows-container,.block-editor .so-rows-container',
4306
+ axis: 'y',
4307
+ tolerance: 'pointer',
4308
+ scroll: false,
4309
+ remove: function ( e, ui ) {
4310
+ builderView.model.get( 'rows' ).remove(
4311
+ $( ui.item ).data( 'view' ).model,
4312
+ { silent: true }
4313
+ );
4314
+ builderView.model.refreshPanelsData();
4315
+ },
4316
+ receive: function ( e, ui ) {
4317
+ builderView.model.get( 'rows' ).add(
4318
+ $( ui.item ).data( 'view' ).model,
4319
+ { silent: true, at: $( ui.item ).index() }
4320
+ );
4321
+ builderView.model.refreshPanelsData();
4322
+ },
4323
+ stop: function ( e, ui ) {
4324
+ var $$ = $( ui.item ),
4325
+ row = $$.data( 'view' ),
4326
+ rows = builderView.model.get( 'rows' );
4327
+
4328
+ // If this hasn't already been removed and added to a different builder.
4329
+ if ( rows.get( row.model ) ) {
4330
+ builderView.addHistoryEntry( 'row_moved' );
4331
+
4332
+ rows.remove( row.model, {
4333
+ 'silent': true
4334
+ } );
4335
+ rows.add( row.model, {
4336
+ 'silent': true,
4337
+ 'at': $$.index()
4338
+ } );
4339
+
4340
+ row.trigger( 'move', $$.index() );
4341
+
4342
+ builderView.model.refreshPanelsData();
4343
+ }
4344
+ }
4345
+ } );
4346
+
4347
+ return this;
4348
+ },
4349
+
4350
+ /**
4351
+ * Refresh the row sortable
4352
+ */
4353
+ refreshSortable: function () {
4354
+ // Refresh the sortable to account for the new row
4355
+ if ( !_.isNull( this.rowsSortable ) ) {
4356
+ this.rowsSortable.sortable( 'refresh' );
4357
+ }
4358
+ },
4359
+
4360
+ /**
4361
+ * Set the field that's used to store the data
4362
+ * @param field
4363
+ * @param options
4364
+ */
4365
+ setDataField: function ( field, options ) {
4366
+ options = _.extend( {
4367
+ load: true
4368
+ }, options );
4369
+
4370
+ this.dataField = field;
4371
+ this.dataField.data( 'builder', this );
4372
+
4373
+ if ( options.load && field.val() !== '' ) {
4374
+ var data = this.dataField.val();
4375
+ try {
4376
+ data = JSON.parse( data );
4377
+ }
4378
+ catch ( err ) {
4379
+ console.log( "Failed to parse Page Builder layout data from supplied data field." );
4380
+ data = {};
4381
+ }
4382
+
4383
+ this.setData( data );
4384
+ }
4385
+
4386
+ return this;
4387
+ },
4388
+
4389
+ /**
4390
+ * Set the current panels data to be used.
4391
+ *
4392
+ * @param data
4393
+ */
4394
+ setData: function( data ) {
4395
+ this.model.loadPanelsData( data );
4396
+ this.currentData = data;
4397
+ this.toggleWelcomeDisplay();
4398
+ },
4399
+
4400
+ /**
4401
+ * Get the current panels data.
4402
+ *
4403
+ */
4404
+ getData: function() {
4405
+ return this.model.get( 'data' );
4406
+ },
4407
+
4408
+ /**
4409
+ * Store the model data in the data html field set in this.setDataField.
4410
+ */
4411
+ storeModelData: function () {
4412
+ var data = JSON.stringify( this.model.get( 'data' ) );
4413
+
4414
+ if ( $( this.dataField ).val() !== data ) {
4415
+ // If the data is different, set it and trigger a content_change event
4416
+ $( this.dataField ).val( data );
4417
+ $( this.dataField ).trigger( 'change' );
4418
+ this.trigger( 'content_change' );
4419
+ }
4420
+ },
4421
+
4422
+ /**
4423
+ * HAndle the visual side of adding a new row to the builder.
4424
+ *
4425
+ * @param row
4426
+ * @param collection
4427
+ * @param options
4428
+ */
4429
+ onAddRow: function ( row, collection, options ) {
4430
+ options = _.extend( { noAnimate: false }, options );
4431
+ // Create a view for the row
4432
+ var rowView = new panels.view.row( { model: row } );
4433
+ rowView.builder = this;
4434
+ rowView.render();
4435
+
4436
+ // Attach the row elements to this builder
4437
+ if ( _.isUndefined( options.at ) || collection.length <= 1 ) {
4438
+ // Insert this at the end of the widgets container
4439
+ rowView.$el.appendTo( this.$( '.so-rows-container' ) );
4440
+ } else {
4441
+ // We need to insert this at a specific position
4442
+ rowView.$el.insertAfter(
4443
+ this.$( '.so-rows-container .so-row-container' ).eq( options.at - 1 )
4444
+ );
4445
+ }
4446
+
4447
+ if ( options.noAnimate === false ) {
4448
+ rowView.visualCreate();
4449
+ }
4450
+
4451
+ this.refreshSortable();
4452
+ rowView.resize();
4453
+ this.trigger( 'row_added' );
4454
+ },
4455
+
4456
+ /**
4457
+ * Display the dialog to add a new widget.
4458
+ *
4459
+ * @returns {boolean}
4460
+ */
4461
+ displayAddWidgetDialog: function () {
4462
+ this.dialogs.widgets.openDialog();
4463
+ },
4464
+
4465
+ /**
4466
+ * Display the dialog to add a new row.
4467
+ */
4468
+ displayAddRowDialog: function () {
4469
+ var row = new panels.model.row();
4470
+ var cells = new panels.collection.cells( [ { weight: 0.5 }, { weight: 0.5 } ] );
4471
+ cells.each( function ( cell ) {
4472
+ cell.row = row;
4473
+ } );
4474
+ row.set( 'cells', cells );
4475
+ row.builder = this.model;
4476
+
4477
+ this.dialogs.row.setRowModel( row );
4478
+ this.dialogs.row.openDialog();
4479
+ },
4480
+
4481
+ /**
4482
+ * Display the dialog to add prebuilt layouts.
4483
+ *
4484
+ * @returns {boolean}
4485
+ */
4486
+ displayAddPrebuiltDialog: function () {
4487
+ this.dialogs.prebuilt.openDialog();
4488
+ },
4489
+
4490
+ /**
4491
+ * Display the history dialog.
4492
+ *
4493
+ * @returns {boolean}
4494
+ */
4495
+ displayHistoryDialog: function () {
4496
+ this.dialogs.history.openDialog();
4497
+ },
4498
+
4499
+ /**
4500
+ * Handle pasting a row into the builder.
4501
+ */
4502
+ pasteRowHandler: function () {
4503
+ var pastedModel = panels.helpers.clipboard.getModel( 'row-model' );
4504
+
4505
+ if ( !_.isEmpty( pastedModel ) && pastedModel instanceof panels.model.row ) {
4506
+ this.addHistoryEntry( 'row_pasted' );
4507
+ pastedModel.builder = this.model;
4508
+ this.model.get( 'rows' ).add( pastedModel, {
4509
+ at: this.model.get( 'rows' ).indexOf( this.model ) + 1
4510
+ } );
4511
+ this.model.refreshPanelsData();
4512
+ }
4513
+ },
4514
+
4515
+ /**
4516
+ * Get the model for the currently selected cell
4517
+ */
4518
+ getActiveCell: function ( options ) {
4519
+ options = _.extend( {
4520
+ createCell: true,
4521
+ }, options );
4522
+
4523
+ if ( !this.model.get( 'rows' ).length ) {
4524
+ // There aren't any rows yet
4525
+ if ( options.createCell ) {
4526
+ // Create a row with a single cell
4527
+ this.model.addRow( {}, [ { weight: 1 } ], { noAnimate: true } );
4528
+ } else {
4529
+ return null;
4530
+ }
4531
+ }
4532
+
4533
+ // Make sure the active cell isn't empty, and it's in a row that exists
4534
+ var activeCell = this.activeCell;
4535
+ if ( _.isEmpty( activeCell ) || this.model.get( 'rows' ).indexOf( activeCell.model.row ) === -1 ) {
4536
+ return this.model.get( 'rows' ).last().get( 'cells' ).first();
4537
+ } else {
4538
+ return activeCell.model;
4539
+ }
4540
+ },
4541
+
4542
+ /**
4543
+ * Add a live editor to the builder
4544
+ *
4545
+ * @returns {panels.view.builder}
4546
+ */
4547
+ addLiveEditor: function () {
4548
+ if ( _.isEmpty( this.config.liveEditorPreview ) ) {
4549
+ return this;
4550
+ }
4551
+
4552
+ // Create the live editor and set the builder to this.
4553
+ this.liveEditor = new panels.view.liveEditor( {
4554
+ builder: this,
4555
+ previewUrl: this.config.liveEditorPreview
4556
+ } );
4557
+
4558
+ // Display the live editor button in the toolbar
4559
+ if ( this.liveEditor.hasPreviewUrl() ) {
4560
+ this.$( '.so-builder-toolbar .so-live-editor' ).show();
4561
+ }
4562
+
4563
+ this.trigger( 'builder_live_editor_added' );
4564
+
4565
+ return this;
4566
+ },
4567
+
4568
+ /**
4569
+ * Show the current live editor
4570
+ */
4571
+ displayLiveEditor: function () {
4572
+ if ( _.isUndefined( this.liveEditor ) ) {
4573
+ return;
4574
+ }
4575
+
4576
+ this.liveEditor.open();
4577
+ },
4578
+
4579
+ /**
4580
+ * Add the history browser.
4581
+ *
4582
+ * @return {panels.view.builder}
4583
+ */
4584
+ addHistoryBrowser: function () {
4585
+ if ( _.isEmpty( this.config.liveEditorPreview ) ) {
4586
+ return this;
4587
+ }
4588
+
4589
+ this.dialogs.history = new panels.dialog.history();
4590
+ this.dialogs.history.builder = this;
4591
+ this.dialogs.history.entries.builder = this.model;
4592
+
4593
+ // Set the revert entry
4594
+ this.dialogs.history.setRevertEntry( this.model );
4595
+
4596
+ // Display the live editor button in the toolbar
4597
+ this.$( '.so-builder-toolbar .so-history' ).show();
4598
+ },
4599
+
4600
+ /**
4601
+ * Add an entry.
4602
+ *
4603
+ * @param text
4604
+ * @param data
4605
+ */
4606
+ addHistoryEntry: function ( text, data ) {
4607
+ if ( _.isUndefined( data ) ) {
4608
+ data = null;
4609
+ }
4610
+
4611
+ if ( !_.isUndefined( this.dialogs.history ) ) {
4612
+ this.dialogs.history.entries.addEntry( text, data );
4613
+ }
4614
+ },
4615
+
4616
+ supports: function ( thing ) {
4617
+
4618
+ if ( thing === 'rowAction' ) {
4619
+ // Check if this supports any row action
4620
+ return this.supports( 'addRow' ) || this.supports( 'editRow' ) || this.supports( 'deleteRow' );
4621
+ } else if ( thing === 'widgetAction' ) {
4622
+ // Check if this supports any widget action
4623
+ return this.supports( 'addWidget' ) || this.supports( 'editWidget' ) || this.supports( 'deleteWidget' );
4624
+ }
4625
+
4626
+ return _.isUndefined( this.config.builderSupports[ thing ] ) ? false : this.config.builderSupports[ thing ];
4627
+ },
4628
+
4629
+ /**
4630
+ * Handle a change of the content
4631
+ */
4632
+ handleContentChange: function () {
4633
+
4634
+ // Make sure we actually need to copy content.
4635
+ if ( panelsOptions.copy_content && this.attachedToEditor && this.$el.is( ':visible' ) ) {
4636
+
4637
+ var panelsData = this.model.getPanelsData();
4638
+ if ( !_.isEmpty( panelsData.widgets ) ) {
4639
+ // We're going to create a copy of page builder content into the post content
4640
+ $.post(
4641
+ panelsOptions.ajaxurl,
4642
+ {
4643
+ action: 'so_panels_builder_content',
4644
+ panels_data: JSON.stringify( panelsData ),
4645
+ post_id: this.config.postId
4646
+ },
4647
+ function ( content ) {
4648
+ if ( content !== '' ) {
4649
+ this.updateEditorContent( content );
4650
+ }
4651
+ }.bind( this )
4652
+ );
4653
+ }
4654
+ }
4655
+ },
4656
+
4657
+ /**
4658
+ * Update editor content with the given content.
4659
+ *
4660
+ * @param content
4661
+ */
4662
+ updateEditorContent: function ( content ) {
4663
+ // Switch back to the standard editor
4664
+ if ( this.config.editorType !== 'tinyMCE' || typeof tinyMCE === 'undefined' || _.isNull( tinyMCE.get( "content" ) ) ) {
4665
+ var $editor = $( this.config.editorId );
4666
+ $editor.val( content ).trigger( 'change' ).trigger( 'keyup' );
4667
+ } else {
4668
+ var contentEd = tinyMCE.get( "content" );
4669
+
4670
+ contentEd.setContent( content );
4671
+
4672
+ contentEd.fire( 'change' );
4673
+ contentEd.fire( 'keyup' );
4674
+ }
4675
+
4676
+ this.triggerYoastSeoChange();
4677
+ },
4678
+
4679
+ /**
4680
+ * Trigger a change on Yoast SEO
4681
+ */
4682
+ triggerYoastSeoChange: function () {
4683
+ if ( $( '#yoast_wpseo_focuskw_text_input' ).length ) {
4684
+ var element = document.getElementById( 'yoast_wpseo_focuskw_text_input' ), event;
4685
+
4686
+ if ( document.createEvent ) {
4687
+ event = document.createEvent( "HTMLEvents" );
4688
+ event.initEvent( "keyup", true, true );
4689
+ } else {
4690
+ event = document.createEventObject();
4691
+ event.eventType = "keyup";
4692
+ }
4693
+
4694
+ event.eventName = "keyup";
4695
+
4696
+ if ( document.createEvent ) {
4697
+ element.dispatchEvent( event );
4698
+ } else {
4699
+ element.fireEvent( "on" + event.eventType, event );
4700
+ }
4701
+ }
4702
+ },
4703
+
4704
+ /**
4705
+ * Handle displaying the builder
4706
+ */
4707
+ handleDisplayBuilder: function () {
4708
+ var editor = typeof tinyMCE !== 'undefined' ? tinyMCE.get( 'content' ) : false;
4709
+ var editorContent = ( editor && _.isFunction( editor.getContent ) ) ? editor.getContent() : $( 'textarea#content' ).val();
4710
+
4711
+ if (
4712
+ (
4713
+ _.isEmpty( this.model.get( 'data' ) ) ||
4714
+ ( _.isEmpty( this.model.get( 'data' ).widgets ) && _.isEmpty( this.model.get( 'data' ).grids ) )
4715
+ ) &&
4716
+ editorContent !== ''
4717
+ ) {
4718
+ var editorClass = panelsOptions.text_widget;
4719
+ // There is a small chance a theme will have removed this, so check
4720
+ if ( _.isEmpty( editorClass ) ) {
4721
+ return;
4722
+ }
4723
+
4724
+ // Create the existing page content in a single widget
4725
+ this.model.loadPanelsData( this.model.getPanelsDataFromHtml( editorContent, editorClass ) );
4726
+ this.model.trigger( 'change' );
4727
+ this.model.trigger( 'change:data' );
4728
+ }
4729
+
4730
+ $( '#post-status-info' ).addClass( 'for-siteorigin-panels' );
4731
+ },
4732
+
4733
+ handleHideBuilder: function () {
4734
+ $( '#post-status-info' ).show().removeClass( 'for-siteorigin-panels' );
4735
+ },
4736
+
4737
+ wrapEditorExpandAdjust: function () {
4738
+ try {
4739
+ var events = ( $.hasData( window ) && $._data( window ) ).events.scroll,
4740
+ event;
4741
+
4742
+ for ( var i = 0; i < events.length; i++ ) {
4743
+ if ( events[ i ].namespace === 'editor-expand' ) {
4744
+ event = events[ i ];
4745
+
4746
+ // Wrap the call
4747
+ $( window ).unbind( 'scroll', event.handler );
4748
+ $( window ).bind( 'scroll', function ( e ) {
4749
+ if ( !this.attachedVisible ) {
4750
+ event.handler( e );
4751
+ }
4752
+ }.bind( this ) );
4753
+
4754
+ break;
4755
+ }
4756
+ }
4757
+ }
4758
+ catch ( e ) {
4759
+ // We tried, we failed
4760
+ return;
4761
+ }
4762
+ },
4763
+
4764
+ /**
4765
+ * Either add or remove the narrow class
4766
+ * @returns {exports}
4767
+ */
4768
+ handleBuilderSizing: function () {
4769
+ var width = this.$el.width();
4770
+
4771
+ if ( !width ) {
4772
+ return this;
4773
+ }
4774
+
4775
+ if ( width < 575 ) {
4776
+ this.$el.addClass( 'so-display-narrow' );
4777
+ } else {
4778
+ this.$el.removeClass( 'so-display-narrow' );
4779
+ }
4780
+
4781
+ return this;
4782
+ },
4783
+
4784
+ /**
4785
+ * Set the parent dialog for all the dialogs in this builder.
4786
+ *
4787
+ * @param text
4788
+ * @param dialog
4789
+ */
4790
+ setDialogParents: function ( text, dialog ) {
4791
+ _.each( this.dialogs, function ( p, i, d ) {
4792
+ d[ i ].setParent( text, dialog );
4793
+ } );
4794
+
4795
+ // For any future dialogs
4796
+ this.on( 'add_dialog', function ( newDialog ) {
4797
+ newDialog.setParent( text, dialog );
4798
+ }, this );
4799
+ },
4800
+
4801
+ /**
4802
+ * This shows or hides the welcome display depending on whether there are any rows in the collection.
4803
+ */
4804
+ toggleWelcomeDisplay: function () {
4805
+ if ( !this.model.get( 'rows' ).isEmpty() ) {
4806
+ this.$( '.so-panels-welcome-message' ).hide();
4807
+ } else {
4808
+ this.$( '.so-panels-welcome-message' ).show();
4809
+ }
4810
+ },
4811
+
4812
+ /**
4813
+ * Activate the contextual menu
4814
+ * @param e
4815
+ * @param menu
4816
+ */
4817
+ activateContextMenu: function ( e, menu ) {
4818
+ var builder = this;
4819
+
4820
+ // Only run this if the event target is a descendant of this builder's DOM element.
4821
+ if ( $.contains( builder.$el.get( 0 ), e.target ) ) {
4822
+ // Get the element we're currently hovering over
4823
+ var over = $( [] )
4824
+ .add( builder.$( '.so-panels-welcome-message:visible' ) )
4825
+ .add( builder.$( '.so-rows-container > .so-row-container' ) )
4826
+ .add( builder.$( '.so-cells > .cell' ) )
4827
+ .add( builder.$( '.cell-wrapper > .so-widget' ) )
4828
+ .filter( function ( i ) {
4829
+ return menu.isOverEl( $( this ), e );
4830
+ } );
4831
+
4832
+ var activeView = over.last().data( 'view' );
4833
+ if ( activeView !== undefined && activeView.buildContextualMenu !== undefined ) {
4834
+ // We'll pass this to the current active view so it can populate the contextual menu
4835
+ activeView.buildContextualMenu( e, menu );
4836
+ }
4837
+ else if ( over.last().hasClass( 'so-panels-welcome-message' ) ) {
4838
+ // The user opened the contextual menu on the welcome message
4839
+ this.buildContextualMenu( e, menu );
4840
+ }
4841
+ }
4842
+ },
4843
+
4844
+ /**
4845
+ * Build the contextual menu for the main builder - before any content has been added.
4846
+ */
4847
+ buildContextualMenu: function ( e, menu ) {
4848
+ var actions = {};
4849
+
4850
+ if ( this.supports( 'addRow' ) ) {
4851
+ actions.add_row = { title: panelsOptions.loc.contextual.add_row };
4852
+ }
4853
+
4854
+ if ( panels.helpers.clipboard.canCopyPaste() ) {
4855
+ if ( panels.helpers.clipboard.isModel( 'row-model' ) && this.supports( 'addRow' ) ) {
4856
+ actions.paste_row = { title: panelsOptions.loc.contextual.row_paste };
4857
+ }
4858
+ }
4859
+
4860
+ if ( !_.isEmpty( actions ) ) {
4861
+ menu.addSection(
4862
+ 'builder-actions',
4863
+ {
4864
+ sectionTitle: panelsOptions.loc.contextual.row_actions,
4865
+ search: false,
4866
+ },
4867
+ actions,
4868
+ function ( c ) {
4869
+ switch ( c ) {
4870
+ case 'add_row':
4871
+ this.displayAddRowDialog();
4872
+ break;
4873
+
4874
+ case 'paste_row':
4875
+ this.pasteRowHandler();
4876
+ break;
4877
+ }
4878
+ }.bind( this )
4879
+ );
4880
+ }
4881
+ },
4882
+ } );
4883
+
4884
+ },{}],24:[function(require,module,exports){
4885
+ var panels = window.panels, $ = jQuery;
4886
+
4887
+ module.exports = Backbone.View.extend( {
4888
+ template: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-builder-cell' ).html() ) ),
4889
+ events: {
4890
+ 'click .cell-wrapper': 'handleCellClick'
4891
+ },
4892
+
4893
+ /* The row view that this cell is a part of */
4894
+ row: null,
4895
+ widgetSortable: null,
4896
+
4897
+ initialize: function () {
4898
+ this.listenTo(this.model.get('widgets'), 'add', this.onAddWidget );
4899
+ },
4900
+
4901
+ /**
4902
+ * Render the actual cell
4903
+ */
4904
+ render: function () {
4905
+ var templateArgs = {
4906
+ weight: this.model.get( 'weight' ),
4907
+ totalWeight: this.row.model.get('cells').totalWeight()
4908
+ };
4909
+
4910
+ this.setElement( this.template( templateArgs ) );
4911
+ this.$el.data( 'view', this );
4912
+
4913
+ // Now lets render any widgets that are currently in the row
4914
+ var thisView = this;
4915
+ this.model.get('widgets').each( function ( widget ) {
4916
+ var widgetView = new panels.view.widget( {model: widget} );
4917
+ widgetView.cell = thisView;
4918
+ widgetView.render();
4919
+
4920
+ widgetView.$el.appendTo( thisView.$( '.widgets-container' ) );
4921
+ } );
4922
+
4923
+ this.initSortable();
4924
+ this.initResizable();
4925
+
4926
+ return this;
4927
+ },
4928
+
4929
+ /**
4930
+ * Initialize the widget sortable
4931
+ */
4932
+ initSortable: function () {
4933
+ if( ! this.row.builder.supports( 'moveWidget' ) ) {
4934
+ return this;
4935
+ }
4936
+
4937
+ var cellView = this;
4938
+ var builder = cellView.row.builder;
4939
+
4940
+ // Go up the view hierarchy until we find the ID attribute
4941
+ var builderID = builder.$el.attr( 'id' );
4942
+ var builderModel = builder.model;
4943
+
4944
+ // Create a widget sortable that's connected with all other cells
4945
+ this.widgetSortable = this.$( '.widgets-container' ).sortable( {
4946
+ placeholder: "so-widget-sortable-highlight",
4947
+ connectWith: '#' + builderID + ' .so-cells .cell .widgets-container,.block-editor .so-cells .cell .widgets-container',
4948
+ tolerance: 'pointer',
4949
+ scroll: false,
4950
+ over: function ( e, ui ) {
4951
+ // This will make all the rows in the current builder resize
4952
+ cellView.row.builder.trigger( 'widget_sortable_move' );
4953
+ },
4954
+ remove: function ( e, ui ) {
4955
+ cellView.model.get( 'widgets' ).remove(
4956
+ $( ui.item ).data( 'view' ).model,
4957
+ { silent: true }
4958
+ );
4959
+ builderModel.refreshPanelsData();
4960
+ },
4961
+ receive: function ( e, ui ) {
4962
+ var widgetView = $( ui.item ).data( 'view' );
4963
+ widgetView.cell = cellView;
4964
+ var widgetModel = widgetView.model;
4965
+ widgetModel.cell = cellView.model;
4966
+ cellView.model.get( 'widgets' ).add(
4967
+ widgetModel,
4968
+ { silent: true, at: $( ui.item ).index() }
4969
+ );
4970
+ builderModel.refreshPanelsData();
4971
+ },
4972
+ stop: function ( e, ui ) {
4973
+ var $$ = $( ui.item ),
4974
+ widget = $$.data( 'view' ),
4975
+ targetCell = $$.closest( '.cell' ).data( 'view' );
4976
+
4977
+
4978
+ // If this hasn't already been removed and added to a different builder.
4979
+ if ( cellView.model.get( 'widgets' ).get( widget.model ) ) {
4980
+
4981
+ cellView.row.builder.addHistoryEntry( 'widget_moved' );
4982
+
4983
+ // Move the model and the view to the new cell
4984
+ widget.model.moveToCell( targetCell.model, {}, $$.index() );
4985
+ widget.cell = targetCell;
4986
+
4987
+ builderModel.refreshPanelsData();
4988
+ }
4989
+ },
4990
+ helper: function ( e, el ) {
4991
+ var helper = el.clone()
4992
+ .css( {
4993
+ 'width': el.outerWidth(),
4994
+ 'z-index': 10000,
4995
+ 'position': 'fixed'
4996
+ } )
4997
+ .addClass( 'widget-being-dragged' ).appendTo( 'body' );
4998
+
4999
+ // Center the helper to the mouse cursor.
5000
+ if ( el.outerWidth() > 720 ) {
5001
+ helper.animate( {
5002
+ 'margin-left': e.pageX - el.offset().left - (
5003
+ 480 / 2
5004
+ ),
5005
+ 'width': 480
5006
+ }, 'fast' );
5007
+ }
5008
+
5009
+ return helper;
5010
+ }
5011
+ } );
5012
+
5013
+ return this;
5014
+ },
5015
+
5016
+ /**
5017
+ * Refresh the widget sortable when a new widget is added
5018
+ */
5019
+ refreshSortable: function () {
5020
+ if ( ! _.isNull( this.widgetSortable ) ) {
5021
+ this.widgetSortable.sortable( 'refresh' );
5022
+ }
5023
+ },
5024
+
5025
+ /**
5026
+ * This will make the cell resizble
5027
+ */
5028
+ initResizable: function () {
5029
+ if( ! this.row.builder.supports( 'editRow' ) ) {
5030
+ return this;
5031
+ }
5032
+
5033
+ // var neighbor = this.$el.previous().data('view');
5034
+ var handle = this.$( '.resize-handle' ).css( 'position', 'absolute' );
5035
+ var container = this.row.$el;
5036
+ var cellView = this;
5037
+
5038
+ // The view of the cell to the left is stored when dragging starts.
5039
+ var previousCell;
5040
+
5041
+ handle.draggable( {
5042
+ axis: 'x',
5043
+ containment: container,
5044
+ start: function ( e, ui ) {
5045
+ // Set the containment to the cell parent
5046
+ previousCell = cellView.$el.prev().data( 'view' );
5047
+ if ( _.isUndefined( previousCell ) ) {
5048
+ return;
5049
+ }
5050
+
5051
+ // Create the clone for the current cell
5052
+ var newCellClone = cellView.$el.clone().appendTo( ui.helper ).css( {
5053
+ position: 'absolute',
5054
+ top: '0',
5055
+ width: cellView.$el.outerWidth(),
5056
+ left: 5,
5057
+ height: cellView.$el.outerHeight()
5058
+ } );
5059
+ newCellClone.find( '.resize-handle' ).remove();
5060
+
5061
+ // Create the clone for the previous cell
5062
+ var prevCellClone = previousCell.$el.clone().appendTo( ui.helper ).css( {
5063
+ position: 'absolute',
5064
+ top: '0',
5065
+ width: previousCell.$el.outerWidth(),
5066
+ right: 5,
5067
+ height: previousCell.$el.outerHeight()
5068
+ } );
5069
+ prevCellClone.find( '.resize-handle' ).remove();
5070
+
5071
+ $( this ).data( {
5072
+ 'newCellClone': newCellClone,
5073
+ 'prevCellClone': prevCellClone
5074
+ } );
5075
+ },
5076
+ drag: function ( e, ui ) {
5077
+ // Calculate the new cell and previous cell widths as a percent
5078
+ var containerWidth = cellView.row.$el.width() + 10;
5079
+ var ncw = cellView.model.get( 'weight' ) - (
5080
+ (
5081
+ ui.position.left + handle.outerWidth() / 2
5082
+ ) / containerWidth
5083
+ );
5084
+ var pcw = previousCell.model.get( 'weight' ) + (
5085
+ (
5086
+ ui.position.left + handle.outerWidth() / 2
5087
+ ) / containerWidth
5088
+ );
5089
+
5090
+ $( this ).data( 'newCellClone' ).css( 'width', containerWidth * ncw )
5091
+ .find( '.preview-cell-weight' ).html( Math.round( ncw * 1000 ) / 10 );
5092
+
5093
+ $( this ).data( 'prevCellClone' ).css( 'width', containerWidth * pcw )
5094
+ .find( '.preview-cell-weight' ).html( Math.round( pcw * 1000 ) / 10 );
5095
+ },
5096
+ stop: function ( e, ui ) {
5097
+ // Remove the clones
5098
+ $( this ).data( 'newCellClone' ).remove();
5099
+ $( this ).data( 'prevCellClone' ).remove();
5100
+
5101
+ var containerWidth = cellView.row.$el.width() + 10;
5102
+ var ncw = cellView.model.get( 'weight' ) - (
5103
+ (
5104
+ ui.position.left + handle.outerWidth() / 2
5105
+ ) / containerWidth
5106
+ );
5107
+ var pcw = previousCell.model.get( 'weight' ) + (
5108
+ (
5109
+ ui.position.left + handle.outerWidth() / 2
5110
+ ) / containerWidth
5111
+ );
5112
+
5113
+ if ( ncw > 0.02 && pcw > 0.02 ) {
5114
+ cellView.row.builder.addHistoryEntry( 'cell_resized' );
5115
+ cellView.model.set( 'weight', ncw );
5116
+ previousCell.model.set( 'weight', pcw );
5117
+ cellView.row.resize();
5118
+ }
5119
+
5120
+ ui.helper.css( 'left', - handle.outerWidth() / 2 );
5121
+
5122
+ // Refresh the panels data
5123
+ cellView.row.builder.model.refreshPanelsData();
5124
+ }
5125
+ } );
5126
+
5127
+ return this;
5128
+ },
5129
+
5130
+ /**
5131
+ * This is triggered when ever a widget is added to the row collection.
5132
+ *
5133
+ * @param widget
5134
+ */
5135
+ onAddWidget: function ( widget, collection, options ) {
5136
+ options = _.extend( {noAnimate: false}, options );
5137
+
5138
+ // Create the view for the widget
5139
+ var view = new panels.view.widget( {
5140
+ model: widget
5141
+ } );
5142
+ view.cell = this;
5143
+
5144
+ if ( _.isUndefined( widget.isDuplicate ) ) {
5145
+ widget.isDuplicate = false;
5146
+ }
5147
+
5148
+ // Render and load the form if this is a duplicate
5149
+ view.render( {
5150
+ 'loadForm': widget.isDuplicate
5151
+ } );
5152
+
5153
+ if ( _.isUndefined( options.at ) || collection.length <= 1 ) {
5154
+ // Insert this at the end of the widgets container
5155
+ view.$el.appendTo( this.$( '.widgets-container' ) );
5156
+ } else {
5157
+ // We need to insert this at a specific position
5158
+ view.$el.insertAfter(
5159
+ this.$( '.widgets-container .so-widget' ).eq( options.at - 1 )
5160
+ );
5161
+ }
5162
+
5163
+ if ( options.noAnimate === false ) {
5164
+ // We need an animation
5165
+ view.visualCreate();
5166
+ }
5167
+
5168
+ this.refreshSortable();
5169
+ this.row.resize();
5170
+ this.row.builder.trigger( 'widget_added', view );
5171
+ },
5172
+
5173
+ /**
5174
+ * Handle this cell being clicked on
5175
+ *
5176
+ * @param e
5177
+ * @returns {boolean}
5178
+ */
5179
+ handleCellClick: function ( e ) {
5180
+ // Remove all existing selected cell indication for this builder
5181
+ this.row.builder.$el.find( '.so-cells .cell' ).removeClass( 'cell-selected' );
5182
+
5183
+ if( this.row.builder.activeCell === this && ! this.model.get('widgets').length ) {
5184
+ // This is a click on an empty cell
5185
+ this.row.builder.activeCell = null;
5186
+ }
5187
+ else {
5188
+ this.$el.addClass( 'cell-selected' );
5189
+ this.row.builder.activeCell = this;
5190
+ }
5191
+ },
5192
+
5193
+ /**
5194
+ * Insert a widget from the clipboard
5195
+ */
5196
+ pasteHandler: function(){
5197
+ var pastedModel = panels.helpers.clipboard.getModel( 'widget-model' );
5198
+ if( ! _.isEmpty( pastedModel ) && pastedModel instanceof panels.model.widget ) {
5199
+ this.row.builder.addHistoryEntry( 'widget_pasted' );
5200
+ pastedModel.cell = this.model;
5201
+ this.model.get('widgets').add( pastedModel );
5202
+ this.row.builder.model.refreshPanelsData();
5203
+ }
5204
+ },
5205
+
5206
+ /**
5207
+ * Build up the contextual menu for a cell
5208
+ *
5209
+ * @param e
5210
+ * @param menu
5211
+ */
5212
+ buildContextualMenu: function ( e, menu ) {
5213
+ var thisView = this;
5214
+
5215
+ if( ! menu.hasSection( 'add-widget-below' ) ) {
5216
+ menu.addSection(
5217
+ 'add-widget-cell',
5218
+ {
5219
+ sectionTitle: panelsOptions.loc.contextual.add_widget_cell,
5220
+ searchPlaceholder: panelsOptions.loc.contextual.search_widgets,
5221
+ defaultDisplay: panelsOptions.contextual.default_widgets
5222
+ },
5223
+ panelsOptions.widgets,
5224
+ function ( c ) {
5225
+ thisView.row.builder.trigger('before_user_adds_widget')
5226
+ thisView.row.builder.addHistoryEntry( 'widget_added' );
5227
+
5228
+ var widget = new panels.model.widget( {
5229
+ class: c
5230
+ } );
5231
+
5232
+ // Add the widget to the cell model
5233
+ widget.cell = thisView.model;
5234
+ widget.cell.get('widgets').add( widget );
5235
+
5236
+ thisView.row.builder.model.refreshPanelsData();
5237
+ thisView.row.builder.trigger('after_user_adds_widget', widget);
5238
+ }
5239
+ );
5240
+ }
5241
+
5242
+ var actions = {};
5243
+ if ( this.row.builder.supports('addWidget') && panels.helpers.clipboard.isModel( 'widget-model' ) ) {
5244
+ actions.paste = {title: panelsOptions.loc.contextual.cell_paste_widget};
5245
+ }
5246
+
5247
+ if( ! _.isEmpty( actions ) ) {
5248
+ menu.addSection(
5249
+ 'cell-actions',
5250
+ {
5251
+ sectionTitle: panelsOptions.loc.contextual.cell_actions,
5252
+ search: false,
5253
+ },
5254
+ actions,
5255
+ function ( c ) {
5256
+ switch ( c ) {
5257
+ case 'paste':
5258
+ this.pasteHandler();
5259
+ break;
5260
+ }
5261
+
5262
+ this.row.builder.model.refreshPanelsData();
5263
+ }.bind( this )
5264
+ );
5265
+ }
5266
+
5267
+ // Add the contextual menu for the parent row
5268
+ this.row.buildContextualMenu( e, menu );
5269
+ }
5270
+ } );
5271
+
5272
+ },{}],25:[function(require,module,exports){
5273
+ var panels = window.panels, $ = jQuery;
5274
+
5275
+ module.exports = Backbone.View.extend( {
5276
+ dialogTemplate: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-dialog' ).html() ) ),
5277
+ dialogTabTemplate: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-dialog-tab' ).html() ) ),
5278
+
5279
+ tabbed: false,
5280
+ rendered: false,
5281
+ builder: false,
5282
+ className: 'so-panels-dialog-wrapper',
5283
+ dialogClass: '',
5284
+ dialogIcon: '',
5285
+ parentDialog: false,
5286
+ dialogOpen: false,
5287
+ editableLabel: false,
5288
+
5289
+ events: {
5290
+ 'click .so-close': 'closeDialog',
5291
+ 'click .so-nav.so-previous': 'navToPrevious',
5292
+ 'click .so-nav.so-next': 'navToNext',
5293
+ },
5294
+
5295
+ initialize: function () {
5296
+ // The first time this dialog is opened, render it
5297
+ this.once( 'open_dialog', this.render );
5298
+ this.once( 'open_dialog', this.attach );
5299
+ this.once( 'open_dialog', this.setDialogClass );
5300
+
5301
+ this.trigger( 'initialize_dialog', this );
5302
+
5303
+ if ( ! _.isUndefined( this.initializeDialog ) ) {
5304
+ this.initializeDialog();
5305
+ }
5306
+
5307
+ _.bindAll( this, 'initSidebars', 'hasSidebar', 'onResize', 'toggleLeftSideBar', 'toggleRightSideBar' );
5308
+ },
5309
+
5310
+ /**
5311
+ * Returns the next dialog in the sequence. Should be overwritten by a child dialog.
5312
+ * @returns {null}
5313
+ */
5314
+ getNextDialog: function () {
5315
+ return null;
5316
+ },
5317
+
5318
+ /**
5319
+ * Returns the previous dialog in this sequence. Should be overwritten by child dialog.
5320
+ * @returns {null}
5321
+ */
5322
+ getPrevDialog: function () {
5323
+ return null;
5324
+ },
5325
+
5326
+ /**
5327
+ * Adds a dialog class to uniquely identify this dialog type
5328
+ */
5329
+ setDialogClass: function () {
5330
+ if ( this.dialogClass !== '' ) {
5331
+ this.$( '.so-panels-dialog' ).addClass( this.dialogClass );
5332
+ }
5333
+ },
5334
+
5335
+ /**
5336
+ * Set the builder that controls this dialog.
5337
+ * @param {panels.view.builder} builder
5338
+ */
5339
+ setBuilder: function ( builder ) {
5340
+ this.builder = builder;
5341
+
5342
+ // Trigger an add dialog event on the builder so it can modify the dialog in any way
5343
+ builder.trigger( 'add_dialog', this, this.builder );
5344
+
5345
+ return this;
5346
+ },
5347
+
5348
+ /**
5349
+ * Attach the dialog to the window
5350
+ */
5351
+ attach: function () {
5352
+ this.$el.appendTo( 'body' );
5353
+
5354
+ return this;
5355
+ },
5356
+
5357
+ /**
5358
+ * Converts an HTML representation of the dialog into arguments for a dialog box
5359
+ * @param html HTML for the dialog
5360
+ * @param args Arguments passed to the template
5361
+ * @returns {}
5362
+ */
5363
+ parseDialogContent: function ( html, args ) {
5364
+ // Add a CID
5365
+ args = _.extend( {cid: this.cid}, args );
5366
+
5367
+
5368
+ var c = $( (
5369
+ _.template( panels.helpers.utils.processTemplate( html ) )
5370
+ )( args ) );
5371
+ var r = {
5372
+ title: c.find( '.title' ).html(),
5373
+ buttons: c.find( '.buttons' ).html(),
5374
+ content: c.find( '.content' ).html()
5375
+ };
5376
+
5377
+ if ( c.has( '.left-sidebar' ) ) {
5378
+ r.left_sidebar = c.find( '.left-sidebar' ).html();
5379
+ }
5380
+
5381
+ if ( c.has( '.right-sidebar' ) ) {
5382
+ r.right_sidebar = c.find( '.right-sidebar' ).html();
5383
+ }
5384
+
5385
+ return r;
5386
+
5387
+ },
5388
+
5389
+ /**
5390
+ * Render the dialog and initialize the tabs
5391
+ *
5392
+ * @param attributes
5393
+ * @returns {panels.view.dialog}
5394
+ */
5395
+ renderDialog: function ( attributes ) {
5396
+ attributes = _.extend( {
5397
+ editableLabel: this.editableLabel,
5398
+ dialogIcon: this.dialogIcon,
5399
+ }, attributes );
5400
+
5401
+ this.$el.html( this.dialogTemplate( attributes ) ).hide();
5402
+ this.$el.data( 'view', this );
5403
+ this.$el.addClass( 'so-panels-dialog-wrapper' );
5404
+
5405
+ if ( this.parentDialog !== false ) {
5406
+ // Add a link to the parent dialog as a sort of crumbtrail.
5407
+ var dialogParent = $( '<h3 class="so-parent-link"></h3>' ).html( this.parentDialog.text + '<div class="so-separator"></div>' );
5408
+ dialogParent.click( function ( e ) {
5409
+ e.preventDefault();
5410
+ this.closeDialog();
5411
+ this.parentDialog.dialog.openDialog();
5412
+ }.bind(this) );
5413
+ this.$( '.so-title-bar .so-title' ).before( dialogParent );
5414
+ }
5415
+
5416
+ if( this.$( '.so-title-bar .so-title-editable' ).length ) {
5417
+ // Added here because .so-edit-title is only available after the template has been rendered.
5418
+ this.initEditableLabel();
5419
+ }
5420
+
5421
+ setTimeout( this.initSidebars, 1 );
5422
+
5423
+ return this;
5424
+ },
5425
+
5426
+ initSidebars: function () {
5427
+ var $leftButton = this.$( '.so-show-left-sidebar' ).hide();
5428
+ var $rightButton = this.$( '.so-show-right-sidebar' ).hide();
5429
+ var hasLeftSidebar = this.hasSidebar( 'left' );
5430
+ var hasRightSidebar = this.hasSidebar( 'right' );
5431
+ // Set up resize handling
5432
+ if ( hasLeftSidebar || hasRightSidebar ) {
5433
+ $( window ).on( 'resize', this.onResize );
5434
+ if ( hasLeftSidebar ) {
5435
+ $leftButton.show();
5436
+ $leftButton.on( 'click', this.toggleLeftSideBar );
5437
+ }
5438
+ if ( hasRightSidebar ) {
5439
+ $rightButton.show();
5440
+ $rightButton.on( 'click', this.toggleRightSideBar );
5441
+ }
5442
+ }
5443
+
5444
+ this.onResize();
5445
+ },
5446
+
5447
+ /**
5448
+ * Initialize the sidebar tabs
5449
+ */
5450
+ initTabs: function () {
5451
+ var tabs = this.$( '.so-sidebar-tabs li a' );
5452
+
5453
+ if ( tabs.length === 0 ) {
5454
+ return this;
5455
+ }
5456
+
5457
+ var thisDialog = this;
5458
+ tabs.click( function ( e ) {
5459
+ e.preventDefault();
5460
+ var $$ = $( this );
5461
+
5462
+ thisDialog.$( '.so-sidebar-tabs li' ).removeClass( 'tab-active' );
5463
+ thisDialog.$( '.so-content .so-content-tabs > *' ).hide();
5464
+
5465
+ $$.parent().addClass( 'tab-active' );
5466
+
5467
+ var url = $$.attr( 'href' );
5468
+ if ( ! _.isUndefined( url ) && url.charAt( 0 ) === '#' ) {
5469
+ // Display the new tab
5470
+ var tabName = url.split( '#' )[1];
5471
+ thisDialog.$( '.so-content .so-content-tabs .tab-' + tabName ).show();
5472
+ }
5473
+
5474
+ // This lets other dialogs implement their own custom handlers
5475
+ thisDialog.trigger( 'tab_click', $$ );
5476
+
5477
+ } );
5478
+
5479
+ // Trigger a click on the first tab
5480
+ this.$( '.so-sidebar-tabs li a' ).first().click();
5481
+ return this;
5482
+ },
5483
+
5484
+ initToolbar: function () {
5485
+ // Trigger simplified click event for elements marked as toolbar buttons.
5486
+ var buttons = this.$( '.so-toolbar .so-buttons .so-toolbar-button' );
5487
+ buttons.click( function ( e ) {
5488
+ e.preventDefault();
5489
+
5490
+ this.trigger( 'button_click', $( e.currentTarget ) );
5491
+ }.bind( this ) );
5492
+
5493
+ // Handle showing and hiding the dropdown list items
5494
+ var $dropdowns = this.$( '.so-toolbar .so-buttons .so-dropdown-button' );
5495
+ $dropdowns.click( function ( e ) {
5496
+ e.preventDefault();
5497
+ var $dropdownButton = $( e.currentTarget );
5498
+ var $dropdownList = $dropdownButton.siblings( '.so-dropdown-links-wrapper' );
5499
+ if ( $dropdownList.is( '.hidden' ) ) {
5500
+ $dropdownList.removeClass( 'hidden' );
5501
+ } else {
5502
+ $dropdownList.addClass( 'hidden' );
5503
+ }
5504
+
5505
+ }.bind( this ) );
5506
+
5507
+ // Hide dropdown list on click anywhere, unless it's a dropdown option which requires confirmation in it's
5508
+ // unconfirmed state.
5509
+ $( 'html' ).click( function ( e ) {
5510
+ this.$( '.so-dropdown-links-wrapper' ).not( '.hidden' ).each( function ( index, el ) {
5511
+ var $dropdownList = $( el );
5512
+ var $trgt = $( e.target );
5513
+ if ( $trgt.length === 0 || !(
5514
+ (
5515
+ $trgt.is('.so-needs-confirm') && !$trgt.is('.so-confirmed')
5516
+ ) || $trgt.is('.so-dropdown-button')
5517
+ ) ) {
5518
+ $dropdownList.addClass('hidden');
5519
+ }
5520
+ } );
5521
+ }.bind( this ) );
5522
+ },
5523
+
5524
+ /**
5525
+ * Initialize the editable dialog title
5526
+ */
5527
+ initEditableLabel: function(){
5528
+ var $editElt = this.$( '.so-title-bar .so-title-editable' );
5529
+
5530
+ $editElt.keypress( function ( event ) {
5531
+ var enterPressed = event.type === 'keypress' && event.keyCode === 13;
5532
+ if ( enterPressed ) {
5533
+ // Need to make sure tab focus is on another element, otherwise pressing enter multiple times refocuses
5534
+ // the element and allows newlines.
5535
+ var tabbables = $( ':tabbable' );
5536
+ var curTabIndex = tabbables.index( $editElt );
5537
+ tabbables.eq( curTabIndex + 1 ).focus();
5538
+ // After the above, we're somehow left with the first letter of text selected,
5539
+ // so this removes the selection.
5540
+ window.getSelection().removeAllRanges();
5541
+ }
5542
+ return !enterPressed;
5543
+ } ).blur( function () {
5544
+ var newValue = $editElt.text().replace( /^\s+|\s+$/gm, '' );
5545
+ var oldValue = $editElt.data( 'original-value' ).replace( /^\s+|\s+$/gm, '' );
5546
+ if ( newValue !== oldValue ) {
5547
+ $editElt.text( newValue );
5548
+ this.trigger( 'edit_label', newValue );
5549
+ }
5550
+
5551
+ }.bind( this ) );
5552
+
5553
+ $editElt.focus( function() {
5554
+ $editElt.data( 'original-value', $editElt.text() );
5555
+ panels.helpers.utils.selectElementContents( this );
5556
+ } );
5557
+ },
5558
+
5559
+ /**
5560
+ * Quickly setup the dialog by opening and closing it.
5561
+ */
5562
+ setupDialog: function () {
5563
+ this.openDialog();
5564
+ this.closeDialog();
5565
+ },
5566
+
5567
+ /**
5568
+ * Refresh the next and previous buttons.
5569
+ */
5570
+ refreshDialogNav: function () {
5571
+ this.$( '.so-title-bar .so-nav' ).show().removeClass( 'so-disabled' );
5572
+
5573
+ // Lets also hide the next and previous if we don't have a next and previous dialog
5574
+ var nextDialog = this.getNextDialog();
5575
+ var nextButton = this.$( '.so-title-bar .so-next' );
5576
+
5577
+ var prevDialog = this.getPrevDialog();
5578
+ var prevButton = this.$( '.so-title-bar .so-previous' );
5579
+
5580
+ if ( nextDialog === null ) {
5581
+ nextButton.hide();
5582
+ }
5583
+ else if ( nextDialog === false ) {
5584
+ nextButton.addClass( 'so-disabled' );
5585
+ }
5586
+
5587
+ if ( prevDialog === null ) {
5588
+ prevButton.hide();
5589
+ }
5590
+ else if ( prevDialog === false ) {
5591
+ prevButton.addClass( 'so-disabled' );
5592
+ }
5593
+ },
5594
+
5595
+ /**
5596
+ * Open the dialog
5597
+ */
5598
+ openDialog: function ( options ) {
5599
+ options = _.extend( {
5600
+ silent: false
5601
+ }, options );
5602
+
5603
+ if ( ! options.silent ) {
5604
+ this.trigger( 'open_dialog' );
5605
+ }
5606
+
5607
+ this.dialogOpen = true;
5608
+
5609
+ this.refreshDialogNav();
5610
+
5611
+ // Stop scrolling for the main body
5612
+ panels.helpers.pageScroll.lock();
5613
+
5614
+ this.onResize();
5615
+
5616
+ this.$el.show();
5617
+
5618
+ if ( ! options.silent ) {
5619
+ // This triggers once everything is visible
5620
+ this.trigger( 'open_dialog_complete' );
5621
+ this.builder.trigger( 'open_dialog', this );
5622
+ $( document ).trigger( 'open_dialog', this );
5623
+ }
5624
+ },
5625
+
5626
+ /**
5627
+ * Close the dialog
5628
+ *
5629
+ * @param e
5630
+ * @returns {boolean}
5631
+ */
5632
+ closeDialog: function ( options ) {
5633
+ options = _.extend( {
5634
+ silent: false
5635
+ }, options );
5636
+
5637
+ if ( ! options.silent ) {
5638
+ this.trigger( 'close_dialog' );
5639
+ }
5640
+
5641
+ this.dialogOpen = false;
5642
+
5643
+ this.$el.hide();
5644
+ panels.helpers.pageScroll.unlock();
5645
+
5646
+ if ( ! options.silent ) {
5647
+ // This triggers once everything is hidden
5648
+ this.trigger( 'close_dialog_complete' );
5649
+ this.builder.trigger( 'close_dialog', this );
5650
+ }
5651
+ },
5652
+
5653
+ /**
5654
+ * Navigate to the previous dialog
5655
+ */
5656
+ navToPrevious: function () {
5657
+ this.closeDialog();
5658
+
5659
+ var prev = this.getPrevDialog();
5660
+ if ( prev !== null && prev !== false ) {
5661
+ prev.openDialog();
5662
+ }
5663
+ },
5664
+
5665
+ /**
5666
+ * Navigate to the next dialog
5667
+ */
5668
+ navToNext: function () {
5669
+ this.closeDialog();
5670
+
5671
+ var next = this.getNextDialog();
5672
+ if ( next !== null && next !== false ) {
5673
+ next.openDialog();
5674
+ }
5675
+ },
5676
+
5677
+ /**
5678
+ * Get the values from the form and convert them into a data array
5679
+ */
5680
+ getFormValues: function ( formSelector ) {
5681
+ if ( _.isUndefined( formSelector ) ) {
5682
+ formSelector = '.so-content';
5683
+ }
5684
+
5685
+ var $f = this.$( formSelector );
5686
+
5687
+ var data = {}, parts;
5688
+
5689
+ // Find all the named fields in the form
5690
+ $f.find( '[name]' ).each( function () {
5691
+ var $$ = $( this );
5692
+
5693
+ try {
5694
+
5695
+ var name = /([A-Za-z_]+)\[(.*)\]/.exec( $$.attr( 'name' ) );
5696
+ if ( _.isEmpty( name ) ) {
5697
+ return true;
5698
+ }
5699
+
5700
+ // Create an array with the parts of the name
5701
+ if ( _.isUndefined( name[2] ) ) {
5702
+ parts = $$.attr( 'name' );
5703
+ } else {
5704
+ parts = name[2].split( '][' );
5705
+ parts.unshift( name[1] );
5706
+ }
5707
+
5708
+ parts = parts.map( function ( e ) {
5709
+ if ( ! isNaN( parseFloat( e ) ) && isFinite( e ) ) {
5710
+ return parseInt( e );
5711
+ } else {
5712
+ return e;
5713
+ }
5714
+ } );
5715
+
5716
+ var sub = data;
5717
+ var fieldValue = null;
5718
+
5719
+ var fieldType = (
5720
+ _.isString( $$.attr( 'type' ) ) ? $$.attr( 'type' ).toLowerCase() : false
5721
+ );
5722
+
5723
+ // First we need to get the value from the field
5724
+ if ( fieldType === 'checkbox' ) {
5725
+ if ( $$.is( ':checked' ) ) {
5726
+ fieldValue = $$.val() !== '' ? $$.val() : true;
5727
+ } else {
5728
+ fieldValue = null;
5729
+ }
5730
+ }
5731
+ else if ( fieldType === 'radio' ) {
5732
+ if ( $$.is( ':checked' ) ) {
5733
+ fieldValue = $$.val();
5734
+ } else {
5735
+ //skip over unchecked radios
5736
+ return;
5737
+ }
5738
+ }
5739
+ else if ( $$.prop( 'tagName' ) === 'SELECT' ) {
5740
+ var selected = $$.find( 'option:selected' );
5741
+
5742
+ if ( selected.length === 1 ) {
5743
+ fieldValue = $$.find( 'option:selected' ).val();
5744
+ }
5745
+ else if ( selected.length > 1 ) {
5746
+ // This is a mutli-select field
5747
+ fieldValue = _.map( $$.find( 'option:selected' ), function ( n, i ) {
5748
+ return $( n ).val();
5749
+ } );
5750
+ }
5751
+
5752
+ } else {
5753
+ // This is a fallback that will work for most fields
5754
+ fieldValue = $$.val();
5755
+ }
5756
+
5757
+ // Now, we need to filter this value if necessary
5758
+ if ( ! _.isUndefined( $$.data( 'panels-filter' ) ) ) {
5759
+ switch ( $$.data( 'panels-filter' ) ) {
5760
+ case 'json_parse':
5761
+ // Attempt to parse the JSON value of this field
5762
+ try {
5763
+ fieldValue = JSON.parse( fieldValue );
5764
+ }
5765
+ catch ( err ) {
5766
+ fieldValue = '';
5767
+ }
5768
+ break;
5769
+ }
5770
+ }
5771
+
5772
+ // Now convert this into an array
5773
+ if ( fieldValue !== null ) {
5774
+ for ( var i = 0; i < parts.length; i ++ ) {
5775
+ if ( i === parts.length - 1 ) {
5776
+ if ( parts[i] === '' ) {
5777
+ // This needs to be an array
5778
+ sub.push( fieldValue );
5779
+ } else {
5780
+ sub[parts[i]] = fieldValue;
5781
+ }
5782
+ } else {
5783
+ if ( _.isUndefined( sub[parts[i]] ) ) {
5784
+ if ( parts[i + 1] === '' ) {
5785
+ sub[parts[i]] = [];
5786
+ } else {
5787
+ sub[parts[i]] = {};
5788
+ }
5789
+ }
5790
+ sub = sub[parts[i]];
5791
+ }
5792
+ }
5793
+ }
5794
+ }
5795
+ catch ( error ) {
5796
+ // Ignore this error, just log the message for debugging
5797
+ console.log( 'Field [' + $$.attr('name') + '] could not be processed and was skipped - ' + error.message );
5798
+ }
5799
+
5800
+ } ); // End of each through input fields
5801
+
5802
+ return data;
5803
+ },
5804
+
5805
+ /**
5806
+ * Set a status message for the dialog
5807
+ */
5808
+ setStatusMessage: function ( message, loading, error ) {
5809
+ var msg = error ? '<span class="dashicons dashicons-warning"></span>' + message : message;
5810
+ this.$( '.so-toolbar .so-status' ).html( msg );
5811
+ if ( ! _.isUndefined( loading ) && loading ) {
5812
+ this.$( '.so-toolbar .so-status' ).addClass( 'so-panels-loading' );
5813
+ } else {
5814
+ this.$( '.so-toolbar .so-status' ).removeClass( 'so-panels-loading' );
5815
+ }
5816
+ },
5817
+
5818
+ /**
5819
+ * Set the parent after.
5820
+ */
5821
+ setParent: function ( text, dialog ) {
5822
+ this.parentDialog = {
5823
+ text: text,
5824
+ dialog: dialog
5825
+ };
5826
+ },
5827
+
5828
+ onResize: function () {
5829
+ var mediaQuery = window.matchMedia( '(max-width: 980px)' );
5830
+ var sides = [ 'left', 'right' ];
5831
+
5832
+ sides.forEach( function ( side ) {
5833
+ var $sideBar = this.$( '.so-' + side + '-sidebar' );
5834
+ var $showSideBarButton = this.$( '.so-show-' + side + '-sidebar' );
5835
+ if ( this.hasSidebar( side ) ) {
5836
+ $showSideBarButton.hide();
5837
+ if ( mediaQuery.matches ) {
5838
+ $showSideBarButton.show();
5839
+ $showSideBarButton.closest( '.so-title-bar' ).addClass( 'so-has-' + side + '-button' );
5840
+ $sideBar.hide();
5841
+ $sideBar.closest( '.so-panels-dialog' ).removeClass( 'so-panels-dialog-has-' + side + '-sidebar' );
5842
+ } else {
5843
+ $showSideBarButton.hide();
5844
+ $showSideBarButton.closest( '.so-title-bar' ).removeClass( 'so-has-' + side + '-button' );
5845
+ $sideBar.show();
5846
+ $sideBar.closest( '.so-panels-dialog' ).addClass( 'so-panels-dialog-has-' + side + '-sidebar' );
5847
+ }
5848
+ } else {
5849
+ $sideBar.hide();
5850
+ $showSideBarButton.hide();
5851
+ }
5852
+ }.bind( this ) );
5853
+ },
5854
+
5855
+ hasSidebar: function ( side ) {
5856
+ return this.$( '.so-' + side + '-sidebar' ).children().length > 0;
5857
+ },
5858
+
5859
+ toggleLeftSideBar: function () {
5860
+ this.toggleSidebar( 'left' );
5861
+ },
5862
+
5863
+ toggleRightSideBar: function () {
5864
+ this.toggleSidebar( 'right' );
5865
+ },
5866
+
5867
+ toggleSidebar: function ( side ) {
5868
+ var sidebar = this.$( '.so-' + side + '-sidebar' );
5869
+
5870
+ if ( sidebar.is( ':visible' ) ) {
5871
+ sidebar.hide();
5872
+ } else {
5873
+ sidebar.show();
5874
+ }
5875
+ },
5876
+
5877
+ } );
5878
+
5879
+ },{}],26:[function(require,module,exports){
5880
+ var panels = window.panels, $ = jQuery;
5881
+
5882
+ module.exports = Backbone.View.extend( {
5883
+ template: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-live-editor' ).html() ) ),
5884
+
5885
+ previewScrollTop: 0,
5886
+ loadTimes: [],
5887
+ previewFrameId: 1,
5888
+
5889
+ previewUrl: null,
5890
+ previewIframe: null,
5891
+
5892
+ events: {
5893
+ 'click .live-editor-close': 'close',
5894
+ 'click .live-editor-save': 'closeAndSave',
5895
+ 'click .live-editor-collapse': 'collapse',
5896
+ 'click .live-editor-mode': 'mobileToggle'
5897
+ },
5898
+
5899
+ initialize: function ( options ) {
5900
+ options = _.extend( {
5901
+ builder: false,
5902
+ previewUrl: false,
5903
+ }, options );
5904
+
5905
+ if( _.isEmpty( options.previewUrl ) ) {
5906
+ options.previewUrl = panelsOptions.ajaxurl + "&action=so_panels_live_editor_preview";
5907
+ }
5908
+
5909
+ this.builder = options.builder;
5910
+ this.previewUrl = options.previewUrl;
5911
+
5912
+ this.listenTo( this.builder.model, 'refresh_panels_data', this.handleRefreshData );
5913
+ this.listenTo( this.builder.model, 'load_panels_data', this.handleLoadData );
5914
+ },
5915
+
5916
+ /**
5917
+ * Render the live editor
5918
+ */
5919
+ render: function () {
5920
+ this.setElement( this.template() );
5921
+ this.$el.hide();
5922
+
5923
+ if ( $( '#submitdiv #save-post' ).length > 0 ) {
5924
+ var $saveButton = this.$el.find( '.live-editor-save' );
5925
+ $saveButton.text( $saveButton.data( 'save' ) );
5926
+ }
5927
+
5928
+ var isMouseDown = false;
5929
+ $( document )
5930
+ .mousedown( function () {
5931
+ isMouseDown = true;
5932
+ } )
5933
+ .mouseup( function () {
5934
+ isMouseDown = false;
5935
+ } );
5936
+
5937
+ // Handle highlighting the relevant widget in the live editor preview
5938
+ var liveEditorView = this;
5939
+ this.$el.on( 'mouseenter', '.so-widget-wrapper', function () {
5940
+ var $$ = $( this ),
5941
+ previewWidget = $$.data( 'live-editor-preview-widget' );
5942
+
5943
+ if ( ! isMouseDown && previewWidget !== undefined && previewWidget.length && ! liveEditorView.$( '.so-preview-overlay' ).is( ':visible' ) ) {
5944
+ liveEditorView.highlightElement( previewWidget );
5945
+ liveEditorView.scrollToElement( previewWidget );
5946
+ }
5947
+ } );
5948
+
5949
+ this.$el.on( 'mouseleave', '.so-widget-wrapper', function () {
5950
+ this.resetHighlights();
5951
+ }.bind(this) );
5952
+
5953
+ this.listenTo( this.builder, 'open_dialog', function () {
5954
+ this.resetHighlights();
5955
+ } );
5956
+
5957
+ return this;
5958
+ },
5959
+
5960
+ /**
5961
+ * Attach the live editor to the document
5962
+ */
5963
+ attach: function () {
5964
+ this.$el.appendTo( 'body' );
5965
+ },
5966
+
5967
+ /**
5968
+ * Display the live editor
5969
+ */
5970
+ open: function () {
5971
+ if ( this.$el.html() === '' ) {
5972
+ this.render();
5973
+ }
5974
+ if ( this.$el.closest( 'body' ).length === 0 ) {
5975
+ this.attach();
5976
+ }
5977
+
5978
+ // Disable page scrolling
5979
+ panels.helpers.pageScroll.lock();
5980
+
5981
+ if ( this.$el.is( ':visible' ) ) {
5982
+ return this;
5983
+ }
5984
+
5985
+ // Refresh the preview display
5986
+ this.$el.show();
5987
+ this.refreshPreview( this.builder.model.getPanelsData() );
5988
+
5989
+ // Move the builder view into the Live Editor
5990
+ this.originalContainer = this.builder.$el.parent();
5991
+ this.builder.$el.appendTo( this.$( '.so-live-editor-builder' ) );
5992
+ this.builder.$( '.so-tool-button.so-live-editor' ).hide();
5993
+ this.builder.trigger( 'builder_resize' );
5994
+
5995
+
5996
+ if( $('#original_post_status' ).val() === 'auto-draft' && ! this.autoSaved ) {
5997
+ // The live editor requires a saved draft post, so we'll create one for auto-draft posts
5998
+ var thisView = this;
5999
+
6000
+ if ( wp.autosave ) {
6001
+ // Set a temporary post title so the autosave triggers properly
6002
+ if( $('#title[name="post_title"]' ).val() === '' ) {
6003
+ $('#title[name="post_title"]' ).val( panelsOptions.loc.draft ).trigger('keydown');
6004
+ }
6005
+
6006
+ $( document ).one( 'heartbeat-tick.autosave', function(){
6007
+ thisView.autoSaved = true;
6008
+ thisView.refreshPreview( thisView.builder.model.getPanelsData() );
6009
+ } );
6010
+ wp.autosave.server.triggerSave();
6011
+ }
6012
+ }
6013
+ },
6014
+
6015
+ /**
6016
+ * Close the Live Editor
6017
+ */
6018
+ close: function () {
6019
+ if ( ! this.$el.is( ':visible' ) ) {
6020
+ return this;
6021
+ }
6022
+
6023
+ this.$el.hide();
6024
+ panels.helpers.pageScroll.unlock();
6025
+
6026
+ // Move the builder back to its original container
6027
+ this.builder.$el.appendTo( this.originalContainer );
6028
+ this.builder.$( '.so-tool-button.so-live-editor' ).show();
6029
+ this.builder.trigger( 'builder_resize' );
6030
+ },
6031
+
6032
+ /**
6033
+ * Close the Live Editor and save the post.
6034
+ */
6035
+ closeAndSave: function(){
6036
+ this.close();
6037
+ // Finds the submit input for saving without publishing draft posts.
6038
+ $('#submitdiv input[type="submit"][name="save"]').click();
6039
+ },
6040
+
6041
+ /**
6042
+ * Collapse the live editor
6043
+ */
6044
+ collapse: function () {
6045
+ this.$el.toggleClass( 'so-collapsed' );
6046
+ },
6047
+
6048
+ /**
6049
+ * Create an overlay in the preview.
6050
+ *
6051
+ * @param over
6052
+ * @return {*|Object} The item we're hovering over.
6053
+ */
6054
+ highlightElement: function ( over ) {
6055
+ if( ! _.isUndefined( this.resetHighlightTimeout ) ) {
6056
+ clearTimeout( this.resetHighlightTimeout );
6057
+ }
6058
+
6059
+ // Remove any old overlays
6060
+
6061
+ var body = this.previewIframe.contents().find( 'body' );
6062
+ body.find( '.panel-grid .panel-grid-cell .so-panel' )
6063
+ .filter( function () {
6064
+ // Filter to only include non nested
6065
+ return $( this ).parents( '.so-panel' ).length === 0;
6066
+ } )
6067
+ .not( over )
6068
+ .addClass( 'so-panels-faded' );
6069
+
6070
+ over.removeClass( 'so-panels-faded' ).addClass( 'so-panels-highlighted' );
6071
+ },
6072
+
6073
+ /**
6074
+ * Reset highlights in the live preview
6075
+ */
6076
+ resetHighlights: function() {
6077
+
6078
+ var body = this.previewIframe.contents().find( 'body' );
6079
+ this.resetHighlightTimeout = setTimeout( function(){
6080
+ body.find( '.panel-grid .panel-grid-cell .so-panel' )
6081
+ .removeClass( 'so-panels-faded so-panels-highlighted' );
6082
+ }, 100 );
6083
+ },
6084
+
6085
+ /**
6086
+ * Scroll over an element in the live preview
6087
+ * @param over
6088
+ */
6089
+ scrollToElement: function( over ) {
6090
+ var contentWindow = this.$( '.so-preview iframe' )[0].contentWindow;
6091
+ contentWindow.liveEditorScrollTo( over );
6092
+ },
6093
+
6094
+ handleRefreshData: function ( newData ) {
6095
+ if ( ! this.$el.is( ':visible' ) ) {
6096
+ return this;
6097
+ }
6098
+
6099
+ this.refreshPreview( newData );
6100
+ },
6101
+
6102
+ handleLoadData: function () {
6103
+ if ( ! this.$el.is( ':visible' ) ) {
6104
+ return this;
6105
+ }
6106
+
6107
+ this.refreshPreview( this.builder.model.getPanelsData() );
6108
+ },
6109
+
6110
+ /**
6111
+ * Refresh the Live Editor preview.
6112
+ * @returns {exports}
6113
+ */
6114
+ refreshPreview: function ( data ) {
6115
+ var loadTimePrediction = this.loadTimes.length ?
6116
+ _.reduce( this.loadTimes, function ( memo, num ) {
6117
+ return memo + num;
6118
+ }, 0 ) / this.loadTimes.length : 1000;
6119
+
6120
+ // Store the last preview iframe position
6121
+ if( ! _.isNull( this.previewIframe ) ) {
6122
+ if ( ! this.$( '.so-preview-overlay' ).is( ':visible' ) ) {
6123
+ this.previewScrollTop = this.previewIframe.contents().scrollTop();
6124
+ }
6125
+ }
6126
+
6127
+ // Add a loading bar
6128
+ this.$( '.so-preview-overlay' ).show();
6129
+ this.$( '.so-preview-overlay .so-loading-bar' )
6130
+ .clearQueue()
6131
+ .css( 'width', '0%' )
6132
+ .animate( {width: '100%'}, parseInt( loadTimePrediction ) + 100 );
6133
+
6134
+
6135
+ this.postToIframe(
6136
+ {
6137
+ live_editor_panels_data: JSON.stringify( data ),
6138
+ live_editor_post_ID: this.builder.config.postId
6139
+ },
6140
+ this.previewUrl,
6141
+ this.$('.so-preview')
6142
+ );
6143
+
6144
+ this.previewIframe.data( 'load-start', new Date().getTime() );
6145
+ },
6146
+
6147
+ /**
6148
+ * Use a temporary form to post data to an iframe.
6149
+ *
6150
+ * @param data The data to send
6151
+ * @param url The preview URL
6152
+ * @param target The target iframe
6153
+ */
6154
+ postToIframe: function( data, url, target ){
6155
+ // Store the old preview
6156
+
6157
+ if( ! _.isNull( this.previewIframe ) ) {
6158
+ this.previewIframe.remove();
6159
+ }
6160
 
6161
+ var iframeId = 'siteorigin-panels-live-preview-' + this.previewFrameId;
6162
+
6163
+ // Remove the old preview frame
6164
+ this.previewIframe = $('<iframe src="javascript:false;" />')
6165
+ .attr( {
6166
+ 'id' : iframeId,
6167
+ 'name' : iframeId,
6168
+ } )
6169
+ .appendTo( target );
6170
+
6171
+ this.setupPreviewFrame( this.previewIframe );
6172
+
6173
+ // We can use a normal POST form submit
6174
+ var tempForm = $('<form id="soPostToPreviewFrame" method="post" />')
6175
+ .attr( {
6176
+ id: iframeId,
6177
+ target: this.previewIframe.attr('id'),
6178
+ action: url
6179
+ } )
6180
+ .appendTo( 'body' );
6181
+
6182
+ $.each( data, function( name, value ){
6183
+ $('<input type="hidden" />')
6184
+ .attr( {
6185
+ name: name,
6186
+ value: value
6187
+ } )
6188
+ .appendTo( tempForm );
6189
+ } );
6190
+
6191
+ tempForm
6192
+ .submit()
6193
+ .remove();
6194
+
6195
+ this.previewFrameId++;
6196
+
6197
+ return this.previewIframe;
6198
+ },
6199
+
6200
+ /**
6201
+ * Do all the basic setup for the preview Iframe element
6202
+ * @param iframe
6203
+ */
6204
+ setupPreviewFrame: function( iframe ){
6205
+ var thisView = this;
6206
+ iframe
6207
+ .data( 'iframeready', false )
6208
+ .on( 'iframeready', function () {
6209
+ var $$ = $( this ),
6210
+ $iframeContents = $$.contents();
6211
+
6212
+ if( $$.data( 'iframeready' ) ) {
6213
+ // Skip this if the iframeready function has already run
6214
+ return;
6215
+ }
6216
+
6217
+ $$.data( 'iframeready', true );
6218
+
6219
+ if ( $$.data( 'load-start' ) !== undefined ) {
6220
+ thisView.loadTimes.unshift( new Date().getTime() - $$.data( 'load-start' ) );
6221
+
6222
+ if ( ! _.isEmpty( thisView.loadTimes ) ) {
6223
+ thisView.loadTimes = thisView.loadTimes.slice( 0, 4 );
6224
+ }
6225
+ }
6226
+
6227
+ setTimeout( function(){
6228
+ // Scroll to the correct position
6229
+ $iframeContents.scrollTop( thisView.previewScrollTop );
6230
+ thisView.$( '.so-preview-overlay' ).hide();
6231
+ }, 100 );
6232
+
6233
+
6234
+ // Lets find all the first level grids. This is to account for the Page Builder layout widget.
6235
+ var layoutWrapper = $iframeContents.find( '#pl-' + thisView.builder.config.postId );
6236
+ layoutWrapper.find( '.panel-grid .panel-grid-cell .so-panel' )
6237
+ .filter( function () {
6238
+ // Filter to only include non nested
6239
+ return $( this ).closest( '.panel-layout' ).is( layoutWrapper );
6240
+ } )
6241
+ .each( function ( i, el ) {
6242
+ var $$ = $( el );
6243
+ var widgetEdit = thisView.$( '.so-live-editor-builder .so-widget-wrapper' ).eq( $$.data( 'index' ) );
6244
+ widgetEdit.data( 'live-editor-preview-widget', $$ );
6245
+
6246
+ $$
6247
+ .css( {
6248
+ 'cursor': 'pointer'
6249
+ } )
6250
+ .mouseenter( function () {
6251
+ widgetEdit.parent().addClass( 'so-hovered' );
6252
+ thisView.highlightElement( $$ );
6253
+ } )
6254
+ .mouseleave( function () {
6255
+ widgetEdit.parent().removeClass( 'so-hovered' );
6256
+ thisView.resetHighlights();
6257
+ } )
6258
+ .click( function ( e ) {
6259
+ e.preventDefault();
6260
+ // When we click a widget, send that click to the form
6261
+ widgetEdit.find( '.title h4' ).click();
6262
+ } );
6263
  } );
6264
+
6265
+ // Prevent default clicks inside the preview iframe
6266
+ $iframeContents.find( "a" ).css( {'pointer-events': 'none'} ).click( function ( e ) {
6267
+ e.preventDefault();
6268
  } );
6269
+
6270
+ } )
6271
+ .on( 'load', function(){
6272
+ var $$ = $( this );
6273
+ if( ! $$.data( 'iframeready' ) ) {
6274
+ $$.trigger('iframeready');
6275
+ }
6276
  } );
6277
+ },
6278
 
6279
+ /**
6280
+ * Return true if the live editor has a valid preview URL.
6281
+ * @return {boolean}
6282
+ */
6283
+ hasPreviewUrl: function () {
6284
+ return this.$( 'form.live-editor-form' ).attr( 'action' ) !== '';
6285
+ },
6286
 
6287
+ /**
6288
+ * Toggle the size of the preview iframe to simulate mobile devices.
6289
+ * @param e
6290
+ */
6291
+ mobileToggle: function( e ){
6292
+ var button = $( e.currentTarget );
6293
+ this.$('.live-editor-mode' ).not( button ).removeClass('so-active');
6294
+ button.addClass( 'so-active' );
6295
+
6296
+ this.$el
6297
+ .removeClass( 'live-editor-desktop-mode live-editor-tablet-mode live-editor-mobile-mode' )
6298
+ .addClass( 'live-editor-' + button.data( 'mode' ) + '-mode' );
 
 
 
 
 
 
 
 
 
 
 
6299
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6300
  }
6301
  } );
6302
 
6303
+ },{}],27:[function(require,module,exports){
6304
+ var panels = window.panels, $ = jQuery;
 
 
6305
 
6306
+ module.exports = Backbone.View.extend( {
6307
+ template: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-builder-row' ).html() ) ),
6308
 
6309
+ events: {
6310
+ 'click .so-row-settings': 'editSettingsHandler',
6311
+ 'click .so-row-duplicate': 'duplicateHandler',
6312
+ 'click .so-row-delete': 'confirmedDeleteHandler',
6313
+ 'click .so-row-color': 'rowColorChangeHandler',
6314
  },
6315
 
6316
+ builder: null,
6317
+ dialog: null,
6318
 
6319
  /**
6320
+ * Initialize the row view
6321
  */
6322
  initialize: function () {
6323
+
6324
+ var rowCells = this.model.get('cells');
6325
+ this.listenTo(rowCells, 'add', this.handleCellAdd );
6326
+ this.listenTo(rowCells, 'remove', this.handleCellRemove );
6327
+
6328
+ this.listenTo( this.model, 'reweight_cells', this.resize );
6329
+ this.listenTo( this.model, 'destroy', this.onModelDestroy );
6330
+
6331
+ var thisView = this;
6332
+ rowCells.each( function ( cell ) {
6333
+ thisView.listenTo( cell.get('widgets'), 'add', thisView.resize );
6334
+ } );
6335
+
6336
+ // When ever a new cell is added, listen to it for new widgets
6337
+ rowCells.on( 'add', function ( cell ) {
6338
+ thisView.listenTo( cell.get('widgets'), 'add', thisView.resize );
6339
+ }, this );
6340
+
6341
+ this.listenTo( this.model, 'change:label', this.onLabelChange );
6342
  },
6343
 
6344
  /**
6345
+ * Render the row.
6346
+ *
6347
+ * @returns {panels.view.row}
6348
  */
6349
+ render: function () {
6350
+ var rowColorLabel = this.model.has( 'color_label' ) ? this.model.get( 'color_label' ) : 1;
6351
+ var rowLabel = this.model.has( 'label' ) ? this.model.get( 'label' ) : '';
6352
+ this.setElement( this.template( { rowColorLabel: rowColorLabel, rowLabel: rowLabel } ) );
6353
+ this.$el.data( 'view', this );
6354
+
6355
+ // Create views for the cells in this row
6356
+ var thisView = this;
6357
+ this.model.get('cells').each( function ( cell ) {
6358
+ var cellView = new panels.view.cell( {
6359
+ model: cell
6360
+ } );
6361
+ cellView.row = thisView;
6362
+ cellView.render();
6363
+ cellView.$el.appendTo( thisView.$( '.so-cells' ) );
6364
+ } );
6365
+
6366
+ // Remove any unsupported actions
6367
+ if( ! this.builder.supports( 'rowAction' ) ) {
6368
+ this.$('.so-row-toolbar .so-dropdown-wrapper' ).remove();
6369
+ this.$el.addClass('so-row-no-actions');
6370
+ }
6371
+ else {
6372
+ if( ! this.builder.supports( 'editRow' ) ) {
6373
+ this.$('.so-row-toolbar .so-dropdown-links-wrapper .so-row-settings' ).parent().remove();
6374
+ this.$el.addClass('so-row-no-edit');
6375
+ }
6376
+ if( ! this.builder.supports( 'addRow' ) ) {
6377
+ this.$('.so-row-toolbar .so-dropdown-links-wrapper .so-row-duplicate' ).parent().remove();
6378
+ this.$el.addClass('so-row-no-duplicate');
6379
+ }
6380
+ if( ! this.builder.supports( 'deleteRow' ) ) {
6381
+ this.$('.so-row-toolbar .so-dropdown-links-wrapper .so-row-delete' ).parent().remove();
6382
+ this.$el.addClass('so-row-no-delete');
6383
+ }
6384
+ }
6385
+ if( ! this.builder.supports( 'moveRow' ) ) {
6386
+ this.$('.so-row-toolbar .so-row-move' ).remove();
6387
+ this.$el.addClass('so-row-no-move');
6388
+ }
6389
+ if( !$.trim( this.$('.so-row-toolbar').html() ).length ) {
6390
+ this.$('.so-row-toolbar' ).remove();
6391
+ }
6392
+
6393
+ // Resize the rows when ever the widget sortable moves
6394
+ this.listenTo( this.builder, 'widget_sortable_move', this.resize );
6395
+ this.listenTo( this.builder, 'builder_resize', this.resize );
6396
+
6397
+ this.resize();
6398
+
6399
+ return this;
6400
  },
6401
 
6402
  /**
6403
+ * Give a visual indication of the creation of this row
6404
  */
6405
+ visualCreate: function () {
6406
+ this.$el.hide().fadeIn( 'fast' );
6407
+ },
6408
+
6409
+ /**
6410
+ * Visually resize the row so that all cell heights are the same and the widths so that they balance to 100%
6411
+ *
6412
+ * @param e
6413
+ */
6414
+ resize: function ( e ) {
6415
+ // Don't resize this
6416
+ if ( ! this.$el.is( ':visible' ) ) {
6417
+ return;
6418
+ }
6419
+
6420
+ // Reset everything to have an automatic height
6421
+ this.$( '.so-cells .cell-wrapper' ).css( 'min-height', 0 );
6422
+ this.$( '.so-cells .resize-handle' ).css( 'height', 0 );
6423
+
6424
+ // We'll tie the values to the row view, to prevent issue with values going to different rows
6425
+ var height = 0;
6426
+ this.$( '.so-cells .cell' ).each( function () {
6427
+ height = Math.max(
6428
+ height,
6429
+ $( this ).height()
6430
+ );
6431
+
6432
+ $( this ).css(
6433
+ 'width',
6434
+ ( $( this ).data( 'view' ).model.get( 'weight' ) * 100) + "%"
6435
+ );
6436
+ } );
6437
+
6438
+ // Resize all the grids and cell wrappers
6439
+ this.$( '.so-cells .cell-wrapper' ).css( 'min-height', Math.max( height, 63 ) );
6440
+ this.$( '.so-cells .resize-handle' ).css( 'height', this.$( '.so-cells .cell-wrapper' ).outerHeight() );
6441
+ },
6442
+
6443
+ /**
6444
+ * Remove the view from the dom.
6445
+ */
6446
+ onModelDestroy: function () {
6447
+ this.remove();
6448
+ },
6449
+
6450
+ /**
6451
+ * Fade out the view and destroy the model
6452
+ */
6453
+ visualDestroyModel: function () {
6454
+ this.builder.addHistoryEntry( 'row_deleted' );
6455
+ var thisView = this;
6456
+ this.$el.fadeOut( 'normal', function () {
6457
+ thisView.model.destroy();
6458
+ thisView.builder.model.refreshPanelsData();
6459
+ } );
6460
+ },
6461
+
6462
+ onLabelChange: function( model, text ) {
6463
+ if ( this.$('.so-row-label').length == 0 ) {
6464
+ this.$( '.so-row-toolbar' ).prepend( '<h3 class="so-row-label">' + text + '</h3>' );
6465
+ } else {
6466
+ this.$('.so-row-label').text( text );
6467
+ }
6468
+ },
6469
+
6470
+ /**
6471
+ * Duplicate this row.
6472
+ *
6473
+ * @return {boolean}
6474
+ */
6475
+ duplicateHandler: function () {
6476
+ this.builder.addHistoryEntry( 'row_duplicated' );
6477
+
6478
+ var duplicateRow = this.model.clone( this.builder.model );
6479
+
6480
+ this.builder.model.get('rows').add( duplicateRow, {
6481
+ at: this.builder.model.get('rows').indexOf( this.model ) + 1
6482
+ } );
6483
+
6484
+ this.builder.model.refreshPanelsData();
6485
+ },
6486
+
6487
+ /**
6488
+ * Copy the row to a localStorage
6489
+ */
6490
+ copyHandler: function(){
6491
+ panels.helpers.clipboard.setModel( this.model );
6492
+ },
6493
+
6494
+ /**
6495
+ * Create a new row and insert it
6496
+ */
6497
+ pasteHandler: function(){
6498
+ var pastedModel = panels.helpers.clipboard.getModel( 'row-model' );
6499
+
6500
+ if( ! _.isEmpty( pastedModel ) && pastedModel instanceof panels.model.row ) {
6501
+ this.builder.addHistoryEntry( 'row_pasted' );
6502
+ pastedModel.builder = this.builder.model;
6503
+ this.builder.model.get('rows').add( pastedModel, {
6504
+ at: this.builder.model.get('rows').indexOf( this.model ) + 1
6505
+ } );
6506
+ this.builder.model.refreshPanelsData();
6507
+ }
6508
+ },
6509
+
6510
+ /**
6511
+ * Handles deleting the row with a confirmation.
6512
+ */
6513
+ confirmedDeleteHandler: function ( e ) {
6514
+ var $$ = $( e.target );
6515
+
6516
+ // The user clicked on the dashicon
6517
+ if ( $$.hasClass( 'dashicons' ) ) {
6518
+ $$ = $$.parent();
6519
+ }
6520
+
6521
+ if ( $$.hasClass( 'so-confirmed' ) ) {
6522
+ this.visualDestroyModel();
6523
+ } else {
6524
+ var originalText = $$.html();
6525
+
6526
+ $$.addClass( 'so-confirmed' ).html(
6527
+ '<span class="dashicons dashicons-yes"></span>' + panelsOptions.loc.dropdown_confirm
6528
+ );
6529
+
6530
+ setTimeout( function () {
6531
+ $$.removeClass( 'so-confirmed' ).html( originalText );
6532
+ }, 2500 );
6533
+ }
6534
+ },
6535
+
6536
+ /**
6537
+ * Handle displaying the settings dialog
6538
+ */
6539
+ editSettingsHandler: function () {
6540
+ if ( ! this.builder.supports( 'editRow' ) ) {
6541
+ return;
6542
+ }
6543
+ // Lets open up an instance of the settings dialog
6544
+ if ( this.dialog === null ) {
6545
+ // Create the dialog
6546
+ this.dialog = new panels.dialog.row();
6547
+ this.dialog.setBuilder( this.builder ).setRowModel( this.model );
6548
+ this.dialog.rowView = this;
6549
+ }
6550
+
6551
+ this.dialog.openDialog();
6552
+
6553
+ return this;
6554
+ },
6555
+
6556
+ /**
6557
+ * Handle deleting this entire row.
6558
+ */
6559
+ deleteHandler: function () {
6560
+ this.model.destroy();
6561
+ return this;
6562
+ },
6563
+
6564
+ /**
6565
+ * Change the row background color.
6566
+ */
6567
+ rowColorChangeHandler: function ( event ) {
6568
+ this.$( '.so-row-color' ).removeClass( 'so-row-color-selected' );
6569
+ var clickedColorElem = $( event.target );
6570
+ var newColorLabel = clickedColorElem.data( 'color-label' );
6571
+ var oldColorLabel = this.model.has( 'color_label' ) ? this.model.get( 'color_label' ) : 1;
6572
+ clickedColorElem.addClass( 'so-row-color-selected' );
6573
+ this.$el.removeClass( 'so-row-color-' + oldColorLabel );
6574
+ this.$el.addClass( 'so-row-color-' + newColorLabel );
6575
+ this.model.set( 'color_label', newColorLabel );
6576
+ },
6577
+
6578
+ /**
6579
+ * Handle a new cell being added to this row view. For now we'll assume the new cell is always last
6580
+ */
6581
+ handleCellAdd: function ( cell ) {
6582
+ var cellView = new panels.view.cell( {
6583
+ model: cell
6584
+ } );
6585
+ cellView.row = this;
6586
+ cellView.render();
6587
+ cellView.$el.appendTo( this.$( '.so-cells' ) );
6588
+ },
6589
+
6590
+ /**
6591
+ * Handle a cell being removed from this row view
6592
+ */
6593
+ handleCellRemove: function ( cell ) {
6594
+ // Find the view that ties in to the cell we're removing
6595
+ this.$( '.so-cells > .cell' ).each( function () {
6596
+ var view = $( this ).data( 'view' );
6597
+ if ( _.isUndefined( view ) ) {
6598
+ return;
6599
+ }
6600
+
6601
+ if ( view.model.cid === cell.cid ) {
6602
+ // Remove this view
6603
+ view.remove();
6604
+ }
6605
+ } );
6606
+ },
6607
+
6608
+ /**
6609
+ * Build up the contextual menu for a row
6610
+ *
6611
+ * @param e
6612
+ * @param menu
6613
+ */
6614
+ buildContextualMenu: function ( e, menu ) {
6615
+ var options = [];
6616
+ for ( var i = 1; i < 5; i ++ ) {
6617
+ options.push( {
6618
+ title: i + ' ' + panelsOptions.loc.contextual.column
6619
+ } );
6620
+ }
6621
+
6622
+ if( this.builder.supports( 'addRow' ) ) {
6623
+ menu.addSection(
6624
+ 'add-row',
6625
+ {
6626
+ sectionTitle: panelsOptions.loc.contextual.add_row,
6627
+ search: false
6628
+ },
6629
+ options,
6630
+ function ( c ) {
6631
+ this.builder.addHistoryEntry( 'row_added' );
6632
+
6633
+ var columns = Number( c ) + 1;
6634
+ var weights = [];
6635
+ for ( var i = 0; i < columns; i ++ ) {
6636
+ weights.push( {weight: 100 / columns } );
6637
+ }
6638
+
6639
+ // Create the actual row
6640
+ var newRow = new panels.model.row( {
6641
+ collection: this.collection
6642
+ } );
6643
+
6644
+ var cells = new panels.collection.cells(weights);
6645
+ cells.each(function (cell) {
6646
+ cell.row = newRow;
6647
+ });
6648
+ newRow.setCells(cells);
6649
+ newRow.builder = this.builder.model;
6650
+
6651
+ this.builder.model.get('rows').add( newRow, {
6652
+ at: this.builder.model.get('rows').indexOf( this.model ) + 1
6653
+ } );
6654
+
6655
+ this.builder.model.refreshPanelsData();
6656
+ }.bind( this )
6657
+ );
6658
  }
 
6659
 
6660
+ var actions = {};
 
 
6661
 
6662
+ if( this.builder.supports( 'editRow' ) ) {
6663
+ actions.edit = { title: panelsOptions.loc.contextual.row_edit };
 
 
 
6664
  }
6665
 
6666
+ // Copy and paste functions
6667
+ if ( panels.helpers.clipboard.canCopyPaste() ) {
6668
+ actions.copy = { title: panelsOptions.loc.contextual.row_copy };
6669
+ if ( this.builder.supports( 'addRow' ) && panels.helpers.clipboard.isModel( 'row-model' ) ) {
6670
+ actions.paste = { title: panelsOptions.loc.contextual.row_paste };
6671
+ }
6672
+ }
6673
 
6674
+ if( this.builder.supports( 'addRow' ) ) {
6675
+ actions.duplicate = { title: panelsOptions.loc.contextual.row_duplicate };
6676
+ }
6677
 
6678
+ if( this.builder.supports( 'deleteRow' ) ) {
6679
+ actions.delete = { title: panelsOptions.loc.contextual.row_delete, confirm: true };
6680
+ }
6681
+
6682
+ if( ! _.isEmpty( actions ) ) {
6683
+ menu.addSection(
6684
+ 'row-actions',
6685
+ {
6686
+ sectionTitle: panelsOptions.loc.contextual.row_actions,
6687
+ search: false,
6688
+ },
6689
+ actions,
6690
+ function ( c ) {
6691
+ switch ( c ) {
6692
+ case 'edit':
6693
+ this.editSettingsHandler();
6694
+ break;
6695
+ case 'copy':
6696
+ this.copyHandler();
6697
+ break;
6698
+ case 'paste':
6699
+ this.pasteHandler();
6700
+ break;
6701
+ case 'duplicate':
6702
+ this.duplicateHandler();
6703
+ break;
6704
+ case 'delete':
6705
+ this.visualDestroyModel();
6706
+ break;
6707
+ }
6708
+ }.bind( this )
6709
+ );
6710
+ }
6711
+ },
6712
  } );
6713
 
6714
+ },{}],28:[function(require,module,exports){
6715
+ var panels = window.panels, $ = jQuery;
 
 
6716
 
6717
+ module.exports = Backbone.View.extend( {
 
 
6718
 
6719
+ stylesLoaded: false,
6720
 
 
 
 
6721
  initialize: function () {
 
 
 
 
 
 
 
 
 
 
 
6722
 
6723
+ },
6724
+
6725
  /**
6726
+ * Render the visual styles object.
6727
  *
6728
+ * @param stylesType
6729
+ * @param postId
6730
+ * @param args
6731
  */
6732
+ render: function ( stylesType, postId, args ) {
6733
+ if ( _.isUndefined( stylesType ) ) {
6734
+ return;
6735
+ }
 
 
 
 
 
 
6736
 
6737
+ // Add in the default args
6738
+ args = _.extend( {
6739
+ builderType: '',
6740
+ dialog: null
6741
+ }, args );
6742
 
6743
+ this.$el.addClass( 'so-visual-styles so-' + stylesType + '-styles so-panels-loading' );
 
 
6744
 
6745
+ var postArgs = {
6746
+ builderType: args.builderType
6747
+ };
6748
 
6749
+ if ( stylesType === 'cell') {
6750
+ postArgs.index = args.index;
 
 
 
 
 
6751
  }
6752
+
6753
+ // Load the form
6754
+ $.post(
6755
+ panelsOptions.ajaxurl,
6756
+ {
6757
+ action: 'so_panels_style_form',
6758
+ type: stylesType,
6759
+ style: this.model.get( 'style' ),
6760
+ args: JSON.stringify( postArgs ),
6761
+ postId: postId
6762
+ },
6763
+ null,
6764
+ 'html'
6765
+ ).done( function ( response ) {
6766
+ this.$el.html( response );
6767
+ this.setupFields();
6768
+ this.stylesLoaded = true;
6769
+ this.trigger( 'styles_loaded', !_.isEmpty( response ) );
6770
+ if ( !_.isNull( args.dialog ) ) {
6771
+ args.dialog.trigger( 'styles_loaded', !_.isEmpty( response ) );
6772
+ }
6773
+ }.bind( this ) )
6774
+ .fail( function ( error ) {
6775
+ var html;
6776
+ if ( error && error.responseText ) {
6777
+ html = error.responseText;
6778
+ } else {
6779
+ html = panelsOptions.forms.loadingFailed;
6780
+ }
6781
+
6782
+ this.$el.html( html );
6783
+ }.bind( this ) )
6784
+ .always( function () {
6785
+ this.$el.removeClass( 'so-panels-loading' );
6786
+ }.bind( this ) );
6787
 
6788
+ return this;
 
6789
  },
6790
 
6791
  /**
6792
+ * Attach the style view to the DOM.
6793
+ *
6794
+ * @param wrapper
6795
  */
6796
+ attach: function ( wrapper ) {
6797
+ wrapper.append( this.$el );
 
 
 
 
 
 
 
 
 
 
 
6798
  },
6799
 
6800
  /**
6801
+ * Detach the styles view from the DOM
6802
  */
6803
+ detach: function () {
6804
+ this.$el.detach();
 
 
6805
  },
6806
 
6807
  /**
6808
+ * Setup all the fields
 
 
 
 
6809
  */
6810
+ setupFields: function () {
 
 
 
6811
 
6812
+ // Set up the sections as collapsible
6813
+ this.$( '.style-section-wrapper' ).each( function () {
6814
+ var $s = $( this );
6815
 
6816
+ $s.find( '.style-section-head' ).click( function ( e ) {
6817
+ e.preventDefault();
6818
+ $s.find( '.style-section-fields' ).slideToggle( 'fast' );
6819
+ } );
6820
  } );
6821
 
6822
+ // Set up the color fields
6823
+ if ( ! _.isUndefined( $.fn.wpColorPicker ) ) {
6824
+ if ( _.isObject( panelsOptions.wpColorPickerOptions.palettes ) && ! $.isArray( panelsOptions.wpColorPickerOptions.palettes ) ) {
6825
+ panelsOptions.wpColorPickerOptions.palettes = $.map( panelsOptions.wpColorPickerOptions.palettes, function ( el ) {
6826
+ return el;
6827
+ } );
6828
+ }
6829
+ this.$( '.so-wp-color-field' ).wpColorPicker( panelsOptions.wpColorPickerOptions );
6830
+ }
6831
 
6832
+ // Set up the image select fields
6833
+ this.$( '.style-field-image' ).each( function () {
6834
+ var frame = null;
6835
+ var $s = $( this );
6836
 
6837
+ $s.find( '.so-image-selector' ).click( function ( e ) {
6838
+ e.preventDefault();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6839
 
6840
+ if ( frame === null ) {
6841
+ // Create the media frame.
6842
+ frame = wp.media( {
6843
+ // Set the title of the modal.
6844
+ title: 'choose',
6845
+
6846
+ // Tell the modal to show only images.
6847
+ library: {
6848
+ type: 'image'
6849
+ },
6850
+
6851
+ // Customize the submit button.
6852
+ button: {
6853
+ // Set the text of the button.
6854
+ text: 'Done',
6855
+ close: true
6856
+ }
6857
+ } );
6858
 
6859
+ frame.on( 'select', function () {
6860
+ var attachment = frame.state().get( 'selection' ).first().attributes;
 
6861
 
6862
+ var url = attachment.url;
6863
+ if ( ! _.isUndefined( attachment.sizes ) ) {
6864
+ try {
6865
+ url = attachment.sizes.thumbnail.url;
6866
+ }
6867
+ catch ( e ) {
6868
+ // We'll use the full image instead
6869
+ url = attachment.sizes.full.url;
6870
+ }
6871
+ }
6872
+ $s.find( '.current-image' ).css( 'background-image', 'url(' + url + ')' );
6873
 
6874
+ // Store the ID
6875
+ $s.find( '.so-image-selector > input' ).val( attachment.id );
6876
+
6877
+ $s.find( '.remove-image' ).removeClass( 'hidden' );
6878
+ } );
6879
+ }
6880
 
6881
+ frame.open();
 
 
 
 
 
 
 
6882
 
6883
+ } );
 
 
 
 
6884
 
6885
+ // Handle clicking on remove
6886
+ $s.find( '.remove-image' ).click( function ( e ) {
 
 
6887
  e.preventDefault();
6888
+ $s.find( '.current-image' ).css( 'background-image', 'none' );
6889
+ $s.find( '.so-image-selector > input' ).val( '' );
6890
+ $s.find( '.remove-image' ).addClass( 'hidden' );
6891
+ } );
6892
+ } );
6893
 
6894
+ // Set up all the measurement fields
6895
+ this.$( '.style-field-measurement' ).each( function () {
6896
+ var $$ = $( this );
 
6897
 
6898
+ var text = $$.find( 'input[type="text"]' );
6899
+ var unit = $$.find( 'select' );
6900
+ var hidden = $$.find( 'input[type="hidden"]' );
6901
 
6902
+ text.focus( function(){
6903
+ $(this).select();
6904
+ } );
6905
 
6906
+ /**
6907
+ * Load value into the visible input fields.
6908
+ * @param value
6909
+ */
6910
+ var loadValue = function( value ) {
6911
+ if( value === '' ) {
6912
+ return;
6913
+ }
6914
+
6915
+ var re = /(?:([0-9\.,\-]+)(.*))+/;
6916
+ var valueList = hidden.val().split( ' ' );
6917
+ var valueListValue = [];
6918
+ for ( var i in valueList ) {
6919
+ var match = re.exec( valueList[i] );
6920
+ if ( ! _.isNull( match ) && ! _.isUndefined( match[1] ) && ! _.isUndefined( match[2] ) ) {
6921
+ valueListValue.push( match[1] );
6922
+ unit.val( match[2] );
6923
+ }
6924
+ }
6925
+
6926
+ if( text.length === 1 ) {
6927
+ // This is a single input text field
6928
+ text.val( valueListValue.join( ' ' ) );
6929
+ }
6930
+ else {
6931
+ // We're dealing with a multiple field
6932
+ if( valueListValue.length === 1 ) {
6933
+ valueListValue = [ valueListValue[0], valueListValue[0], valueListValue[0], valueListValue[0] ];
6934
+ }
6935
+ else if( valueListValue.length === 2 ) {
6936
+ valueListValue = [ valueListValue[0], valueListValue[1], valueListValue[0], valueListValue[1] ];
6937
+ }
6938
+ else if( valueListValue.length === 3 ) {
6939
+ valueListValue = [ valueListValue[0], valueListValue[1], valueListValue[2], valueListValue[1] ];
6940
+ }
6941
+
6942
+ // Store this in the visible fields
6943
+ text.each( function( i, el ) {
6944
+ $( el ).val( valueListValue[i] );
6945
+ } );
6946
+ }
6947
+ };
6948
+ loadValue( hidden.val() );
6949
+
6950
+ /**
6951
+ * Set value of the hidden field based on inputs
6952
+ */
6953
+ var setValue = function( e ){
6954
+ var i;
6955
+
6956
+ if( text.length === 1 ) {
6957
+ // We're dealing with a single measurement
6958
+ var fullString = text
6959
+ .val()
6960
+ .split( ' ' )
6961
+ .filter( function ( value ) {
6962
+ return value !== '';
6963
+ } )
6964
+ .map( function ( value ) {
6965
+ return value + unit.val();
6966
+ } )
6967
+ .join( ' ' );
6968
+ hidden.val( fullString );
6969
+ }
6970
+ else {
6971
+ var target = $( e.target ),
6972
+ valueList = [],
6973
+ emptyIndex = [],
6974
+ fullIndex = [];
6975
+
6976
+ text.each( function( i, el ) {
6977
+ var value = $( el ).val( ) !== '' ? parseFloat( $( el ).val( ) ) : null;
6978
+ valueList.push( value );
6979
+
6980
+ if( value === null ) {
6981
+ emptyIndex.push( i );
6982
+ }
6983
+ else {
6984
+ fullIndex.push( i );
6985
+ }
6986
+ } );
6987
+
6988
+ if( emptyIndex.length === 3 && fullIndex[0] === text.index( target ) ) {
6989
+ text.val( target.val() );
6990
+ valueList = [ target.val(), target.val(), target.val(), target.val() ];
6991
+ }
6992
+
6993
+ if( JSON.stringify( valueList ) === JSON.stringify( [ null, null, null, null ] ) ) {
6994
+ hidden.val('');
6995
+ }
6996
+ else {
6997
+ hidden.val( valueList.map( function( k ){
6998
+ return ( k === null ? 0 : k ) + unit.val();
6999
+ } ).join( ' ' ) );
7000
+ }
7001
+ }
7002
+ };
7003
+
7004
+ // Set the value when ever anything changes
7005
+ text.change( setValue );
7006
+ unit.change( setValue );
7007
  } );
7008
+ }
7009
 
7010
+ } );
 
 
7011
 
7012
+ },{}],29:[function(require,module,exports){
7013
+ var panels = window.panels, $ = jQuery;
7014
+
7015
+ module.exports = Backbone.View.extend( {
7016
+ template: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-builder-widget' ).html() ) ),
7017
+
7018
+ // The cell view that this widget belongs to
7019
+ cell: null,
7020
+
7021
+ // The edit dialog
7022
+ dialog: null,
7023
+
7024
+ events: {
7025
+ 'click .widget-edit': 'editHandler',
7026
+ 'click .title h4': 'editHandler',
7027
+ 'click .actions .widget-duplicate': 'duplicateHandler',
7028
+ 'click .actions .widget-delete': 'deleteHandler'
7029
  },
7030
 
7031
  /**
7032
+ * Initialize the widget
 
 
7033
  */
7034
+ initialize: function () {
7035
+ this.listenTo(this.model, 'destroy', this.onModelDestroy);
7036
+ this.listenTo(this.model, 'change:values', this.onModelChange);
7037
+ this.listenTo(this.model, 'change:label', this.onLabelChange);
7038
+ },
7039
+
7040
+ /**
7041
+ * Render the widget
7042
+ */
7043
+ render: function ( options ) {
7044
+ options = _.extend( {'loadForm': false}, options );
7045
 
7046
+ this.setElement( this.template( {
7047
+ title: this.model.getWidgetField( 'title' ),
7048
+ description: this.model.getTitle(),
7049
+ widget_class: this.model.attributes.class
7050
+ } ) );
7051
 
7052
+ this.$el.data( 'view', this );
 
7053
 
7054
+ // Remove any unsupported actions
7055
+ if( ! this.cell.row.builder.supports( 'editWidget' ) || this.model.get( 'read_only' ) ) {
7056
+ this.$( '.actions .widget-edit' ).remove();
7057
+ this.$el.addClass('so-widget-no-edit');
7058
  }
7059
+ if( ! this.cell.row.builder.supports( 'addWidget' ) ) {
7060
+ this.$( '.actions .widget-duplicate' ).remove();
7061
+ this.$el.addClass('so-widget-no-duplicate');
7062
  }
7063
+ if( ! this.cell.row.builder.supports( 'deleteWidget' ) ) {
7064
+ this.$( '.actions .widget-delete' ).remove();
7065
+ this.$el.addClass('so-widget-no-delete');
 
7066
  }
7067
+ if( ! this.cell.row.builder.supports( 'moveWidget' ) ) {
7068
+ this.$el.addClass('so-widget-no-move');
7069
+ }
7070
+ if( !$.trim( this.$('.actions').html() ).length ) {
7071
+ this.$( '.actions' ).remove();
7072
  }
7073
 
7074
+ if( this.model.get( 'read_only' ) ) {
7075
+ this.$el.addClass('so-widget-read-only');
7076
+ }
 
 
 
 
7077
 
7078
+ if ( _.size( this.model.get( 'values' ) ) === 0 || options.loadForm ) {
7079
+ // If this widget doesn't have a value, create a form and save it
7080
+ var dialog = this.getEditDialog();
7081
 
7082
+ // Save the widget as soon as the form is loaded
7083
+ dialog.once( 'form_loaded', dialog.saveWidget, dialog );
 
7084
 
7085
+ // Setup the dialog to load the form
7086
+ dialog.setupDialog();
7087
+ }
7088
+
7089
+ // Add the global builder listeners
7090
+ this.listenTo(this.cell.row.builder, 'after_user_adds_widget', this.afterUserAddsWidgetHandler);
7091
+
7092
+ return this;
7093
  },
7094
 
7095
  /**
7096
+ * Display an animation that implies creation using a visual animation
7097
  */
7098
+ visualCreate: function () {
7099
+ this.$el.hide().fadeIn( 'fast' );
7100
+ },
7101
 
7102
+ /**
7103
+ * Get the dialog view of the form that edits this widget
7104
+ *
7105
+ * @returns {null}
7106
+ */
7107
+ getEditDialog: function () {
7108
+ if ( this.dialog === null ) {
7109
+ this.dialog = new panels.dialog.widget( {
7110
+ model: this.model
7111
+ } );
7112
+ this.dialog.setBuilder( this.cell.row.builder );
7113
+
7114
+ // Store the widget view
7115
+ this.dialog.widgetView = this;
7116
  }
7117
+ return this.dialog;
7118
  },
7119
 
7120
  /**
7121
+ * Handle clicking on edit widget.
 
7122
  */
7123
+ editHandler: function () {
7124
+ // Create a new dialog for editing this
7125
+ if ( ! this.cell.row.builder.supports( 'editWidget' ) || this.model.get( 'read_only' ) ) {
7126
+ return this;
7127
  }
7128
+
7129
+ this.getEditDialog().openDialog();
7130
+ return this;
7131
  },
7132
 
7133
  /**
7134
+ * Handle clicking on duplicate.
7135
  *
7136
+ * @returns {boolean}
 
 
7137
  */
7138
+ duplicateHandler: function () {
7139
+ // Add the history entry
7140
+ this.cell.row.builder.addHistoryEntry( 'widget_duplicated' );
 
 
 
 
 
 
 
 
 
 
 
7141
 
7142
+ // Create the new widget and connect it to the widget collection for the current row
7143
+ var newWidget = this.model.clone( this.model.cell );
 
 
 
 
7144
 
7145
+ this.cell.model.get('widgets').add( newWidget, {
7146
+ // Add this after the existing model
7147
+ at: this.model.collection.indexOf( this.model ) + 1
 
7148
  } );
7149
 
7150
+ this.cell.row.builder.model.refreshPanelsData();
7151
+ return this;
7152
+ },
7153
 
7154
+ /**
7155
+ * Copy the row to a cookie based clipboard
7156
+ */
7157
+ copyHandler: function(){
7158
+ panels.helpers.clipboard.setModel( this.model );
7159
+ },
7160
 
7161
+ /**
7162
+ * Handle clicking on delete.
7163
+ *
7164
+ * @returns {boolean}
7165
+ */
7166
+ deleteHandler: function () {
7167
+ this.visualDestroyModel();
7168
+ return this;
7169
+ },
7170
 
7171
+ onModelChange: function () {
7172
+ // Update the description when ever the model changes
7173
+ this.$( '.description' ).html( this.model.getTitle() );
7174
+ },
 
7175
 
7176
+ onLabelChange: function( model ) {
7177
+ this.$( '.title > h4' ).text( model.getWidgetField( 'title' ) );
7178
+ },
7179
 
7180
+ /**
7181
+ * When the model is destroyed, fade it out
7182
+ */
7183
+ onModelDestroy: function () {
7184
+ this.remove();
7185
  },
7186
 
7187
  /**
7188
+ * Visually destroy a model
 
 
 
7189
  */
7190
+ visualDestroyModel: function () {
7191
+ // Add the history entry
7192
+ this.cell.row.builder.addHistoryEntry( 'widget_deleted' );
7193
+
7194
+ this.$el.fadeOut( 'fast', function () {
7195
+ this.cell.row.resize();
7196
+ this.model.destroy();
7197
+ this.cell.row.builder.model.refreshPanelsData();
7198
+ this.remove();
7199
+ }.bind(this) );
7200
+
7201
+ return this;
7202
  },
7203
 
7204
  /**
7205
+ * Build up the contextual menu for a widget
7206
  *
7207
  * @param e
7208
+ * @param menu
7209
  */
7210
+ buildContextualMenu: function ( e, menu ) {
7211
+ if( this.cell.row.builder.supports( 'addWidget' ) ) {
7212
+ menu.addSection(
7213
+ 'add-widget-below',
7214
+ {
7215
+ sectionTitle: panelsOptions.loc.contextual.add_widget_below,
7216
+ searchPlaceholder: panelsOptions.loc.contextual.search_widgets,
7217
+ defaultDisplay: panelsOptions.contextual.default_widgets
7218
+ },
7219
+ panelsOptions.widgets,
7220
+ function ( c ) {
7221
+ this.cell.row.builder.trigger('before_user_adds_widget');
7222
+ this.cell.row.builder.addHistoryEntry( 'widget_added' );
7223
+
7224
+ var widget = new panels.model.widget( {
7225
+ class: c
7226
+ } );
7227
+ widget.cell = this.cell.model;
7228
 
7229
+ // Insert the new widget below
7230
+ this.cell.model.get('widgets').add( widget, {
7231
+ // Add this after the existing model
7232
+ at: this.model.collection.indexOf( this.model ) + 1
7233
+ } );
7234
 
7235
+ this.cell.row.builder.model.refreshPanelsData();
 
7236
 
7237
+ this.cell.row.builder.trigger('after_user_adds_widget', widget);
7238
+ }.bind( this )
7239
+ );
7240
+ }
7241
 
7242
+ var actions = {};
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7243
 
7244
+ if( this.cell.row.builder.supports( 'editWidget' ) && ! this.model.get( 'read_only' ) ) {
7245
+ actions.edit = { title: panelsOptions.loc.contextual.widget_edit };
 
 
 
 
 
 
 
 
 
7246
  }
7247
 
7248
+ // Copy and paste functions
7249
+ if ( panels.helpers.clipboard.canCopyPaste() ) {
7250
+ actions.copy = {title: panelsOptions.loc.contextual.widget_copy};
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7251
  }
7252
 
7253
+ if( this.cell.row.builder.supports( 'addWidget' ) ) {
7254
+ actions.duplicate = { title: panelsOptions.loc.contextual.widget_duplicate };
7255
+ }
 
7256
 
7257
+ if( this.cell.row.builder.supports( 'deleteWidget' ) ) {
7258
+ actions.delete = { title: panelsOptions.loc.contextual.widget_delete, confirm: true };
7259
+ }
7260
 
7261
+ if( ! _.isEmpty( actions ) ) {
7262
+ menu.addSection(
7263
+ 'widget-actions',
7264
+ {
7265
+ sectionTitle: panelsOptions.loc.contextual.widget_actions,
7266
+ search: false,
7267
+ },
7268
+ actions,
7269
+ function ( c ) {
7270
+ switch ( c ) {
7271
+ case 'edit':
7272
+ this.editHandler();
7273
+ break;
7274
+ case 'copy':
7275
+ this.copyHandler();
7276
+ break;
7277
+ case 'duplicate':
7278
+ this.duplicateHandler();
7279
+ break;
7280
+ case 'delete':
7281
+ this.visualDestroyModel();
7282
+ break;
7283
+ }
7284
+ }.bind( this )
7285
+ );
7286
  }
7287
+
7288
+ // Lets also add the contextual menu for the entire row
7289
+ this.cell.buildContextualMenu( e, menu );
7290
  },
7291
 
7292
  /**
7293
+ * Handler for any action after the user adds a new widget.
7294
+ * @param widget
 
7295
  */
7296
+ afterUserAddsWidgetHandler: function( widget ) {
7297
+ if( this.model === widget && panelsOptions.instant_open ) {
7298
+ setTimeout(this.editHandler.bind(this), 350);
7299
+ }
 
 
 
 
 
 
 
7300
  }
7301
 
7302
  } );
7303
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7304
  },{}],30:[function(require,module,exports){
7305
  var $ = jQuery;
7306
 
7407
  module.exports = mediaWidget;
7408
 
7409
  },{}],33:[function(require,module,exports){
7410
+ var $ = jQuery;
7411
+
7412
+ var textWidget = {
7413
+ addWidget: function( idBase, widgetContainer, widgetId ) {
7414
+ var component = wp.textWidgets;
7415
+
7416
+ var options = {};
7417
+ var visualField = widgetContainer.find( '.visual' );
7418
+ // 'visual' field and syncContainer were introduced together in 4.8.1
7419
+ if ( visualField.length > 0 ) {
7420
+ // If 'visual' field has no value it's a legacy text widget.
7421
+ if ( ! visualField.val() ) {
7422
+ return null;
7423
+ }
7424
+
7425
+ var fieldContainer = $( '<div></div>' );
7426
+ var syncContainer = widgetContainer.find( '.widget-content:first' );
7427
+ syncContainer.before( fieldContainer );
7428
+
7429
+ options = {
7430
+ el: fieldContainer,
7431
+ syncContainer: syncContainer,
7432
+ };
7433
+ } else {
7434
+ options = { el: widgetContainer };
7435
+ }
7436
+
7437
+ var widgetControl = new component.TextWidgetControl( options );
7438
+ var wpEditor = wp.oldEditor ? wp.oldEditor : wp.editor;
7439
+ if ( wpEditor && wpEditor.hasOwnProperty( 'autop' ) ) {
7440
+ wp.editor.autop = wpEditor.autop;
7441
+ wp.editor.removep = wpEditor.removep;
7442
+ wp.editor.initialize = wpEditor.initialize
7443
+ }
7444
+
7445
+ widgetControl.initializeEditor();
7446
+
7447
+ return widgetControl;
7448
+ }
7449
+ };
7450
+
7451
+ module.exports = textWidget;
7452
 
7453
  },{}]},{},[16]);
js/siteorigin-panels-2107.min.js ADDED
@@ -0,0 +1 @@
 
1
+ !function o(n,a,r){function d(t,e){if(!a[t]){if(!n[t]){var i="function"==typeof require&&require;if(!e&&i)return i(t,!0);if(c)return c(t,!0);var s=new Error("Cannot find module '"+t+"'");throw s.code="MODULE_NOT_FOUND",s}var l=a[t]={exports:{}};n[t][0].call(l.exports,function(e){return d(n[t][1][e]||e)},l,l.exports,o,n,a,r)}return a[t].exports}for(var c="function"==typeof require&&require,e=0;e<r.length;e++)d(r[e]);return d}({1:[function(e,t,i){var s=window.panels;t.exports=Backbone.Collection.extend({model:s.model.cell,initialize:function(){},totalWeight:function(){var t=0;return this.each(function(e){t+=e.get("weight")}),t}})},{}],2:[function(e,t,i){var s=window.panels;t.exports=Backbone.Collection.extend({model:s.model.historyEntry,builder:null,maxSize:12,initialize:function(){this.on("add",this.onAddEntry,this)},addEntry:function(e,t){_.isEmpty(t)&&(t=this.builder.getPanelsData());var i=new s.model.historyEntry({text:e,data:JSON.stringify(t),time:parseInt((new Date).getTime()/1e3),collection:this});this.add(i)},onAddEntry:function(e){if(1<this.models.length){var t=this.at(this.models.length-2);(e.get("text")===t.get("text")&&e.get("time")-t.get("time")<15||e.get("data")===t.get("data"))&&(this.remove(e),t.set("count",t.get("count")+1))}for(;this.models.length>this.maxSize;)this.shift()}})},{}],3:[function(e,t,i){var s=window.panels;t.exports=Backbone.Collection.extend({model:s.model.row,empty:function(){for(var e;;){if(!(e=this.collection.first()))break;e.destroy()}}})},{}],4:[function(e,t,i){var s=window.panels;t.exports=Backbone.Collection.extend({model:s.model.widget,initialize:function(){}})},{}],5:[function(e,t,i){var s=window.panels,l=jQuery;t.exports=s.view.dialog.extend({dialogClass:"so-panels-dialog-add-builder",render:function(){this.renderDialog(this.parseDialogContent(l("#siteorigin-panels-dialog-builder").html(),{})),this.$(".so-content .siteorigin-panels-builder").append(this.builder.$el)},initializeDialog:function(){var e=this;this.once("open_dialog_complete",function(){e.builder.initSortable()}),this.on("open_dialog_complete",function(){e.builder.trigger("builder_resize")})}})},{}],6:[function(e,t,i){var s=window.panels,l=jQuery;t.exports=s.view.dialog.extend({historyEntryTemplate:_.template(s.helpers.utils.processTemplate(l("#siteorigin-panels-dialog-history-entry").html())),entries:{},currentEntry:null,revertEntry:null,selectedEntry:null,previewScrollTop:null,dialogClass:"so-panels-dialog-history",dialogIcon:"history",events:{"click .so-close":"closeDialog","click .so-restore":"restoreSelectedEntry"},initializeDialog:function(){this.entries=new s.collection.historyEntries,this.on("open_dialog",this.setCurrentEntry,this),this.on("open_dialog",this.renderHistoryEntries,this)},render:function(){var t=this;this.renderDialog(this.parseDialogContent(l("#siteorigin-panels-dialog-history").html(),{})),this.$("iframe.siteorigin-panels-history-iframe").load(function(){var e=l(this);e.show(),e.contents().scrollTop(t.previewScrollTop)})},setRevertEntry:function(e){this.revertEntry=new s.model.historyEntry({data:JSON.stringify(e.getPanelsData()),time:parseInt((new Date).getTime()/1e3)})},setCurrentEntry:function(){this.currentEntry=new s.model.historyEntry({data:JSON.stringify(this.builder.model.getPanelsData()),time:parseInt((new Date).getTime()/1e3)}),this.selectedEntry=this.currentEntry,this.previewEntry(this.currentEntry),this.$(".so-buttons .so-restore").addClass("disabled")},renderHistoryEntries:function(){var i=this,s=this.$(".history-entries").empty();this.currentEntry.get("data")===this.revertEntry.get("data")&&_.isEmpty(this.entries.models)||l(this.historyEntryTemplate({title:panelsOptions.loc.history.revert,count:1})).data("historyEntry",this.revertEntry).prependTo(s),this.entries.each(function(e){var t=i.historyEntryTemplate({title:panelsOptions.loc.history[e.get("text")],count:e.get("count")});l(t).data("historyEntry",e).prependTo(s)}),l(this.historyEntryTemplate({title:panelsOptions.loc.history.current,count:1})).data("historyEntry",this.currentEntry).addClass("so-selected").prependTo(s),s.find(".history-entry").click(function(){var e=jQuery(this);s.find(".history-entry").not(e).removeClass("so-selected"),e.addClass("so-selected");var t=e.data("historyEntry");i.selectedEntry=t,i.selectedEntry.cid!==i.currentEntry.cid?i.$(".so-buttons .so-restore").removeClass("disabled"):i.$(".so-buttons .so-restore").addClass("disabled"),i.previewEntry(t)}),this.updateEntryTimes()},previewEntry:function(e){var t=this.$("iframe.siteorigin-panels-history-iframe");t.hide(),this.previewScrollTop=t.contents().scrollTop(),this.$('form.history-form input[name="live_editor_panels_data"]').val(e.get("data")),this.$('form.history-form input[name="live_editor_post_ID"]').val(this.builder.config.postId),this.$("form.history-form").submit()},restoreSelectedEntry:function(){return this.$(".so-buttons .so-restore").hasClass("disabled")||(this.currentEntry.get("data")===this.selectedEntry.get("data")||("restore"!==this.selectedEntry.get("text")&&this.builder.addHistoryEntry("restore",this.builder.model.getPanelsData()),this.builder.model.loadPanelsData(JSON.parse(this.selectedEntry.get("data")))),this.closeDialog()),!1},updateEntryTimes:function(){var s=this;this.$(".history-entries .history-entry").each(function(){var e=jQuery(this),t=e.find(".timesince"),i=e.data("historyEntry");t.html(s.timeSince(i.get("time")))})},timeSince:function(e){var t,i=parseInt((new Date).getTime()/1e3)-e,s=[];return 3600<i&&(1===(t=Math.floor(i/3600))?s.push(panelsOptions.loc.time.hour.replace("%d",t)):s.push(panelsOptions.loc.time.hours.replace("%d",t)),i-=3600*t),60<i&&(1===(t=Math.floor(i/60))?s.push(panelsOptions.loc.time.minute.replace("%d",t)):s.push(panelsOptions.loc.time.minutes.replace("%d",t)),i-=60*t),0<i&&(1===i?s.push(panelsOptions.loc.time.second.replace("%d",i)):s.push(panelsOptions.loc.time.seconds.replace("%d",i))),_.isEmpty(s)?panelsOptions.loc.time.now:panelsOptions.loc.time.ago.replace("%s",s.slice(0,2).join(", "))}})},{}],7:[function(e,t,i){var s=window.panels,r=jQuery;t.exports=s.view.dialog.extend({directoryTemplate:_.template(s.helpers.utils.processTemplate(r("#siteorigin-panels-directory-items").html())),builder:null,dialogClass:"so-panels-dialog-prebuilt-layouts",dialogIcon:"layouts",layoutCache:{},currentTab:!1,directoryPage:1,events:{"click .so-close":"closeDialog","click .so-sidebar-tabs li a":"tabClickHandler","click .so-content .layout":"layoutClickHandler","keyup .so-sidebar-search":"searchHandler","click .so-screenshot, .so-title":"directoryItemClickHandler"},initializeDialog:function(){var e=this;this.on("open_dialog",function(){e.$(".so-sidebar-tabs li a").first().click(),e.$(".so-status").removeClass("so-panels-loading")}),this.on("button_click",this.toolbarButtonClick,this)},render:function(){this.renderDialog(this.parseDialogContent(r("#siteorigin-panels-dialog-prebuilt").html(),{})),this.initToolbar()},tabClickHandler:function(e){e.preventDefault(),this.selectedLayoutItem=null,this.uploadedLayout=null,this.updateButtonState(!1),this.$(".so-sidebar-tabs li").removeClass("tab-active");var t=r(e.target),i=t.attr("href").split("#")[1];t.parent().addClass("tab-active");this.$(".so-content").empty(),"import"==(this.currentTab=i)?this.displayImportExport():this.displayLayoutDirectory("",1,i),this.$(".so-sidebar-search").val("")},displayImportExport:function(){var e=this.$(".so-content").empty().removeClass("so-panels-loading");e.html(r("#siteorigin-panels-dialog-prebuilt-importexport").html());var l=this,o=l.$(".import-upload-ui"),t=new plupload.Uploader({runtimes:"html5,silverlight,flash,html4",browse_button:o.find(".file-browse-button").get(0),container:o.get(0),drop_element:o.find(".drag-upload-area").get(0),file_data_name:"panels_import_data",multiple_queues:!1,max_file_size:panelsOptions.plupload.max_file_size,url:panelsOptions.plupload.url,flash_swf_url:panelsOptions.plupload.flash_swf_url,silverlight_xap_url:panelsOptions.plupload.silverlight_xap_url,filters:[{title:panelsOptions.plupload.filter_title,extensions:"json"}],multipart_params:{action:"so_panels_import_layout"},init:{PostInit:function(e){e.features.dragdrop&&o.addClass("has-drag-drop"),o.find(".progress-precent").css("width","0%")},FilesAdded:function(e){o.find(".file-browse-button").blur(),o.find(".drag-upload-area").removeClass("file-dragover"),o.find(".progress-bar").fadeIn("fast"),l.$(".js-so-selected-file").text(panelsOptions.loc.prebuilt_loading),e.start()},UploadProgress:function(e,t){o.find(".progress-precent").css("width",t.percent+"%")},FileUploaded:function(e,t,i){var s=JSON.parse(i.response);_.isUndefined(s.widgets)?alert(panelsOptions.plupload.error_message):(l.uploadedLayout=s,o.find(".progress-bar").hide(),l.$(".js-so-selected-file").text(panelsOptions.loc.ready_to_insert.replace("%s",t.name)),l.updateButtonState(!0))},Error:function(){alert(panelsOptions.plupload.error_message)}}});t.init(),/Edge\/\d./i.test(navigator.userAgent)&&setTimeout(function(){t.refresh()},250),o.find(".drag-upload-area").on("dragover",function(){r(this).addClass("file-dragover")}).on("dragleave",function(){r(this).removeClass("file-dragover")}),e.find(".so-export").submit(function(e){var t=r(this),i=l.builder.model.getPanelsData(),s=r('input[name="post_title"]').val();s=s||r('input[name="post_ID"]').val(),i.name=s,t.find('input[name="panels_export_data"]').val(JSON.stringify(i))})},displayLayoutDirectory:function(s,l,o){var n=this,a=this.$(".so-content").empty().addClass("so-panels-loading");if(void 0===s&&(s=""),void 0===l&&(l=1),void 0===o&&(o="directory-siteorigin"),o.match("^directory-")&&!panelsOptions.directory_enabled)return a.removeClass("so-panels-loading").html(r("#siteorigin-panels-directory-enable").html()),void a.find(".so-panels-enable-directory").click(function(e){e.preventDefault(),r.get(panelsOptions.ajaxurl,{action:"so_panels_directory_enable"},function(){}),panelsOptions.directory_enabled=!0,a.addClass("so-panels-loading"),n.displayLayoutDirectory(s,l,o)});r.get(panelsOptions.ajaxurl,{action:"so_panels_layouts_query",search:s,page:l,type:o},function(e){if(n.currentTab===o){a.removeClass("so-panels-loading").html(n.directoryTemplate(e));var t=a.find(".so-previous"),i=a.find(".so-next");l<=1?t.addClass("button-disabled"):t.click(function(e){e.preventDefault(),n.displayLayoutDirectory(s,l-1,n.currentTab)}),l===e.max_num_pages||0===e.max_num_pages?i.addClass("button-disabled"):i.click(function(e){e.preventDefault(),n.displayLayoutDirectory(s,l+1,n.currentTab)}),a.find(".so-screenshot").each(function(){var e=r(this),t=e.find(".so-screenshot-wrapper");if(t.css("height",t.width()/4*3+"px").addClass("so-loading"),""!==e.data("src"))var i=r("<img/>").attr("src",e.data("src")).load(function(){t.removeClass("so-loading").css("height","auto"),i.appendTo(t).hide().fadeIn("fast")});else r("<img/>").attr("src",panelsOptions.prebuiltDefaultScreenshot).appendTo(t).hide().fadeIn("fast")}),a.find(".so-directory-browse").html(e.title)}},"json")},directoryItemClickHandler:function(e){var t=this.$(e.target).closest(".so-directory-item");this.$(".so-directory-items").find(".selected").removeClass("selected"),t.addClass("selected"),this.selectedLayoutItem={lid:t.data("layout-id"),type:t.data("layout-type")},this.updateButtonState(!0)},toolbarButtonClick:function(e){if(!this.canAddLayout())return!1;var t=e.data("value");if(_.isUndefined(t))return!1;if(this.updateButtonState(!1),e.hasClass("so-needs-confirm")&&!e.hasClass("so-confirmed")){if(this.updateButtonState(!0),e.hasClass("so-confirming"))return;e.addClass("so-confirming");var i=e.html();return e.html('<span class="dashicons dashicons-yes"></span>'+e.data("confirm")),setTimeout(function(){e.removeClass("so-confirmed").html(i)},2500),setTimeout(function(){e.removeClass("so-confirming"),e.addClass("so-confirmed")},200),!1}this.addingLayout=!0,"import"===this.currentTab?this.addLayoutToBuilder(this.uploadedLayout,t):this.loadSelectedLayout().then(function(e){this.addLayoutToBuilder(e,t)}.bind(this))},canAddLayout:function(){return(this.selectedLayoutItem||this.uploadedLayout)&&!this.addingLayout},loadSelectedLayout:function(){this.setStatusMessage(panelsOptions.loc.prebuilt_loading,!0);var e=_.extend(this.selectedLayoutItem,{action:"so_panels_get_layout"}),i=new r.Deferred;return r.get(panelsOptions.ajaxurl,e,function(e){var t="";e.success?i.resolve(e.data):(t=e.data.message,i.reject(e.data)),this.setStatusMessage(t,!1,!e.success),this.updateButtonState(!0)}.bind(this)),i.promise()},searchHandler:function(e){13===e.keyCode&&this.displayLayoutDirectory(r(e.currentTarget).val(),1,this.currentTab)},updateButtonState:function(e){e=e&&(this.selectedLayoutItem||this.uploadedLayout);var t=this.$(".so-import-layout");t.prop("disabled",!e),e?t.removeClass("disabled"):t.addClass("disabled")},addLayoutToBuilder:function(e,t){this.builder.addHistoryEntry("prebuilt_loaded"),this.builder.model.loadPanelsData(e,t),this.addingLayout=!1,this.closeDialog()}})},{}],8:[function(e,t,i){var a=window.panels,c=jQuery;t.exports=a.view.dialog.extend({cellPreviewTemplate:_.template(a.helpers.utils.processTemplate(c("#siteorigin-panels-dialog-row-cell-preview").html())),editableLabel:!0,events:{"click .so-close":"closeDialog","click .so-toolbar .so-save":"saveHandler","click .so-toolbar .so-insert":"insertHandler","click .so-toolbar .so-delete":"deleteHandler","click .so-toolbar .so-duplicate":"duplicateHandler","change .row-set-form > *":"setCellsFromForm","click .row-set-form button.set-row":"setCellsFromForm"},rowView:null,dialogIcon:"add-row",dialogClass:"so-panels-dialog-row-edit",styleType:"row",dialogType:"edit",row:{cells:null,style:{}},cellStylesCache:[],initializeDialog:function(){this.on("open_dialog",function(){_.isUndefined(this.model)||_.isEmpty(this.model.get("cells"))?this.setRowModel(null):this.setRowModel(this.model),this.regenerateRowPreview(),this.renderStyles(),this.openSelectedCellStyles()},this),this.row={cells:new a.collection.cells([{weight:.5},{weight:.5}]),style:{}},this.dialogFormsLoaded=0;var e=this;this.on("form_loaded styles_loaded",function(){this.dialogFormsLoaded++,2===this.dialogFormsLoaded&&e.updateModel({refreshArgs:{silent:!0}})}),this.on("close_dialog",this.closeHandler),this.on("edit_label",function(e){if(e!==panelsOptions.loc.row.add&&e!==panelsOptions.loc.row.edit||(e=""),this.model.set("label",e),_.isEmpty(e)){var t="create"===this.dialogType?panelsOptions.loc.row.add:panelsOptions.loc.row.edit;this.$(".so-title").text(t)}}.bind(this))},setRowDialogType:function(e){this.dialogType=e},render:function(){var e="create"===this.dialogType?panelsOptions.loc.row.add:panelsOptions.loc.row.edit;this.renderDialog(this.parseDialogContent(c("#siteorigin-panels-dialog-row").html(),{title:e,dialogType:this.dialogType}));var t=this.$(".so-title");return this.model.has("label")&&!_.isEmpty(this.model.get("label"))&&t.text(this.model.get("label")),this.$(".so-edit-title").val(t.text()),this.builder.supports("addRow")||this.$(".so-buttons .so-duplicate").remove(),this.builder.supports("deleteRow")||this.$(".so-buttons .so-delete").remove(),_.isUndefined(this.model)||(this.$('input[name="cells"].so-row-field').val(this.model.get("cells").length),this.model.has("ratio")&&this.$('select[name="ratio"].so-row-field').val(this.model.get("ratio")),this.model.has("ratio_direction")&&this.$('select[name="ratio_direction"].so-row-field').val(this.model.get("ratio_direction"))),this.$("input.so-row-field").keyup(function(){c(this).trigger("change")}),this},renderStyles:function(){this.styles&&(this.styles.off("styles_loaded"),this.styles.remove()),this.styles=new a.view.styles,this.styles.model=this.model,this.styles.render("row",this.builder.config.postId,{builderType:this.builder.config.builderType,dialog:this});var t=this.$(".so-sidebar.so-right-sidebar");this.styles.attach(t),this.styles.on("styles_loaded",function(e){e||(this.styles.remove(),0===t.children().length&&(t.closest(".so-panels-dialog").removeClass("so-panels-dialog-has-right-sidebar"),t.hide()))},this)},setRowModel:function(e){return this.model=e,_.isEmpty(this.model)||(this.row={cells:this.model.get("cells").clone(),style:{},ratio:this.model.get("ratio"),ratio_direction:this.model.get("ratio_direction")},this.$('input[name="cells"].so-row-field').val(this.model.get("cells").length),this.model.has("ratio")&&this.$('select[name="ratio"].so-row-field').val(this.model.get("ratio")),this.model.has("ratio_direction")&&this.$('select[name="ratio_direction"].so-row-field').val(this.model.get("ratio_direction")),this.clearCellStylesCache()),this},regenerateRowPreview:function(){var t,r=this,d=this.$(".row-preview"),s=this.getSelectedCellIndex();d.empty(),this.row.cells.each(function(i,n){var o=c(this.cellPreviewTemplate({weight:i.get("weight")}));d.append(o),n==s&&o.find(".preview-cell-in").addClass("cell-selected");var e,a=o.prev();a.length&&((e=c('<div class="resize-handle"></div>')).appendTo(o).dblclick(function(){var e=r.row.cells.at(n-1),t=i.get("weight")+e.get("weight");i.set("weight",t/2),e.set("weight",t/2),r.scaleRowWidths()}),e.draggable({axis:"x",containment:d,start:function(e,t){var i=o.clone().appendTo(t.helper).css({position:"absolute",top:"0",width:o.outerWidth(),left:6,height:o.outerHeight()});i.find(".resize-handle").remove();var s=a.clone().appendTo(t.helper).css({position:"absolute",top:"0",width:a.outerWidth(),right:6,height:a.outerHeight()});s.find(".resize-handle").remove(),c(this).data({newCellClone:i,prevCellClone:s}),o.find("> .preview-cell-in").css("visibility","hidden"),a.find("> .preview-cell-in").css("visibility","hidden")},drag:function(e,t){var i=r.row.cells.at(n).get("weight"),s=r.row.cells.at(n-1).get("weight"),l=i-(t.position.left+6)/d.width(),o=s+(t.position.left+6)/d.width();t.helper.offset().left,d.offset().left;c(this).data("newCellClone").css("width",d.width()*l).find(".preview-cell-weight").html(Math.round(1e3*l)/10),c(this).data("prevCellClone").css("width",d.width()*o).find(".preview-cell-weight").html(Math.round(1e3*o)/10)},stop:function(e,t){c(this).data("newCellClone").remove(),c(this).data("prevCellClone").remove(),o.find(".preview-cell-in").css("visibility","visible"),a.find(".preview-cell-in").css("visibility","visible");var i=(t.position.left+6)/d.width(),s=r.row.cells.at(n),l=r.row.cells.at(n-1);.02<s.get("weight")-i&&.02<l.get("weight")+i&&(s.set("weight",s.get("weight")-i),l.set("weight",l.get("weight")+i)),r.scaleRowWidths(),t.helper.css("left",-6)}})),o.click(function(e){if(c(e.target).is(".preview-cell")||c(e.target).is(".preview-cell-in")){var t=c(e.target);t.closest(".row-preview").find(".preview-cell .preview-cell-in").removeClass("cell-selected"),t.addClass("cell-selected"),this.openSelectedCellStyles()}}.bind(this)),o.find(".preview-cell-weight").click(function(e){r.$(".resize-handle").css("pointer-event","none").draggable("disable"),d.find(".preview-cell-weight").each(function(){var e=jQuery(this).hide();c('<input type="text" class="preview-cell-weight-input no-user-interacted" />').val(parseFloat(e.html())).insertAfter(e).focus(function(){clearTimeout(t)}).keyup(function(e){9!==e.keyCode&&c(this).removeClass("no-user-interacted"),13===e.keyCode&&(e.preventDefault(),c(this).blur())}).keydown(function(e){if(9===e.keyCode){e.preventDefault();var t=d.find(".preview-cell-weight-input"),i=t.index(c(this));i===t.length-1?t.eq(0).focus().select():t.eq(i+1).focus().select()}}).blur(function(){d.find(".preview-cell-weight-input").each(function(e,t){isNaN(parseFloat(c(t).val()))&&c(t).val(Math.floor(1e3*r.row.cells.at(e).get("weight"))/10)}),t=setTimeout(function(){if(0===d.find(".preview-cell-weight-input").length)return!1;var l=[],o=[],n=0,a=0;if(d.find(".preview-cell-weight-input").each(function(e,t){var i=parseFloat(c(t).val());i=isNaN(i)?1/r.row.cells.length:Math.round(10*i)/1e3;var s=!c(t).hasClass("no-user-interacted");l.push(i),o.push(s),s?n+=i:a+=i}),0<n&&0<a&&0<1-n)for(var e=0;e<l.length;e++)o[e]||(l[e]=l[e]/a*(1-n));var t=_.reduce(l,function(e,t){return e+t});l=l.map(function(e){return e/t}),.01<Math.min.apply(Math,l)&&r.row.cells.each(function(e,t){e.set("weight",l[t])}),d.find(".preview-cell").each(function(e,t){var i=r.row.cells.at(e).get("weight");c(t).animate({width:Math.round(1e3*i)/10+"%"},250),c(t).find(".preview-cell-weight-input").val(Math.round(1e3*i)/10)}),d.find(".preview-cell").css("overflow","visible"),setTimeout(r.regenerateRowPreview.bind(r),260)},100)}).click(function(){c(this).select()})}),c(this).siblings(".preview-cell-weight-input").select()})},this),this.trigger("form_loaded",this)},getSelectedCellIndex:function(){var i=-1;return this.$(".preview-cell .preview-cell-in").each(function(e,t){c(t).is(".cell-selected")&&(i=e)}),i},openSelectedCellStyles:function(){if(!_.isUndefined(this.cellStyles)){if(this.cellStyles.stylesLoaded){var e={};try{e=this.getFormValues(".so-sidebar .so-visual-styles.so-cell-styles").style}catch(e){console.log("Error retrieving cell styles - "+e.message)}this.cellStyles.model.set("style",e)}this.cellStyles.detach()}if(this.cellStyles=this.getSelectedCellStyles(),this.cellStyles){var t=this.$(".so-sidebar.so-right-sidebar");this.cellStyles.attach(t),this.cellStyles.on("styles_loaded",function(e){e&&(t.closest(".so-panels-dialog").addClass("so-panels-dialog-has-right-sidebar"),t.show())})}},getSelectedCellStyles:function(){var e=this.getSelectedCellIndex();if(-1<e){var t=this.cellStylesCache[e];t||((t=new a.view.styles).model=this.row.cells.at(e),t.render("cell",this.builder.config.postId,{builderType:this.builder.config.builderType,dialog:this,index:e}),this.cellStylesCache[e]=t)}return t},clearCellStylesCache:function(){this.cellStylesCache.forEach(function(e){e.remove(),e.off("styles_loaded")}),this.cellStylesCache=[]},scaleRowWidths:function(){var s=this;this.$(".row-preview .preview-cell").each(function(e,t){var i=s.row.cells.at(e);c(t).css("width",100*i.get("weight")+"%").find(".preview-cell-weight").html(Math.round(1e3*i.get("weight"))/10)})},setCellsFromForm:function(){try{var e={cells:parseInt(this.$('.row-set-form input[name="cells"]').val()),ratio:parseFloat(this.$('.row-set-form select[name="ratio"]').val()),direction:this.$('.row-set-form select[name="ratio_direction"]').val()};_.isNaN(e.cells)&&(e.cells=1),isNaN(e.ratio)&&(e.ratio=1),e.cells<1?(e.cells=1,this.$('.row-set-form input[name="cells"]').val(e.cells)):12<e.cells&&(e.cells=12,this.$('.row-set-form input[name="cells"]').val(e.cells)),this.$('.row-set-form select[name="ratio"]').val(e.ratio);for(var t=[],i=this.row.cells.length!==e.cells,s=1,l=0;l<e.cells;l++)t.push(s),s*=e.ratio;var o=_.reduce(t,function(e,t){return e+t});if(t=_.map(t,function(e){return e/o}),t=_.filter(t,function(e){return.01<e}),"left"===e.direction&&(t=t.reverse()),this.row.cells=new a.collection.cells(this.row.cells.first(t.length)),_.each(t,function(e,t){var i=this.row.cells.at(t);i?i.set("weight",e):(i=new a.model.cell({weight:e,row:this.model}),this.row.cells.add(i))}.bind(this)),this.row.ratio=e.ratio,this.row.ratio_direction=e.direction,i)this.regenerateRowPreview();else{var n=this;this.$(".preview-cell").each(function(e,t){var i=n.row.cells.at(e).get("weight");c(t).animate({width:Math.round(1e3*i)/10+"%"},250),c(t).find(".preview-cell-weight").html(Math.round(1e3*i)/10)}),this.$(".preview-cell").css("overflow","visible"),setTimeout(n.regenerateRowPreview.bind(n),260)}}catch(e){console.log("Error setting cells - "+e.message)}this.$(".row-set-form .so-button-row-set").removeClass("button-primary")},tabClickHandler:function(e){"#row-layout"===e.attr("href")?this.$(".so-panels-dialog").addClass("so-panels-dialog-has-right-sidebar"):this.$(".so-panels-dialog").removeClass("so-panels-dialog-has-right-sidebar")},updateModel:function(e){if(e=_.extend({refresh:!0,refreshArgs:null},e),_.isEmpty(this.model)||(this.model.setCells(this.row.cells),this.model.set("ratio",this.row.ratio),this.model.set("ratio_direction",this.row.ratio_direction)),!_.isUndefined(this.styles)&&this.styles.stylesLoaded){var t={};try{t=this.getFormValues(".so-sidebar .so-visual-styles.so-row-styles").style}catch(e){console.log("Error retrieving row styles - "+e.message)}this.model.set("style",t)}if(!_.isUndefined(this.cellStyles)&&this.cellStyles.stylesLoaded){t={};try{t=this.getFormValues(".so-sidebar .so-visual-styles.so-cell-styles").style}catch(e){console.log("Error retrieving cell styles - "+e.message)}this.cellStyles.model.set("style",t)}e.refresh&&this.builder.model.refreshPanelsData(e.refreshArgs)},insertHandler:function(){this.builder.addHistoryEntry("row_added"),this.updateModel();var e=this.builder.getActiveCell({createCell:!1}),t={};return null!==e&&(t.at=this.builder.model.get("rows").indexOf(e.row)+1),this.model.collection=this.builder.model.get("rows"),this.builder.model.get("rows").add(this.model,t),this.closeDialog(),this.builder.model.refreshPanelsData(),!1},saveHandler:function(){return this.builder.addHistoryEntry("row_edited"),this.updateModel(),this.closeDialog(),this.builder.model.refreshPanelsData(),!1},deleteHandler:function(){return this.rowView.visualDestroyModel(),this.closeDialog({silent:!0}),!1},duplicateHandler:function(){this.builder.addHistoryEntry("row_duplicated");var e=this.model.clone(this.builder.model);return this.builder.model.get("rows").add(e,{at:this.builder.model.get("rows").indexOf(this.model)+1}),this.closeDialog({silent:!0}),!1},closeHandler:function(){this.clearCellStylesCache(),_.isUndefined(this.cellStyles)||(this.cellStyles=void 0)}})},{}],9:[function(e,t,i){var s=window.panels,l=jQuery,o=e("../view/widgets/js-widget");t.exports=s.view.dialog.extend({builder:null,sidebarWidgetTemplate:_.template(s.helpers.utils.processTemplate(l("#siteorigin-panels-dialog-widget-sidebar-widget").html())),dialogClass:"so-panels-dialog-edit-widget",dialogIcon:"add-widget",widgetView:!1,savingWidget:!1,editableLabel:!0,events:{"click .so-close":"saveHandler","click .so-nav.so-previous":"navToPrevious","click .so-nav.so-next":"navToNext","click .so-toolbar .so-delete":"deleteHandler","click .so-toolbar .so-duplicate":"duplicateHandler"},initializeDialog:function(){var e=this;this.listenTo(this.model,"change:values",this.handleChangeValues),this.listenTo(this.model,"destroy",this.remove),this.dialogFormsLoaded=0,this.on("form_loaded styles_loaded",function(){this.dialogFormsLoaded++,2===this.dialogFormsLoaded&&e.updateModel({refreshArgs:{silent:!0}})}),this.on("edit_label",function(e){e===panelsOptions.widgets[this.model.get("class")].title&&(e=""),this.model.set("label",e),_.isEmpty(e)&&this.$(".so-title").text(this.model.getWidgetField("title"))}.bind(this))},render:function(){this.renderDialog(this.parseDialogContent(l("#siteorigin-panels-dialog-widget").html(),{})),this.loadForm();var e=this.model.getWidgetField("title");this.$(".so-title .widget-name").html(e),this.$(".so-edit-title").val(e),this.builder.supports("addWidget")||this.$(".so-buttons .so-duplicate").remove(),this.builder.supports("deleteWidget")||this.$(".so-buttons .so-delete").remove(),this.styles=new s.view.styles,this.styles.model=this.model,this.styles.render("widget",this.builder.config.postId,{builderType:this.builder.config.builderType,dialog:this});var t=this.$(".so-sidebar.so-right-sidebar");this.styles.attach(t),this.styles.on("styles_loaded",function(e){e||(t.closest(".so-panels-dialog").removeClass("so-panels-dialog-has-right-sidebar"),t.remove())},this)},getPrevDialog:function(){var e=this.builder.$(".so-cells .cell .so-widget");if(e.length<=1)return!1;var t,i=e.index(this.widgetView.$el);if(0===i)return!1;do{if(t=e.eq(--i).data("view"),!_.isUndefined(t)&&!t.model.get("read_only"))return t.getEditDialog()}while(!_.isUndefined(t)&&0<i);return!1},getNextDialog:function(){var e=this.builder.$(".so-cells .cell .so-widget");if(e.length<=1)return!1;var t,i=e.index(this.widgetView.$el);if(i===e.length-1)return!1;do{if(t=e.eq(++i).data("view"),!_.isUndefined(t)&&!t.model.get("read_only"))return t.getEditDialog()}while(!_.isUndefined(t));return!1},loadForm:function(){if(this.$("> *").length){this.$(".so-content").addClass("so-panels-loading");var e={action:"so_panels_widget_form",widget:this.model.get("class"),instance:JSON.stringify(this.model.get("values")),raw:this.model.get("raw")},i=this.$(".so-content");l.post(panelsOptions.ajaxurl,e,null,"html").done(function(e){var t=e.replace(/{\$id}/g,this.model.cid);i.removeClass("so-panels-loading").html(t),this.trigger("form_loaded",this),this.$(".panel-dialog").trigger("panelsopen"),this.on("close_dialog",this.updateModel,this),0<i.find("> .widget-content").length&&o.addWidget(i,this.model.widget_id)}.bind(this)).fail(function(e){var t;t=e&&e.responseText?e.responseText:panelsOptions.forms.loadingFailed,i.removeClass("so-panels-loading").html(t)})}},updateModel:function(e){if(e=_.extend({refresh:!0,refreshArgs:null},e),this.savingWidget=!0,!this.model.get("missing")){var t=this.getFormValues();t=_.isUndefined(t.widgets)?{}:(t=t.widgets)[Object.keys(t)[0]],this.model.setValues(t),this.model.set("raw",!0)}if(this.styles.stylesLoaded){var i={};try{i=this.getFormValues(".so-sidebar .so-visual-styles").style}catch(e){}this.model.set("style",i)}this.savingWidget=!1,e.refresh&&this.builder.model.refreshPanelsData(e.refreshArgs)},handleChangeValues:function(){this.savingWidget||this.loadForm()},saveHandler:function(){this.builder.addHistoryEntry("widget_edited"),this.closeDialog()},deleteHandler:function(){return this.widgetView.visualDestroyModel(),this.closeDialog({silent:!0}),this.builder.model.refreshPanelsData(),!1},duplicateHandler:function(){return this.widgetView.duplicateHandler(),this.closeDialog({silent:!0}),this.builder.model.refreshPanelsData(),!1}})},{"../view/widgets/js-widget":31}],10:[function(e,t,i){var s=window.panels,o=jQuery;t.exports=s.view.dialog.extend({builder:null,widgetTemplate:_.template(s.helpers.utils.processTemplate(o("#siteorigin-panels-dialog-widgets-widget").html())),filter:{},dialogClass:"so-panels-dialog-add-widget",dialogIcon:"add-widget",events:{"click .so-close":"closeDialog","click .widget-type":"widgetClickHandler","keyup .so-sidebar-search":"searchHandler"},initializeDialog:function(){this.on("open_dialog",function(){this.filter.search="",this.filterWidgets(this.filter)},this),this.on("open_dialog_complete",function(){this.$(".so-sidebar-search").val("").focus(),this.balanceWidgetHeights()}),this.on("tab_click",this.tabClickHandler,this)},render:function(){this.renderDialog(this.parseDialogContent(o("#siteorigin-panels-dialog-widgets").html(),{})),_.each(panelsOptions.widgets,function(e){var t=o(this.widgetTemplate({title:e.title,description:e.description}));_.isUndefined(e.icon)&&(e.icon="dashicons dashicons-admin-generic"),o('<span class="widget-icon" />').addClass(e.icon).prependTo(t.find(".widget-type-wrapper")),t.data("class",e.class).appendTo(this.$(".widget-type-list"))},this);var i=this.$(".so-sidebar-tabs");_.each(panelsOptions.widget_dialog_tabs,function(e,t){o(this.dialogTabTemplate({title:e.title,tab:t})).data({message:e.message,filter:e.filter}).appendTo(i)},this),this.initTabs();var e=this;o(window).resize(function(){e.balanceWidgetHeights()})},tabClickHandler:function(e){this.filter=e.parent().data("filter"),this.filter.search=this.$(".so-sidebar-search").val();var t=e.parent().data("message");return _.isEmpty(t)&&(t=""),this.$(".so-toolbar .so-status").html(t),this.filterWidgets(this.filter),!1},searchHandler:function(e){if(13===e.which){var t=this.$(".widget-type-list .widget-type:visible");1===t.length&&t.click()}else this.filter.search=o(e.target).val().trim(),this.filterWidgets(this.filter)},filterWidgets:function(l){_.isUndefined(l)&&(l={}),_.isUndefined(l.groups)&&(l.groups=""),this.$(".widget-type-list .widget-type").each(function(){var e,t=o(this),i=t.data("class"),s=_.isUndefined(panelsOptions.widgets[i])?null:panelsOptions.widgets[i];(e=!!_.isEmpty(l.groups)||null!==s&&!_.isEmpty(_.intersection(l.groups,panelsOptions.widgets[i].groups)))&&(_.isUndefined(l.search)||""===l.search||-1===s.title.toLowerCase().indexOf(l.search.toLowerCase())&&(e=!1)),e?t.show():t.hide()}),this.balanceWidgetHeights()},widgetClickHandler:function(e){this.builder.trigger("before_user_adds_widget"),this.builder.addHistoryEntry("widget_added");var t=o(e.currentTarget),i=new s.model.widget({class:t.data("class")});i.cell=this.builder.getActiveCell(),i.cell.get("widgets").add(i),this.closeDialog(),this.builder.model.refreshPanelsData(),this.builder.trigger("after_user_adds_widget",i)},balanceWidgetHeights:function(e){var s=[[]],l=null,i=Math.round(this.$(".widget-type").parent().width()/this.$(".widget-type").width());this.$(".widget-type").css("clear","none").filter(":visible").each(function(e,t){e%i==0&&0!==e&&o(t).css("clear","both")}),this.$(".widget-type-wrapper").css("height","auto").filter(":visible").each(function(e,t){var i=o(t);null!==l&&l.position().top!==i.position().top&&(s[s.length]=[]),l=i,s[s.length-1].push(i)}),_.each(s,function(e,t){var i=_.max(e.map(function(e){return e.height()}));_.each(e,function(e){e.height(i)})})}})},{}],11:[function(e,t,i){t.exports={canCopyPaste:function(){return"undefined"!=typeof Storage&&panelsOptions.user},setModel:function(e){if(!this.canCopyPaste())return!1;var t=panels.helpers.serialize.serialize(e);return e instanceof panels.model.row?t.thingType="row-model":e instanceof panels.model.widget&&(t.thingType="widget-model"),localStorage["panels_clipboard_"+panelsOptions.user]=JSON.stringify(t),!0},isModel:function(e){if(!this.canCopyPaste())return!1;var t=localStorage["panels_clipboard_"+panelsOptions.user];return void 0!==t&&((t=JSON.parse(t)).thingType&&t.thingType===e)},getModel:function(e){if(!this.canCopyPaste())return null;var t=localStorage["panels_clipboard_"+panelsOptions.user];return void 0!==t&&(t=JSON.parse(t)).thingType&&t.thingType===e?panels.helpers.serialize.unserialize(t,t.thingType,null):null}}},{}],12:[function(e,t,i){t.exports={lock:function(){if("hidden"!==jQuery("body").css("overflow")){var e=[self.pageXOffset||document.documentElement.scrollLeft||document.body.scrollLeft,self.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop];jQuery("body").data({"scroll-position":e}).css("overflow","hidden"),_.isUndefined(e)||window.scrollTo(e[0],e[1])}},unlock:function(){if("hidden"===jQuery("body").css("overflow")&&!jQuery(".so-panels-dialog-wrapper").is(":visible")&&!jQuery(".so-panels-live-editor").is(":visible")){jQuery("body").css("overflow","visible");var e=jQuery("body").data("scroll-position");_.isUndefined(e)||window.scrollTo(e[0],e[1])}}}},{}],13:[function(e,t,i){t.exports={serialize:function(e){var t;if(e instanceof Backbone.Model){var i={};for(var s in e.attributes)if(e.attributes.hasOwnProperty(s)){if("builder"===s||"collection"===s)continue;(t=e.attributes[s])instanceof Backbone.Model||t instanceof Backbone.Collection?i[s]=this.serialize(t):i[s]=t}return i}if(e instanceof Backbone.Collection){for(var l=[],o=0;o<e.models.length;o++)(t=e.models[o])instanceof Backbone.Model||t instanceof Backbone.Collection?l.push(this.serialize(t)):l.push(t);return l}},unserialize:function(e,t,i){var s;switch(t){case"row-model":(s=new panels.model.row).builder=i;var l={style:e.style};e.hasOwnProperty("label")&&(l.label=e.label),e.hasOwnProperty("color_label")&&(l.color_label=e.color_label),s.set(l),s.setCells(this.unserialize(e.cells,"cell-collection",s));break;case"cell-model":(s=new panels.model.cell).row=i,s.set("weight",e.weight),s.set("style",e.style),s.set("widgets",this.unserialize(e.widgets,"widget-collection",s));break;case"widget-model":for(var o in(s=new panels.model.widget).cell=i,e)e.hasOwnProperty(o)&&s.set(o,e[o]);s.set("widget_id",panels.helpers.utils.generateUUID());break;case"cell-collection":s=new panels.collection.cells;for(var n=0;n<e.length;n++)s.push(this.unserialize(e[n],"cell-model",i));break;case"widget-collection":s=new panels.collection.widgets;for(n=0;n<e.length;n++)s.push(this.unserialize(e[n],"widget-model",i));break;default:console.log("Unknown Thing - "+t)}return s}}},{}],14:[function(e,t,i){t.exports={generateUUID:function(){var i=(new Date).getTime();return window.performance&&"function"==typeof window.performance.now&&(i+=performance.now()),"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){var t=(i+16*Math.random())%16|0;return i=Math.floor(i/16),("x"==e?t:3&t|8).toString(16)})},processTemplate:function(e){return _.isUndefined(e)||_.isNull(e)?"":e=(e=(e=e.replace(/{{%/g,"<%")).replace(/%}}/g,"%>")).trim()},selectElementContents:function(e){var t=document.createRange();t.selectNodeContents(e);var i=window.getSelection();i.removeAllRanges(),i.addRange(t)}}},{}],15:[function(e,t,i){var d=window.panels,c=jQuery;t.exports=function(a,r){return this.each(function(){var e=jQuery(this);if(!e.data("soPanelsBuilderWidgetInitialized")||r){var t=e.closest("form").find(".widget-id").val(),i=c.extend(!0,{},a);if(_.isUndefined(t)||!(-1<t.indexOf("__i__"))){var s=new d.model.builder,l=new d.view.builder({model:s,config:i}),o=e.closest(".so-panels-dialog-wrapper").data("view");_.isUndefined(o)||(o.on("close_dialog",function(){s.refreshPanelsData()}),o.on("open_dialog_complete",function(){l.trigger("builder_resize")}),o.model.on("destroy",function(){s.emptyRows().destroy()}),l.setDialogParents(panelsOptions.loc.layout_widget,o));var n=Boolean(e.closest(".widget-content").length);l.render().attach({container:e,dialog:n||"dialog"===e.data("mode"),type:e.data("type")}).setDataField(e.find("input.panels-data")),n||"dialog"===e.data("mode")?(l.setDialogParents(panelsOptions.loc.layout_widget,l.dialog),e.find(".siteorigin-panels-display-builder").click(function(e){e.preventDefault(),l.dialog.openDialog()})):e.find(".siteorigin-panels-display-builder").parent().remove(),c(document).trigger("panels_setup",l),e.data("soPanelsBuilderWidgetInitialized",!0)}}})}},{}],16:[function(e,t,i){var s={};window.panels=s,(window.siteoriginPanels=s).helpers={},s.helpers.clipboard=e("./helpers/clipboard"),s.helpers.utils=e("./helpers/utils"),s.helpers.serialize=e("./helpers/serialize"),s.helpers.pageScroll=e("./helpers/page-scroll"),s.model={},s.model.widget=e("./model/widget"),s.model.cell=e("./model/cell"),s.model.row=e("./model/row"),s.model.builder=e("./model/builder"),s.model.historyEntry=e("./model/history-entry"),s.collection={},s.collection.widgets=e("./collection/widgets"),s.collection.cells=e("./collection/cells"),s.collection.rows=e("./collection/rows"),s.collection.historyEntries=e("./collection/history-entries"),s.view={},s.view.widget=e("./view/widget"),s.view.cell=e("./view/cell"),s.view.row=e("./view/row"),s.view.builder=e("./view/builder"),s.view.dialog=e("./view/dialog"),s.view.styles=e("./view/styles"),s.view.liveEditor=e("./view/live-editor"),s.dialog={},s.dialog.builder=e("./dialog/builder"),s.dialog.widgets=e("./dialog/widgets"),s.dialog.widget=e("./dialog/widget"),s.dialog.prebuilt=e("./dialog/prebuilt"),s.dialog.row=e("./dialog/row"),s.dialog.history=e("./dialog/history"),s.utils={},s.utils.menu=e("./utils/menu"),jQuery.fn.soPanelsSetupBuilderWidget=e("./jquery/setup-builder-widget"),jQuery(function(i){var e,t,s,l,o=i("#siteorigin-panels-metabox");if(s=i("form#post"),o.length&&s.length)t=(e=o).find(".siteorigin-panels-data-field"),l={editorType:"tinyMCE",postId:i("#post_ID").val(),editorId:"#content",builderType:o.data("builder-type"),builderSupports:o.data("builder-supports"),loadOnAttach:panelsOptions.loadOnAttach&&1==i("#auto_draft").val(),loadLiveEditor:1==o.data("live-editor"),liveEditorPreview:e.data("preview-url")};else if(i(".siteorigin-panels-builder-form").length){var n=i(".siteorigin-panels-builder-form");e=n.find(".siteorigin-panels-builder-container"),t=n.find('input[name="panels_data"]'),l={editorType:"standalone",postId:(s=n).data("post-id"),editorId:"#post_content",builderType:n.data("type"),builderSupports:n.data("builder-supports"),loadLiveEditor:!1,liveEditorPreview:n.data("preview-url")}}if(!_.isUndefined(e)){var a=window.siteoriginPanels,r=new a.model.builder,d=new a.view.builder({model:r,config:l});i(document).trigger("before_panels_setup",d),d.render().attach({container:e}).setDataField(t).attachToEditor(),s.submit(function(){r.refreshPanelsData()}),e.removeClass("so-panels-loading"),i(document).trigger("panels_setup",d,window.panels)}i(document).on("widget-added",function(e,t){i(t).find(".siteorigin-page-builder-widget").soPanelsSetupBuilderWidget()}),i("body").hasClass("wp-customizer")||i(function(){i(".siteorigin-page-builder-widget").soPanelsSetupBuilderWidget()}),i(window).on("keyup",function(e){27===e.which&&i(".so-panels-dialog-wrapper, .so-panels-live-editor").filter(":visible").last().find(".so-title-bar .so-close, .live-editor-close").click()})})},{"./collection/cells":1,"./collection/history-entries":2,"./collection/rows":3,"./collection/widgets":4,"./dialog/builder":5,"./dialog/history":6,"./dialog/prebuilt":7,"./dialog/row":8,"./dialog/widget":9,"./dialog/widgets":10,"./helpers/clipboard":11,"./helpers/page-scroll":12,"./helpers/serialize":13,"./helpers/utils":14,"./jquery/setup-builder-widget":15,"./model/builder":17,"./model/cell":18,"./model/history-entry":19,"./model/row":20,"./model/widget":21,"./utils/menu":22,"./view/builder":23,"./view/cell":24,"./view/dialog":25,"./view/live-editor":26,"./view/row":27,"./view/styles":28,"./view/widget":29}],17:[function(e,t,i){t.exports=Backbone.Model.extend({layoutPosition:{BEFORE:"before",AFTER:"after",REPLACE:"replace"},rows:{},defaults:{data:{widgets:[],grids:[],grid_cells:[]}},initialize:function(){this.set("rows",new panels.collection.rows)},addRow:function(e,t,i){i=_.extend({noAnimate:!1},i);var s=new panels.collection.cells(t);e=_.extend({collection:this.get("rows"),cells:s},e);var l=new panels.model.row(e);return(l.builder=this).get("rows").add(l,i),l},loadPanelsData:function(s,e){try{e===this.layoutPosition.BEFORE?s=this.concatPanelsData(s,this.getPanelsData()):e===this.layoutPosition.AFTER&&(s=this.concatPanelsData(this.getPanelsData(),s)),this.emptyRows(),this.set("data",JSON.parse(JSON.stringify(s)),{silent:!0});var t,i=[];if(_.isUndefined(s.grid_cells))return void this.trigger("load_panels_data");for(var l=0;l<s.grid_cells.length;l++)t=parseInt(s.grid_cells[l].grid),_.isUndefined(i[t])&&(i[t]=[]),i[t].push(s.grid_cells[l]);var o=this;if(_.each(i,function(e,t){var i={};_.isUndefined(s.grids[t].style)||(i.style=s.grids[t].style),_.isUndefined(s.grids[t].ratio)||(i.ratio=s.grids[t].ratio),_.isUndefined(s.grids[t].ratio_direction)||(i.ratio_direction=s.grids[t].ratio_direction),_.isUndefined(s.grids[t].color_label)||(i.color_label=s.grids[t].color_label),_.isUndefined(s.grids[t].label)||(i.label=s.grids[t].label),o.addRow(i,e,{noAnimate:!0})}),_.isUndefined(s.widgets))return;_.each(s.widgets,function(e){var t=null;_.isUndefined(e.panels_info)?(t=e.info,delete e.info):(t=e.panels_info,delete e.panels_info);var i=o.get("rows").at(parseInt(t.grid)).get("cells").at(parseInt(t.cell)),s=new panels.model.widget({class:t.class,values:e});_.isUndefined(t.style)||s.set("style",t.style),_.isUndefined(t.read_only)||s.set("read_only",t.read_only),_.isUndefined(t.widget_id)?s.set("widget_id",panels.helpers.utils.generateUUID()):s.set("widget_id",t.widget_id),_.isUndefined(t.label)||s.set("label",t.label),(s.cell=i).get("widgets").add(s,{noAnimate:!0})}),this.trigger("load_panels_data")}catch(e){console.log("Error loading data: "+e.message)}},concatPanelsData:function(e,t){if(_.isUndefined(t)||_.isUndefined(t.grids)||_.isEmpty(t.grids)||_.isUndefined(t.grid_cells)||_.isEmpty(t.grid_cells))return e;if(_.isUndefined(e)||_.isUndefined(e.grids)||_.isEmpty(e.grids))return t;var i,s=e.grids.length,l=_.isUndefined(e.widgets)?0:e.widgets.length,o={grids:[],grid_cells:[],widgets:[]};for(o.grids=e.grids.concat(t.grids),_.isUndefined(e.grid_cells)||(o.grid_cells=e.grid_cells.slice()),_.isUndefined(e.widgets)||(o.widgets=e.widgets.slice()),i=0;i<t.grid_cells.length;i++){var n=t.grid_cells[i];n.grid=parseInt(n.grid)+s,o.grid_cells.push(n)}if(!_.isUndefined(t.widgets))for(i=0;i<t.widgets.length;i++){var a=t.widgets[i];a.panels_info.grid=parseInt(a.panels_info.grid)+s,a.panels_info.id=parseInt(a.panels_info.id)+l,o.widgets.push(a)}return o},getPanelsData:function(){var n={widgets:[],grids:[],grid_cells:[]},a=0;return this.get("rows").each(function(e,o){e.get("cells").each(function(e,l){e.get("widgets").each(function(e,t){var i={class:e.get("class"),raw:e.get("raw"),grid:o,cell:l,id:a++,widget_id:e.get("widget_id"),style:e.get("style"),label:e.get("label")};_.isEmpty(i.widget_id)&&(i.widget_id=panels.helpers.utils.generateUUID());var s=_.extend(_.clone(e.get("values")),{panels_info:i});n.widgets.push(s)}),n.grid_cells.push({grid:o,index:l,weight:e.get("weight"),style:e.get("style")})}),n.grids.push({cells:e.get("cells").length,style:e.get("style"),ratio:e.get("ratio"),ratio_direction:e.get("ratio_direction"),color_label:e.get("color_label"),label:e.get("label")})}),n},refreshPanelsData:function(e){e=_.extend({silent:!1},e);var t=this.get("data"),i=this.getPanelsData();this.set("data",i,{silent:!0}),e.silent||JSON.stringify(i)===JSON.stringify(t)||(this.trigger("change"),this.trigger("change:data"),this.trigger("refresh_panels_data",i,e))},emptyRows:function(){return _.invoke(this.get("rows").toArray(),"destroy"),this.get("rows").reset(),this},isValidLayoutPosition:function(e){return e===this.layoutPosition.BEFORE||e===this.layoutPosition.AFTER||e===this.layoutPosition.REPLACE},getPanelsDataFromHtml:function(e,h){var t,u=this,i=jQuery('<div id="wrapper">'+e+"</div>");if(i.find(".panel-layout .panel-grid").length){function p(e){var t,i=e.find("div");if(!i.length)return e.html();for(t=0;t<i.length-1&&jQuery.trim(i.eq(t).text())==jQuery.trim(i.eq(t+1).text());t++);var s=i.eq(t).find(".widget-title:header"),l="";return s.length&&(l=s.html(),s.remove()),{title:l,text:i.eq(t).html()}}function s(e,t){return jQuery(t).closest(".panel-layout").is(l)}var g={grids:[],grid_cells:[],widgets:[]},f=new RegExp(panelsOptions.siteoriginWidgetRegex,"i"),w=(t=document.createElement("div"),function(e){return e&&"string"==typeof e&&(e=(e=e.replace(/<script[^>]*>([\S\s]*?)<\/script>/gim,"")).replace(/<\/?\w(?:[^"'>]|"[^"]*"|'[^']*')*>/gim,""),t.innerHTML=e,e=t.textContent,t.textContent=""),e}),l=i.find(".panel-layout").eq(0);return i.find("> .panel-layout > .panel-grid").filter(s).each(function(c,e){var t=jQuery(e),i=t.find(".panel-grid-cell").filter(s);g.grids.push({cells:i.length,style:t.data("style"),ratio:t.data("ratio"),ratio_direction:t.data("ratio-direction"),color_label:t.data("color-label"),label:t.data("label")}),i.each(function(d,e){var t=jQuery(e),i=t.find(".so-panel").filter(s);g.grid_cells.push({grid:c,weight:_.isUndefined(t.data("weight"))?1:parseFloat(t.data("weight")),style:t.data("style")}),i.each(function(e,t){var i=jQuery(t),s=i.find(".panel-widget-style").length?i.find(".panel-widget-style").html():i.html(),l={grid:c,cell:d,style:i.data("style"),raw:!1,label:i.data("label")};s=s.trim();var o=f.exec(s);if(_.isNull(o)||""!==s.replace(f,"").trim())return-1!==s.indexOf("panel-layout")&&jQuery("<div>"+s+"</div>").find(".panel-layout .panel-grid").length?(l.class="SiteOrigin_Panels_Widgets_Layout",g.widgets.push({panels_data:u.getPanelsDataFromHtml(s,h),panels_info:l})):(l.class=h,g.widgets.push(_.extend(p(i),{filter:"1",type:"visual",panels_info:l}))),!0;try{var n=/class="(.*?)"/.exec(o[3]),a=jQuery(o[5]),r=JSON.parse(w(a.val())).instance;l.class=n[1].replace(/\\\\+/g,"\\"),l.raw=!1,r.panels_info=l,g.widgets.push(r)}catch(e){l.class=h,g.widgets.push(_.extend(p(i),{filter:"1",type:"visual",panels_info:l}))}return!0})})}),i.find(".panel-layout").remove(),i.find("style[data-panels-style-for-post]").remove(),i.html().replace(/^\s+|\s+$/gm,"").length&&(g.grids.push({cells:1,style:{}}),g.grid_cells.push({grid:g.grids.length-1,weight:1}),g.widgets.push({filter:"1",text:i.html().replace(/^\s+|\s+$/gm,""),title:"",type:"visual",panels_info:{class:h,raw:!1,grid:g.grids.length-1,cell:0}})),g}return{grid_cells:[{grid:0,weight:1}],grids:[{cells:1}],widgets:[{filter:"1",text:e,title:"",type:"visual",panels_info:{class:h,raw:!1,grid:0,cell:0}}]}}})},{}],18:[function(e,t,i){t.exports=Backbone.Model.extend({widgets:{},row:null,defaults:{weight:0,style:{}},indexes:null,initialize:function(){this.set("widgets",new panels.collection.widgets),this.on("destroy",this.onDestroy,this)},onDestroy:function(){_.invoke(this.get("widgets").toArray(),"destroy"),this.get("widgets").reset()},clone:function(e,t){_.isUndefined(e)&&(e=this.row),t=_.extend({cloneWidgets:!0},t);var i=new this.constructor(this.attributes);return i.set("collection",e.get("cells"),{silent:!0}),i.row=e,t.cloneWidgets&&this.get("widgets").each(function(e){i.get("widgets").add(e.clone(i,t),{silent:!0})}),i}})},{}],19:[function(e,t,i){t.exports=Backbone.Model.extend({defaults:{text:"",data:"",time:null,count:1}})},{}],20:[function(e,t,i){t.exports=Backbone.Model.extend({builder:null,defaults:{style:{}},indexes:null,initialize:function(){_.isEmpty(this.get("cells"))?this.set("cells",new panels.collection.cells):this.get("cells").each(function(e){e.row=this}.bind(this)),this.on("destroy",this.onDestroy,this)},setCells:function(n){var a=this.get("cells")||new panels.collection.cells,r=[];a.each(function(e,t){var i=n.at(t);if(i)e.set("weight",i.get("weight"));else{for(var s=a.at(n.length-1),l=e.get("widgets").models.slice(),o=0;o<l.length;o++)l[o].moveToCell(s,{silent:!1});r.push(e)}}),_.each(r,function(e){a.remove(e)}),n.length>a.length&&_.each(n.slice(a.length,n.length),function(e){e.set({collection:a}),e.row=this,a.add(e)}.bind(this)),this.reweightCells()},reweightCells:function(){var t=0,e=this.get("cells");e.each(function(e){t+=e.get("weight")}),e.each(function(e){e.set("weight",e.get("weight")/t)}),this.trigger("reweight_cells")},onDestroy:function(){_.invoke(this.get("cells").toArray(),"destroy"),this.get("cells").reset()},clone:function(e){_.isUndefined(e)&&(e=this.builder);var t=new this.constructor(this.attributes);t.set("collection",e.get("rows"),{silent:!0}),t.builder=e;var i=new panels.collection.cells;return this.get("cells").each(function(e){i.add(e.clone(t),{silent:!0})}),t.set("cells",i),t}})},{}],21:[function(e,t,i){t.exports=Backbone.Model.extend({cell:null,defaults:{class:null,missing:!1,values:{},raw:!1,style:{},read_only:!1,widget_id:""},indexes:null,initialize:function(){var e=this.get("class");!_.isUndefined(panelsOptions.widgets[e])&&panelsOptions.widgets[e].installed||this.set("missing",!0)},getWidgetField:function(e){return _.isUndefined(panelsOptions.widgets[this.get("class")])?"title"===e||"description"===e?panelsOptions.loc.missing_widget[e]:"":this.has("label")&&!_.isEmpty(this.get("label"))?this.get("label"):panelsOptions.widgets[this.get("class")][e]},moveToCell:function(e,t,i){return t=_.extend({silent:!0},t),this.cell=e,this.collection.remove(this,t),e.get("widgets").add(this,_.extend({at:i},t)),this.trigger("move_to_cell",e,i),this},setValues:function(e){var t=!1;JSON.stringify(e)!==JSON.stringify(this.get("values"))&&(t=!0),this.set("values",e,{silent:!0}),t&&(this.trigger("change",this),this.trigger("change:values"))},clone:function(e,t){_.isUndefined(e)&&(e=this.cell);var i=new this.constructor(this.attributes),s=JSON.parse(JSON.stringify(this.get("values"))),l=function(i){return _.each(i,function(e,t){_.isString(t)&&"_"===t[0]?delete i[t]:_.isObject(i[t])&&l(i[t])}),i};return s=l(s),"SiteOrigin_Panels_Widgets_Layout"===this.get("class")&&(s.builder_id=Math.random().toString(36).substr(2)),i.set("widget_id",""),i.set("values",s,{silent:!0}),i.set("collection",e.get("widgets"),{silent:!0}),i.cell=e,i.isDuplicate=!0,i},getTitle:function(){var e=panelsOptions.widgets[this.get("class")];if(_.isUndefined(e))return this.get("class").replace(/_/g," ");if(!_.isUndefined(e.panels_title)&&!1===e.panels_title)return panelsOptions.widgets[this.get("class")].description;var t=this.get("values"),i=["title","text"];for(var s in t)"_"!==s.charAt(0)&&"so_sidebar_emulator_id"!==s&&"option_name"!==s&&t.hasOwnProperty(s)&&i.push(s);for(var l in i=_.uniq(i))if(!_.isUndefined(t[i[l]])&&_.isString(t[i[l]])&&""!==t[i[l]]&&"on"!==t[i[l]]&&"_"!==i[l][0]&&!jQuery.isNumeric(t[i[l]])){var o=t[i[l]],n=(o=o.replace(/<\/?[^>]+(>|$)/g,"")).split(" ");return(n=n.slice(0,20)).join(" ")}return this.getWidgetField("description")}})},{}],22:[function(e,t,i){var s=window.panels,r=jQuery;t.exports=Backbone.View.extend({wrapperTemplate:_.template(s.helpers.utils.processTemplate(r("#siteorigin-panels-context-menu").html())),sectionTemplate:_.template(s.helpers.utils.processTemplate(r("#siteorigin-panels-context-menu-section").html())),contexts:[],active:!1,events:{"keyup .so-search-wrapper input":"searchKeyUp"},initialize:function(){this.listenContextMenu(),this.render(),this.attach()},listenContextMenu:function(){var t=this;r(window).on("contextmenu",function(e){return t.active&&!t.isOverEl(t.$el,e)?(t.closeMenu(),t.active=!1,e.preventDefault(),!1):!!t.active||(t.active=!1,t.trigger("activate_context",e,t),void(t.active&&(e.preventDefault(),t.openMenu({left:e.pageX,top:e.pageY}))))})},render:function(){this.setElement(this.wrapperTemplate())},attach:function(){this.$el.appendTo("body")},openMenu:function(e){this.trigger("open_menu"),r(window).on("keyup",{menu:this},this.keyboardListen),r(window).on("click",{menu:this},this.clickOutsideListen),this.$el.css("max-height",r(window).height()-20),e.left+this.$el.outerWidth()+10>=r(window).width()&&(e.left=r(window).width()-this.$el.outerWidth()-10),e.left<=0&&(e.left=10),e.top+this.$el.outerHeight()-r(window).scrollTop()+10>=r(window).height()&&(e.top=r(window).height()+r(window).scrollTop()-this.$el.outerHeight()-10),e.left<=0&&(e.left=10),this.$el.css({left:e.left+1,top:e.top+1}).show(),this.$(".so-search-wrapper input").focus()},closeMenu:function(){this.trigger("close_menu"),r(window).off("keyup",this.keyboardListen),r(window).off("click",this.clickOutsideListen),this.active=!1,this.$el.empty().hide()},keyboardListen:function(e){var t=e.data.menu;switch(e.which){case 27:t.closeMenu()}},clickOutsideListen:function(e){var t=e.data.menu;3!==e.which&&t.$el.is(":visible")&&!t.isOverEl(t.$el,e)&&t.closeMenu()},addSection:function(e,t,i,s){var l=this;t=_.extend({display:5,defaultDisplay:!1,search:!0,sectionTitle:"",searchPlaceholder:"",titleKey:"title"},t);var o=r(this.sectionTemplate({settings:t,items:i})).attr("id","panels-menu-section-"+e);this.$el.append(o),o.find(".so-item:not(.so-confirm)").click(function(){var e=r(this);s(e.data("key")),l.closeMenu()}),o.find(".so-item.so-confirm").click(function(){var e=r(this);if(e.hasClass("so-confirming"))return s(e.data("key")),void l.closeMenu();e.data("original-text",e.html()).addClass("so-confirming").html('<span class="dashicons dashicons-yes"></span> '+panelsOptions.loc.dropdown_confirm),setTimeout(function(){e.removeClass("so-confirming"),e.html(e.data("original-text"))},2500)}),o.data("settings",t).find(".so-search-wrapper input").trigger("keyup"),this.active=!0},hasSection:function(e){return 0<this.$el.find("#panels-menu-section-"+e).length},searchKeyUp:function(e){var t=r(e.currentTarget),i=t.closest(".so-section"),s=i.data("settings");if(38===e.which||40===e.which){var l=i.find("ul li:visible"),o=l.filter(".so-active").eq(0);if(o.length){l.removeClass("so-active");var n=l.index(o);38===e.which?o=n-1<0?l.last():l.eq(n-1):40===e.which&&(o=n+1>=l.length?l.first():l.eq(n+1))}else 38===e.which?o=l.last():40===e.which&&(o=l.first());return o.addClass("so-active"),!1}if(13===e.which)return 1===i.find("ul li:visible").length?i.find("ul li:visible").trigger("click"):i.find("ul li.so-active:visible").trigger("click"),!1;if(""===t.val())if(s.defaultDisplay){i.find(".so-item").hide();for(var a=0;a<s.defaultDisplay.length;a++)i.find('.so-item[data-key="'+s.defaultDisplay[a]+'"]').show()}else i.find(".so-item").show();else i.find(".so-item").hide().each(function(){var e=r(this);-1!==e.html().toLowerCase().indexOf(t.val().toLowerCase())&&e.show()});i.find(".so-item:visible:gt("+(s.display-1)+")").hide(),0===i.find(".so-item:visible").length&&""!==t.val()?i.find(".so-no-results").show():i.find(".so-no-results").hide()},isOverEl:function(e,t){var i=[[e.offset().left,e.offset().top],[e.offset().left+e.outerWidth(),e.offset().top+e.outerHeight()]];return t.pageX>=i[0][0]&&t.pageX<=i[1][0]&&t.pageY>=i[0][1]&&t.pageY<=i[1][1]}})},{}],23:[function(e,t,i){var a=window.panels,n=jQuery;t.exports=Backbone.View.extend({config:{},template:_.template(a.helpers.utils.processTemplate(n("#siteorigin-panels-builder").html())),dialogs:{},rowsSortable:null,dataField:!1,currentData:"",attachedToEditor:!1,attachedVisible:!1,liveEditor:void 0,menu:!1,activeCell:null,events:{"click .so-tool-button.so-widget-add":"displayAddWidgetDialog","click .so-tool-button.so-row-add":"displayAddRowDialog","click .so-tool-button.so-prebuilt-add":"displayAddPrebuiltDialog","click .so-tool-button.so-history":"displayHistoryDialog","click .so-tool-button.so-live-editor":"displayLiveEditor"},rows:null,initialize:function(e){var s=this;return this.config=_.extend({loadLiveEditor:!1,builderSupports:{}},e.config),this.config.builderSupports=_.extend({addRow:!0,editRow:!0,deleteRow:!0,moveRow:!0,addWidget:!0,editWidget:!0,deleteWidget:!0,moveWidget:!0,prebuilt:!0,history:!0,liveEditor:!0,revertToEditor:!0},this.config.builderSupports),e.config.loadLiveEditor&&this.on("builder_live_editor_added",function(){this.displayLiveEditor()}),this.dialogs={widgets:new a.dialog.widgets,row:new a.dialog.row,prebuilt:new a.dialog.prebuilt},_.each(this.dialogs,function(e,t,i){i[t].setBuilder(s)}),this.dialogs.row.setRowDialogType("create"),this.listenTo(this.model.get("rows"),"add",this.onAddRow),n(window).resize(function(e){e.target===window&&s.trigger("builder_resize")}),this.listenTo(this.model,"change:data load_panels_data",this.storeModelData),this.listenTo(this.model,"change:data load_panels_data",this.toggleWelcomeDisplay),this.on("content_change",this.handleContentChange,this),this.on("display_builder",this.handleDisplayBuilder,this),this.on("hide_builder",this.handleHideBuilder,this),this.on("builder_rendered builder_resize",this.handleBuilderSizing,this),this.on("display_builder",this.wrapEditorExpandAdjust,this),this.menu=new a.utils.menu({}),this.listenTo(this.menu,"activate_context",this.activateContextMenu),this.config.loadOnAttach&&this.on("builder_attached_to_editor",function(){this.displayAttachedBuilder({confirm:!1})},this),this},render:function(){return this.setElement(this.template()),this.$el.attr("id","siteorigin-panels-builder-"+this.cid).addClass("so-builder-container"),this.trigger("builder_rendered"),this},attach:function(e){(e=_.extend({container:!1,dialog:!1},e)).dialog?(this.dialog=new a.dialog.builder,this.dialog.builder=this):(this.$el.appendTo(e.container),this.metabox=e.container.closest(".postbox"),this.initSortable(),this.trigger("attached_to_container",e.container)),this.trigger("builder_attached"),this.supports("liveEditor")&&this.addLiveEditor(),this.supports("history")&&this.addHistoryBrowser();var t=this.$(".so-builder-toolbar"),i=this.$(".so-panels-welcome-message"),s=panelsOptions.loc.welcomeMessage,l=[];this.supports("addWidget")?l.push(s.addWidgetButton):t.find(".so-widget-add").hide(),this.supports("addRow")?l.push(s.addRowButton):t.find(".so-row-add").hide(),this.supports("prebuilt")?l.push(s.addPrebuiltButton):t.find(".so-prebuilt-add").hide();var o="";3===l.length?o=s.threeEnabled:2===l.length?o=s.twoEnabled:1===l.length?o=s.oneEnabled:0===l.length&&(o=s.addingDisabled);var n=_.template(a.helpers.utils.processTemplate(o))({items:l})+" "+s.docsMessage;return i.find(".so-message-wrapper").html(n),this},attachToEditor:function(){if("tinyMCE"!==this.config.editorType)return this;this.attachedToEditor=!0;var t=this.metabox,l=this;n("#wp-content-wrap .wp-editor-tabs").find(".wp-switch-editor").click(function(e){e.preventDefault(),n("#wp-content-editor-container").show(),n("#wp-content-wrap").removeClass("panels-active"),n("#content-resize-handle").show(),l.trigger("hide_builder")}).end().append(n('<button type="button" id="content-panels" class="hide-if-no-js wp-switch-editor switch-panels">'+t.find(".hndle span").html()+"</button>").click(function(e){l.displayAttachedBuilder({confirm:!0})&&e.preventDefault()})),this.supports("revertToEditor")&&t.find(".so-switch-to-standard").click(function(e){e.preventDefault(),confirm(panelsOptions.loc.confirm_stop_builder)&&(l.addHistoryEntry("back_to_editor"),l.model.loadPanelsData(!1),n("#wp-content-wrap").show(),t.hide(),n(window).resize(),l.attachedVisible=!1,l.trigger("hide_builder"))}).show(),t.insertAfter("#wp-content-wrap").hide().addClass("attached-to-editor");var e=this.model.get("data");_.isEmpty(e.widgets)&&_.isEmpty(e.grids)&&this.supports("revertToEditor")||this.displayAttachedBuilder({confirm:!1});function i(){var e=l.$(".so-builder-toolbar");if(l.$el.hasClass("so-display-narrow"))return e.css({top:0,left:0,width:"100%",position:"absolute"}),void l.$el.css("padding-top",e.outerHeight());var t=n(window).scrollTop()-l.$el.offset().top;"fixed"===n("#wpadminbar").css("position")&&(t+=n("#wpadminbar").outerHeight());var i=0,s=l.$el.outerHeight()-e.outerHeight()+20;i<t&&t<s?"fixed"!==e.css("position")&&e.css({top:n("#wpadminbar").outerHeight(),left:l.$el.offset().left,width:l.$el.outerWidth(),position:"fixed"}):e.css({top:Math.min(Math.max(t,0),l.$el.outerHeight()-e.outerHeight()+20),left:0,width:"100%",position:"absolute"}),l.$el.css("padding-top",e.outerHeight())}return this.on("builder_resize",i,this),n(document).scroll(i),i(),this.trigger("builder_attached_to_editor"),this},displayAttachedBuilder:function(e){if((e=_.extend({confirm:!0},e)).confirm){var t="undefined"!=typeof tinyMCE&&tinyMCE.get("content");if(""!==(t&&_.isFunction(t.getContent)?t.getContent():n("textarea#content").val())&&!confirm(panelsOptions.loc.confirm_use_builder))return!1}return n("#wp-content-wrap").hide(),n("#editor-expand-toggle").on("change.editor-expand",function(){n(this).prop("checked")||n("#wp-content-wrap").hide()}),this.metabox.show().find("> .inside").show(),n(window).resize(),n(document).scroll(),this.attachedVisible=!0,this.trigger("display_builder"),!0},initSortable:function(){if(!this.supports("moveRow"))return this;var o=this,e=o.$el.attr("id");return this.rowsSortable=this.$(".so-rows-container").sortable({appendTo:"#wpwrap",items:".so-row-container",handle:".so-row-move",connectWith:"#"+e+".so-rows-container,.block-editor .so-rows-container",axis:"y",tolerance:"pointer",scroll:!1,remove:function(e,t){o.model.get("rows").remove(n(t.item).data("view").model,{silent:!0}),o.model.refreshPanelsData()},receive:function(e,t){o.model.get("rows").add(n(t.item).data("view").model,{silent:!0,at:n(t.item).index()}),o.model.refreshPanelsData()},stop:function(e,t){var i=n(t.item),s=i.data("view"),l=o.model.get("rows");l.get(s.model)&&(o.addHistoryEntry("row_moved"),l.remove(s.model,{silent:!0}),l.add(s.model,{silent:!0,at:i.index()}),s.trigger("move",i.index()),o.model.refreshPanelsData())}}),this},refreshSortable:function(){_.isNull(this.rowsSortable)||this.rowsSortable.sortable("refresh")},setDataField:function(e,t){if(t=_.extend({load:!0},t),this.dataField=e,this.dataField.data("builder",this),t.load&&""!==e.val()){var i=this.dataField.val();try{i=JSON.parse(i)}catch(e){console.log("Failed to parse Page Builder layout data from supplied data field."),i={}}this.setData(i)}return this},setData:function(e){this.model.loadPanelsData(e),this.currentData=e,this.toggleWelcomeDisplay()},getData:function(){return this.model.get("data")},storeModelData:function(){var e=JSON.stringify(this.model.get("data"));n(this.dataField).val()!==e&&(n(this.dataField).val(e),n(this.dataField).trigger("change"),this.trigger("content_change"))},onAddRow:function(e,t,i){i=_.extend({noAnimate:!1},i);var s=new a.view.row({model:e});s.builder=this,s.render(),_.isUndefined(i.at)||t.length<=1?s.$el.appendTo(this.$(".so-rows-container")):s.$el.insertAfter(this.$(".so-rows-container .so-row-container").eq(i.at-1)),!1===i.noAnimate&&s.visualCreate(),this.refreshSortable(),s.resize(),this.trigger("row_added")},displayAddWidgetDialog:function(){this.dialogs.widgets.openDialog()},displayAddRowDialog:function(){var t=new a.model.row,e=new a.collection.cells([{weight:.5},{weight:.5}]);e.each(function(e){e.row=t}),t.set("cells",e),t.builder=this.model,this.dialogs.row.setRowModel(t),this.dialogs.row.openDialog()},displayAddPrebuiltDialog:function(){this.dialogs.prebuilt.openDialog()},displayHistoryDialog:function(){this.dialogs.history.openDialog()},pasteRowHandler:function(){var e=a.helpers.clipboard.getModel("row-model");!_.isEmpty(e)&&e instanceof a.model.row&&(this.addHistoryEntry("row_pasted"),e.builder=this.model,this.model.get("rows").add(e,{at:this.model.get("rows").indexOf(this.model)+1}),this.model.refreshPanelsData())},getActiveCell:function(e){if(e=_.extend({createCell:!0},e),!this.model.get("rows").length){if(!e.createCell)return null;this.model.addRow({},[{weight:1}],{noAnimate:!0})}var t=this.activeCell;return _.isEmpty(t)||-1===this.model.get("rows").indexOf(t.model.row)?this.model.get("rows").last().get("cells").first():t.model},addLiveEditor:function(){return _.isEmpty(this.config.liveEditorPreview)||(this.liveEditor=new a.view.liveEditor({builder:this,previewUrl:this.config.liveEditorPreview}),this.liveEditor.hasPreviewUrl()&&this.$(".so-builder-toolbar .so-live-editor").show(),this.trigger("builder_live_editor_added")),this},displayL