root/CPS3/products/CPSSchemas/trunk/ExtendedWidgets.py

Revision 53555, 66.8 kB (checked in by gracinet, 4 months ago)

#1961: factored cid parts code out of render() for easy subclassing (Photo)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1 # (C) Copyright 2003-2009 Nuxeo SA <http://nuxeo.com>
2 # Authors:
3 # Florent Guillaume <fg@nuxeo.com>
4 # M.-A. Darche <madarche@nuxeo.com>
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License version 2 as published
8 # by the Free Software Foundation.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
18 # 02111-1307, USA.
19 #
20 # $Id$
21 """Extended widget types.
22
23 Definition of extended widget types.
24 """
25
26 from logging import getLogger
27 import warnings
28 import zipfile
29 import operator
30 import os
31 from tempfile import mkstemp
32 from cgi import escape
33 from re import match
34
35 from Globals import InitializeClass
36 from Acquisition import aq_base, aq_parent, aq_inner
37 from DateTime.DateTime import DateTime
38
39 from Products.PythonScripts.standard import newline_to_br
40 from Products.PythonScripts.standard import structured_text
41 from reStructuredText import HTML
42
43 from Products.CMFCore.utils import getToolByName
44
45 from Products.CPSUtil.html import XhtmlSanitizer
46 from Products.CPSSchemas.Widget import CPSWidget
47 from Products.CPSSchemas.Widget import widgetRegistry
48 from Products.CPSSchemas.BasicWidgets import CPSSelectWidget
49 from Products.CPSSchemas.BasicWidgets import CPSMultiSelectWidget
50 from Products.CPSSchemas.BasicWidgets import CPSStringWidget
51 from Products.CPSSchemas.BasicWidgets import CPSImageWidget
52 from Products.CPSSchemas.BasicWidgets import CPSFileWidget
53 from Products.CPSSchemas.BasicWidgets import renderHtmlTag
54 from Products.CPSSchemas.BasicWidgets import CPSProgrammerCompoundWidget
55 from Products.CPSSchemas.swfHeaderData import analyseContent
56
57 ##################################################
58 # previously named CPSTextAreaWidget in BasicWidget r1.78
59 class CPSTextWidget(CPSStringWidget):
60     """Text widget."""
61     meta_type = 'Text Widget'
62
63     # Warning if configurable the widget require field[1] and field[2]
64     field_types = ('CPS String Field',  # text value
65                    'CPS String Field',  # render_position if configurable
66                    'CPS String Field')  # render_format if configurable
67     field_inits = ({'is_searchabletext': 1,}, {}, {})
68
69     _properties = CPSWidget._properties + (
70         {'id': 'width', 'type': 'int', 'mode': 'w',
71          'label': 'Width'},
72         {'id': 'height', 'type': 'int', 'mode': 'w',
73          'label': 'Height'},
74         {'id': 'size_max', 'type': 'int', 'mode': 'w',
75          'label': 'Max Size'},
76
77         {'id': 'xhtml_sanitize', 'type': 'selection', 'mode': 'w',
78          'select_variable': 'all_xhtml_sanitize_options',
79          'label': 'XHTML sanitize the content'},
80         {'id': 'xhtml_sanitize_system', 'type': 'string', 'mode': 'w',
81          'label': 'XHTML sanitize through system command line'},
82         {'id': 'file_uploader', 'type': 'boolean', 'mode': 'w',
83          'label': 'Add a file uploader to the widget UI'},
84
85         {'id': 'html_editor_type', 'type': 'string', 'mode': 'w',
86          'label': 'The name of the HTML editor to use'},
87
88         {'id': 'html_editor_position', 'type': 'selection', 'mode': 'w',
89          'select_variable': 'all_html_editor_positions',
90          'label': 'HTML rich text editor position'},
91         {'id': 'render_format', 'type': 'selection', 'mode': 'w',
92          'select_variable': 'all_render_formats',
93          'label': 'Render format'},
94         {'id': 'render_position', 'type': 'selection', 'mode': 'w',
95          'select_variable': 'all_render_positions',
96          'label': 'Render position'},
97         {'id': 'configurable', 'type': 'selection', 'mode': 'w',
98          'select_variable': 'all_configurable',
99          'label': 'What is user configurable (require extra fields)'},
100         )
101     all_xhtml_sanitize_options = ['no', 'builtin', 'system']
102     all_configurable = ['nothing', 'position', 'format', 'position and format']
103     all_render_positions = ['normal', 'col_left', 'col_right']
104     all_render_formats = ['text', 'html', 'rst']
105     all_html_editor_positions = ['popup', 'embedded']
106
107     width = 40
108     height = 5
109     size_max = 2*1024*1024
110     xhtml_sanitize = False
111     # Notes about using tidy :
112     # * tidy doesn't know explicitly about the latin9 encoding but specifying
113     #   latin1 works well with latin9 encoded content.
114     # * force-output makes tidy produce an output even if errors were found.
115     # * show-body-only outputs only the content of the body tag.
116     # * write-back modifies the file in place.
117     xhtml_sanitize_system = 'tidy -indent -wrap 80 --input-encoding latin1 --output-encoding latin1 --force-output yes --clean yes --drop-font-tags yes --drop-proprietary-attributes yes --show-body-only yes --write-back yes --output-xhtml yes --show-errors 0 --show-warnings no --hide-comments no %s 2>/dev/null'
118     file_uploader = False
119
120     # Possible values are "tinymce" and "fckeditor"
121     html_editor_type = 'tinymce'
122
123     render_position = all_render_positions[0]
124     render_format = all_render_formats[0]
125     html_editor_position = all_html_editor_positions[0]
126     configurable = 'nothing'
127     input_encoding = 'iso-8859-15'
128     output_encoding = 'iso-8859-15'
129
130     # Associating the widget label with an input area to improve the widget
131     # accessibility.
132     has_input_area = True
133
134     xhtml_sanitizer = XhtmlSanitizer()
135
136     def prepare(self, datastructure, **kw):
137         """Prepare datastructure from datamodel."""
138         datamodel = datastructure.getDataModel()
139         widget_id = self.getWidgetId()
140         datastructure[widget_id] = str(datamodel[self.fields[0]])
141         rposition = self.render_position
142         rformat = self.render_format
143         if self.configurable != 'nothing':
144             if len(self.fields) > 1:
145                 v  = datamodel[self.fields[1]]
146                 if v in self.all_render_positions:
147                     rposition = v
148             if len(self.fields) > 2:
149                 v = datamodel[self.fields[2]]
150                 if v in self.all_render_formats:
151                     rformat = v
152         datastructure[widget_id + '_fileupload'] = None
153         datastructure[widget_id + '_rposition'] = rposition
154         datastructure[widget_id + '_rformat'] = rformat
155
156     def validate(self, datastructure, **kw):
157         """Validate datastructure and update datamodel."""
158         widget_id = self.getWidgetId()
159         file_upload = datastructure.get(widget_id + '_fileupload', None)
160         file_upload_valid = False
161         if file_upload is not None:
162             ms = self.size_max
163             file_upload.seek(0)
164             read_size = len(file_upload.read(ms + 1))
165             if read_size > ms:
166                 # Size is expressed in human readable value
167                 max_size_str = self.getHumanReadableSize(ms)
168                 err = 'cpsschemas_err_file_too_big ${max_size}'
169                 err_mapping = {'max_size': max_size_str}
170                 return self.doesNotValidate(err, err_mapping,
171                                             file, datastructure)
172             file_upload.seek(0)
173             value = file_upload.read()
174             read_size = len(value)
175             if read_size > 0:
176                 file_upload_valid = True
177         if not file_upload_valid:
178             value = datastructure[widget_id]
179         err, v = self._extractValue(value)
180
181         if err:
182             datastructure.setError(widget_id, err)
183             datastructure[widget_id] = v
184         else:
185             datamodel = datastructure.getDataModel()
186             # Validating rposition and rformat entered by the user and
187             # correcting them if necessary.
188             if self.configurable != 'nothing':
189                 if len(self.fields) > 1:
190                     rposition = datastructure[widget_id + '_rposition']
191                     if rposition and rposition in self.all_render_positions:
192                         datamodel[self.fields[1]] = rposition
193                 if len(self.fields) > 2:
194                     rformat = datastructure[widget_id + '_rformat']
195                     if rformat and rformat in self.all_render_formats:
196                         datamodel[self.fields[2]] = rformat
197             else:
198                 # Defaulting to the widget property since no fields are used to
199                 # store the format or the position.
200                 rformat = self.render_format
201             if rformat == 'html':
202                 if self.xhtml_sanitize == 'builtin':
203                     self.xhtml_sanitizer.reset()
204                     self.xhtml_sanitizer.feed(v)
205                     v = self.xhtml_sanitizer.getResult()
206                 elif self.xhtml_sanitize == 'system':
207                     file_to_clean_fd, file_to_clean_path = mkstemp(
208                         suffix=".xhtml",
209                         prefix="cps-schemas",
210                         )
211                     file_to_clean = os.fdopen(file_to_clean_fd, 'w')
212                     file_to_clean.write(v)
213                     file_to_clean.close()
214                     os.system(self.xhtml_sanitize_system % file_to_clean_path)
215                     file_to_clean = open(file_to_clean_path)
216                     v = file_to_clean.read()
217                     file_to_clean.close()
218                     os.remove(file_to_clean_path)
219
220             datamodel[self.fields[0]] = v
221             if file_upload_valid or self.xhtml_sanitize:
222                 # If the file_upload is valid we update the datastructure so
223                 # that the immediate view after the modification has been done
224                 # shows the content of the file upload instead of the old value.
225                 self.prepare(datastructure)
226         return not err
227
228     def render(self, mode, datastructure, **kw):
229         """Render in mode from datastructure."""
230         render_method = 'widget_text_render'
231         meth = getattr(self, render_method, None)
232         if meth is None:
233             raise RuntimeError("Unknown Render Method %s for widget type %s"
234                                % (render_method, self.getId()))
235         widget_id = self.getWidgetId()
236         value = datastructure[widget_id]
237         rposition = datastructure[widget_id + '_rposition']
238         rformat = datastructure[widget_id + '_rformat']
239         if mode == 'view':
240             if rformat == 'text':
241                 value = newline_to_br(escape(value))
242             elif rformat == 'html':
243                 pass
244             elif rformat == 'rst':
245                 value = HTML(value,
246                              output_encoding=self.output_encoding,
247                              input_encoding=self.input_encoding,
248                              initial_header_level=2, report_level=0)
249             # The pre render format is not a proposed choice in the UI anymore.
250             # BBB compatibility code, will be removed in CPS 3.5.0.
251             elif rformat == 'pre':
252                 value = '<pre>' + escape(value) + '</pre>'
253             # The stx render format is not a proposed choice in the UI anymore.
254             # BBB compatibility code, will be removed in CPS 3.5.0.
255             elif rformat == 'stx':
256                 value = structured_text(value)
257             else:
258                 RuntimeError("unknown render_format '%s' for '%s'" %
259                              (rformat, self.getId()))
260         return meth(mode=mode, datastructure=datastructure, value=value,
261                     file_uploader=self.file_uploader,
262                     html_editor_type=self.html_editor_type,
263                     render_position=rposition, render_format=rformat,
264                     html_editor_position=self.html_editor_position,
265                     configurable=str(self.configurable))
266
267 InitializeClass(CPSTextWidget)
268
269 widgetRegistry.register(CPSTextWidget)
270
271 ##################################################
272 # previously named CPSDateWidget in BasicWidget r1.78
273 class CPSDateTimeWidget(CPSWidget):
274     """DateTime widget.
275
276     A widget that displays and makes it possible to edit a DateTime object.
277     View and edit mode can be done in the ISO 8601 date format (YYYY-mm-dd)
278     or in a localized format (mm/dd/YYYY for English and dd/mm/YYYY for the rest
279     of the world) cf. http://www.w3.org/TR/NOTE-datetime
280     """
281     meta_type = 'DateTime Widget'
282
283     field_types = ('CPS DateTime Field',)
284
285     _properties = CPSWidget._properties + (
286         {'id': 'view_format', 'type': 'string', 'mode': 'w',
287          'label': 'View format (short, medium or long)'},
288         {'id': 'time_setting', 'type': 'boolean', 'mode': 'w',
289          'label': 'Enabling the setting of time of the day'},
290         {'id': 'time_hour_default', 'type': 'string', 'mode': 'w',
291          'label': 'default hour for time'},
292         {'id': 'time_minutes_default', 'type': 'string', 'mode': 'w',
293          'label': 'default minutes for time'},
294         )
295     # When will CPS default to the more sensible ISO 8601 date format?
296     #view_format = 'iso8601_medium_easy'
297     view_format = 'medium'
298     time_setting = 1
299     time_hour_default = '12'
300     time_minutes_default = '00'
301
302     # Associating the widget label with an input area to improve the widget
303     # accessibility.
304     has_input_area = True
305
306     def getDateTimeInfo(self, value, mode=None):
307         """Return a tuple that is used to set the datastructure
308
309         Called in prepare when mode is not known, and called again in render
310         when mode is known, because a default value has to be provided in edit
311         mode (current date time).
312         """
313         # default values
314         date = ''
315         hour = ''
316         minute = ''
317
318         # value is set to current time if:
319         # - value is not alrady set and
320         # - widget is required an
321         # - mode is 'edit' or 'create'
322         if not value and self.is_required and mode in ['edit', 'create']:
323             value = DateTime()
324
325         if value == 'None':
326             value = None
327         if value:
328             # Backward compatibility test, this logic is not used by the
329             # current code.
330             if isinstance(value, str):
331                 value = DateTime(value)
332             d = str(value.day())
333             m = str(value.month())
334             y = str(value.year())
335             if self.view_format.startswith('iso8601'):
336                 date = '%s-%s-%s' % (y, m, d)
337             else:
338                 locale = self.translation_service.getSelectedLanguage()
339                 if locale in ('en', 'hu'):
340                     date = '%s/%s/%s' % (m, d, y)
341                 else:
342                     date = '%s/%s/%s' % (d, m, y)
343             hour = str(value.h_24())
344             minute = str(value.minute())
345
346         # if hour and minute are not set, set default values
347         hour = hour or self.time_hour_default
348         minute = minute or self.time_minutes_default
349
350         return (value, date, hour, minute)
351
352     def prepare(self, datastructure, **kw):
353         """Prepare datastructure from datamodel."""
354         datamodel = datastructure.getDataModel()
355         v = datamodel[self.fields[0]]
356
357         # get date time info, mode is not known here
358         v, date, hour, minute = self.getDateTimeInfo(v, mode=None)
359
360         widget_id = self.getWidgetId()
361         datastructure[widget_id] = v
362         datastructure[widget_id + '_date'] = date
363         datastructure[widget_id + '_hour'] = hour or self.time_hour_default
364         datastructure[widget_id + '_minute'] = minute or self.time_minutes_default
365
366     def validate(self, datastructure, **kw):
367         """Validate datastructure and update datamodel."""
368         datamodel = datastructure.getDataModel()
369         field_id = self.fields[0]
370         widget_id = self.getWidgetId()
371
372         date = datastructure[widget_id + '_date'].strip()
373         hour = datastructure[widget_id + '_hour'].strip() or \
374                self.time_hour_default
375         minute = datastructure[widget_id + '_minute'].strip() or \
376                  self.time_minutes_default
377
378         if not (date):
379             if self.is_required:
380                 datastructure[widget_id] = ''
381                 datastructure.setError(widget_id, 'cpsschemas_err_required')
382                 return 0
383             else:
384                 datamodel[field_id] = None
385                 return 1
386
387         if self.view_format.startswith('iso8601'):
388             if match(r'^[0-9]+-[0-9]{2}-[0-9]{2}', date) is not None:
389                 y, m, d = date.split('-')
390             else:
391                 datastructure.setError(widget_id, 'cpsschemas_err_date')
392                 return 0
393         else:
394             if match(r'^[0-9]?[0-9]/[0-9]?[0-9]/[0-9]{2,4}$', date) is not None:
395                 locale = self.translation_service.getSelectedLanguage()
396                 if locale in ('en', 'hu'):
397                     m, d, y = date.split('/')
398                 else:
399                     d, m, y = date.split('/')
400             else:
401                 datastructure.setError(widget_id, 'cpsschemas_err_date')
402                 return 0
403
404         try:
405             v = DateTime(int(y), int(m), int(d), int(hour), int(minute))
406         except (ValueError, TypeError, DateTime.DateTimeError,
407                 DateTime.SyntaxError, DateTime.DateError):
408             datastructure.setError(widget_id, 'cpsschemas_err_date')
409             return 0
410         else:
411             datastructure[widget_id] = v
412             datamodel[field_id] = v
413             return 1
414
415     def render(self, mode, datastructure, **kw):
416         """Render in mode from datastructure."""
417         render_method = 'widget_datetime_render'
418         meth = getattr(self, render_method, None)
419         if meth is None:
420             raise RuntimeError("Unknown Render Method %s for widget type %s"
421                                % (render_method, self.getId()))
422
423         # XXX AT: datastructure has to be set again here, in case we're in edit
424         # or create mode, because a default value has to be provided.
425         if mode in ['edit', 'create']:
426             datamodel = datastructure.getDataModel()
427             v = datamodel[self.fields[0]]
428             v, date, hour, minute = self.getDateTimeInfo(v, mode=mode)
429             widget_id = self.getWidgetId()
430             datastructure[widget_id] = v
431             datastructure[widget_id + '_date'] = date
432             datastructure[widget_id + '_hour'] = hour or self.time_hour_default
433             datastructure[widget_id + '_minute'] = minute or self.time_minutes_default
434
435         return meth(mode=mode, datastructure=datastructure)
436
437
438 InitializeClass(CPSDateTimeWidget)
439
440 widgetRegistry.register(CPSDateTimeWidget)
441
442 ##################################################
443 class CPSAttachedFileWidget(CPSFileWidget):
444     """AttachedFile widget."""
445     meta_type = 'AttachedFile Widget'
446
447     field_types = ('CPS File Field',   # File
448                    'CPS String Field', # Plain text for indexing (optional)
449                    'CPS File Field',   # Preview (HTML, optional)
450                    'CPS SubObjects Field',)
451
452     field_inits = ({'is_searchabletext': 0,
453                     'suffix_text': '_f1', # _f# are autocomputed field ext
454                     'suffix_html': '_f2',
455                     'suffix_html_subfiles': '_f3',
456                     },
457                    {'is_searchabletext': 1}, {}, {},
458                    )
459
460     _properties = CPSFileWidget._properties + (
461         {'id': 'display_html_preview', 'type': 'boolean', 'mode': 'w',
462          'label': 'Display link to HTML preview in view mode'},
463         {'id': 'display_printable_version', 'type': 'boolean', 'mode': 'w',
464          'label': 'Display link to printable version in view mode'},
465         {'id': 'allowed_suffixes', 'type': 'tokens', 'mode': 'w',
466          'label': 'Allowed file suffixes (ex: .html .odt)'},
467         )
468     display_html_preview = True
469     display_printable_version = True
470     allowed_suffixes = []
471
472     def prepare(self, datastructure, **kw):
473         """Prepare datastructure from datamodel."""
474         CPSFileWidget.prepare(self, datastructure, **kw)
475         datamodel = datastructure.getDataModel()
476         widget_id = self.getWidgetId()
477
478         # Compute preview info for widget.
479         if len(self.fields) > 2 and datamodel.get(self.fields[2]) is not None:
480             preview_id = self.fields[2]
481         else:
482             preview_id = None
483         datastructure[widget_id + '_preview'] = preview_id
484
485     def checkFileName(self, fileid, mimetype):
486         if self.allowed_suffixes:
487             base, suffix = os.path.splitext(fileid)
488             if suffix not in self.allowed_suffixes:
489                 err = 'cpsschemas_err_file_bad_suffix ${allowed_file_suffixes}'
490                 err_mapping = {'allowed_file_suffixes':
491                                ' '.join(self.allowed_suffixes)}
492                 return err, err_mapping
493         return '', {}
494
495     def render(self, mode, datastructure, **kw):
496         """Render in mode from datastructure."""
497         render_method = 'widget_attachedfile_render'
498         meth = getattr(self, render_method, None)
499         if meth is None:
500             raise RuntimeError("Unknown Render Method %s for widget type %s"
501                                % (render_method, self.getId()))
502         file_info = self.getFileInfo(datastructure)
503
504         return meth(mode=mode, datastructure=datastructure,
505                     **file_info)
506
507 InitializeClass(CPSAttachedFileWidget)
508
509 widgetRegistry.register(CPSAttachedFileWidget)
510
511 ##################################################
512
513 class CPSZippedHtmlWidget(CPSAttachedFileWidget):
514     """CPS ZippedHtml widget.
515
516     A zip file that contains html which can be viewed online.
517     Use index.html or any html file from the zip as preview page.
518     """
519
520     meta_type = 'ZippedHtml Widget'
521
522     size_max = 1024*1024
523
524     # FIXME checkFileName() from ancestor is never called after changeset [30791]
525     # We need to fix it over there.
526     #allowed_suffixes = ['.zip']
527
528     def _is_zipfile(self, datastructure, **kw):
529         # Check the zip file validity
530         choice = datastructure[self.getWidgetId()+'_choice']
531         if choice == 'change':
532             validated = False
533             file_upload = datastructure.get(self.getWidgetId())
534             try:
535                 zf = zipfile.ZipFile(file_upload, 'r')
536             except zipfile.BadZipfile:
537                 pass
538             else:
539                 if zf.testzip() is None:
540                     validated = True
541                     zf.close()
542             return validated
543         return True
544
545     def validate(self, datastructure, **kw):
546         # Validate datastructure and update datamodel.
547         return (CPSAttachedFileWidget.validate(self, datastructure, **kw) and
548                 self._is_zipfile(datastructure, **kw))
549
550     def _getIndexPath(self, datastructure):
551         file_upload = datastructure.get(self.getWidgetId())
552         # Creation time
553         if not file_upload:
554             return ''
555         # Here the zipfile has been validated already.
556         zf = zipfile.ZipFile(file_upload, 'r')
557         all_files = [info.filename for info in zf.infolist()]
558         html_files = [f for f in all_files
559                       if (f.lower().endswith('.html') or
560                           f.lower().endswith('.htm'))]
561         index_files = [f for f in html_files
562                        if f.lower().find('index.htm') >= 0]
563         index_path = None
564         if index_files:
565             index_path = index_files[0]
566         elif html_files:
567             index_path = html_files[0]
568         zf.close()
569         return index_path
570
571     def render(self, mode, datastructure, **kw):
572         render_method = 'widget_zippedhtml_render'
573         meth = getattr(self, render_method, None)
574         if meth is None:
575             raise RuntimeError("Unknown Render Method %s for widget type %s"
576                                % (render_method, self.getId()))
577         file_info = self.getFileInfo(datastructure)
578         file_info['index_path'] = self._getIndexPath(datastructure)
579         return meth(mode=mode, datastructure=datastructure, **file_info)
580
581 InitializeClass(CPSZippedHtmlWidget)
582
583 widgetRegistry.register(CPSZippedHtmlWidget)
584
585 #################################################
586
587 class CPSRichTextEditorWidget(CPSWidget):
588     """Rich Text Editor widget.
589
590     THIS WIDGET SHOULD NOT BE USED AND IS DEPRECATED.
591
592     Use the Text Widget which provides both HTML and text formats.
593     """
594     meta_type = 'Rich Text Editor Widget'
595
596     field_types = ('CPS String Field',)
597     field_inits = ({'is_searchabletext': 1,},)
598
599     width = 40
600     height = 5
601     _properties = CPSWidget._properties + (
602         {'id': 'width', 'type': 'int', 'mode': 'w',
603          'label': 'Width'},
604         {'id': 'height', 'type': 'int', 'mode': 'w',
605          'label': 'Height'},
606         )
607
608     def prepare(self, datastructure, **kw):
609         """Prepare datastructure from datamodel."""
610         datamodel = datastructure.getDataModel()
611         datastructure[self.getWidgetId()] = datamodel[self.fields[0]]
612
613     def validate(self, datastructure, **kw):
614         """Validate datastructure and update datamodel."""
615         value = datastructure[self.getWidgetId()]
616         try:
617             v = str(value)
618         except ValueError:
619             datastructure.setError(self.getWidgetId(),
620                                    "cpsschemas_err_textarea")
621             ok = 0
622         else:
623             datamodel = datastructure.getDataModel()
624             datamodel[self.fields[0]] = v
625             ok = 1
626         return ok
627
628     def render(self, mode, datastructure, **kw):
629         """Render in mode from datastructure."""
630         warnings.warn("The Rich Text Editor Widget (%s/%s) is deprecated "
631                       "and will be removed in CPS 3.5.0. Use a Text Widget "
632                       "instead" % (aq_parent(aq_inner(self)).getId(),
633                                    self.getWidgetId()), DeprecationWarning)
634         value = datastructure[self.getWidgetId()]
635         if mode == 'view':
636             # Return HTML directly
637             return value
638         elif mode == 'edit':
639             render_method = 'widget_rte_render'
640             meth = getattr(self, render_method, None)
641             if meth is None:
642                 raise RuntimeError("Unknown Render Method %s for widget type %s"
643                                    % (render_method, self.getId()))
644             value = datastructure[self.getWidgetId()]
645             if hasattr(aq_base(value), 'getId'):
646                 current_name = value.getId()
647             else:
648                 current_name = '-'
649             return meth(mode=mode, datastructure=datastructure,
650                         current_name=current_name)
651         raise RuntimeError("unknown mode %s" % mode)
652
653 InitializeClass(CPSRichTextEditorWidget)
654
655 widgetRegistry.register(CPSRichTextEditorWidget)
656
657 ##########################################
658
659 class CPSExtendedSelectWidget(CPSSelectWidget):
660     """Extended Select widget."""
661     meta_type = 'ExtendedSelect Widget'
662
663     def render(self, mode, datastructure, **kw):
664         """Render in mode from datastructure."""
665
666         if mode == 'view':
667             return CPSSelectWidget.render(self, mode, datastructure)
668
669         elif mode == 'edit':
670             render_method = 'widget_extendedselect_render'
671
672             meth = getattr(self, render_method, None)
673             if meth is None:
674                 raise RuntimeError("Unknown Render Method %s for widget type %s"
675                                    % (render_method, self.getId()))
676             return meth(mode=mode, datastructure=datastructure,
677                         vocabulary=self._getVocabulary(datastructure))
678
679         else:
680             raise RuntimeError('unknown mode %s' % mode)
681
682 InitializeClass(CPSExtendedSelectWidget)
683
684 widgetRegistry.register(CPSExtendedSelectWidget)
685
686 ##########################################
687
688 class CPSInternalLinksWidget(CPSWidget):
689     """Internal Links widget."""
690     meta_type = 'InternalLinks Widget'
691
692     field_types = ('CPS String List Field',)
693     field_inits = ({'is_searchabletext': 1,},)
694
695     _properties = CPSWidget._properties + (
696         {'id': 'new_window', 'type': 'boolean', 'mode': 'w',
697          'label': 'Display in a new window'},
698         {'id': 'size', 'type': 'int', 'mode': 'w',
699          'label': 'Links displayed'},
700         {'id': 'absolute', 'type': 'boolean', 'mode': 'w',
701          'label': 'Links displayed with absolute URL'},
702         )
703     new_window = 0
704     size = 0
705     absolute = False
706
707     def prepare(self, datastructure, **kw):
708         """Prepare datastructure from datamodel."""
709         datamodel = datastructure.getDataModel()
710         datastructure[self.getWidgetId()] = datamodel[self.fields[0]]
711
712     def validate(self, datastructure, **kw):
713         """Validate datastructure and update datamodel."""
714         widget_id = self.getWidgetId()
715         value = datastructure[widget_id]
716         err = 0
717         if self.is_required and (value == [] or value == ['']):
718             err = 'cpsschemas_err_required'
719         v = []
720
721
722         for line in value:
723             if line.strip():
724                 v.append(line)
725
726         if err:
727             datastructure.setError(widget_id, err)
728         else:
729             datamodel = datastructure.getDataModel()
730             datamodel[self.fields[0]] = v
731             self.prepare(datastructure)
732
733         return not err
734
735     def render(self, mode, datastructure, **kw):
736         """Render in mode from datastructure."""
737         if mode not in ('view', 'edit'):
738             raise RuntimeError('unknown mode %s' % mode)
739
740         render_method = 'widget_internallinks_render'
741         meth = getattr(self, render_method, None)
742
743         return meth(mode=mode, datastructure=datastructure)
744
745 InitializeClass(CPSInternalLinksWidget)
746
747 widgetRegistry.register(CPSInternalLinksWidget)
748
749 ##################################################
750
751 class CPSPhotoWidget(CPSImageWidget):
752     """Photo widget."""
753     meta_type = 'Photo Widget'
754
755     field_types = ('CPS Image Field',   # Image
756                    'CPS String Field',  # Caption
757                    'CPS String Field',  # render_position if configurable
758                    'CPS Image Field',   # Original photo
759                    'CPS String Field',  # Title
760                    'CPS String Field',  # Alternate text for accessibility
761                    )
762     field_inits = ({}, {'is_searchabletext': 1,}, {}, {}, {}, {})
763
764     _properties = CPSImageWidget._properties + (
765         {'id': 'render_position', 'type': 'selection', 'mode': 'w',
766          'select_variable': 'all_render_positions',
767          'label': 'Render position'},
768         {'id': 'configurable', 'type': 'selection', 'mode': 'w',
769          'select_variable': 'all_configurable',
770          'label': 'What is user configurable, require extra fields'},
771         {'id': 'keep_original', 'type': 'boolean', 'mode': 'w',
772          'label': 'Keep original image'},
773         )
774     all_configurable = ['nothing', 'position']
775     all_render_positions = ['left', 'center', 'right']
776
777     allow_resize = True
778     render_position = all_render_positions[0]
779     configurable = all_configurable[0]
780     keep_original = True
781
782     def prepare(self, datastructure, **kw):
783         """Prepare datastructure from datamodel."""
784         CPSImageWidget.prepare(self, datastructure, **kw)
785         datamodel = datastructure.getDataModel()
786         widget_id = self.getWidgetId()
787
788         if len(self.fields) > 1:
789             datastructure[widget_id + '_subtitle'] = datamodel[self.fields[1]]
790         else:
791             datastructure[widget_id + '_subtitle'] = ''
792
793         rposition = self.render_position
794         if self.configurable != 'nothing':
795             if len(self.fields) > 2:
796                 v = datamodel[self.fields[2]]
797                 if v in self.all_render_positions:
798                     rposition = v
799         datastructure[widget_id + '_rposition'] = rposition
800
801         if self.keep_original and len(self.fields) > 3:
802             datastructure[widget_id + '_resize_kept'] = ''
803
804         title = ''
805         if len(self.fields) > 4:
806             title = datamodel[self.fields[4]]
807         datastructure[widget_id + '_title'] = title
808
809         alt = ''
810         if len(self.fields) > 5:
811             alt = datamodel[self.fields[5]]
812             # Defaulting to the file name if there is an image file and if no
813             # alt has been given yet. This is the case when the document is
814             # created.
815             if not alt:
816                 alt = datastructure[widget_id + '_filename']
817         datastructure[widget_id + '_alt'] = alt
818
819     def otherProcessing(self, choice, datastructure):
820         datamodel = datastructure.getDataModel()
821         widget_id = self.getWidgetId()
822
823         # Caption
824         if len(self.fields) > 1:
825             subtitle = datastructure[widget_id + '_subtitle']
826             datamodel[self.fields[1]] = subtitle
827
828         # Title
829         if len(self.fields) > 4:
830             title = datastructure[widget_id + '_title']
831             datamodel[self.fields[4]] = title
832
833         # Alt
834         if len(self.fields) > 5:
835             alt = datastructure[widget_id + '_alt']
836             datamodel[self.fields[5]] = alt
837
838         # Position
839         if self.configurable != 'nothing' and len(self.fields) > 2:
840             rposition = datastructure[widget_id + '_rposition']
841             if rposition and rposition in self.all_render_positions:
842                 datamodel[self.fields[2]] = rposition
843
844         # Resize
845         if choice != 'resize':
846             return
847         if not self.canKeepOriginal():
848             return
849         image = datamodel[self.fields[0]]
850         original_image = datamodel[self.fields[3]]
851         if original_image is None and image is None:
852             return
853
854         if original_image is None:
855             original_image = image
856             datamodel[self.fields[3]] = original_image
857
858         filename = original_image.title
859         resize_op = datastructure[widget_id + '_resize_kept']
860         image = self.getResizedImage(original_image, filename, resize_op)
861         datamodel[self.fields[0]] = image
862
863     def canKeepOriginal(self):
864         return (self.keep_original and
865                 self.allow_resize and
866                 len(self.fields) > 3)
867
868     def maybeKeepOriginal(self, image, datastructure):
869         if self.canKeepOriginal():
870             datamodel = datastructure.getDataModel()
871             datamodel[self.fields[3]] = image
872
873     def render(self, mode, datastructure, **kw):
874         """Render in mode from datastructure."""
875         render_method = 'widget_photo_render'
876         meth = getattr(self, render_method, None)
877         if meth is None:
878             raise RuntimeError("Unknown Render Method %s for widget type %s"
879                                % (render_method, self.getId()))
880
881         widget_id = self.getWidgetId()
882         rposition = datastructure[widget_id + '_rposition']
883         subtitle = datastructure[widget_id + '_subtitle']
884
885         img_info = self.getImageInfo(datastructure, dump_cid_parts=True, **kw)
886         return meth(mode=mode, datastructure=datastructure,
887                     subtitle=subtitle,
888                     render_position=rposition,
889                     configurable=str(self.configurable),
890                     **img_info)
891
892
893 InitializeClass(CPSPhotoWidget)
894
895 widgetRegistry.register(CPSPhotoWidget)
896
897 ##################################################
898
899 class CPSGenericSelectWidget(CPSSelectWidget):
900     """Generic Select widget."""
901     meta_type = 'Generic Select Widget'
902
903     _properties = CPSSelectWidget._properties + (
904         {'id': 'render_format', 'type': 'selection', 'mode': 'w',
905          'select_variable': 'render_formats',
906          'label': 'Render format'},
907         # Provide an 'other' option where free input is accepted
908         # (ignored if render format is 'select')
909         {'id': 'other_option', 'type': 'boolean', 'mode':'w',
910          'label': "Provide an 'other' option"},
911         {'id': 'other_option_display_width', 'type': 'int', 'mode': 'w',
912          'label': "'other' option display width"},
913         {'id': 'other_option_size_max', 'type': 'int', 'mode': 'w',
914          'label': "'other' option maximum input width"},
915         # Enables the possibility to add blank values to vocabulary just to
916         # change the way the list is presented (using items like 'choose a
917         # category' or '------------' to separate items) and not affect the way
918         # the value will be validated if the widget is required.
919         # Before this, if the widget was required, default behavior was to
920         # accept blank values if they were in the vocabulary (e.g
921         # blank_value_ok_if_required = 1)
922         {'id': 'blank_value_ok_if_required', 'type': 'boolean', 'mode':'w',
923          'label': "Accept blank values when validating"},
924         {'id': 'onchange', 'type': 'string', 'mode':'w',
925          'label': "onchange attribute (edit mode only)"}
926         )
927     render_formats = ['select', 'radio']
928
929     render_format = render_formats[0]
930     other_option = 0
931     other_option_display_width = 20
932     other_option_size_max = 0
933     blank_value_ok_if_required = 1
934     onchange = ''
935
936     # BBB for [46171]. Remove this once an upgrade step has been written
937     sorted = False
938
939     def prepare(self, datastructure, **kw):
940         """Prepare datastructure from datamodel."""
941         datamodel = datastructure.getDataModel()
942         value = datamodel[self.fields[0]]
943         if isinstance(value, (list, tuple)):
944             if len(value):
945                 value = value[0]
946             else:
947                 value = ''
948         datastructure[self.getWidgetId()] = value
949
950     def validate(self, datastructure, **kw):
951         """Validate datastructure and update datamodel."""
952         widget_id = self.getWidgetId()
953         value = datastructure[widget_id]
954         try:
955             v = str(value)
956         except ValueError:
957             datastructure.setError(widget_id, "cpsschemas_err_select")
958             return 0
959         vocabulary = self._getVocabulary(datastructure)
960         if len(value)>0:
961             if not vocabulary.has_key(value):
962                 if self.render_format == 'select' or not self.other_option:
963                     datastructure.setError(widget_id, "cpsschemas_err_select")
964                     return 0
965         else:
966             if self.is_required:
967                 # set error unless vocabulary holds blank values and
968                 # blank_value_ok_if_required is set to 1
969                 if vocabulary.has_key(value):
970                     if not self.blank_value_ok_if_required:
971                         datastructure.setError(widget_id, "cpsschemas_err_required")
972                         return 0
973                 else:
974                     datastructure.setError(widget_id, "cpsschemas_err_required")
975                     return 0
976         datamodel = datastructure.getDataModel()
977         datamodel[self.fields[0]] = v
978         return 1
979
980     def render(self, mode, datastructure, **kw):
981         """Render in mode from datastructure."""
982         widget_id = self.getWidgetId()
983         value = datastructure[widget_id]
984         # allow int values to work
985         if value is not None:
986             value = str(value)
987         vocabulary = self._getVocabulary(datastructure)
988         portal = getToolByName(self, 'portal_url').getPortalObject()
989         cpsmcat = portal.translation_service
990         if mode == 'view':
991             if not vocabulary.has_key(value):
992                 # for free input
993                 if value is not None:
994                     return escape(value)
995                 else:
996                     return ''
997             else:
998                 if getattr(self, 'translated', None):
999                     return escape(cpsmcat(vocabulary.getMsgid(value, value)).encode('ISO-8859-15', 'ignore'))
1000                 else:
1001                     return escape(vocabulary.get(value, value))
1002         elif mode == 'edit':
1003             in_selection = 0
1004             in_other_selection = 0
1005             res = ''
1006             html_widget_id = self.getHtmlWidgetId()
1007             render_format = self.render_format
1008             if render_format not in self.render_formats:
1009                 raise RuntimeError('unknown render format %s' % render_format)
1010             if render_format == 'select':
1011                 res = renderHtmlTag('select',
1012                                     name=html_widget_id, id=html_widget_id,
1013                                     onchange=self.onchange or None)
1014             # vocabulary options
1015             vocabulary_items = vocabulary.items()
1016             if self.sorted:
1017                 vocabulary_items.sort(key=lambda obj: obj[1].lower())
1018             for k, v in vocabulary_items:
1019                 # this enable to work with vocabulary that have integer keys
1020                 k = str(k)
1021                 if getattr(self, 'translated', None):
1022                     v = cpsmcat(vocabulary.getMsgid(k, k)).encode('ISO-8859-15', 'ignore')
1023                 if render_format == 'select':
1024                     kw = {'value': k, 'contents': v}
1025                     # do not add a selected option if it is already set
1026                     if value == k and not in_selection:
1027                         kw['selected'] = 'selected'
1028                         in_selection = 1
1029                     res += renderHtmlTag('option', **kw)
1030                     res += '\n'
1031                 else:
1032                     kw = {'id': html_widget_id+'_'+k,
1033                           'type': render_format,
1034                           'name': html_widget_id,
1035                           'value': k,
1036                           }
1037                     # do not add a selected option if it is already set
1038                     if value == k and not in_selection:
1039                         kw['checked'] = 'checked'
1040                         in_selection = 1
1041                     res += renderHtmlTag('input', **kw)
1042                     kw = {'for': html_widget_id+'_'+k,
1043                           'contents': v,
1044                           }
1045                     res += renderHtmlTag('label', **kw)
1046                     res += '<br/>\n'
1047             # invalid or free selections
1048             if value and not in_selection:
1049                 if render_format == 'select':
1050                     in_selection = 1
1051                     kw = {'value': value,
1052                           'contents': 'invalid: '+value,
1053                           'selected': 'selected',
1054                           }
1055                     res += renderHtmlTag('option', **kw)
1056                     res += '\n'
1057                 else:
1058                     if self.other_option:
1059                         in_selection = 1
1060                         in_other_selection = 1
1061                         kw = {'id': html_widget_id+'_other_selection',
1062                               'type': render_format,
1063                               'name': html_widget_id,
1064                               'value':  value,
1065                               'checked': 'checked',
1066                               }
1067                         res += renderHtmlTag('input', **kw)
1068                         kw = {'for': html_widget_id+'_other_selection',
1069                               'contents': cpsmcat('label_other_selection').encode('ISO-8859-15', 'ignore'),
1070                               }
1071                         res += renderHtmlTag('label', **kw)
1072                         kw = {'type': 'text',
1073                               'name': html_widget_id+'_other',
1074                               'value': value,
1075                               'onchange': "document.getElementById('"+html_widget_id+"_other_selection').value = this.value",
1076                               'onclick': "document.getElementById('"+html_widget_id+"_other_selection').checked='checked'",
1077                               'size': self.other_option_display_width,
1078                               }
1079                         if self.other_option_size_max:
1080                             kw['maxlength'] = self.other_option_size_max
1081                         res += renderHtmlTag('input', **kw)
1082                         res += '<br/>\n'
1083                     else:
1084                         kw = {'id': html_widget_id+'_'+value,
1085                               'type': render_format,
1086                               'name': html_widget_id,
1087                               'value': value,
1088                               'disabled': 'disabled',
1089                               }
1090                         res += renderHtmlTag('input', **kw)
1091                         kw = {'for': html_widget_id+'_'+value,
1092                               'contents': 'invalid: '+value,
1093                               }
1094                         res += renderHtmlTag('label', **kw)
1095                         res += '<br/>\n'
1096             # 'other' option
1097             if self.other_option and not in_other_selection:
1098                 if render_format != 'select':
1099                     kw = {'id': html_widget_id+'_other_selection',
1100                           'type': render_format,
1101                           'name': html_widget_id,
1102                           'value': '',
1103                           }
1104                     res += renderHtmlTag('input', **kw)
1105                     kw = {'for': html_widget_id+'_other_selection',
1106                           'contents': cpsmcat('label_other_selection').encode('ISO-8859-15', 'ignore'),
1107                           }
1108                     res += renderHtmlTag('label', **kw)
1109                     kw = {'type': 'text',
1110                           'name': html_widget_id+'_other',
1111                           'value': "",
1112                           'onchange': "document.getElementById('"+html_widget_id+"_other_selection').value = this.value",
1113                           'onclick': "document.getElementById('"+html_widget_id+"_other_selection').checked='checked'",
1114                           'size': self.other_option_display_width,
1115                           }
1116                     if self.other_option_size_max:
1117                         kw['maxlength'] = self.other_option_size_max
1118                     res += renderHtmlTag('input', **kw)
1119                     res += '<br/>\n'
1120             # default option
1121             if not self.is_required and not vocabulary.has_key(''):
1122                 if render_format == 'select':
1123                     kw = {'value': '',
1124                           'contents': cpsmcat('label_none_selection').encode('ISO-8859-15', 'ignore'),
1125                           }
1126                     if not in_selection:
1127                         kw['selected'] = 'selected'
1128                     res += renderHtmlTag('option', **kw)
1129                     res += '\n'
1130                 else:
1131                     kw = {'id': html_widget_id+'_empty',
1132                           'type': render_format,
1133                           'name': html_widget_id,
1134                           'value': '',
1135                           }
1136                     if not in_selection:
1137                         kw['checked'] = 'checked'
1138                     res += renderHtmlTag('input', **kw)
1139                     kw = {'for': html_widget_id+'_empty',
1140                           'contents': cpsmcat('label_none_selection').encode('ISO-8859-15', 'ignore'),
1141                           }
1142                     res += renderHtmlTag('label', **kw)
1143                     res += '<br/>\n'
1144             if render_format == 'select':
1145                 res += '</select>'
1146             return res
1147         raise RuntimeError('unknown mode %s' % mode)
1148
1149 InitializeClass(CPSGenericSelectWidget)
1150
1151 widgetRegistry.register(CPSGenericSelectWidget)
1152
1153 ##################################################
1154
1155 class CPSGenericMultiSelectWidget(CPSMultiSelectWidget):
1156     """Generic MultiSelect widget."""
1157     meta_type = 'Generic MultiSelect Widget'
1158
1159     _properties = CPSMultiSelectWidget._properties + (
1160         {'id': 'render_format', 'type': 'selection', 'mode': 'w',
1161          'select_variable': 'render_formats',
1162          'label': 'Render format'},
1163         # Enables the possibility to add blank values to vocabulary just to
1164         # change the way the list is presented (using items like 'choose a
1165         # category' or '------------' to separate items) and not affect the way
1166         # the value will be validated if the widget is required.
1167         # Before this, if the widget was required, default behavior was to
1168         # accept blank values if they were in the vocabulary (e.g
1169         # blank_value_ok_if_required = 1)
1170         {'id': 'blank_value_ok_if_required', 'type': 'boolean', 'mode':'w',
1171          'label': "Accept blank values when validating"},
1172         )
1173     render_formats = ['select', 'radio', 'checkbox']
1174
1175     render_format = render_formats[0]
1176     blank_value_ok_if_required = 1
1177
1178     # BBB for [46171]. Remove this once an upgrade step has been written
1179     sorted = False
1180
1181     def prepare(self, datastructure, **kw):
1182         """Prepare datastructure from datamodel."""
1183         datamodel = datastructure.getDataModel()
1184         value = datamodel[self.fields[0]]
1185         if isinstance(value, str):
1186             if value:
1187                 value = [value,]
1188             else:
1189                 value = []
1190         datastructure[self.getWidgetId()] = value
1191
1192
1193     def validate(self, datastructure, **kw):
1194         """Validate datastructure and update datamodel."""
1195         widget_id = self.getWidgetId()
1196         value = datastructure[widget_id]
1197         if not isinstance(value, (list, tuple)):
1198             datastructure.setError(widget_id, "cpsschemas_err_multiselect")
1199             return 0
1200         vocabulary = self._getVocabulary(datastructure)
1201         v = []
1202         for i in value:
1203             if i != '':
1204                 try:
1205                     i = str(i)
1206                 except ValueError:
1207                     datastructure.setError(widget_id, "cpsschemas_err_multiselect")
1208                     return 0
1209                 if not vocabulary.has_key(i):
1210                     datastructure.setError(widget_id, "cpsschemas_err_multiselect")
1211                     return 0
1212             else:
1213                 if self.is_required:
1214                     # set error unless vocabulary holds blank values and
1215                     # blank_value_ok_if_required is set to 1
1216                     if vocabulary.has_key(i):
1217                         if not self.blank_value_ok_if_required:
1218                             datastructure.setError(widget_id, "cpsschemas_err_required")
1219                             return 0
1220                     else:
1221                         datastructure.setError(widget_id, "cpsschemas_err_required")
1222                         return 0
1223             v.append(i)
1224         if self.is_required and not len(v):
1225             datastructure.setError(widget_id, "cpsschemas_err_required")
1226             return 0
1227         datamodel = datastructure.getDataModel()
1228         datamodel[self.fields[0]] = v
1229         return 1
1230
1231     def render(self, mode, datastructure, **kw):
1232         """Render in mode from datastructure."""
1233         widget_id = self.getWidgetId()
1234         value = datastructure[widget_id]
1235         vocabulary = self._getVocabulary(datastructure)
1236         portal = getToolByName(self, 'portal_url').getPortalObject()
1237         cpsmcat = portal.translation_service
1238         if mode == 'view':
1239             if not value:
1240                 # XXX L10N empty format may be subject to i18n.
1241                 return self.format_empty
1242             # XXX customize view mode, lots of displays are possible
1243             else:
1244                 return self.getEntriesHtml(value, vocabulary, self.translated)
1245         elif mode == 'edit':
1246             in_selection = 0
1247             res = ''
1248             html_widget_id = self.getHtmlWidgetId()
1249             render_format = self.render_format
1250             if render_format not in self.render_formats:
1251                 raise RuntimeError('unknown render format %s' % render_format)
1252             if render_format == 'select':
1253                 kw = {'name': html_widget_id + ':list',
1254                       'id': html_widget_id,
1255                       'multiple': 'multiple',
1256                       }
1257                 if self.size:
1258                     kw['size'] = self.size
1259                 res = renderHtmlTag('select', **kw)
1260             # vocabulary options
1261             vocabulary_items = vocabulary.items()
1262             if self.sorted:
1263                 vocabulary_items.sort(key=lambda obj: obj[1].lower())
1264             for k, v in vocabulary_items:
1265                 if getattr(self, 'translated', None):
1266                     v = cpsmcat(vocabulary.getMsgid(k, k)).encode('ISO-8859-15', 'ignore')
1267                 if render_format == 'select':
1268                     kw = {'value': k, 'contents': v}
1269                     if k in value:
1270                         kw['selected'] = 'selected'
1271                         in_selection = 1
1272                     res += renderHtmlTag('option', **kw)
1273                     res += '\n'
1274                 else:
1275                     kw = {'id': html_widget_id+'_'+k,
1276                           'type': render_format,
1277                           'name': html_widget_id+':list',
1278                           'value': k,
1279                           }
1280                     if k in value:
1281                         kw['checked'] = 'checked'
1282                         in_selection = 1
1283                     res += renderHtmlTag('input', **kw)
1284                     kw = {'for': html_widget_id+'_'+k,
1285                           'contents': v,
1286                           }
1287                     res += renderHtmlTag('label', **kw)
1288                     res += '<br/>\n'
1289             # invalid selections
1290             for value_item in value:
1291                 if value_item and value_item not in vocabulary.keys():
1292                     if render_format == 'select':
1293                         kw = {'value': value_item,
1294                               # XXX missing i18n for invalid
1295                               'contents': 'invalid: ' + value_item,
1296                               'selected': 'selected',
1297                               }
1298                         res += renderHtmlTag('option', **kw)
1299                         res += '\n'
1300                     else:
1301                         kw = {'id': html_widget_id+'_'+value_item,
1302                               'type': render_format,
1303                               'name': html_widget_id+':list',
1304                               'value': value_item,
1305                               'disabled': 'disabled',
1306                               }
1307                         res += renderHtmlTag('input', **kw)
1308                         kw = {'for': html_widget_id+'_'+value_item,
1309                               'contents': 'invalid: '+value_item,
1310                               }
1311                         res += renderHtmlTag('label', **kw)
1312                         res += '<br/>\n'
1313             # default option
1314             if not self.is_required and not vocabulary.has_key(''):
1315                 if render_format == 'select':
1316                     kw = {'value': '',
1317                           'contents': cpsmcat('label_none_selection').encode('ISO-8859-15', 'ignore'),
1318                           }
1319                     if not in_selection:
1320                         kw['selected'] = 'selected'
1321                     res += renderHtmlTag('option', **kw)
1322                     res += '\n'
1323                 # not interesting to have a default choice for checkboxes
1324                 elif render_format == 'radio':
1325                     kw = {'id': html_widget_id+'_empty',
1326                           'type': render_format,
1327                           'name': html_widget_id+':list',
1328                           'value': '',
1329                           }
1330                     if not in_selection:
1331                         kw['checked'] = 'checked'
1332                     res += renderHtmlTag('input', **kw)
1333                     kw = {'for': html_widget_id+'_empty',
1334                           'contents': cpsmcat('label_none_selection').encode('ISO-8859-15', 'ignore'),
1335                           }
1336                     res += renderHtmlTag('label', **kw)
1337                     res += '<br/>\n'
1338             if render_format == 'select':
1339                 res += '</select>'
1340             default_tag = renderHtmlTag('input',
1341                                         type='hidden',
1342                                         name=html_widget_id+':tokens:default',
1343                                         value='')
1344             return default_tag+res
1345         raise RuntimeError('unknown mode %s' % mode)
1346
1347 InitializeClass(CPSGenericMultiSelectWidget)
1348
1349 widgetRegistry.register(CPSGenericMultiSelectWidget)
1350
1351 ##################################################
1352
1353 class CPSRangeListWidget(CPSWidget):
1354     """Range List widget."""
1355     meta_type = 'Range List Widget'
1356
1357     field_types = ('CPS Range List Field',)
1358     field_inits = ({'is_searchabletext': 1,},)
1359
1360     _properties = CPSWidget._properties + (
1361         {'id': 'display_width', 'type': 'int', 'mode': 'w',
1362          'label': 'Display width'},
1363         {'id': 'format_empty', 'type': 'string', 'mode': 'w',
1364          'label': 'Format for empty list'},
1365         )
1366
1367     display_width = 0
1368     format_empty = ''
1369
1370     def prepare(self, datastructure, **kw):
1371         """Prepare datastructure from datamodel."""
1372         datamodel = datastructure.getDataModel()
1373         value = datamodel[self.fields[0]]
1374         datastructure[self.getWidgetId()] = value
1375
1376
1377     def validate(self, datastructure, **kw):
1378         """Validate datastructure and update datamodel."""
1379         widget_id = self.getWidgetId()
1380         value = datastructure[widget_id]
1381         if not isinstance(value, list):
1382             datastructure.setError(widget_id, "cpsschemas_err_rangelist")
1383             return 0
1384         v = []
1385         for i in value:
1386             i = i.split('-')
1387             if len(i) == 1:
1388                 try:
1389                     i[0] = int(i[0])
1390                 except ValueError:
1391                     datastructure.setError(widget_id, "cpsschemas_err_rangelist")
1392                     return 0
1393                 v.append((i[0],))
1394             elif len(i) == 2:
1395                 try:
1396                     i[0] = int(i[0])
1397                     i[1] = int(i[1])
1398                 except ValueError:
1399                     datastructure.setError(widget_id, "cpsschemas_err_rangelist")
1400                     return 0
1401                 v.append((i[0], i[1]))
1402             else:
1403                 datastructure.setError(widget_id, "cpsschemas_err_rangelist")
1404                 return 0
1405         if self.is_required and not len(v):
1406             datastructure.setError(widget_id, "cpsschemas_err_required")
1407             return 0
1408         datamodel = datastructure.getDataModel()
1409         datamodel[self.fields[0]] = v
1410         return 1
1411
1412     def render(self, mode, datastructure, **kw):
1413         """Render in mode from datastructure."""
1414         value = datastructure[self.getWidgetId()]
1415         res = ''
1416
1417         for i in value:
1418             if isinstance(i, str):
1419                 res += i + ' '
1420             elif len(i) == 1:
1421                 res += str(i[0]) + ' '
1422             else:
1423                 res += str(i[0]) + '-' + str(i[1]) + ' '
1424
1425         if mode == 'view':
1426             return escape(res)
1427         elif mode == 'edit':
1428             return renderHtmlTag('input',
1429                                  type='text',
1430                                  id=self.getHtmlWidgetId(),
1431                                  name=self.getHtmlWidgetId()+":tokens",
1432                                  value=escape(res),
1433                                  size=self.display_width,
1434                                  )
1435         raise RuntimeError('unknown mode %s' % mode)
1436
1437 InitializeClass(CPSRangeListWidget)
1438
1439 widgetRegistry.register(CPSRangeListWidget)
1440
1441 ##################################################
1442
1443 class CPSDocumentLanguageSelectWidget(CPSWidget):
1444     """Document Language Selection widget."""
1445     meta_type = 'Document Language Select Widget'
1446
1447     field_types = ('CPS String Field',)
1448     field_inits = ({'is_searchabletext': 0,},)
1449
1450     def prepare(self, datastructure, **kw):
1451         """Prepare datastructure from datamodel."""
1452         datamodel = datastructure.getDataModel()
1453         value = datamodel[self.fields[0]]
1454         datastructure[self.getWidgetId()] = value
1455
1456
1457     def validate(self, datastructure, **kw):
1458         """Validate datastructure and update datamodel."""
1459         return 1
1460
1461     def render(self, mode, datastructure, **kw):
1462         """Render in mode from datastructure."""
1463         res = ''
1464         datamodel = datastructure.getDataModel()
1465         proxy = datamodel.getProxy()
1466         if proxy is None:
1467             return res
1468         proxy_url = proxy.absolute_url()
1469         languages = proxy.getProxyLanguages()
1470         if len(languages) <= 1:
1471             return res
1472         current_language = datamodel[self.fields[0]]
1473         translation_service = getToolByName(self, 'translation_service')
1474         if translation_service is not None:
1475             mcat = translation_service
1476         else:
1477             def mcat(msgid):
1478                 return msgid
1479
1480         for language in languages:
1481             language_title = mcat('label_language_%s'% language).encode(
1482                 'ISO-8859-15', 'ignore')
1483             contents = language
1484             if current_language != language:
1485                 contents = renderHtmlTag('a', href='%s/switchLanguage/%s' %
1486                                          (proxy_url, language),
1487                                          title=language_title,
1488                                          contents=contents,
1489                                          css_class='availableLang')
1490             else:
1491                 contents = renderHtmlTag('span', contents=contents,
1492                                          title=language_title,
1493                                          css_class='selectedLang')
1494             contents += ' '
1495             res += renderHtmlTag('li', contents=contents)
1496         res = renderHtmlTag('ul', contents=res)
1497         res = '<div class="headerActions">%s</div>' % res
1498         return res
1499
1500 InitializeClass(CPSDocumentLanguageSelectWidget)
1501
1502 widgetRegistry.register(CPSDocumentLanguageSelectWidget)
1503
1504 ##################################################
1505
1506 class CPSSubjectWidget(CPSMultiSelectWidget):
1507     """A widget featuring links to items by subject.
1508
1509     The CPS Subject Widget is like the CPS MultiSelect Widget from which it
1510     derives, except in "view" mode where the listed entries have link on them
1511     to other documents on the portal which have the same subjects.
1512     """
1513     meta_type = 'Subject Widget'
1514
1515     def getEntriesHtml(self, entries, vocabulary, translated=False):
1516         entries_html_list = []
1517         if translated:
1518             cpsmcat = getToolByName(self, 'translation_service', None)
1519             if cpsmcat is None:
1520                 translated = False
1521         for subject_name in entries:
1522             if translated:
1523                 subject_label = cpsmcat(
1524                     vocabulary.getMsgid(subject_name, subject_name),
1525                     subject_name).encode('ISO-8859-15', 'ignore')
1526                 entries_html_list.append(self.getSubjectSearchLink(
1527                     subject_name, subject_label))
1528             else:
1529                 entries_html_list.append(
1530                     self.getSubjectSearchLink(subject_name,
1531                                               subject_name))
1532         return ', '.join(entries_html_list)
1533
1534     def getSubjectSearchLink(self, subject_name, subject_label):
1535         """Return an HTML link for the subject name with the given label."""
1536         return ('<a href="%s">%s</a>'
1537                 % (self.getSubjectSearchUrl(escape(subject_name)),
1538                    escape(subject_label)))
1539
1540     def getSubjectSearchUrl(self, subject_name):
1541         """Return the subject search URL.
1542
1543         The provided links are actually requests to the portal search engine.
1544         """
1545         return "%s/search_form?Subject=%s" % (self.portal_url(), subject_name)
1546
1547 InitializeClass(CPSSubjectWidget)
1548
1549 widgetRegistry.register(CPSSubjectWidget)
1550
1551 ##################################################
1552
1553 class CPSFlashWidget(CPSAttachedFileWidget):
1554     """Flash Widget.
1555     """
1556     meta_type = 'Flash Widget'
1557
1558     field_types = ('CPS File Field',   # File
1559                    'CPS String Field', # Caption (optional)
1560                    'CPS File Field')   # Preview (optional)
1561     field_inits = ({'is_searchabletext': 0,
1562                     'suffix_text': '_f1', # _f# are autocomputed field ext
1563                     'suffix_html': '_f2',},
1564                    {'is_searchabletext': 1}, {},
1565                    )
1566
1567     def _flash_validate(self, datastructure, **kw):
1568         """Check that this is a Flash animation
1569         """
1570         widget_id = self.getWidgetId()
1571         choice = datastructure[widget_id+'_choice']
1572         if choice == 'change':
1573             fileinfo = self.getFileInfo(datastructure)
1574             if fileinfo is not None:
1575                 cond = fileinfo['mimetype'] == 'application/x-shockwave-flash'
1576                 if not cond:
1577                     datastructure.setError(self.getWidgetId(),
1578                                            'cpsschemas_err_file')
1579                     return False
1580         return True
1581
1582     def validate(self, datastructure, **kw):
1583         """Validate datastructure and update datamodel.
1584         """
1585         return (CPSAttachedFileWidget.validate(self, datastructure, **kw) and
1586                 self._flash_validate(datastructure, **kw))
1587
1588     def render(self, mode, datastructure, **kw):
1589         """Render this widget from the datastructure or datamodel.
1590         """
1591         render_method = 'widget_flash_render'
1592         meth = getattr(self, render_method, None)
1593
1594         if meth is None:
1595             raise RuntimeError("Unknown Render Method %s for widget type %s"
1596                                % (render_method, self.getId()))
1597
1598         file = datastructure[self.getWidgetId()]
1599
1600         # Get common File props
1601         file_info = self.getFileInfo(datastructure)
1602
1603         # Update with speficic swf header props
1604         if file is not None:
1605             try:
1606                 file_info.update(analyseContent(
1607                     str(file.read()), file_info['size']))
1608             except TypeError:
1609                 pass
1610
1611         return meth(mode=mode, datastructure=datastructure, **file_info)
1612
1613 InitializeClass(CPSFlashWidget)
1614
1615 widgetRegistry.register(CPSFlashWidget)
1616
1617 ##################################################
1618
1619 class CPSLinkWidget(CPSProgrammerCompoundWidget):
1620     """Widget for an HTTP link.
1621     """
1622     meta_type = 'Link Widget'
1623     render_method = 'widget_link_render'
1624     prepare_validate_method = ''
1625
1626 InitializeClass(CPSLinkWidget)
1627
1628 widgetRegistry.register(CPSLinkWidget)
1629
1630
1631 class CPSTextImageWidget(CPSProgrammerCompoundWidget):
1632     """Widget for text+image.
1633     """
1634     meta_type = 'Text Image Widget'
1635     render_method = 'widget_textimage_render'
1636     prepare_validate_method = 'widget_textimage_prepare_validate'
1637
1638 InitializeClass(CPSTextImageWidget)
1639
1640 widgetRegistry.register(CPSTextImageWidget)
1641
1642
1643 class CPSImageLinkWidget(CPSProgrammerCompoundWidget):
1644     """
1645     """
1646     meta_type = 'Image Link Widget'
1647     render_method = 'widget_imagelink_render'
1648     prepare_validate_method = 'widget_imagelink_prepare_validate'
1649
1650 InitializeClass(CPSImageLinkWidget)
1651
1652 widgetRegistry.register(CPSImageLinkWidget)
1653
1654 class CPSAutocompletionStringWidget(CPSStringWidget):
1655     """Autocompletion String widget."""
1656     meta_type = 'Autocompletion String Widget'
1657     server_method = ''
1658
1659     _properties = CPSStringWidget._properties + (
1660         {'id': 'server_method', 'type': 'string', 'mode': 'w',
1661          'label': 'Server method',},)
1662
1663     def render(self, mode, datastructure, **kw):
1664         """Render in mode from datastructure."""
1665         render_method = 'widget_autocompletion_string_render'
1666         meth = getattr(self, render_method, None)
1667         if meth is None:
1668             raise RuntimeError("Unknown Render Method %s for widget type %s"
1669                                % (render_method, self.getId()))
1670         if mode not in ('view', 'edit'):
1671             raise RuntimeError('unknown mode %s' % mode)
1672
1673         value = datastructure[self.getWidgetId()]
1674         widget_id = self.getWidgetId()
1675         html_widget_id = self.getHtmlWidgetId()
1676
1677         return meth(mode=mode, widget_id=widget_id,value=value,
1678                     size=self.display_width, server_method=self.server_method,
1679                     html_widget_id=html_widget_id)
1680
1681 InitializeClass(CPSAutocompletionStringWidget)
1682
1683 widgetRegistry.register(CPSAutocompletionStringWidget)
1684
Note: See TracBrowser for help on using the browser.