+# -*- coding: utf-8 -*-
+# (c) 2003 Centre de Recherche en Informatique ENSMP Fontainebleau <http://cri.ensmp.fr>
+# (c) 2003 Benoît PIN <mailto:pin@cri.ensmp.fr>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as published
+# by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+# 02111-1307, USA.
+
+
+from Products.CMFCore.PortalFolder import PortalFolder
+from Globals import InitializeClass
+from AccessControl import ClassSecurityInfo
+from AccessControl.Permission import Permission
+from Products.CMFCore.utils import getToolByName
+from Products.CMFCore.permissions import View, ModifyPortalContent, AccessContentsInformation
+from random import randrange
+from DateTime import DateTime
+from types import InstanceType, StringType, DictType
+from MosaicBlockInformation import RuleError
+
+from OFS.Moniker import Moniker, loadMoniker
+from OFS.CopySupport import _cb_encode, _cb_decode#, cookie_path
+from MosaicBlockInformation import RuleError
+
+
+class MosaicBlock(PortalFolder) :
+ """ Block class for 'Mosaic Document' """
+
+ meta_type = 'Mosaic Block'
+ _properties = ({'id' : 'xpos', 'type' : 'int', 'mode' : 'w'},
+ {'id' : 'minimized', 'type' : 'boolean', 'mode' : 'w'},)
+
+ def __init__( self, id, title='', xpos = 0):
+ PortalFolder.__init__(self, id, title)
+ self.manage_changeProperties(xpos=xpos)
+ self.manage_changeProperties(minimized=0)
+
+ security = ClassSecurityInfo()
+
+
+ ## Utils methods
+
+ def _getRootBlock(self) :
+ """Return the root block object ie :
+ the first block in the tree which have a "_isRootBlock = 1" flag"""
+
+ urlTool = getToolByName(self, 'portal_url')
+ portalObject = urlTool.getPortalObject()
+
+ block = self
+ while block != portalObject :
+ if hasattr(block.aq_self, '_isRootBlock') :
+ return block
+ else :
+ block = block.aq_parent
+ return None
+
+ def _getSelfRules(self) :
+ """Return block rules informations"""
+ mosTool = getToolByName(self, 'mosaic_tool')
+ myTi = mosTool.getTypeInfo(self)
+ ruleDic = {}
+ for rule in myTi.objectValues(['Rule Information']) :
+ ruleDic[rule.getId()] = rule
+ return ruleDic
+
+ def _allowedMoves(self, block) :
+ if type(block) == StringType :
+ block = getattr(self, block)
+ rules = self._getSelfRules()[block.portal_type]
+ move_dic = {'global' : rules.allowMove,
+ 'rightLeft' : rules.allowMoveRightAndLeft,
+ 'upDown' : rules.allowMoveUpAndDown,
+ }
+ return move_dic
+
+ security.declarePrivate('_getParentRules')
+ def _getParentRules(self) :
+ """Return block rules informations"""
+ mosTool = getToolByName(self, 'mosaic_tool')
+ parentTi = mosTool.getTypeInfo(self.aq_parent)
+ return parentTi.objectValues(['Rule Information'])
+
+ def _redirectAfterEdit(self, REQUEST, rootBlock=None, blockId=None) :
+ if rootBlock is None :
+ rootBlock = self._getRootBlock()
+
+ if REQUEST.get('ajax') :
+ url = rootBlock.getActionInfo('object_ajax/edit')['url']
+ elif REQUEST.SESSION.get('editBoxes') :
+ utool = getToolByName(self, 'portal_url')
+ url = utool() + '/manage_boxes'
+ else :
+ url = rootBlock.getActionInfo('object/edit')['url'] + (blockId and '#' + blockId or '')
+ return REQUEST.RESPONSE.redirect(url)
+
+ security.declareProtected(ModifyPortalContent, 'getAllowedBlocks')
+ def getAllowedBlocks(self) :
+ """Return a list with allowed blocks"""
+ rules = self._getSelfRules()
+ mosTool = getToolByName(self, 'mosaic_tool')
+ allowedBlocks = ()
+ for ruleId in rules.keys() :
+ ti = mosTool.getTypeInfo(ruleId)
+ try :
+ if ti.isConstructionAllowed(self) :
+ allowedBlocks += ({'id' : ti.id, 'title' : ti.title},)
+ except :
+ continue
+ return allowedBlocks
+
+ security.declareProtected(ModifyPortalContent, 'haveRules')
+ def haveRules(self) :
+ """ return 1 if type info from self have rules """
+ mosTool = getToolByName(self, 'mosaic_tool')
+ myTi = mosTool.getTypeInfo(self)
+ if myTi.objectValues(['Rule Information']) :
+ return 1
+ else :
+ return 0
+
+
+ security.declareProtected(View, 'getSlots')
+ def getSlots(self) :
+ """return slots"""
+ return [ob for ob in self.objectValues() if hasattr(ob, '_isMosaicSlot')]
+
+ security.declareProtected(View, 'getSlotsDic')
+ def getSlotsDic(self) :
+ """return slots in dictionary"""
+ slots = self.getSlots()
+ slotDic = {}
+ for slot in slots :
+ slotDic[slot.getId()] = slot
+ return slotDic
+
+ security.declarePublic('Title')
+ def Title(self) :
+ """Return title"""
+ return self.getProperty('title', d='') or (hasattr(self, 'caption') and self.caption.text) or ''
+
+ title = Title
+
+ security.declareProtected(ModifyPortalContent, 'setTitle')
+ def setTitle(self, title) :
+ if hasattr(self, 'caption') :
+ self.caption.text = title
+ self.title = title
+
+ def title_or_id(self) :
+ """Return title or id"""
+ return self.Title() or self.id
+
+ ## Methods for displaying
+
+ security.declareProtected(View, 'getBlocksTable')
+ def getBlocksTable(self, filteredTypes=[]) :
+ """return blocks ordered in a 2 dimensions table"""
+ blocks = self.objectValues(['Mosaic Block',])
+
+ if filteredTypes :
+ blocks = [block for block in blocks if block.portal_type in filteredTypes]
+
+ #blocks.sort(lambda x, y : cmp(x.xpos, y.xpos)) inutile ???
+ rows = 0
+ try :
+ cols = blocks[-1].xpos
+ except :
+ cols = 0
+ columnsTable = [] # columns list
+ rules = self._getSelfRules()
+ for xpos in range(cols + 1) :
+ colBlockList = [ block for block in blocks if block.xpos == xpos ] # opt : baliser les debuts de colonne
+ colBlockListLength = len(colBlockList)
+
+ # build a column by iterating over blocks with the position xpos
+ colBlockInfoList = []
+ for blockIndex in range(colBlockListLength) :
+ block = colBlockList[blockIndex]
+ blockRule = rules[block.portal_type]
+ moveDic = {'up' : 0,
+ 'down' : 0,
+ 'left' : 0,
+ 'right' : 0}
+ if blockRule.allowMoveUpAndDown :
+ moveDic['up'] = blockIndex > 0
+ moveDic['down'] = blockIndex < colBlockListLength - 1
+ if blockRule.allowMoveRightAndLeft :
+ moveDic['left'] = xpos > 0
+ moveDic['right'] = 1
+
+ # every block will be displayed in a cell in a table
+ colBlockInfoList.append({'block' : block,
+ 'col' : block.xpos,
+ 'moves' : moveDic,
+ 'mode' : blockRule.mode})
+
+ # append the new column in the column list ie : columnsTable
+ if colBlockListLength > rows :
+ rows = colBlockListLength
+ columnsTable.append(colBlockInfoList)
+
+ # Now the max number of rows in known.
+ # We can determine rowspan attributes,
+ # Building lines for an easy iterating over <tr> tag
+
+ linesTable = []
+ cols += 1
+ for lineIndex in range(rows) :
+ line = []
+ for columnIndex in range(cols) :
+ try :
+ blockInfo = columnsTable[columnIndex][lineIndex]
+ if lineIndex == rows - 1 :
+ blockInfo.update({'lastOne' : 1})
+ line.append(blockInfo)
+
+ except :
+ if lineIndex and linesTable[lineIndex - 1][columnIndex]['block'] is not None :
+ linesTable[lineIndex - 1][columnIndex].update({'rowspan' : rows - lineIndex + 1,
+ 'lastOne' : 1})
+
+ if lineIndex and linesTable[lineIndex - 1][columnIndex].get('rowspan') :
+ rowspan = -1 # flag for ignoring td insertion
+ else :
+ rowspan = rows - lineIndex
+ line.append({'block' : None,
+ 'col' : columnIndex,
+ 'rowspan' : rowspan})
+
+ linesTable.append(line)
+
+ tableInfo = {'rows' : rows,
+ 'cols' : cols,
+ 'lines' : linesTable,
+ 'columns' : columnsTable}
+ return tableInfo
+
+ security.declareProtected(View, 'callTemplate')
+ def callTemplate(self, displayAction='renderer', **kw) :
+ """ Return block template name from block meta fti """
+ mosTool = getToolByName(self, 'mosaic_tool')
+ mfti = mosTool.getTypeInfo(self)
+ templateObj = self.restrictedTraverse(mfti.template)
+ return templateObj(block = self, displayAction = displayAction, **kw)
+
+ security.declareProtected(ModifyPortalContent, 'toggle_minimized')
+ def toggle_minimized(self, REQUEST = None) :
+ "toggle minimized property"
+ if not self.minimized :
+ self.minimized = 1
+ else :
+ self.minimized = 0
+ if REQUEST is not None:
+ return self._redirectAfterEdit(REQUEST, blockId = self.id)
+
+ security.declareProtected(View, 'ypos')
+ def ypos(self) :
+ """ Return the position of self into
+ the parent block """
+ return self.aq_parent.getObjectPosition(self.id)
+
+ ## Block edition methods
+
+ security.declareProtected(ModifyPortalContent, 'addBlock')
+ def addBlock(self, blockType, xpos, id='', beforeBlock='', afterBlock='', REQUEST=None) :
+ """ add a new block type """
+ mosTool = getToolByName(self, 'mosaic_tool')
+ blockId = id or str(int(DateTime()))+str(randrange(1000,10000))
+ mosTool.constructContent(blockType,
+ self,
+ blockId,
+ xpos=xpos,
+ beforeBlock=beforeBlock,
+ afterBlock=afterBlock)
+ if REQUEST is not None :
+ return self._redirectAfterEdit(REQUEST, blockId = blockId)
+ else :
+ return blockId
+
+ security.declareProtected(ModifyPortalContent, 'saveBlock')
+ def saveBlock(self, REQUEST=None, **kw) :
+ """ Save block content """
+ mosTool = getToolByName(self, 'mosaic_tool')
+ ti = mosTool.getTypeInfo(self)
+ slotIds = ti.objectIds(['Slot Information',])
+
+ if REQUEST is not None: kw.update(REQUEST.form)
+ dicArgsListItems = [ (argKey, kw[argKey]) for argKey in kw.keys() if type(kw[argKey]) in [InstanceType, DictType] ]
+
+ # iteration over slots for applying edit method
+ # an exception is raised when a slot name is not defined in the portal type
+ for slotId, kwords in dicArgsListItems :
+ if slotId in slotIds :
+ slot = getattr(self, slotId) # could raise an exception
+ kwArgs = {}
+ for k in kwords.keys() : #kwords is an InstanceType not a DictType...
+ kwArgs[k] = kwords[k]
+ slot.edit(**kwArgs)
+ else :
+ raise KeyError, "this slot : '%s' is not defined in the type information" % slotId
+
+ rootBlock = self._getRootBlock()
+ if rootBlock is not None :
+ rootBlock.reindexObject()
+
+ if REQUEST is not None :
+ return self._redirectAfterEdit(REQUEST, rootBlock = rootBlock, blockId = self.getId())
+
+ security.declareProtected(View, 'SearchableText')
+ def SearchableText(self) :
+ blocks = self.objectValues(['Mosaic Block',])
+ slots = [ slot for slot in self.objectValues() if hasattr(slot, '_isMosaicSlot') ]
+ text = ''
+
+ for slot in slots :
+ text += ' %s' % slot.SearchableText()
+ for block in blocks :
+ text += ' %s' % block.SearchableText()
+
+ return text
+
+ security.declareProtected(ModifyPortalContent, 'deleteBlock')
+ def deleteBlock(self, blockId, REQUEST=None) :
+ """ Delete the blockId """
+ old_pos = self.getObjectPosition(blockId)
+ if old_pos != 0 :
+ redirectBlockId = self._objects[old_pos -1]['id']
+ else :
+ redirectBlockId = self.getId()
+
+ self.manage_delObjects([blockId,])
+
+ if REQUEST :
+ # évite des appels répétitifs à reindexObject lorsque deleBlock est appelée
+ # par deleteBlocks
+ rootBlock = self._getRootBlock()
+ rootBlock.reindexObject()
+ return self._redirectAfterEdit(REQUEST, blockId = redirectBlockId)
+ else :
+ return redirectBlockId
+
+ security.declareProtected(ModifyPortalContent, 'deleteBlocks')
+ def deleteBlocks(self, blockIds, REQUEST=None) :
+ """ Delete block id list"""
+ redirectBlockId = ''
+ for blockId in blockIds :
+ redirectBlockId = self.deleteBlock(blockId)
+
+ rootBlock = self._getRootBlock()
+ rootBlock.reindexObject()
+ if REQUEST :
+ return self._redirectAfterEdit(REQUEST, rootBlock = rootBlock, blockId = redirectBlockId)
+
+
+ ## cut and paste methods
+
+ security.declareProtected(ModifyPortalContent, 'pushCp')
+ def pushCp(self, blockId, REQUEST) :
+ """ push block in clipboard """
+ previousCp = None
+ oblist = []
+ if REQUEST.has_key('__cp') :
+ previousCp = REQUEST['__cp']
+ previousCp = _cb_decode(previousCp)
+ oblist = previousCp[1]
+
+ block = getattr(self, blockId)
+ m = Moniker(block)
+ oblist.append(m.dump())
+ cp=(0, oblist)
+ cp=_cb_encode(cp)
+
+ resp=REQUEST['RESPONSE']
+ resp.setCookie('__cp', cp, path='/')
+ REQUEST['__cp'] = cp
+ return self._redirectAfterEdit(REQUEST, blockId = blockId)
+
+ security.declareProtected(ModifyPortalContent, 'pasteBlocks')
+ def pasteBlocks(self, REQUEST) :
+ """ check rules and paste blocks from cp """
+
+ if REQUEST.has_key('__cp') :
+ cp = REQUEST['__cp']
+ cp = _cb_decode(cp)
+ mosTool = getToolByName(self, 'mosaic_tool')
+ app = self.getPhysicalRoot()
+ self_fti = getattr(mosTool, self.portal_type)
+
+
+ # paste element one by one for
+ # checking rules on every iteration
+ for mdata in cp[1] :
+ m = loadMoniker(mdata)
+ ob = m.bind(app)
+ ob_type = ob.portal_type
+ ob_fti = getattr(mosTool, ob_type)
+ isBlock = (ob_fti.meta_type == 'Mosaic Block Information')
+ isRootBlock = getattr(ob, '_isRootBlock', 0) # a block witch have this attribute is a content type
+
+ if isBlock and not isRootBlock and ob_fti.isConstructionAllowed(self) :
+ # internal copy and paste handling
+ cp = (0, [m.dump(),])
+ cp=_cb_encode(cp)
+ self.manage_pasteObjects(cb_copy_data = cp)
+
+ _reOrderBlocks(self)
+
+ self.flushCp(REQUEST)
+ return self._redirectAfterEdit(REQUEST, blockId = self.getId())
+
+ security.declarePublic('flushCp')
+ def flushCp(self, REQUEST) :
+ """ Expire cp cookie """
+ REQUEST.RESPONSE.expireCookie('__cp', path='/')
+
+ security.declareProtected(ModifyPortalContent, 'getCpInfos')
+ def getCpInfos(self, REQUEST) :
+ """ Return information about loaded objects in cp """
+ if REQUEST.has_key('__cp') :
+ cp = REQUEST['__cp']
+ cp = _cb_decode(cp)
+ return len(cp[1])
+ else :
+ return 0
+
+
+ ## moves methods
+
+ security.declareProtected(ModifyPortalContent, 'moveLeft')
+ def moveLeft(self, blockId, REQUEST=None) :
+ """Move block left"""
+ move_dic = self._allowedMoves(blockId)
+ if not(move_dic['global'] or move_dic['rightLeft']) :
+ raise RuleError, "It's not allowed to move this block in this context"
+ block = getattr(self, blockId)
+ if block.xpos > 0 :
+ block.xpos -= 1
+ _reOrderBlocks(self)
+ if REQUEST is not None :
+ return self._redirectAfterEdit(REQUEST, blockId = blockId)
+
+
+
+ security.declareProtected(ModifyPortalContent, 'moveRight')
+ def moveRight(self, blockId, REQUEST=None) :
+ """Move block Right"""
+ move_dic = self._allowedMoves(blockId)
+ if not (move_dic['global'] or move_dic['rightLeft']) :
+ raise RuleError, "It's not allowed to move this block in this context"
+ block = getattr(self, blockId)
+ block.xpos += 1
+ _reOrderBlocks(self)
+ if REQUEST is not None :
+ return self._redirectAfterEdit(REQUEST, blockId = blockId)
+
+
+ security.declareProtected(ModifyPortalContent, 'moveUp')
+ def moveUp(self, blockId, REQUEST=None) :
+ """Move block Up"""
+ move_dic = self._allowedMoves(blockId)
+ if not(move_dic['global'] or move_dic['upDown']) :
+ raise RuleError, "It's not allowed to move this block in this context"
+ self.moveObjectsUp(blockId)
+ if REQUEST is not None :
+ return self._redirectAfterEdit(REQUEST, blockId = blockId)
+
+
+ security.declareProtected(ModifyPortalContent, 'moveDown')
+ def moveDown(self, blockId, REQUEST=None) :
+ """Move block left"""
+ move_dic = self._allowedMoves(blockId)
+ if not(move_dic['global'] or move_dic['upDown']) :
+ raise RuleError, "It's not allowed to move this block in this context"
+ self.moveObjectsDown(blockId)
+ if REQUEST is not None :
+ return self._redirectAfterEdit(REQUEST, blockId = blockId)
+
+ security.declareProtected(ModifyPortalContent, 'movesUp')
+ def movesUp(self, blockIds = [], REQUEST=None) :
+ """move blocks up"""
+ for blockId in blockIds :
+ self.moveUp(blockId)
+ if REQUEST is not None :
+ return self._redirectAfterEdit(REQUEST, blockId = blockId)
+
+ security.declareProtected(ModifyPortalContent, 'movesDown')
+ def movesDown(self, blockIds = [], REQUEST=None) :
+ """move blocks down"""
+ for blockId in blockIds :
+ self.moveDown(blockId)
+ if REQUEST is not None :
+ return self._redirectAfterEdit(REQUEST, blockId = blockId)
+
+
+InitializeClass(MosaicBlock)
+
+def _reOrderBlocks(container) :
+ # This method order blocks.
+ # It's useful when a left or right move or a block creation in a middle of a column happens.
+ blocks = list(container.objectValues(['Mosaic Block',]))
+
+ # get the maximum value for xpos attribute
+ blocks.sort(lambda b1, b2 : cmp(b1.xpos, b2.xpos))
+ rows = 0
+ try :
+ cols = blocks[-1].xpos
+ except :
+ cols = 0
+
+ blockPosition = 0
+ for xpos in range(cols + 1) :
+ colBlockList = [ block for block in blocks if block.xpos == xpos ]
+ colBlockListLength = len(colBlockList)
+
+ for blockIndex in range(colBlockListLength) :
+ block = colBlockList[blockIndex]
+ container.moveObjectToPosition(block.getId(), blockPosition)
+ blockPosition += 1
+
+
+def addMosaicBlock(dispatcher, id, xpos=0, beforeBlock='', afterBlock='') :
+ """Add a new mosaicBlock"""
+ parentBlock = dispatcher.Destination()
+ parentBlock._setObject(id, MosaicBlock(id, xpos=xpos))
+ if beforeBlock :
+ position = parentBlock.getObjectPosition(beforeBlock)
+ parentBlock.moveObjectToPosition(id, position)
+
+ elif afterBlock :
+ position = parentBlock.getObjectPosition(afterBlock)
+ parentBlock.moveObjectToPosition(id, position + 1)
+ else :
+ try : _reOrderBlocks(parentBlock)
+ except : pass
+