: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
constructor(el, rows,customCss) {
fr = createDocumentFragment();
cl.remove('not_editable_builder');
cl.add('tb_active_builder','tf_rel');
_this.id=el.dataset.postid;
el.id='themify_builder_content-' + _this.id;
_this.customCss=customCss || '';
_this.constructor.items.push(_this);
for (let i = 0; i < rows.length; ++i) {
fr.appendChild((new api.Row(rows[i])).el);
let css_id='tb_custom_css_'+_this.id,
builderCss=doc.tfId(css_id);
builderCss = createElement('style',{id:css_id});
doc.head.appendChild(builderCss);
builderCss.innerHTML = _this.customCss;
_this.el.appendChild(fr);
api.Registry.on(_this, 'tb_init', _this.init);
api.Utils.onResize(true);
api.Utils.setColumnsCount(this.el.tfClass('module_column'));
this.insertLayoutButton();
api.ModulePageBreak.countModules();
if(this.emptyModules.size>0){
TF_Notification.showHide('warning',i18n.empty_modules.replace('%s',Array.from(this.emptyModules).join(', ')),10000);
this.emptyModules.clear();
index??= this.items.length-1;
return this.items[index];
static backendModeHolder(id){
return createElement('',{
class:'themify_builder themify_builder_content-'+id,
items = el.querySelectorAll('[data-cid]'),
builderItems=_this.constructor.items,
builderCss=doc.tfId('tb_custom_css_'+_this.id);
_this.emptyModules.clear();
for (let i = items.length-1; i>-1; --i) {
api.Registry.get(items[i].dataset.cid)?.destroy(true);
for(let i=builderItems.length-1;i>-1;--i){
if(builderItems[i]===_this){
builderItems.splice(i,1);
el.removeAttribute('id');
cl.remove('tb_active_builder','tf_rel');
cl.add('not_editable_builder');
return cols.length>1 || cols[0].modules?.length>0 || (cols[0].styling && Object.keys(cols[0].styling).length>0);
for (let i = 0; i < rows.length; ++i) {
if (rows[i].classList.contains('module_row')) {
let data = api.Utils.getRowSettings(rows[i], 'row', saving);
if (!saving || (data.styling && Object.keys(data.styling).length>0) || (data.cols && checkNotEmpty(data.cols))) {
for (let importBtn = this.el.tfClass('tb_import_layout_button'),i = importBtn.length - 1; i > -1; --i) {
if (api.isGSPage !== true && this.#lastRow) {
this.removeLayoutButton();
const row = this.el.tfClass('module_row');
if (row.length < 2 && !row[0]?.tfClass('active_module')[0]) {
const importBtn = createElement('a','tb_import_layout_button',i18n.text_import_layout_button);
importBtn.tfOn(_CLICK_, e => {
Themify.triggerEvent(api.ToolBar.el.tfClass('load_layout')[0],e.type);
},{passive:true}).href='javascript:;';
this.#lastRow.getRootNode().host.before(importBtn);
newRowAvailable(ignore) {
if (api.isGSPage !== true) {
const child = this.el.children,
len = ignore===true?0:child.length;
for (let i = len - 1; i > -1; --i) {
if (child[i].tfClass('active_module')[0]===undefined && child[i].classList.contains('module_row')) {
row=api.Registry.get(child[i].dataset.cid);
row = new api.Row(api.Utils.grid(1)[0]);
api.Utils.setColumnsCount(row.el.tfClass('module_column'));
this.#lastRow.getRootNode().host.before(row.el);
this.el.appendChild(row.el);
if(api.isGSPage !== true && (!this.#lastRow || !this.el.contains(this.#lastRow.getRootNode().host))){
const root = createElement('',{class:'tb_disable_sorting tf_w tf_hidden',id:'tb_last_row_add_btn'}),
tpl=doc.tfId('tmpl-last_row_add_btn');
}).appendChild(tpl.content.cloneNode(true));
this.el.appendChild(root);
this.#lastRow = root.shadowRoot.tfId('container');
Themify.on('tb_toolbar_loaded', ()=>{
const fragment=createDocumentFragment();
fragment.append(api.ToolBar.getBaseCss(),api.MainPanel.el.getRootNode().querySelector('#module_drag_grids_style').cloneNode(true));
root.shadowRoot.prepend(fragment);
root.classList.remove('tf_hidden');
this.#lastRow.appendChild(doc.tfId('tmpl-last_row_expand').content.cloneNode(true));
this.#lastRow.tfOn(_CLICK_, function(e) {
grid = target.closest('.tb_grid'),
api.MainPanel.newGrid(grid.dataset.slug,false);
else if (target.closest('.block')) {
const host=this.getRootNode().host;
host.classList.remove('clicked');
api.SmallPanel.show(host);
else if (target.classList.contains('add_btn')) {
},true,api.ToolBar?.isLoaded===true);
async reLoad(json,merge,reset=true) {
await api.LightBox.save();
let data = json.builder_data || json,
customCss=json.custom_css || '',
isMainBuilder=!api.isVisual || builder.parentNode.closest('.themify_builder')===null;//is main builder
data=api.Helper.correctBuilderData(data);
data= this.toJSON().concat(data);
customCss=this.customCss+customCss;
else if(isMainBuilder===true && reset===true){
if (json.used_gs !== undefined) {
//gs data in old versions save as nested array
for(let i in json.used_gs){
st=api.Helper.cloneObject(gs.data),
uniqId=api.Helper.generateUniqueID();
if(type!=='row' && type!=='subrow'){
gs.data[0].cols[0].styling=st;
gs.data[0].cols[0].modules=[{
api.GS.styles = {...api.GS.styles,...json.used_gs};
const newBuilder = new api.Builder(builder,data,customCss);
if (isMainBuilder===false) {
for (let items = newBuilder.el.querySelectorAll('[data-cid]'),i = items.length-1; i>-1; --i) {
settings.push(items[i].dataset.cid);
api.liveStylingInstance.reset();
await api.bootstrap(settings, json.used_gs,false);
await api.setCss(newBuilder.toJSON());
await api.correctColumnPaddings();
api.Registry.trigger(newBuilder,'tb_init');
api.Utils.runJs(newBuilder.el, null, true);
api.Spinner.showLoader('done');
const saveBtnCl=api.ToolBar.el.tfClass('save_wrap')[0].classList;
if(saveBtnCl.contains('disabled')){
saveBtnCl.add('disabled');
await api.LightBox.save();
api.Spinner.showLoader();
const allImages=api.Utils.getAllImages();
try{//if there is an error don't break builder saving
await api.Utils.importThemifyImages(allImages.get('themify'));//upload images and change urls in the settings
data = this.toJSON(true),
customCss=this.customCss || '';
localImages=allImages.get('local');
localImages=localImages.size>0?JSON.stringify(Array.from(localImages)):'';
await api.GS.setImport(api.GS.styles, null, true);
let builder=JSON.stringify(data),
sourceEditor: api.isVisual ? 'frontend' : 'backend',
const prms=[api.Helper.gzip(builder)];
prms.push(api.Helper.gzip(localImages));
const gzip=await Promise.all(prms);
res=await api.LocalFetch(ajaxData);
ajaxData.images=localImages;
res=await api.LocalFetch(ajaxData);
/* new attempt: send the Builder data as binary file to server */
ajaxData.data=new Blob( [ ajaxData.data ], { type: 'application/json' });
res=await api.LocalFetch(ajaxData);
const savedCss=await api.Utils.saveCss(data, customCss, id);
res.css_file=savedCss.css_file;
api.Spinner.showLoader('done');
Themify.trigger('themify_builder_save_data', res);
await Promise.all([api.Spinner.showLoader('error'),TF_Notification.showHide('error',e.status===403?i18n.errorSave403:i18n.errorSaveBuilder,5000)]);
saveBtnCl.remove('disabled');