Commit f804b494 authored by Carsten Schoenert's avatar Carsten Schoenert

New upstream version 3.4.4+dfsg1

parent 2277f4b1
......@@ -90,5 +90,5 @@
// Custom Globals
"globals" : {}, // additional predefined global variables
"predef" : [ "Ext", "Zarafa", "container", "dgettext", "pgettext", "_", "npgettext", "ngettext" ,"settings", "serverconfig", "user", "version", "languages", "urlActionData", "resizeLoginBox"]
"predef" : [ "Ext", "Zarafa", "container", "dgettext", "pgettext", "_", "npgettext", "ngettext" ,"settings", "serverconfig", "user", "version", "languages", "urlActionData", "resizeLoginBox", "expect"]
}
FROM ubuntu:16.04
RUN apt-get update
RUN apt-get install -y chromium-browser phpmd ant ant-optional libxml2-utils \
openjdk-8-jdk php-xml php-zip php-common php-gettext \
wget apt-transport-https gnupg2
# Latest nodejs
RUN wget -qO- https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -
RUN echo "deb https://deb.nodesource.com/node_9.x xenial main" > /etc/apt/sources.list.d/node.list
RUN apt-get update && apt-get -y install nodejs
# Set timezone for JS unit tests
RUN ln -fs /usr/share/zoneinfo/Europe/Amsterdam /etc/localtime && \
dpkg-reconfigure -f noninteractive tzdata
# Add user
RUN useradd -m -u 109 jenkins
# Cleanup
RUN apt-get remove -y gnupg2 apt-transport-https wget
RUN apt-get clean -y && \
apt-get autoclean -y && \
apt-get autoremove -y && \
rm -rf /usr/share/locale/* && \
rm -rf /var/cache/debconf/*-old && \
rm -rf /var/lib/apt/lists/* && \
rm -rf /usr/share/doc/*
#!/usr/bin/env groovy
pipeline {
agent {
dockerfile true
}
stages {
stage('Install') {
steps {
sh 'npm install'
}
}
stage('Deploy') {
steps {
sh 'ant deploy deploy-plugins'
}
}
stage('Test') {
parallel {
stage('JS Lint') {
steps {
sh 'npm run jlint'
}
}
stage('Unittest') {
steps {
sh 'CHROME_BIN=chromium-browser npm run jsunit -- --reporters junit'
}
}
}
}
}
post {
always {
checkstyle canRunOnFailed: true, canComputeNew: false, pattern: 'jshint.xml'
junit 'test/js/result/**/unit.xml'
}
}
}
# Author: Guido Günther <agx@sigxcpu.org>
^kopano-webapp {
#include <abstractions/apache2-common>
#include <abstractions/base>
#include <abstractions/nameservice>
#include <abstractions/php5>
@{PROC}/@{pid}/task/@{tid}/comm rw,
@{PROC}/@{pid}/cmdline r,
/etc/gss/mech.d/ r,
/etc/gss/mech.d/*.conf r,
/etc/kopano/webapp/*.php r,
/usr/share/kopano-webapp/** r,
/var/lib/kopano-webapp/tmp/** rwk,
/var/log/apache2/ r,
# FIXME: we should use separate logfiles for kopano upfront
/var/log/apache2/error.log rw,
/var/log/apache2/other_vhosts_access.log rw,
# Useful when in debugging mode
/usr/share/kopano-webapp/debug.txt rw,
}
......@@ -191,28 +191,28 @@
* Fires specified event for {@link Ext.Element element}.
* @param {String} e The event name
*/
fireEvent : function(eventName)
fireEvent : function(eventName, originalEvent)
{
var HTMLEvts = /^(scroll|resize|load|unload|abort|error)$/,
mouseEvts = /^(click|dblclick|mousedown|mouseup|mouseover|mouseout|contextmenu)$/,
UIEvts = /^(focus|blur|select|change|reset|keypress|keydown|keyup)$/,
WheelEvts = /^(wheel)$/,
onPref = /^on/;
eventName = eventName.toLowerCase();
eventName.replace(onPref, '');
var evt;
if (mouseEvts.test(eventName)) {
var b = this.getBox(),
x = b.x + b.width / 2,
y = b.y + b.height / 2;
evt = document.createEvent("MouseEvents");
evt.initMouseEvent(eventName, true, true, window, (eventName=='dblclick')?2:1, x, y, x, y, false, false, false, false, 0, null);
evt.initMouseEvent(eventName, true, true, window, (eventName=='dblclick')?2:1, originalEvent.screenX, originalEvent.screenY, originalEvent.clientX, originalEvent.clientY, originalEvent.ctrlKey, originalEvent.altKey, originalEvent.shiftKey, originalEvent.metaKey, 0, null);
} else if (UIEvts.test(eventName)) {
evt = document.createEvent("UIEvents");
evt.initUIEvent(eventName, true, true, window, 0);
} else if (HTMLEvts.test(eventName)) {
evt = document.createEvent("HTMLEvents");
evt.initEvent(eventName, true, true);
} else if (WheelEvts.test(eventName)) {
evt = new WheelEvent('wheel', originalEvent);
}
if (evt) {
this.dom.dispatchEvent(evt);
......
This diff is collapsed.
......@@ -96,6 +96,7 @@
}
.k-subject-panel,
.k-createin-panel,
.k-location-panel {
padding: $padding-medium;
border-bottom: 1px solid $grey;
......@@ -209,6 +210,22 @@
}
}
.k-createin-combo {
text-indent: 20px;
background-position-x: 6px;
background-position-y: 4px;
background-repeat: no-repeat;
}
.k-createin-combo-list-svg {
.x-combo-list-inner .x-combo-list-item svg {
position:relative;
top:2px;
right:5px;
}
}
.k-taskdetailtab {
> .x-panel-bwrap {
padding: $padding-medium;
......
......@@ -136,6 +136,7 @@ Zarafa.calendar.dialogs.AppointmentTab = Ext.extend(Ext.form.FormPanel, {
this.createSubjectPanel(),
this.createLocationPanel(),
this.createDateTimePanel(),
this.createinPanel(),
this.createAttachmentsPanel(),
this.createBodyPanel()
]
......@@ -311,6 +312,58 @@ Zarafa.calendar.dialogs.AppointmentTab = Ext.extend(Ext.form.FormPanel, {
};
},
/**
* Create the {@link Ext.Panel panel} containing the form element
* to set the calendar in which the appointment or meeting-request will be
* created.
* @return {Object} Configuration object for the panel containing the fields
* @private
*/
createinPanel : function()
{
var createInStore = this.getCreateInStore();
var tplString = '<tpl for=".">' +
'<div class="x-combo-list-item">' +
'{[Zarafa.calendar.ui.IconCache.getCalendarSvgStructure(values.iconColor)]}' +
'{displayString}' +
'</div>' +
'</tpl>';
return {
xtype: 'panel',
cls: 'k-createin-panel',
hidden: (createInStore.data.length < 2),
layout: 'form',
labelWidth: 85,
labelAlign: 'left',
autoHeight: true,
border: false,
items: [{
xtype: 'combo',
tpl : tplString,
fieldLabel: _('Create in'),
ref: '../comboCreateIn',
store: createInStore,
cls : 'k-createin-combo',
listClass : 'k-createin-combo-list-svg',
mode: 'local',
triggerAction: 'all',
displayField: 'displayString',
valueField: 'entryid',
lazyInit: false,
forceSelection: true,
editable: false,
listeners: {
select: this.onCreateInSelect,
collapse: this.setCursorPosition,
expand: this.setCursorPosition,
scope: this
}
}]
};
},
/**
* Create the {@link Ext.Panel Panel} containing the
* {@link Zarafa.common.ui.DateTimePeriodField DateTimePeriodField}.
......@@ -730,6 +783,13 @@ Zarafa.calendar.dialogs.AppointmentTab = Ext.extend(Ext.form.FormPanel, {
this.editorField.setValue(record.getBody(this.editorField.isHtmlEditor()));
}
if (contentReset && this.comboCreateIn.isVisible()) {
this.comboCreateIn.setValue(record.get('parent_entryid'));
var folderColor = this.getFolderColor(record.get('parent_entryid'));
this.comboCreateIn.el.setStyle('background-image', 'url(\'' + Zarafa.calendar.ui.IconCache.getCalendarSvgIcon(folderColor) + '\')');
}
this.updateExtraInfoPanel();
if (contentReset === true || record.isModifiedSinceLastUpdate('recurring_pattern')) {
......@@ -957,6 +1017,90 @@ Zarafa.calendar.dialogs.AppointmentTab = Ext.extend(Ext.form.FormPanel, {
this.record.set(combo.getName(), record.get(combo.valueField));
},
/**
* Event handler which is fired when a combobox selection has changed.
* This will update the corresponding field inside the {@link Zarafa.core.data.IPMRecord record}
* @param {Ext.form.ComboBox} combo The combobox which was selected
* @param {Ext.data.Record} record The selected calender-folder record
* @param {Number} index The index of the selected record
* @private
*/
onCreateInSelect : function(combo, record, index)
{
if (this.record.phantom) {
this.record.set('parent_entryid', record.get('entryid'));
this.record.set('store_entryid', record.get('store_entryid'));
} else {
this.record.moveTo(record);
}
combo.el.setStyle('background-image', 'url(\'' + Zarafa.calendar.ui.IconCache.getCalendarSvgIcon(record.get('iconColor')) + '\')');
},
/**
* Event handler which is fired when combobox-list expanded or collapsed.
* This will put cursor at the beginning of combobox content to avoid
* text overlapping calendar-icon.
* @param {Ext.form.ComboBox} combo The combobox which was selected
* @private
*/
setCursorPosition : function(combo)
{
combo.el.dom.setSelectionRange(0, 0);
},
/**
* Helper function to obtain color for given folder entryid.
* @param {String} entryid Entry id of a folder
* @return {String} The color as defined in {@link Zarafa.calendar.ui.ColorSchemes color-scheme}
* for given folder
*/
getFolderColor : function(entryid)
{
var calendarContext = container.getContextByName('calendar');
var scheme = calendarContext.getModel().getColorScheme(entryid);
return scheme.base;
},
/**
* Helper function to return object containing config options necessary to create
* store which feeds the data to create-in-combo.
* This store holds all the calendar folders.
* @return {Object} Config set to create store which has all calendar folders.
*/
getCreateInStore : function()
{
var calendarFolders = container.getHierarchyStore().getByContainerClass('IPF.Appointment');
var createInData = [];
calendarFolders.forEach(function(dataItem) {
if (!(dataItem.get('rights') & Zarafa.core.mapi.Rights.RIGHTS_CREATE)) {
return;
}
var displayString = dataItem.get('display_name');
var mapiStore = dataItem.getParentFolder().getMAPIStore();
if (mapiStore.isSharedStore()) {
displayString += ' - ' + mapiStore.get('mailbox_owner_name');
}
createInData.push({
'entryid' : dataItem.get('entryid'),
'store_entryid' : dataItem.get('store_entryid'),
'displayString' : displayString,
'iconColor' : this.getFolderColor(dataItem.get('entryid'))
});
}, this);
return {
xtype: 'jsonstore',
fields: ['entryid', 'store_entryid', 'displayString', 'iconColor'],
data : createInData
};
},
/**
* Event handler which is fired when the {@link Zarafa.common.ui.DateRangeField} has been changed.
* This will update the start and due date inside the {@link #record} accordingly.
......
......@@ -7,10 +7,8 @@ Ext.namespace('Zarafa.calendar.ui');
*
* Special class which contains a number of
* icons which are used within the Calendar.
* These icons can be used inside the Canvas
* for drawing, because this implies that the
* images must be downloaded to the client,
* we do that in this cache class.
* It also contains some svg icons which can
* declared only once and used at multiple places.
*/
Zarafa.calendar.ui.IconCache = {
......@@ -129,5 +127,35 @@ Zarafa.calendar.ui.IconCache = {
'ACH5BAEAAAEALAAAAAASAAwABwg9AAMIHEiwYEEABxEeJKhQIICGDg0qfPhwIMSFFg0GoLhR4saKGBlq' +
'7HiRJMaSEzN21JjSIsWJL0c65GgwIAA7';
return function() { return image; };
}()
}(),
/**
* Obtain calendar icon in svg format.
* @param {String} color The icon color.
* @return {String} The calendar icon
*/
getCalendarSvgIcon : function(color)
{
return 'data:image/svg+xml;charset=utf8,' + encodeURIComponent(this.getCalendarSvgStructure(color));
},
/**
* Obtain svg structure for calendar icon.
* @param {String} color The icon color.
* @return {String} The svg structure of calendar icon
*/
getCalendarSvgStructure : function(color)
{
return '<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" width="15" height="13" viewBox="0 0 15 13" style="color:'+ color +';">' +
'<g>' +
'<g class="icbg" style="fill:currentColor;stroke:none">' +
'<rect width="15" height="12" x="0" y="1" />' +
'<rect width="1" height="1" x="2" y="0" />' +
'<rect width="1" height="1" x="7" y="0" />' +
'<rect width="1" height="1" x="12" y="0" />' +
'</g>' +
'<path class="icgr" d="M 2.5,6.5 h 10 v 4 h -10 v -4.5 M 4.5,6.5 v 4 M 6.5,6.5 v 4 M 8.5,6.5 v 4 M 10.5,6.5 v 4 M 2.5,8.5 h 9.5" style="fill:currentColor;stroke:#ffffff;stroke-width:1;stroke-linejoin=miter" />' +
'</g>' +
'</svg>';
}
};
......@@ -68,6 +68,13 @@ Zarafa.common.recipientfield.ui.RecipientField = Ext.extend(Zarafa.common.ui.Box
*/
dropZone : undefined,
/**
* @cfg {Number} The length of time in milliseconds to delay between
* the start of typing and sending the query to filter the dropdown
* list
*/
queryDelay: 250,
/**
* @constructor
* @param config Configuration object
......
......@@ -167,7 +167,14 @@ Zarafa.common.ui.HtmlEditor = Ext.extend(Ext.ux.form.TinyMCETextArea, {
tinymceEditor.on('keydown', this.onKeyDown.createDelegate(this), this);
tinymceEditor.on('paste', this.onPaste.createDelegate(this), this);
tinymceEditor.on('mousedown', this.onMouseDown.createDelegate(this), this);
tinymceEditor.on('mousedown', this.relayIframeEvent.createDelegate(this), this);
// Listen for wheel event on underlying iframe as tinyMCE editor isn't providing
// wheel event.
if (Zarafa.isDeskApp) {
var iframeElement = this.getEditor().iframeElement;
iframeElement.contentWindow.addEventListener('wheel', this.relayIframeEvent.createDelegate(this), true);
}
if (Ext.isGecko) {
tinymceEditor.on('dblclick', this.onDBLClick.createDelegate(this));
......@@ -544,13 +551,13 @@ Zarafa.common.ui.HtmlEditor = Ext.extend(Ext.ux.form.TinyMCETextArea, {
* Function is called when mouse is clicked in the editor.
* Editor mousedown event needs to be relayed for the document element of WebApp page,
* to hide the context-menu.
* TODO : Try to use {@link Ext.util.Observable#relayEvents}.
* Tried the same but the event doesn't bubbled up to the document element.
* wheel event propagated from underlying iframe needs to be relayed to WepApp document
* to perform zoom functionality in DeskApp.
* @param {Object} event The event object
*/
onMouseDown : function(event)
relayIframeEvent : function(event)
{
Ext.getDoc().fireEvent('mousedown');
Ext.getDoc().fireEvent(event.type, event);
},
/**
......
......@@ -203,7 +203,7 @@ Zarafa.common.ui.IconClass = {
// and assignee declined task from latest webapp version in that case
// mail icon shows instead of decline task icon to avoid this we add below code.
// Note : In future we can remove this check.
if (messageClass.toUpperCase() === 'IPM.TASK' && !Ext.isEmpty(record.get('icon_index'))) {
if (messageClass && messageClass.toUpperCase() === 'IPM.TASK' && !Ext.isEmpty(record.get('icon_index'))) {
declinedTask = record.isTaskOrganized() && record.isTaskDeclined();
}
} else if (Ext.isObject(options)) {
......
......@@ -117,18 +117,40 @@ Zarafa.common.ui.messagepanel.MessageBody = Ext.extend(Ext.Container, {
{
var iframeWindow = this.getEl().dom.contentWindow;
var iframeDocument = iframeWindow.document;
var eventsToRelay = ['mousedown'];
if (Zarafa.isDeskApp) {
eventsToRelay.push('wheel', 'keydown');
}
// mousedown needs to be relayed to hide contextmenu.
// keydown and wheel needs to be relayed to perform zoom functionality in DeskApp.
this.relayIframeEvent(iframeDocument, eventsToRelay);
},
iframeDocument.addEventListener('mousedown', this.onMouseDown.createDelegate(this), true);
/**
* Helper function to add event listeners to the given iframe element for given
* events with common handler.
* @param {HTMLElement} iframeElement The iframe node to which given events needs
* to be listened.
* @param {Array} events The set of events for which listeners should be attached
* to given iframe.
*/
relayIframeEvent : function(iframeElement, events)
{
events.forEach(function(event){
iframeElement.addEventListener(event, this.relayEventHandlers.createDelegate(this), true);
}, this);
},
/**
* Function is called when mouse is clicked in the editor.
* iframe mousedown event needs to be relayed for the document element of WebApp page,
* to hide the context-menu.
* Function is called when specified events performed in the iframe.
* Basically this function relay those events to document element
* belongs to WebApp window.
* @param {Object} event The event object
*/
onMouseDown : function()
relayEventHandlers : function(event)
{
Ext.getDoc().fireEvent('mousedown');
Ext.getDoc().fireEvent(event.type, event);
},
/**
......
......@@ -974,6 +974,27 @@ Zarafa.hierarchy.data.HierarchyStore = Ext.extend(Zarafa.core.data.IPFStore, {
record.setEventPropagation(true);
}
},
/**
* Returns folder(s) which has the provided container class.
* @param {String} containerClass The container_class of the folders.
* @return {Zarafa.core.data.IPFRecord[]} An array of folders
*/
getByContainerClass : function(containerClass)
{
var finalResult = [];
this.getStores().forEach( function(store) {
var subFolders = store.getFolderStore().getRange();
var subResult = subFolders.filter( function(folder) {
return folder.get('container_class') === containerClass;
});
finalResult = finalResult.concat(subResult);
});
return finalResult;
}
});
......
......@@ -39,6 +39,9 @@
// Name of the cookie that is used for the session
define("COOKIE_NAME", "KOPANO_WEBAPP");
// Set to 'true' to disable secure session cookies and to allow log-in without HTTPS.
define("INSECURE_COOKIES", false);
// The timeout (in seconds) for the session. User will be logged out of WebApp
// when he has not actively used the WebApp for this time.
// Set to 0 (or remove) for no timeout during browser session.
......
......@@ -103,6 +103,12 @@
$error = _("You have been automatically logged out");
} else {
$error = WebAppAuthentication::getErrorMessage();
if(empty($error) && useSecureCookies() && getRequestProtocol() == 'http') {
header("HTTP/1.0 400 Bad Request");
include(BASE_PATH . 'server/includes/templates/BadRequest.php');
error_log("Rejected insecure request as configuration for 'INSECURE_COOKIES' is false.");
die();
}
}
// If a username was passed as GET parameter we will prefill the username input
......@@ -196,7 +202,7 @@
}
$Language->setLanguage($lang);
setcookie('lang', $lang, 0, '/', '', isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] ? true : false);
setcookie('lang', $lang, 0, '/', '', getRequestProtocol() === 'https');
// add extra header
header("X-Zarafa: " . trim(file_get_contents('version')));
......
......@@ -11,6 +11,10 @@ Alias /webapp /usr/share/kopano-webapp
DirectoryIndex index.php
Options -Indexes +FollowSymLinks
<IfModule apparmor_module>
AAHatName kopano-webapp
</IfModule>
<IfVersion < 2.4>
Allow from all
AllowOverride Options Limit
......
......@@ -7,9 +7,9 @@
"scripts": {
"lint": "jshint client/zarafa",
"prelint": "jshint $(git diff-index --name-only HEAD | grep '.js')",
"jlint": "jshint client/zarafa --reporter=checkstyle > result.xml",
"phplint": "phpmd server text .phpmd.xml",
"phplintci": "phpmd server xml .phpmd.xml --reportfile result.xml",
"jlint": "jshint client/zarafa --reporter=checkstyle > jshint.xml",
"phplint": "phpmd server text .phpmd.xml || true",
"phplintci": "phpmd server xml .phpmd.xml --reportfile phpmd.xml || true",
"jsunit": "karma start test/js/unittest.conf.js"
},
"pre-commit": {
......@@ -18,6 +18,7 @@
]
},
"devDependencies": {
"jasmine-core": "^2.8.0",
"jshint": "*",
"jshint-junit-reporter": "*",
"karma": "^1.7.1",
......
......@@ -105,9 +105,14 @@ class EncryptionStore
*/
private function createEncryptionKey() {
EncryptionStore::$_encryptionKey = openssl_random_pseudo_bytes(openssl_cipher_iv_length(EncryptionStore::_CIPHER_METHOD));
// Store it in the cookie (http-only)
setcookie('encryption-store-key', bin2hex(EncryptionStore::$_encryptionKey), 0, '/', '', isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] ? true : false, true);
$cookieName = 'encryption-store-key';
$secure = useSecureCookies();
if ($secure) {
$cookieName = '__Secure-'.$cookieName;
}
$path = ini_get('session.cookie_path');
$domain = ini_get('session.cookie_domain');
setcookie($cookieName, bin2hex(EncryptionStore::$_encryptionKey), 0, $path, $domain, $secure, true);
}
/**
......
......@@ -28,9 +28,8 @@
/**
* @var string The entryid (binary) of the default store
* FIXME: public since class.hierarchynotifier re-opens the store which needs to be fixed.
*/
public $defaultstore;
private $defaultstore;
/**
* @var string The entryid (binary) of the public store
......@@ -413,21 +412,17 @@
*/
function loadMessageStoresFromSession()
{
if(!$this->defaultstore && !$this->publicStore){
$storestables = mapi_getmsgstorestable($this->session);
$rows = mapi_table_queryallrows($storestables, array(PR_ENTRYID, PR_DEFAULT_STORE, PR_MDB_PROVIDER));
foreach($rows as $row) {
$name = '';
if($row[PR_ENTRYID]){
if(isset($row[PR_DEFAULT_STORE]) && $row[PR_DEFAULT_STORE] == true) {
$this->defaultstore = $row[PR_ENTRYID];
$name = 'Default store';
}elseif($row[PR_MDB_PROVIDER] == ZARAFA_STORE_PUBLIC_GUID){
$this->publicStore = $row[PR_ENTRYID];
$name = 'Public store';
}
}
$this->openMessageStore($row[PR_ENTRYID], $name);
$storestables = mapi_getmsgstorestable($this->session);
$rows = mapi_table_queryallrows($storestables, array(PR_ENTRYID, PR_DEFAULT_STORE, PR_MDB_PROVIDER));
foreach($rows as $row) {
if (!$row[PR_ENTRYID]) {
continue;
}
if (isset($row[PR_DEFAULT_STORE]) && $row[PR_DEFAULT_STORE] == true) {
$this->defaultStore = $row[PR_ENTRYID];
} elseif ($row[PR_MDB_PROVIDER] == ZARAFA_STORE_PUBLIC_GUID) {
$this->publicStore = $row[PR_ENTRYID];
}
}
}
......@@ -436,18 +431,31 @@
* Get the current user's default message store
*
* The store is opened only once, subsequent calls will return the previous store object
* @param boolean reopen force re-open
* @return mapistore User's default message store object
*/
function getDefaultMessageStore()
function getDefaultMessageStore($reopen = False)
{
$this->loadMessageStoresFromSession();
// Return cached default store if we have one
if(isset($this->defaultstore) && isset($this->stores[$this->defaultstore])) {
if (!$reopen && isset($this->defaultstore) && isset($this->stores[$this->defaultstore])) {
return $this->stores[$this->defaultstore];
}else{
return false;
}
$this->loadMessageStoresFromSession();
return $this->openMessageStore($this->defaultStore, 'Default store');
}
/**
* The default messagestore entryid
* @return string the entryid of the default messagestore
*/
function getDefaultMessageStoreEntryId()
{
if (!isset($this->defaultStore)) {
$this->loadMessageStoresFromSession();
}
return bin2hex($this->defaultStore);
}
/**
......@@ -487,14 +495,13 @@
*/
function getPublicMessageStore()
{
$this->loadMessageStoresFromSession();
// Return cached public store if we have one
if(isset($this->publicStore) && isset($this->stores[$this->publicStore])) {
return $this->stores[$this->publicStore];
}else{
return false;
}
$this->loadMessageStoresFromSession();