X-Git-Url: https://scm.cri.minesparis.psl.eu/git/MosaicDocument.git/blobdiff_plain/155c6ba3d7e8e9693d30b3cf70f591f0153610b6..99b3ba92670e19c1f86f5de83b8e6bbe4fdc297f:/Products/MosaicDocument/MosaicBlock.py?ds=inline diff --git a/Products/MosaicDocument/MosaicBlock.py b/Products/MosaicDocument/MosaicBlock.py new file mode 100755 index 0000000..2904cbf --- /dev/null +++ b/Products/MosaicDocument/MosaicBlock.py @@ -0,0 +1,546 @@ +# -*- coding: utf-8 -*- +# (c) 2003 Centre de Recherche en Informatique ENSMP Fontainebleau +# (c) 2003 Benoît PIN +# +# 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 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 +