: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
this.$el.addClass('stream-loading');
model = new StreamModel({id: id});
function (response, status, xhr) {
var view, attribute, value;
if (response.feeds && typeof response.feeds === 'string') {
response.feeds = JSON.parse(response.feeds);
for (attribute in response) {
value = response[attribute];
model.set(attribute, typeof value === 'string' ? stripslashes(value) : value)
console.log('new StreamView')
view = new StreamView({model: model});
FlowFlow.$streamsContainer.append(view.$el);
self.$el.removeClass('stream-loading');
if (!viewStays) FlowFlow.switchToView(id);
alert('Something went wrong, please try to reload page')
self.$el.removeClass('stream-loading');
if (!viewStays) FlowFlow.switchToView(id);
var promise = FlowFlow.popup('Just checking for misclick. Delete stream?');
var id = self.model.get('id');
var request = self.model.destroy();
FlowFlow.makeOverlayTo('show');
request.done(function( stream ){
if ( stream && stream.error ) return;
if (streamRowModels.length === 0) {
FlowFlow.$list.append(templates.streamRowEmpty);
FlowFlow.makeOverlayTo('hide');
alert('Something went wrong, please try to reload page');
var request = self.model.clone();
FlowFlow.makeOverlayTo('show');
request.done(function(stream){
var model = streamRowModels.get(stream.id)
var view = new StreamRowView({model: model});
FlowFlow.$list.append(view.$el);
FlowFlow.makeOverlayTo('hide');
alert('Something went wrong, please try to reload page');
getShortcodePages: function() {
var id = this.model.get('id');
action: la_plugin_slug_down + '_get_shortcode_pages',
var $hint = this.$el.find( '.shortcode-pages' );
$hint.html( '<span>.</span><span>.</span><span>.</span>' );
this.ajaxPages = $.post( vars.ajaxurl, data ).done(function( res ){
var data = JSON.parse( res );
$hint.html( 'No pages found' );
for ( var i = 0, len = data.length; i < len; i++ ) {
pages += '<a href="' + page.url + '" target="_blank">' + page.post_title + '</a><br>';
$hint.html( 'Something went wrong, please report error.' );
cancelGetShortcodePages: function () {
if ( this.ajaxPages ) this.ajaxPages.abort();
this.$el.find( '.shortcode-pages' ).html('');
selectShortcode: function(e){
var doc = window.document, sel, range;
if (window.getSelection && doc.createRange) {
sel = window.getSelection();
range = doc.createRange();
range.selectNodeContents(el);
} else if (doc.body.createTextRange) {
range = doc.body.createTextRange();
range.moveToElementText(el);
StreamView = Backbone.View.extend({
template: _.template(templates.stream),
className: "section-stream",
"click .admin-button.submit-button": "saveViaAjax",
"change input, textarea": "updateModel",
"input [type=range]": "updateModel",
"colorpicker-change input": "updateModel",
"change select:not(.stream-streams__select select)": "updateModel",
"click .disabled-button": "disableAction",
// "click .stream-streams__item": "showPreview",
"click .stream-feeds__item": "detachFeed",
"click .stream-feeds__block": "displayFeedsSelect",
"click .stream-feeds__btn": "connectFeed",
"change [id^=stream-layout]": "changeDesignMode",
"change .input-not-obvious input": "saveName",
"keyup .input-not-obvious input": "saveName",
"change .design-step-2 select[id*=align]" : "previewChangeAlign",
"change .design-step-2 select[id*=icons-style]" : "previewChangeIconsLook",
"change .design-step-2 select[id*=upic-pos]" : "previewChangeUpic",
"change .design-step-2 select[id*=upic-style]" : "previewChangeCorners",
"change .design-step-2 select[id*=icon-style]" : "previewChangeIcon",
"keyup .design-step-2 input[id*=bradius]" : "previewChangeBradius",
"keyup .design-step-2 [id*=width]" : "previewChangeWidth",
"change .layout-compact select[id*=compact-style]" : "previewChangeCompact",
//"change .style-choice select[id*=gc-style]" : "previewChangeStyle",
"change .theme-choice input" : "previewChangeTheme"
//this.listenTo(this.model, "change", this.render);
var rendered = this.rendered;
this.model.listenTo(this, 'changeModel', function (data){
// console.log('changeModel event', data);
self.model.set(data.name, data.val);
if (this.model.isNew()) {
this.rowModel = streamRowModels.get(this.model.get('id'));
console.log('binding models..')
this.$preview = this.$el.find('.preview .ff-stream-wrapper');
self.on('preview-update', function () {
var $item = self.$preview.find('.ff-item')
if ($item.find('.ff-item-cont').children().first().is('.ff-item-meta')) {
$item.addClass('ff-meta-first')
$item.removeClass('ff-meta-first')
bindModels: function () {
this.model.listenTo(feedsModel, 'change', function(changedModel){
var streamFeeds = this.get('feeds');
var allFeeds = feedsModel.get('feeds');
var changedFeeds = changedModel.get('feeds_changed');
var triggerRender = false, indexToDelete = -1;
_.each(streamFeeds, function (feed, index) {
var changed = changedFeeds[feed.id];
if (changed['state'] === 'changed') {
streamFeeds[index] = allFeeds[feed.id];
} else if (changed['state'] === 'deleted') {
if (indexToDelete > -1) streamFeeds.splice(indexToDelete, 1);
this.view.renderConnectedFeeds();
console.log('stream listening to feedsModel');
this.rowModel.listenTo(this.model, 'stream-saved', function (model) {
var attrs = self.model.attributes;
for (var prop in attrs) {
if (self.rowModel['attributes'][prop] !== undefined) {
if (typeof attrs[prop] === 'object') {
self.rowModel.set(prop, _.clone(attrs[prop]));
self.rowModel.set(prop, attrs[prop]);
var id = this.model.get('id');
console.log('render stream view');
if ( !this.rendered || !this.currentId ) {
this.$el.attr('data-view-mode', 'streams-update').attr('id', 'stream-view-' + (id || 'new'));
this.$el.html(this.template({
plugin_url: window.plugin_url,
header: id && id != 'new' ? 'Stream #' + id : 'Creating...',
version: window.plugin_ver,
TV: templates.tv ? _.template(templates.tv)({id:id}) : '',
TVtab: templates.tvTab || ''
self.$el.find(".input-not-obvious input").autoresize({padding:1,minWidth:56,maxWidth:400});
FlowFlow.tabsCursor.initFor(this.$el, id);
self.$preview = self.$el.find('.preview .ff-stream-wrapper');
self.applySavedTemplate();
self.trigger('preview-update');
this.renderConnectedFeeds();
$(document).trigger('stream_view_built', this.$el);
var val = e.target.value;
if (/*e.type === 'change' ||*/ e.type === 'keyup' && e.keyCode == 13) {
configDesign: function () {
console.log('config design and cpickers');
this.$el.find('input[type="range"]').on('mouseup', function() {
}).on('change input', function () {
var name = this.name.indexOf('-r-') + 1 ? 'row' : 'column';
var $v = $t.data('el') ? $t.data('el') : $t.next('.range-value');
$v = $t.parent().find('.range-value');
$v.html(this.value + ' ' + name + (this.value > 1 ? 's' : ''));
}).change()/*.rangeslider()*/;
this.$el.find('input[data-color-format]').ColorPickerSliders( this.colorPickersConfig );
this.$el.find('[id^=stream-layout]:checked, select[id*=upic-pos], select[id*=upic-style], select[id*=icon-style], select[id*=icons-style], .design-step-2 select[id*=align]').change();
this.$el.find('.design-step-2 input[id*=bradius]').keyup();
this.$el.find('.ff-item-cont').sortableCustom({
handle: '.ff-item__draggable',
draggable: '.ff-item__draggable',
var $preview = self.$el.find('.ff-item-cont');
$preview.children().each(function () {
var role = $(this).data('template');
if (role) template.push(role);
self.model.set('template', template);
$preview.find('.ff-label-wrapper').insertAfter($preview.find('.ff-item-meta'));
self.trigger('preview-update');
applySavedTemplate: function () {
var template = this.model.get('template');
var $cont = this.$el.find('.ff-item-cont');
'header': $cont.find('[data-template="header"]').detach(),
'text': $cont.find('[data-template="text"]').detach(),
'image': $cont.find('[data-template="image"]').detach(),
'meta': $cont.find('[data-template="meta"]').detach(),
for ( i = 0, len = template.length; i < len; i++ ) {
$cont.append( detached[template[i]] );
$cont.find('.ff-label-wrapper').insertAfter( detached.meta );
$cont.find('> .ff-item-bar').appendTo($cont);
setupCloudToggle: function () {
var cloud = this.model.get( 'cloud' );
var id = this.model.get( 'id' );
this.$el.find( '.section[data-tab="source"]' ).append( '<label for="stream-' + id + '-boosted" class="switcher cloud-switcher"><input id="stream-' + id + '-boosted" class="switcher cloud-switcher" type="checkbox" name="stream-' + id + '-cloud" value="yep"> <div><div></div></div></label><div class="ff-feeds-counter"><span class="ff-feeds-counter__loaded">.</span>/<span class="ff-feeds-counter__total">.</span> feeds<br><span class="dots-loading">boosted</span></div>' );
// this.$el.find( '#stream-' + id + '-boosted' ).prop( 'checked', cloud == 'yep' );
this.$el.find( '[for=stream-' + id + '-boosted]' ).on( 'click', function ( e ) {
var $inp = $t.find( 'input' );
var currentStreamFeeds = self.model.get('feeds');
if ( ! $inp.is( ":checked" ) ) { // intent to boost all feeds
if ( FlowFlow.availableBoosts !== null ) {
if ( FlowFlow.availableBoosts == 'not_active' ) {
FlowFlow.popup( 'No available boosts to access cloud service, please go to Extra tab for more info', 'neutral', false, { right: 'Learn more', left: 'cancel'} )
.then( function yes (value) {
$( '#addons-tab' ).click();
}, function cancel (reason) {
else if ( FlowFlow.availableBoosts < currentStreamFeeds.length ) {
FlowFlow.popup( 'Not enough available boosts, please free up boosts from other feeds or upgrade plan on Extra tab', 'neutral', false, { right: 'Go to extra', left: 'cancel'} )
.then( function yes (value) {
$( '#addons-tab' ).click();
}, function cancel (reason) {
// then check if feeds in other streams
var streams = streamRowModels.models;
for ( var i = 0, len = streams.length; i < len; i++ ) {
if ( streams[ i ].cloud == 'yep' ) continue;
if ( streams[ i ].id == id ) continue; // current stream
streamFeeds = streams[ i ].get( 'feeds' );
found = _.find( streamFeeds, function ( streamFeed ) {
return _.find( currentStreamFeeds, function ( currentStreamFeed ) {
return currentStreamFeed.id == streamFeed.id;
if ( found ) { // this feed in other stream
var alert = FlowFlow.popup( 'One of this stream feeds ' + (found.content ? '("' + found.content + '")' : '' ) + ' is also connected to other stream (Stream #' + streams[ i ].get( 'id' ) + ( streams[ i ].get( 'name' ) ? ' "' + streams[ i ].get( 'name' ) + '"' : '' ) + '). Feed can\'t be in cloud and self-hosted stream simultaneously. Please disconnect feed from other stream first.', 'neutral', false, { right: 'Learn more', left: 'close'} )
.then( function yes (value) {
$( '#addons-tab' ).click();
}, function cancel (reason) {
cancelCloudChange = true;
var wpFeed = _.find( currentStreamFeeds, function ( feed ) {
return feed.type == 'wordpress';
var alert = FlowFlow.popup( 'One of this stream feeds has Wordpress source. WordPress feeds can\'t be in cloud currently. Please disconnect feed from stream first.', 'neutral', false, { right: 'Learn more', left: 'close'} )
.then( function yes (value) {
$( '#addons-tab' ).click();
}, function cancel (reason) {
cancelCloudChange = true;
if ( ! cancelCloudChange ) {
var alert = FlowFlow.popup( 'You are about to enable cloud service for this stream, aka boosting all connected feeds. This process will be run in background, please wait for confirmation notification. Time to wait depends on number of connected feeds. Please don\'t reload browser', 'neutral', false, { right: 'ENABLE CLOUD', left: 'cancel'} )
.then( function yes (value) {
// todo https://www.webniraj.com/2018/10/08/making-ajax-calls-sequentially-using-jquery/
var $loaded = self.$el.find( '.ff-feeds-counter__loaded' );
self.$el.addClass( 'toggling-cloud' );
self.$el.find( '.ff-feeds-counter__total' ).html( currentStreamFeeds.length );
$loaded.html( 0 ).data( 'current', 0);