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

Revision 53962, 67.0 kB (checked in by gracinet, 4 months ago)

#2061: Bad exception raising for inconsistent Text Widgets

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