/*
* Copyright 2022 Anyware Services
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* This tool is a tool for viewing the raw data of a content
* @private
*/
Ext.define('Ametys.plugins.cms.content.tool.ContentDataTool', {
extend: "Ametys.tool.Tool",
/**
* @property {String} _contentId The unique identifier of the content.
* @private
*/
/**
* @cfg {String} [view-name="default-edition"] The name of view of content.
*/
/**
* @property {String} _viewName The content view name. See #cfg-view-name.
*/
/**
* @cfg {String} [fallback-view-name="main"] The name of the fallback view of content.
*/
/**
* @property {String} _fallbackViewName The content fallback view name. See #cfg-fallback-view-name.
*/
/**
* @cfg {Boolean} [show-disable-values=true] true to show disable values (default)
*/
/**
* @property {Boolean} _showDisableValues true to show disable values (default). See #cfg-show-disable-values.
*/
/**
* @property {Ext.data.Store} _viewStore The store which contains the views available for the content.
* @private
*/
/**
* @property {Ext.form.field.ComboBox} _combo The combobox which enables the choosing of a view.
* @private
*/
/**
* @cfg {String} [content-message-type="content"] The message type to send. Defaults to 'content'.
* A MessageTargetFactory handling the message type and accepting an 'ids' array parameter must exist.
*/
/**
* @property {String} _contentMessageType The message type to send. See #cfg-content-message-type
*/
/**
* @private
* @property {Ext.ux.IFrame} _iframe The iframe object
*/
/**
* @private
* @property {Ext.Template} _tooltipDescriptionTpl The template used for tooltip description
*/
/**
* @private
* @property {String} _baseUrl The url that the iframe should stay on
*/
/**
* @cfg {String} showEmptyDataButtonIconCls The separated CSS classes to apply to button to show empty data fields
*/
showEmptyDataButtonIconCls: 'ametysicon-code-css-border-radius decorator-ametysicon-body-part-eye ',
/**
* @cfg {String} showEmptyDataButtonTooltip Tooltip of the button to show empty data fields
*/
showEmptyDataButtonTooltip: "{{i18n PLUGINS_CMS_TOOL_CONTENT_DATA_SHOW_EMPTY_DATA}}",
/**
* @cfg {String} hideEmptyDataButtonIconCls The separated CSS classes to apply to button to hide empty data fields
*/
hideEmptyDataButtonIconCls: 'ametysicon-code-css-border-radius decorator-ametysicon-body-part-eye-no',
/**
* @cfg {String} showEmptyDataButtonTooltip Tooltip of the button to hide empty data fields
*/
hideEmptyDataButtonTooltip: "{{i18n PLUGINS_CMS_TOOL_CONTENT_DATA_HIDE_EMPTY_DATA}}",
/**
* @cfg {String} showTechnicalNamesButtonIconCls The separated CSS classes to apply to button to show the technical names
*/
showTechnicalNamesButtonIconCls: 'ametysicon-code-html-tag decorator-ametysicon-body-part-eye',
/**
* @cfg {String} showEmptyDataButtonTooltip Tooltip of the button to show the technical names
*/
showTechnicalNamesButtonTooltip: "{{i18n PLUGINS_CMS_TOOL_CONTENT_DATA_SHOW_TECHNICAL_NAMES}}",
/**
* @cfg {String} hideTechnicalNamesButtonIconCls The separated CSS classes to apply to button to hide the technical names
*/
hideTechnicalNamesButtonIconCls: 'ametysicon-code-html-tag decorator-ametysicon-body-part-eye-no',
/**
* @cfg {String} hideTechnicalNamesButtonTooltip Tooltip of the button to hide the technical names
*/
hideTechnicalNamesButtonTooltip: "{{i18n PLUGINS_CMS_TOOL_CONTENT_DATA_HIDE_TECHNICAL_NAMES}}",
/**
* @private
* @property {Boolean} _showEmptyData true to show empty data fields
*/
_showEmptyData: false,
/**
* @private
* @property {Boolean} _showTechnicalNames true to show technical names
*/
_showTechnicalNames: false,
constructor: function (config)
{
this.callParent(arguments);
this._tooltipDescriptionTpl = new Ext.XTemplate(
'<tpl if="version">',
"<u>{{i18n PLUGINS_CMS_TOOL_CONTENT_TOOLTIP_VERSION}}</u> : ",
"<b>{version}</b><br/>",
'</tpl>',
"<u>{{i18n PLUGINS_CMS_TOOL_CONTENT_TOOLTIP_AUTHOR}}</u> : ",
"<b>{author}</b><br/>",
"<u>{{i18n PLUGINS_CMS_TOOL_CONTENT_TOOLTIP_LASTMODIFICATION}}</u> : ",
"<b>{lastModified}</b>"
);
this._hintTpl = new Ext.XTemplate(
'{{i18n PLUGINS_CMS_TOOL_CONTENT_DATA_HINT}}',
"<b>{[Ext.String.escapeHtml(values.title)]}</b>",
'<tpl if="version">',
"<b> ({{i18n PLUGINS_CMS_TOOL_CONTENT_DATA_HINT_VERSION}} {version})</b>",
'</tpl>'
);
this._contentMessageType = config['content-message-type'] || Ametys.message.MessageTarget.CONTENT;
Ametys.message.MessageBus.on(Ametys.message.Message.MODIFIED, this._onModified, this);
Ametys.message.MessageBus.on(Ametys.message.Message.DELETED, this._onDeleted, this);
Ametys.message.MessageBus.on(Ametys.message.Message.ARCHIVED, this._onDeleted, this);
Ametys.message.MessageBus.on(Ametys.message.Message.UNARCHIVED, this._onDeleted, this);
},
createPanel: function ()
{
this._iframe = Ext.create("Ext.ux.IFrame", {});
this._iframe.on ('load', this._onIframeLoad, this);
return Ext.create('Ext.Panel', {
cls: 'contentdatatool',
border: false,
layout: 'border',
scrollable: false,
dockedItems: this._getTopPanel(),
items: this._iframe
});
},
getType: function()
{
return Ametys.tool.Tool.TYPE_CONTENT_DATA;
},
getMBSelectionInteraction: function()
{
return Ametys.tool.Tool.MB_TYPE_ACTIVE;
},
/**
* @protected
* Get the top panel for hint and actions
* @return {Ext.Panel} the top panel
*/
_getTopPanel: function ()
{
return Ext.create({
dock: 'top',
xtype: 'container',
layout: {
type: 'hbox',
align: 'middle'
},
cls: 'top',
items: [
{
// hint
xtype: 'component',
itemId: 'hint',
cls: 'hint',
html: '',
flex: 1
},
this._createViewsComboBox(),
{
// show/hide empty data
xtype: 'button',
iconCls: this.showEmptyDataButtonIconCls,
tooltip: this.showEmptyDataButtonTooltip,
scope: this,
enableToggle: true,
toggleHandler: this.showEmptyData,
cls: 'a-btn-light'
},
{
// show/hide technical names
xtype: 'button',
iconCls: this.showTechnicalNamesButtonIconCls,
tooltip: this.showTechnicalNamesButtonTooltip,
scope: this,
enableToggle: true,
toggleHandler: this.showTechnicalNames,
cls: 'a-btn-light'
}
]
});
},
/**
* Create a combo box for the views
* @private
*/
_createViewsComboBox: function()
{
this._viewStore = Ext.create('Ext.data.Store', {
proxy: {
type: 'ametys',
role: 'org.ametys.cms.repository.ContentDAO',
methodName: 'getContentViewsAndAllData',
methodArguments: ['contentId','includeInternal'],
reader: {
type: 'json'
}
},
autoLoad: true,
sortOnLoad: true,
sorters: [{property: 'label', direction:'ASC'}],
listeners: {
load: {fn: this._onLoadViews, scope: this},
beforeload: {fn: this._onBeforeLoadViews, scope: this}
}
});
this._combo = Ext.create('Ext.form.field.ComboBox', {
cls: 'ametys',
forceSelection: true,
editable: false,
queryMode: 'local',
allowBlank: false,
store: this._viewStore,
hidden: false,
valueField: 'name',
displayField: 'label',
fieldLabel:"{{i18n CONTENT_OPEN_DATA_SELECT_VIEW_LABEL}}",
labelWidth: 267,
labelAlign: "right",
listeners: {
change: {fn: this._onChangeView, scope: this}
}
});
return this._combo;
},
/**
* Listener called before load form display view combo box
* @param {Object} store The store
* @param {Object[]} operation The operation
* @private
*/
_onBeforeLoadViews: function(store, operation)
{
operation.setParams( Ext.apply(operation.getParams() || {}, {
contentId: this._contentId,
includeInternal: true
}));
},
/**
* Listener called after load form display view combo box
* @param {Object} store The store
* @param {Object[]} data The data
* @private
*/
_onLoadViews: function(store, data)
{
if (store.find('name', this._viewName) != -1)
{
this._combo.setValue(this._viewName);
}
else
{
this._combo.setValue(this._fallbackViewName);
}
},
/**
* Listener called on change of the view combo box
* @param {Object} combo The combo box
* @param {String} newValue The new value
* @param {String} oldValue The old value
* @private
*/
_onChangeView: function(combo, newValue, oldValue)
{
this._viewName = newValue;
this.showOutOfDate();
},
setParams: function (params)
{
this.callParent(arguments);
this._contentId = params['contentId'] || params['id'];
this._viewName = params['view-name'] || 'default-edition';
this._fallbackViewName = params['fallback-view-name'] || 'main';
this._showDisableValues = params['show-disable-values'] || true;
this._contentVersion = params['contentVersion'];
this._versionName = params['versionName'];
// Register the tool on the history tool
var toolId = this.getFactory().getId();
var toolParams = this.getParams();
Ametys.navhistory.HistoryDAO.addEntry({
id: this.getId(),
label: this.getTitle(),
description: this.getDescription(),
iconGlyph: this.getGlyphIcon(),
iconSmall: this.getSmallIcon(),
iconMedium: this.getMediumIcon(),
iconLarge: this.getLargeIcon(),
type: Ametys.navhistory.HistoryDAO.TOOL_TYPE,
action: Ext.bind(Ametys.tool.ToolsManager.openTool, Ametys.tool.ToolsManager, [toolId, toolParams], false)
});
this.showOutOfDate();
this._updateInfos();
},
refresh: function ()
{
this.showRefreshing();
var wrappedUrl = this.getWrappedContentUrl ();
this._iframe.load(Ametys.CONTEXT_PATH + wrappedUrl)
this._baseUrl = Ametys.CONTEXT_PATH + wrappedUrl;
this.showRefreshed();
this._updateInfos();
},
/**
* Get the wrapped url for content old revision
* @returns {String} The wrapped url
*/
getWrappedContentUrl: function ()
{
var appParameters = Ametys.getAppParameters();
var additionParams = '';
additionParams += '&viewName=' + this._viewName;
additionParams += '&fallbackViewName=' + this._fallbackViewName;
additionParams += '&showDisableValues=' + this._showDisableValues;
additionParams += '&lang=' + Ametys.cms.language.LanguageDAO.getCurrentLanguage(); // default rendering language for multilingual content
additionParams += '&showMissing=' + this._showEmptyData;
additionParams += '&showTechnicalNames=' + this._showTechnicalNames;
Ext.Object.each(appParameters, function(key, value) {
additionParams += '&' + key + '=' + encodeURIComponent(value);
});
return '/_wrapped-content-data' + (this._contentVersion ? '/v_' + this._contentVersion : '') + '.html?contentId=' + this.getContentId() + additionParams;
},
/**
* Get the unique identifier of the content.
* @returns {String} The identifier of the content
*/
getContentId: function()
{
return this._contentId;
},
/**
* Show/hide empty data
* @param {Ext.button.Button} button The button which triggered this method. Parameter not used.
* @param {Boolean} state If true, show empty data
* @private
*/
showEmptyData: function(btn, state)
{
this._showEmptyData = state;
if (this._showEmptyData)
{
btn.setIconCls(this.hideEmptyDataButtonIconCls);
btn.setTooltip(this.hideEmptyDataButtonTooltip)
}
else
{
btn.setIconCls(this.showEmptyDataButtonIconCls);
btn.setTooltip(this.showEmptyDataButtonTooltip);
}
this.refresh();
},
/**
* Show/hide technical names
* @param {Ext.button.Button} button The button which triggered this method. Parameter not used.
* @param {Boolean} state If true, show technical names
* @private
*/
showTechnicalNames: function(btn, state)
{
this._showTechnicalNames = state;
if (this._showTechnicalNames)
{
btn.setIconCls(this.hideTechnicalNamesButtonIconCls);
btn.setTooltip(this.hideTechnicalNamesButtonTooltip)
}
else
{
btn.setIconCls(this.showTechnicalNamesButtonIconCls);
btn.setTooltip(this.showTechnicalNamesButtonTooltip);
}
this.refresh();
},
/**
* @private
* Listener called when the iframe is loaded.
* Protects the iframe by handling links of the internal frame by setting a onclick on every one
* @param {Ext.ux.IFrame} iframe The iframe
*/
_onIframeLoad: function (iframe)
{
if (window.event && window.event.srcElement.readyState && !/loaded|complete/.test(window.event.srcElement.readyState))
{
return;
}
Ametys.plugins.cms.content.tool.ContentTool.__URL_REGEXP.test(window.location.href);
var win = this._iframe.getWin();
var outside = false;
try
{
win.location.href
}
catch (e)
{
outside = true;
}
if (outside || (win.location.href != 'about:blank' && win.location.href.indexOf(RegExp.$1 + this._baseUrl) != 0))
{
var outsideUrl = win.location.href;
// Back to iframe base url
win.location.href = this._baseUrl;
// Open ouside url in a new window
window.open(outsideUrl);
}
else
{
var links = win.document.getElementsByTagName("a");
for (var a = 0; a < links.length; a++)
{
if (links[a].getAttribute("internal") == null && !links[a].onclick)
{
links[a].onclick = Ext.bind(this._handleLink, this, [links[a]], false);
}
}
}
},
/**
* @private
* Lazy handling of links. Each times a link is clicked, check if we should open it in a window, open another tool...
* @param {HTMLElement} link The clicked link
*/
_handleLink: function(link)
{
var currentURI = this._iframe.getWin().location.href;
var absHref = link.href;
var relHref = null;
if (/^(https?:\/\/[^\/]+)(\/.*)?/.test(absHref) && absHref.indexOf(RegExp.$1 + Ametys.CONTEXT_PATH) == 0)
{
relHref = absHref.substring(absHref.indexOf(RegExp.$1 + Ametys.CONTEXT_PATH) + (RegExp.$1 + Ametys.CONTEXT_PATH).length);
}
// Internal link
if (absHref.indexOf(currentURI) == 0) return true;
// JS Link
if (absHref.indexOf("javascript:") == 0) return true;
// Download link
if (relHref != null && relHref.indexOf("/plugins/explorer/download/") == 0) return true;
// Unknown link : open in a new window
window.open(absHref);
return false;
},
/**
* Update tool information
* @private
*/
_updateInfos: function()
{
Ametys.data.ServerComm.callMethod({
role: "org.ametys.cms.repository.ContentDAO",
methodName: 'getContentDescription',
parameters: [ this.getContentId(), null],
waitMessage: false,
cancelCode: "ContentDataTools$updateInfo$" + this.getContentId(),
callback: {
handler: this._updateInfosCb,
scope: this,
ignoreOnError: false
}
});
},
/**
* Callback function called after #_updateInfos is processed
* @param {Object} data The server response
* @param {String} data.title The title
* @param {Object} data.lastContributor The last contributor object
* @param {String} data.lastContributor.fullname The fullname of the last contributor
* @param {String} data.lastModified The last modified date at 'd/m/Y, H:i' format
* @param {String} data.iconGlyph A css class to set a glyph
* @param {String} data.iconDecorator A css class to set a glyph decorator
* @param {String} data.smallIcon The path to the small (16x16) icon. iconGlyph win if specified
* @param {String} data.mediumIcon The path to the medium (32x32) icon. iconGlyph win if specified
* @param {String} data.largeIcon The path to the large (48x48) icon. iconGlyph win if specified
* @param {Object} args The callback parameters passed to the {@link Ametys.data.ServerComm#send} method
* @private
*/
_updateInfosCb: function (data, args)
{
this.setTitle(data.title + (this._versionName ? ' ({{i18n PLUGINS_CMS_TOOL_CONTENT_TOOLTIP_VERSION}} ' + this._versionName + ')' : ''));
var values = {title: data.title, version: this._versionName, author: data.lastContributor.fullname, lastModified: Ext.util.Format.date(data.lastModified, 'd/m/Y, H:i')};
var description = this._tooltipDescriptionTpl.applyTemplate (values);
this.setDescription(description);
// Set title, desc, and icons.
if (data.iconGlyph)
{
this.setGlyphIcon(data.iconGlyph);
this.setIconDecorator(data.iconDecorator);
}
else
{
this.setGlyphIcon(null);
this.setSmallIcon(data.smallIcon);
this.setMediumIcon(data.mediumIcon);
this.setLargeIcon(data.largeIcon);
}
var hint = this._hintTpl.applyTemplate (values);
this.getContentPanel().down("#hint").update(hint);
},
sendCurrentSelection: function()
{
Ametys.cms.content.ContentDAO.getContent(this.getContentId(), Ext.bind(this._createContentTarget, this));
},
/**
* Creates and fires a event of selection on the message bus
* @param {Ametys.cms.content.Content} content The concerned content
* @private
*/
_createContentTarget: function (content)
{
var targetParams = { contents: [content], 'rawData' : true};
if (this._contentVersion)
{
targetParams['version'] = this._contentVersion
}
Ext.create("Ametys.message.Message", {
type: Ametys.message.Message.SELECTION_CHANGED,
targets: {
id: this._contentMessageType,
parameters: targetParams,
subtargets: []
}
});
},
/**
* Listener on {@link Ametys.message.Message#MODIFIED} message. If the current content is concerned, the tool will be out-of-date.
* @param {Ametys.message.Message} message The edited message.
* @protected
*/
_onModfied: function (message)
{
var me = this;
var contentTargets = message.getTargets(function (target) {return me._contentMessageType == target.getId();});
for (var i=0; i < contentTargets.length; i++)
{
if (this._contentId == contentTargets[i].getParameters().id)
{
this.showOutOfDate();
}
}
},
/**
* Listener on {@link Ametys.message.Message#DELETED} message. If the current content is concerned, the tool will be closed.
* @param {Ametys.message.Message} message The deleted message.
* @protected
*/
_onDeleted: function (message)
{
var me = this;
var contentTargets = message.getTargets(function (target) {return me._contentMessageType == target.getId();});
for (var i=0; i < contentTargets.length; i++)
{
if (this._contentId == contentTargets[i].getParameters().id)
{
this.close();
}
}
},
});