1 # -*- coding: utf-8 -*-
2 # (c) 2003 Centre de Recherche en Informatique ENSMP Fontainebleau <http://cri.ensmp.fr>
3 # (c) 2003 Benoît PIN <mailto:pin@cri.ensmp.fr>
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License version 2 as published
7 # by the Free Software Foundation.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
20 from Products
.CMFCore
.PortalFolder
import PortalFolder
21 from Globals
import InitializeClass
22 from AccessControl
import ClassSecurityInfo
23 from AccessControl
.Permission
import Permission
24 from Products
.CMFCore
.utils
import getToolByName
25 from Products
.CMFCore
.permissions
import View
, ModifyPortalContent
, AccessContentsInformation
26 from random
import randrange
27 from DateTime
import DateTime
28 from types
import InstanceType
, StringType
, DictType
29 from MosaicBlockInformation
import RuleError
31 from OFS
.Moniker
import Moniker
, loadMoniker
32 from OFS
.CopySupport
import _cb_encode
, _cb_decode
#, cookie_path
33 from MosaicBlockInformation
import RuleError
36 class MosaicBlock(PortalFolder
) :
37 """ Block class for 'Mosaic Document' """
39 meta_type
= 'Mosaic Block'
40 _properties
= ({'id' : 'xpos', 'type' : 'int', 'mode' : 'w'},
41 {'id' : 'minimized', 'type' : 'boolean', 'mode' : 'w'},)
43 def __init__( self
, id, title
='', xpos
= 0):
44 PortalFolder
.__init
__(self
, id, title
)
45 self
.manage_changeProperties(xpos
=xpos
)
46 self
.manage_changeProperties(minimized
=0)
48 security
= ClassSecurityInfo()
53 def _getRootBlock(self
) :
54 """Return the root block object ie :
55 the first block in the tree which have a "_isRootBlock = 1" flag"""
57 urlTool
= getToolByName(self
, 'portal_url')
58 portalObject
= urlTool
.getPortalObject()
61 while block
!= portalObject
:
62 if hasattr(block
.aq_self
, '_isRootBlock') :
65 block
= block
.aq_parent
68 def _getSelfRules(self
) :
69 """Return block rules informations"""
70 mosTool
= getToolByName(self
, 'mosaic_tool')
71 myTi
= mosTool
.getTypeInfo(self
)
73 for rule
in myTi
.objectValues(['Rule Information']) :
74 ruleDic
[rule
.getId()] = rule
77 def _allowedMoves(self
, block
) :
78 if type(block
) == StringType
:
79 block
= getattr(self
, block
)
80 rules
= self
._getSelfRules
()[block
.portal_type
]
81 move_dic
= {'global' : rules
.allowMove
,
82 'rightLeft' : rules
.allowMoveRightAndLeft
,
83 'upDown' : rules
.allowMoveUpAndDown
,
87 security
.declarePrivate('_getParentRules')
88 def _getParentRules(self
) :
89 """Return block rules informations"""
90 mosTool
= getToolByName(self
, 'mosaic_tool')
91 parentTi
= mosTool
.getTypeInfo(self
.aq_parent
)
92 return parentTi
.objectValues(['Rule Information'])
94 def _redirectAfterEdit(self
, REQUEST
, rootBlock
=None, blockId
=None) :
95 if rootBlock
is None :
96 rootBlock
= self
._getRootBlock
()
98 if REQUEST
.get('ajax') :
99 url
= rootBlock
.getActionInfo('object_ajax/edit')['url']
100 elif REQUEST
.SESSION
.get('editBoxes') :
101 utool
= getToolByName(self
, 'portal_url')
102 url
= utool() + '/manage_boxes'
104 url
= rootBlock
.getActionInfo('object/edit')['url'] + (blockId
and '#' + blockId
or '')
105 return REQUEST
.RESPONSE
.redirect(url
)
107 security
.declareProtected(ModifyPortalContent
, 'getAllowedBlocks')
108 def getAllowedBlocks(self
) :
109 """Return a list with allowed blocks"""
110 rules
= self
._getSelfRules
()
111 mosTool
= getToolByName(self
, 'mosaic_tool')
113 for ruleId
in rules
.keys() :
114 ti
= mosTool
.getTypeInfo(ruleId
)
116 if ti
.isConstructionAllowed(self
) :
117 allowedBlocks
+= ({'id' : ti
.id, 'title' : ti
.title
},)
122 security
.declareProtected(ModifyPortalContent
, 'haveRules')
123 def haveRules(self
) :
124 """ return 1 if type info from self have rules """
125 mosTool
= getToolByName(self
, 'mosaic_tool')
126 myTi
= mosTool
.getTypeInfo(self
)
127 if myTi
.objectValues(['Rule Information']) :
133 security
.declareProtected(View
, 'getSlots')
136 return [ob
for ob
in self
.objectValues() if hasattr(ob
, '_isMosaicSlot')]
138 security
.declareProtected(View
, 'getSlotsDic')
139 def getSlotsDic(self
) :
140 """return slots in dictionary"""
141 slots
= self
.getSlots()
144 slotDic
[slot
.getId()] = slot
147 security
.declarePublic('Title')
150 return self
.getProperty('title', d
='') or (hasattr(self
, 'caption') and self
.caption
.text
) or ''
154 security
.declareProtected(ModifyPortalContent
, 'setTitle')
155 def setTitle(self
, title
) :
156 if hasattr(self
, 'caption') :
157 self
.caption
.text
= title
160 def title_or_id(self
) :
161 """Return title or id"""
162 return self
.Title() or self
.id
164 ## Methods for displaying
166 security
.declareProtected(View
, 'getBlocksTable')
167 def getBlocksTable(self
, filteredTypes
=[]) :
168 """return blocks ordered in a 2 dimensions table"""
169 blocks
= self
.objectValues(['Mosaic Block',])
172 blocks
= [block
for block
in blocks
if block
.portal_type
in filteredTypes
]
174 #blocks.sort(lambda x, y : cmp(x.xpos, y.xpos)) inutile ???
177 cols
= blocks
[-1].xpos
180 columnsTable
= [] # columns list
181 rules
= self
._getSelfRules
()
182 for xpos
in range(cols
+ 1) :
183 colBlockList
= [ block
for block
in blocks
if block
.xpos
== xpos
] # opt : baliser les debuts de colonne
184 colBlockListLength
= len(colBlockList
)
186 # build a column by iterating over blocks with the position xpos
187 colBlockInfoList
= []
188 for blockIndex
in range(colBlockListLength
) :
189 block
= colBlockList
[blockIndex
]
190 blockRule
= rules
[block
.portal_type
]
195 if blockRule
.allowMoveUpAndDown
:
196 moveDic
['up'] = blockIndex
> 0
197 moveDic
['down'] = blockIndex
< colBlockListLength
- 1
198 if blockRule
.allowMoveRightAndLeft
:
199 moveDic
['left'] = xpos
> 0
202 # every block will be displayed in a cell in a table
203 colBlockInfoList
.append({'block' : block
,
206 'mode' : blockRule
.mode
})
208 # append the new column in the column list ie : columnsTable
209 if colBlockListLength
> rows
:
210 rows
= colBlockListLength
211 columnsTable
.append(colBlockInfoList
)
213 # Now the max number of rows in known.
214 # We can determine rowspan attributes,
215 # Building lines for an easy iterating over <tr> tag
219 for lineIndex
in range(rows
) :
221 for columnIndex
in range(cols
) :
223 blockInfo
= columnsTable
[columnIndex
][lineIndex
]
224 if lineIndex
== rows
- 1 :
225 blockInfo
.update({'lastOne' : 1})
226 line
.append(blockInfo
)
229 if lineIndex
and linesTable
[lineIndex
- 1][columnIndex
]['block'] is not None :
230 linesTable
[lineIndex
- 1][columnIndex
].update({'rowspan' : rows
- lineIndex
+ 1,
233 if lineIndex
and linesTable
[lineIndex
- 1][columnIndex
].get('rowspan') :
234 rowspan
= -1 # flag for ignoring td insertion
236 rowspan
= rows
- lineIndex
237 line
.append({'block' : None,
239 'rowspan' : rowspan
})
241 linesTable
.append(line
)
243 tableInfo
= {'rows' : rows
,
245 'lines' : linesTable
,
246 'columns' : columnsTable
}
249 security
.declareProtected(View
, 'callTemplate')
250 def callTemplate(self
, displayAction
='renderer', **kw
) :
251 """ Return block template name from block meta fti """
252 mosTool
= getToolByName(self
, 'mosaic_tool')
253 mfti
= mosTool
.getTypeInfo(self
)
254 templateObj
= self
.restrictedTraverse(mfti
.template
)
255 return templateObj(block
= self
, displayAction
= displayAction
, **kw
)
257 security
.declareProtected(ModifyPortalContent
, 'toggle_minimized')
258 def toggle_minimized(self
, REQUEST
= None) :
259 "toggle minimized property"
260 if not self
.minimized
:
264 if REQUEST
is not None:
265 return self
._redirectAfterEdit
(REQUEST
, blockId
= self
.id)
267 security
.declareProtected(View
, 'ypos')
269 """ Return the position of self into
271 return self
.aq_parent
.getObjectPosition(self
.id)
273 ## Block edition methods
275 security
.declareProtected(ModifyPortalContent
, 'addBlock')
276 def addBlock(self
, blockType
, xpos
, id='', beforeBlock
='', afterBlock
='', REQUEST
=None) :
277 """ add a new block type """
278 mosTool
= getToolByName(self
, 'mosaic_tool')
279 blockId
= id or str(int(DateTime()))+str(randrange(1000,10000))
280 mosTool
.constructContent(blockType
,
284 beforeBlock
=beforeBlock
,
285 afterBlock
=afterBlock
)
286 if REQUEST
is not None :
287 return self
._redirectAfterEdit
(REQUEST
, blockId
= blockId
)
291 security
.declareProtected(ModifyPortalContent
, 'saveBlock')
292 def saveBlock(self
, REQUEST
=None, **kw
) :
293 """ Save block content """
294 mosTool
= getToolByName(self
, 'mosaic_tool')
295 ti
= mosTool
.getTypeInfo(self
)
296 slotIds
= ti
.objectIds(['Slot Information',])
298 if REQUEST
is not None: kw
.update(REQUEST
.form
)
299 dicArgsListItems
= [ (argKey
, kw
[argKey
]) for argKey
in kw
.keys() if type(kw
[argKey
]) in [InstanceType
, DictType
] ]
301 # iteration over slots for applying edit method
302 # an exception is raised when a slot name is not defined in the portal type
303 for slotId
, kwords
in dicArgsListItems
:
304 if slotId
in slotIds
:
305 slot
= getattr(self
, slotId
) # could raise an exception
307 for k
in kwords
.keys() : #kwords is an InstanceType not a DictType...
308 kwArgs
[k
] = kwords
[k
]
311 raise KeyError, "this slot : '%s' is not defined in the type information" % slotId
313 rootBlock
= self
._getRootBlock
()
314 if rootBlock
is not None :
315 rootBlock
.reindexObject()
317 if REQUEST
is not None :
318 return self
._redirectAfterEdit
(REQUEST
, rootBlock
= rootBlock
, blockId
= self
.getId())
320 security
.declareProtected(View
, 'SearchableText')
321 def SearchableText(self
) :
322 blocks
= self
.objectValues(['Mosaic Block',])
323 slots
= [ slot
for slot
in self
.objectValues() if hasattr(slot
, '_isMosaicSlot') ]
327 text
+= ' %s' % slot
.SearchableText()
328 for block
in blocks
:
329 text
+= ' %s' % block
.SearchableText()
333 security
.declareProtected(ModifyPortalContent
, 'deleteBlock')
334 def deleteBlock(self
, blockId
, REQUEST
=None) :
335 """ Delete the blockId """
336 old_pos
= self
.getObjectPosition(blockId
)
338 redirectBlockId
= self
._objects
[old_pos
-1]['id']
340 redirectBlockId
= self
.getId()
342 self
.manage_delObjects([blockId
,])
345 # évite des appels répétitifs à reindexObject lorsque deleBlock est appelée
347 rootBlock
= self
._getRootBlock
()
348 rootBlock
.reindexObject()
349 return self
._redirectAfterEdit
(REQUEST
, blockId
= redirectBlockId
)
351 return redirectBlockId
353 security
.declareProtected(ModifyPortalContent
, 'deleteBlocks')
354 def deleteBlocks(self
, blockIds
, REQUEST
=None) :
355 """ Delete block id list"""
357 for blockId
in blockIds
:
358 redirectBlockId
= self
.deleteBlock(blockId
)
360 rootBlock
= self
._getRootBlock
()
361 rootBlock
.reindexObject()
363 return self
._redirectAfterEdit
(REQUEST
, rootBlock
= rootBlock
, blockId
= redirectBlockId
)
366 ## cut and paste methods
368 security
.declareProtected(ModifyPortalContent
, 'pushCp')
369 def pushCp(self
, blockId
, REQUEST
) :
370 """ push block in clipboard """
373 if REQUEST
.has_key('__cp') :
374 previousCp
= REQUEST
['__cp']
375 previousCp
= _cb_decode(previousCp
)
376 oblist
= previousCp
[1]
378 block
= getattr(self
, blockId
)
380 oblist
.append(m
.dump())
384 resp
=REQUEST
['RESPONSE']
385 resp
.setCookie('__cp', cp
, path
='/')
387 return self
._redirectAfterEdit
(REQUEST
, blockId
= blockId
)
389 security
.declareProtected(ModifyPortalContent
, 'pasteBlocks')
390 def pasteBlocks(self
, REQUEST
) :
391 """ check rules and paste blocks from cp """
393 if REQUEST
.has_key('__cp') :
396 mosTool
= getToolByName(self
, 'mosaic_tool')
397 app
= self
.getPhysicalRoot()
398 self_fti
= getattr(mosTool
, self
.portal_type
)
401 # paste element one by one for
402 # checking rules on every iteration
404 m
= loadMoniker(mdata
)
406 ob_type
= ob
.portal_type
407 ob_fti
= getattr(mosTool
, ob_type
)
408 isBlock
= (ob_fti
.meta_type
== 'Mosaic Block Information')
409 isRootBlock
= getattr(ob
, '_isRootBlock', 0) # a block witch have this attribute is a content type
411 if isBlock
and not isRootBlock
and ob_fti
.isConstructionAllowed(self
) :
412 # internal copy and paste handling
413 cp
= (0, [m
.dump(),])
415 self
.manage_pasteObjects(cb_copy_data
= cp
)
419 self
.flushCp(REQUEST
)
420 return self
._redirectAfterEdit
(REQUEST
, blockId
= self
.getId())
422 security
.declarePublic('flushCp')
423 def flushCp(self
, REQUEST
) :
424 """ Expire cp cookie """
425 REQUEST
.RESPONSE
.expireCookie('__cp', path
='/')
427 security
.declareProtected(ModifyPortalContent
, 'getCpInfos')
428 def getCpInfos(self
, REQUEST
) :
429 """ Return information about loaded objects in cp """
430 if REQUEST
.has_key('__cp') :
440 security
.declareProtected(ModifyPortalContent
, 'moveLeft')
441 def moveLeft(self
, blockId
, REQUEST
=None) :
442 """Move block left"""
443 move_dic
= self
._allowedMoves
(blockId
)
444 if not(move_dic
['global'] or move_dic
['rightLeft']) :
445 raise RuleError
, "It's not allowed to move this block in this context"
446 block
= getattr(self
, blockId
)
450 if REQUEST
is not None :
451 return self
._redirectAfterEdit
(REQUEST
, blockId
= blockId
)
455 security
.declareProtected(ModifyPortalContent
, 'moveRight')
456 def moveRight(self
, blockId
, REQUEST
=None) :
457 """Move block Right"""
458 move_dic
= self
._allowedMoves
(blockId
)
459 if not (move_dic
['global'] or move_dic
['rightLeft']) :
460 raise RuleError
, "It's not allowed to move this block in this context"
461 block
= getattr(self
, blockId
)
464 if REQUEST
is not None :
465 return self
._redirectAfterEdit
(REQUEST
, blockId
= blockId
)
468 security
.declareProtected(ModifyPortalContent
, 'moveUp')
469 def moveUp(self
, blockId
, REQUEST
=None) :
471 move_dic
= self
._allowedMoves
(blockId
)
472 if not(move_dic
['global'] or move_dic
['upDown']) :
473 raise RuleError
, "It's not allowed to move this block in this context"
474 self
.moveObjectsUp(blockId
)
475 if REQUEST
is not None :
476 return self
._redirectAfterEdit
(REQUEST
, blockId
= blockId
)
479 security
.declareProtected(ModifyPortalContent
, 'moveDown')
480 def moveDown(self
, blockId
, REQUEST
=None) :
481 """Move block left"""
482 move_dic
= self
._allowedMoves
(blockId
)
483 if not(move_dic
['global'] or move_dic
['upDown']) :
484 raise RuleError
, "It's not allowed to move this block in this context"
485 self
.moveObjectsDown(blockId
)
486 if REQUEST
is not None :
487 return self
._redirectAfterEdit
(REQUEST
, blockId
= blockId
)
489 security
.declareProtected(ModifyPortalContent
, 'movesUp')
490 def movesUp(self
, blockIds
= [], REQUEST
=None) :
492 for blockId
in blockIds
:
494 if REQUEST
is not None :
495 return self
._redirectAfterEdit
(REQUEST
, blockId
= blockId
)
497 security
.declareProtected(ModifyPortalContent
, 'movesDown')
498 def movesDown(self
, blockIds
= [], REQUEST
=None) :
499 """move blocks down"""
500 for blockId
in blockIds
:
501 self
.moveDown(blockId
)
502 if REQUEST
is not None :
503 return self
._redirectAfterEdit
(REQUEST
, blockId
= blockId
)
506 InitializeClass(MosaicBlock
)
508 def _reOrderBlocks(container
) :
509 # This method order blocks.
510 # It's useful when a left or right move or a block creation in a middle of a column happens.
511 blocks
= list(container
.objectValues(['Mosaic Block',]))
513 # get the maximum value for xpos attribute
514 blocks
.sort(lambda b1
, b2
: cmp(b1
.xpos
, b2
.xpos
))
517 cols
= blocks
[-1].xpos
522 for xpos
in range(cols
+ 1) :
523 colBlockList
= [ block
for block
in blocks
if block
.xpos
== xpos
]
524 colBlockListLength
= len(colBlockList
)
526 for blockIndex
in range(colBlockListLength
) :
527 block
= colBlockList
[blockIndex
]
528 container
.moveObjectToPosition(block
.getId(), blockPosition
)
532 def addMosaicBlock(dispatcher
, id, xpos
=0, beforeBlock
='', afterBlock
='') :
533 """Add a new mosaicBlock"""
534 parentBlock
= dispatcher
.Destination()
535 parentBlock
._setObject
(id, MosaicBlock(id, xpos
=xpos
))
537 position
= parentBlock
.getObjectPosition(beforeBlock
)
538 parentBlock
.moveObjectToPosition(id, position
)
541 position
= parentBlock
.getObjectPosition(afterBlock
)
542 parentBlock
.moveObjectToPosition(id, position
+ 1)
544 try : _reOrderBlocks(parentBlock
)