15 |
# You should have received a copy of the GNU General Public License |
# You should have received a copy of the GNU General Public License |
16 |
# along with this program; if not, write to the Free Software |
# along with this program; if not, write to the Free Software |
17 |
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA |
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA |
18 |
import OpenTAL, pax, PTmetal_handler, pax.paxtransform |
__version__='$Revision$'[11:-2] |
|
from pax.backwards_compatibility import * |
|
|
from ZPythonExpr import PythonExpr, SecureModuleImporter |
|
|
import cPickle as pickle |
|
|
#import pickle |
|
|
|
|
|
import os, AccessControl, Acquisition, sys, fnmatch |
|
|
from App.FactoryDispatcher import FactoryDispatcher |
|
|
from Acquisition import aq_base |
|
|
from Globals import DTMLFile, ImageFile, MessageDialog, package_home |
|
|
from zLOG import LOG, ERROR, INFO |
|
|
from OFS.SimpleItem import SimpleItem |
|
|
from DateTime.DateTime import DateTime |
|
|
from string import join, strip, rstrip, split, replace, lower |
|
|
from Shared.DC.Scripts.Script import Script, BindingsUI |
|
|
from Shared.DC.Scripts.Signature import FuncCode |
|
|
from Shared.DC.Scripts.Bindings import NameAssignments, Bindings |
|
|
from AccessControl import getSecurityManager |
|
|
try: |
|
|
from AccessControl import Unauthorized |
|
|
except ImportError: |
|
|
Unauthorized = "Unauthorized" |
|
|
from OFS.History import Historical, html_diff |
|
|
from OFS.Cache import Cacheable |
|
|
from OFS.Traversable import Traversable |
|
|
from OFS.PropertyManager import PropertyManager |
|
|
from Products.PageTemplates.PageTemplateFile import PageTemplateFile |
|
|
from webdav.Lockable import ResourceLockedError |
|
|
from webdav.WriteLockInterface import WriteLockInterface |
|
|
from ZODB.PersistentMapping import PersistentMapping |
|
|
from OFS.SimpleItem import SimpleItem |
|
|
import Globals |
|
|
from DocumentTemplate.DT_Util import TemplateDict, InstanceDict |
|
|
from AccessControl.DTML import RestrictedDTML |
|
|
class Rtd(RestrictedDTML, TemplateDict): |
|
|
this = None |
|
|
|
|
|
def call_with_ns(f, ns, arg=1): |
|
|
td = Rtd() |
|
|
td.this = ns['here'] |
|
|
td._push(ns['request']) |
|
|
td._push(InstanceDict(td.this, td)) |
|
|
td._push(ns) |
|
|
try: |
|
|
if arg==2: |
|
|
return f(None, td) |
|
|
else: |
|
|
return f(td) |
|
|
finally: |
|
|
td._pop(3) |
|
|
|
|
|
class PTNameAssignments(NameAssignments): |
|
|
# this should emulate the following ugly zpt code: |
|
|
## c = {'template': self, |
|
|
## 'here': self._getContext(), |
|
|
## 'container': self._getContainer(), |
|
|
## 'nothing': None, |
|
|
## 'options': {}, |
|
|
## 'root': root, |
|
|
## 'request': getattr(root, 'REQUEST', None), |
|
|
## 'modules': SecureModuleImporter, |
|
|
## } |
|
|
_exprs = NameAssignments._exprs + ( |
|
|
('name_none', 'None'), |
|
|
('name_options', '{}'), |
|
|
('name_root', 'self._getRoot()'), |
|
|
('name_request', 'getattr(self, "REQUEST", None)'), |
|
|
('name_modules', 'self._getModuleImporter()'), |
|
|
) |
|
|
|
|
|
class PTRuntimeError(RuntimeError): |
|
|
'''The Page Template has template errors that prevent it from rendering.''' |
|
|
pass |
|
|
|
|
|
def make_macro_collection(): |
|
|
col = PersistentMapping() |
|
|
col.__roles__ = None |
|
|
col.__allow_access_to_unprotected_subobjects__ = 1 |
|
|
#col.__altpt_0_2__ = 1 |
|
|
return col |
|
|
|
|
|
def fetchmacros(template, tree, context): |
|
|
macros = getattr(context, 'metal_macros', {}) |
|
|
col = template.macros |
|
|
col.clear() |
|
|
for name, macro in macros.items(): |
|
|
col[name] = pickle.dumps(macro) |
|
|
return tree |
|
|
|
|
|
|
|
|
ZopeExprEngine = OpenTAL.Expressions.Engine() |
|
|
ZopeExprEngine.registerType('python', PythonExpr) |
|
|
ZopeExprEngine.Undefs += (Unauthorized,) |
|
|
|
|
|
class ZopeContext(OpenTAL.Context.Context): |
|
|
def __init__(self, bindings=None): |
|
|
OpenTAL.Context.Context.__init__(self, ZopeExprEngine, bindings) |
|
|
self._securityManager = getSecurityManager() |
|
|
|
|
|
def validate(self, accessed=None, container=None, name=None, value=None, *a): |
|
|
if not self._securityManager.validate(accessed, container, name, value, *a): |
|
|
raise Unauthorized, name |
|
|
|
|
|
def render(self, ob): |
|
|
""" |
|
|
Calls the object, possibly a document template, or just returns it if |
|
|
not callable. (From DT_Util.py) |
|
|
""" |
|
|
ns = self.vars |
|
|
if hasattr(ob, '__render_with_namespace__'): |
|
|
ob = call_with_ns(ob.__render_with_namespace__, ns) |
|
|
else: |
|
|
base = aq_base(ob) |
|
|
if callable(base): |
|
|
try: |
|
|
if getattr(base, 'isDocTemp', 0): |
|
|
ob = call_with_ns(ob, ns, 2) |
|
|
else: |
|
|
ob = ob() |
|
|
except AttributeError, n: |
|
|
if str(n) != '__call__': |
|
|
raise |
|
|
return ob |
|
19 |
|
|
20 |
|
# FIXME: determine what exactly has to be imported. |
21 |
|
# some imports probably aren't needed by Base anymore. |
22 |
|
from OpenPTBase import * |
23 |
|
from OpenPTFile import OpenPTFile |
24 |
|
import os |
25 |
|
|
26 |
# paxtransform handlers |
my_home = package_home(globals()) |
27 |
handler_registry = [OpenTAL.metal_handler, OpenTAL.tal_handler] |
default_text_dir = os.path.join(my_home, 'default_text') |
|
# paxtransform handlers |
|
|
compiler_registry = [OpenTAL.metal_compiler, OpenTAL.tal_compiler] |
|
|
# functions! |
|
|
postcompiler_registry = [fetchmacros] |
|
|
|
|
|
def _registry_replace(registry, new_item, position): |
|
|
name = new_item.name |
|
|
for index, item in enumerate(registry): |
|
|
if item.name == name: |
|
|
del registry[index] |
|
|
registry.insert(position, new_item) |
|
|
|
|
|
def register_handlers(handler=None, compiler=None, position=None): |
|
|
if position is None: |
|
|
position = max(len(handler_registry), len(compiler_registry)) |
|
|
if handler: |
|
|
_registry_replace(handler_registry, handler, position) |
|
|
if compiler: |
|
|
_registry_replace(compiler_registry, compiler, position) |
|
|
|
|
|
# these are calculated automatically, don't touch them |
|
|
handler_namespaces = {} |
|
|
handler_namespaces_rev = {} |
|
|
|
|
|
def PT_initial_namespaces(): |
|
|
if len(handler_namespaces) != len(handler_registry): |
|
|
handler_namespaces.clear() |
|
|
for handler in handler_registry: |
|
|
handler_namespaces[handler.name] = handler.ns |
|
|
return handler_namespaces |
|
|
|
|
|
source_types = {'HTML':pax.html2pax, 'Text':pax.text2pax} |
|
|
if hasattr(pax, 'xml2pax'): |
|
|
source_types['XML'] = pax.xml2pax |
|
|
|
|
|
default_text_dir = os.path.join(package_home(globals()), 'default_text') |
|
|
|
|
|
# FIXME: lots of stuff below can probably be refactored out - |
|
|
# there is still a lot of cruft pasted from ZPT |
|
|
|
|
|
# FIXME2: it would be interesting to check if a non-zope OpenPT class |
|
|
# is doable. If not, all claims to its existence should be removed. |
|
|
|
|
|
class ZopePageTemplate(Script, Historical, Cacheable, |
|
|
Traversable, PropertyManager): |
|
|
"Zope Open Page Template using TAL, TALES, and METAL" |
|
|
|
|
|
__implements__ = (WriteLockInterface,) |
|
|
|
|
|
content_type = 'text/html' |
|
|
source_type = 'HTML' |
|
|
handlers = ('tal', 'metal') |
|
|
encoding = 'utf-8' |
|
|
_errors = () |
|
|
_warnings = () |
|
|
_text = '' |
|
|
_error_start = '<!-- Page Template Diagnostics' |
|
|
_paxtree = '' |
|
|
meta_type = 'Page Template (OpenPT)' |
|
|
_icon = 'misc_/OpenPT/opt.png' |
|
|
|
|
|
func_defaults = None |
|
|
func_code = FuncCode((), 0) |
|
|
|
|
|
_default_bindings = {'name_context': 'here', |
|
|
'name_container': 'container', |
|
|
'name_m_self': 'template', |
|
|
'name_subpath': 'traverse_subpath', |
|
|
'name_none': 'nothing', |
|
|
'name_options': 'options', |
|
|
'name_root': 'root', |
|
|
'name_request': 'request', |
|
|
'name_modules': 'modules', |
|
|
} |
|
28 |
|
|
29 |
|
class ZopePageTemplate(OpenPageTemplate, PropertyManager): |
30 |
|
"Normal (ZMI-managed) Open Page Template" |
31 |
manage_options = ( |
manage_options = ( |
32 |
{'label':'Edit', 'action':'pt_editForm', |
{'label':'Edit', 'action':'pt_editForm', |
33 |
'help': ('PageTemplates', 'PageTemplate_Edit.stx')}, |
'help': ('PageTemplates', 'PageTemplate_Edit.stx')}, |
47 |
) |
) |
48 |
|
|
49 |
def __init__(self, id, text_fn): |
def __init__(self, id, text_fn): |
50 |
self.id = str(id) |
OpenPageTemplate.__init__(self, id) |
51 |
self.ZBindings_edit(self._default_bindings) |
if text_fn is not None: |
|
if text_fn is None: |
|
|
text = '<openpt />' |
|
|
else: |
|
52 |
text = open(os.path.join(default_text_dir, text_fn)+'.pt').read() |
text = open(os.path.join(default_text_dir, text_fn)+'.pt').read() |
53 |
self.macros = make_macro_collection() |
self.write(text) |
|
self.pt_edit(text) |
|
|
|
|
|
def manage_afterAdd(self, item, container): |
|
|
try: |
|
|
self.encoding = container.default_zope_data_encoding |
|
|
except: |
|
|
pass |
|
|
|
|
|
def __setstate__(self, state): |
|
|
ZopePageTemplate.inheritedAttribute('__setstate__')(self, state) |
|
|
###backward compatibility |
|
|
#col = getattr(self, 'macros', None) |
|
|
#if not hasattr(col, '__altpt_0_2__'): |
|
|
# self.macros = make_macro_collection() |
|
|
#if not self._bind_names.isNameAssigned('here'): |
|
|
#x self.ZBindings_edit(self._default_bindings) |
|
|
###/backward compatibility |
|
|
|
|
|
def _setupBindings(self, names={}): |
|
|
self._bind_names = names = PTNameAssignments(names) |
|
|
return names |
|
|
|
|
|
def _getModuleImporter(self): |
|
|
return SecureModuleImporter |
|
|
|
|
|
def _getRoot(self): |
|
|
root = getattr(self, 'getPhysicalRoot', None) |
|
|
# don't know how this happens, but it does: sometimes root is a string |
|
|
if callable(root): |
|
|
root = root() |
|
|
return root |
|
|
|
|
|
def _getContext(self): |
|
|
if hasattr(self, 'aq_parent'): |
|
|
return ZopePageTemplate.inheritedAttribute('_getContext')(self) |
|
|
return self |
|
|
|
|
|
def _setPropValue(self, id, value): |
|
|
PropertyManager._setPropValue(self, id, value) |
|
|
self.ZCacheable_invalidate() |
|
54 |
|
|
55 |
security = AccessControl.ClassSecurityInfo() |
security = AccessControl.ClassSecurityInfo() |
56 |
|
|
|
security.declareObjectProtected('View') |
|
|
security.declareProtected('View', '__call__') |
|
|
|
|
57 |
security.declareProtected('View management screens', |
security.declareProtected('View management screens', |
58 |
'pt_editForm', 'manage_main', 'read', |
'pt_editForm', 'manage_main', 'read', |
59 |
'ZScriptHTML_tryForm', 'PrincipiaSearchSource', |
'ZScriptHTML_tryForm', 'PrincipiaSearchSource', |
62 |
security.declareProtected('FTP access', |
security.declareProtected('FTP access', |
63 |
'manage_FTPstat','manage_FTPget','manage_FTPlist') |
'manage_FTPstat','manage_FTPget','manage_FTPlist') |
64 |
|
|
65 |
pt_editForm = PageTemplateFile('www/ptEdit.pt', globals(), |
pt_editForm = OpenPTFile('pt_editForm', my_home, 'www', 'ptEdit') |
|
__name__='pt_editForm') |
|
|
pt_editForm._owner = None |
|
66 |
manage = manage_main = pt_editForm |
manage = manage_main = pt_editForm |
67 |
|
|
68 |
|
def _setPropValue(self, id, value): |
69 |
|
PropertyManager._setPropValue(self, id, value) |
70 |
|
self.ZCacheable_invalidate() |
71 |
|
|
72 |
security.declareProtected('Change Page Templates', |
security.declareProtected('Change Page Templates', |
73 |
'pt_editAction', 'pt_setTitle', 'pt_edit', 'pt_expand', |
'pt_editAction', 'pt_setTitle', 'pt_edit', 'pt_expand', |
74 |
'pt_upload', 'pt_changePrefs') |
'pt_upload', 'pt_changePrefs') |
122 |
REQUEST.form.update({'dtpref_cols': cols, 'dtpref_rows': rows}) |
REQUEST.form.update({'dtpref_cols': cols, 'dtpref_rows': rows}) |
123 |
return self.manage_main() |
return self.manage_main() |
124 |
|
|
|
def paxtree(self): |
|
|
'fetch the pax tree from the zodb' |
|
|
if not hasattr(self, '_v_paxtree'): |
|
|
self._v_paxtree = pickle.loads(self._paxtree) |
|
|
return self._v_paxtree |
|
|
|
|
|
def read(self, *args, **kw): |
|
|
if not self._errors: |
|
|
return self._text |
|
|
|
|
|
return ('%s\n %s\n-->\n%s' % (self._error_start, |
|
|
join(self._errors, '\n '), |
|
|
self._text)) |
|
|
|
|
|
def write(self, text): |
|
|
self.ZCacheable_invalidate() |
|
|
assert type(text) in (type(''), type(u'')) |
|
|
if text.startswith(self._error_start): |
|
|
errend = text.find('-->') |
|
|
if errend >= 0: |
|
|
text = text[errend + 4:] |
|
|
if self._text != text: |
|
|
self._text = text |
|
|
|
|
|
# "cooking" only ever happens when the template changes |
|
|
parser = source_types[self.source_type] |
|
|
|
|
|
self._errors = () |
|
|
try: |
|
|
tree = parser(text, PT_initial_namespaces(), self.encoding) |
|
|
tr_engine = pax.paxtransform.Engine() |
|
|
for handler in compiler_registry: |
|
|
tr_engine.add_handler(handler) |
|
|
# empty context: |
|
|
# compilers and postcompilers shouldn't access the context anyway |
|
|
context = ZopeContext() |
|
|
context.template = self |
|
|
try: |
|
|
context.encoding = self.default_zope_data_encoding |
|
|
except: |
|
|
context.encoding = self.encoding |
|
|
tr_engine.initialize(context) |
|
|
tree = tr_engine.transform(tree, context) |
|
|
for handler in postcompiler_registry: |
|
|
tree = handler(self, tree, context) |
|
|
self._paxtree = pickle.dumps(tree) |
|
|
if hasattr(self, '_v_paxtree'): |
|
|
del self._v_paxtree |
|
|
except: |
|
|
import traceback, zLOG |
|
|
#zLOG.LOG('OpenPT', 0, 'Compilation error', |
|
|
try: |
|
|
exc = sys.exc_info() |
|
|
traceback.print_exception(*exc) |
|
|
self._errors = ["Compilation failed", |
|
|
"%s: %s" % exc[:2]] |
|
|
finally: |
|
|
del exc |
|
|
if not self._errors: |
|
|
self._errors = ['something very wicked happened.'] |
|
|
|
|
|
def pt_edit(self, text): |
|
|
if hasattr(text, 'read'): |
|
|
text = text.read() |
|
|
self.write(text) |
|
|
|
|
|
def pt_errors(self): |
|
|
return self._errors |
|
|
|
|
|
def pt_warnings(self): |
|
|
return self._warnings |
|
|
|
|
|
def pt_parser_list(self): |
|
|
return source_types.keys() |
|
|
|
|
|
def pt_handler_list(self): |
|
|
return [handler.name for handler in handler_registry] |
|
|
|
|
|
security.declareProtected('Change Page Templates', |
|
|
'PUT', 'manage_FTPput', 'write', |
|
|
'manage_historyCopy', |
|
|
'manage_beforeHistoryCopy', 'manage_afterHistoryCopy') |
|
|
|
|
|
def PUT(self, REQUEST, RESPONSE): |
|
|
""" Handle HTTP PUT requests """ |
|
|
self.dav__init(REQUEST, RESPONSE) |
|
|
self.dav__simpleifhandler(REQUEST, RESPONSE, refresh=1) |
|
|
self.write(REQUEST.get('BODY', '')) |
|
|
RESPONSE.setStatus(204) |
|
|
return RESPONSE |
|
|
|
|
|
manage_FTPput = PUT |
|
|
|
|
|
def manage_FTPget(self): |
|
|
"Get source for FTP download" |
|
|
self.REQUEST.RESPONSE.setHeader('Content-Type', self.content_type) |
|
|
return self.read() |
|
|
|
|
|
def get_size(self): |
|
|
return len(self.read()) |
|
|
getSize = get_size |
|
|
|
|
|
def PrincipiaSearchSource(self): |
|
|
"Support for searching - the document's contents are searched." |
|
|
return self.read() |
|
|
|
|
|
def document_src(self, REQUEST=None, RESPONSE=None): |
|
|
"""Return document source.""" |
|
|
|
|
|
if RESPONSE is not None: |
|
|
RESPONSE.setHeader('Content-Type', 'text/plain') |
|
|
if REQUEST is not None and REQUEST.get('raw'): |
|
|
return self._text |
|
|
return self.read() |
|
|
|
|
|
def pt_src(self, REQUEST=None, RESPONSE=None): |
|
|
"""Return (rendered) document source.""" |
|
|
|
|
|
if RESPONSE is not None: |
|
|
RESPONSE.setHeader('Content-Type', 'text/plain') |
|
|
|
|
|
if hasattr(REQUEST, 'pax') and REQUEST.pax: |
|
|
return str(self.paxtree()) |
|
|
return pax.XML(self.paxtree()) |
|
|
|
|
|
def om_icons(self): |
|
|
"""Return a list of icon URLs to be displayed by an ObjectManager""" |
|
|
icons = ({'path': self._icon, |
|
|
'alt': self.meta_type, 'title': self.meta_type},) |
|
|
if self._errors: |
|
|
icons = icons + ({'path': 'misc_/OpenPT/exclamation.gif', |
|
|
'alt': 'Error', |
|
|
'title': 'This template has an error'},) |
|
|
return icons |
|
|
|
|
|
def _exec(self, bound_names, args, kw, handlers=None, root_ns_map={}): |
|
|
"""Call a Page Template""" |
|
|
__traceback_info__ = 'bindings: %s;\n args: %s;\n kw: %s' % ( |
|
|
repr(bound_names), repr(args), repr(kw)) |
|
|
tr_engine = pax.paxtransform.Engine() |
|
|
if handlers is None: |
|
|
handlers = [h for h in handler_registry if h.name in self.handlers] |
|
|
for handler in handlers: |
|
|
tr_engine.add_handler(handler) |
|
|
|
|
|
if self._errors: |
|
|
raise PTRuntimeError, 'Page Template %s has errors.' % self.id |
|
|
|
|
|
if not kw.has_key('args'): |
|
|
kw['args'] = args |
|
|
bound_names['options'] = kw |
|
|
|
|
|
security=getSecurityManager() |
|
|
bound_names['user'] = security.getUser() |
|
|
|
|
|
# Retrieve the value from the cache. |
|
|
keyset = {} |
|
|
if self.ZCacheable_isCachingEnabled(): |
|
|
for n in ('traverse_subpath', 'options'): |
|
|
keyset[n] = bound_names[n] |
|
|
keyset['here'] = bound_names['here'].absolute_url() |
|
|
result = self.ZCacheable_get(keywords=bound_names) |
|
|
if result is not None: |
|
|
# Got a cached value. |
|
|
return result |
|
|
|
|
|
# Execute the template in a new security context. |
|
|
security.addContext(self) |
|
|
try: |
|
|
context = ZopeContext(bound_names) |
|
|
try: |
|
|
context.encoding = self.default_zope_data_encoding |
|
|
except: |
|
|
context.encoding = self.encoding |
|
|
context.root_xmlns_map = root_ns_map |
|
|
tr_engine.initialize(context) |
|
|
result = pax.XML(tr_engine.transform(self.paxtree(), context), |
|
|
root_ns_map, self.encoding) |
|
|
if keyset is not None: |
|
|
# Store the result in the cache. |
|
|
self.ZCacheable_set(result, keywords=keyset) |
|
|
try: |
|
|
self.REQUEST.RESPONSE.setHeader('content-type', |
|
|
'%s; charset=%s' % ( |
|
|
self.content_type, |
|
|
context.encoding)) |
|
|
except AttributeError: |
|
|
pass |
|
|
return result |
|
|
finally: |
|
|
security.removeContext(self) |
|
|
|
|
|
def pt_expand(self, REQUEST=None): |
|
|
"""Expand macros""" |
|
|
if len(handler_namespaces_rev) != len(handler_registry): |
|
|
handler_namespaces_rev.clear() |
|
|
for handler in handler_registry: |
|
|
handler_namespaces_rev[handler.ns] = handler.name |
|
|
# FIXME: should catch exceptions (?) |
|
|
expanded = self._exec({},(),{}, |
|
|
(PTmetal_handler.metal_expander,), |
|
|
handler_namespaces_rev) |
|
|
self.write(expanded) |
|
|
if REQUEST is not None: |
|
|
REQUEST.set('text', self.read()) # May not equal 'text'! |
|
|
return self.pt_editForm(manage_tabs_message='Macros expanded') |
|
125 |
Globals.InitializeClass(ZopePageTemplate) |
Globals.InitializeClass(ZopePageTemplate) |
126 |
|
|
127 |
|
|
128 |
manage_addPageTemplateForm = ZopePageTemplate('manage_addPageTemplateForm', None) |
manage_addPageTemplateForm = OpenPTFile('manage_addPageTemplateForm', |
129 |
manage_addPageTemplateForm.write( |
my_home, 'www', 'ptAdd.pt') |
|
open(os.path.join(package_home(globals()), 'www', 'ptAdd.pt')).read()) |
|
|
manage_addPageTemplateForm.__name__ = manage_addPageTemplateForm.id |
|
130 |
|
|
131 |
from urllib import quote |
from urllib import quote |
132 |
|
|
158 |
if submit==" Add and Edit ": u="%s/%s" % (u,quote(id)) |
if submit==" Add and Edit ": u="%s/%s" % (u,quote(id)) |
159 |
REQUEST.RESPONSE.redirect(u+'/manage_main') |
REQUEST.RESPONSE.redirect(u+'/manage_main') |
160 |
return '' |
return '' |
|
|
|
|
|
|
|
class __FactoryDispatcher__(FactoryDispatcher): |
|
|
"customized FactoryDispatcher for OpenPT" |
|
|
security = AccessControl.ClassSecurityInfo() |
|
|
security.declareObjectProtected('Add Page Templates') |
|
|
security.declareProtected('Add Page Templates', 'default_text_dir') |
|
|
def default_text_dir(*a, **kw): |
|
|
return [name[:-3] for name in os.listdir(default_text_dir) if |
|
|
fnmatch.fnmatch(name, '*.pt')] |
|
|
Globals.InitializeClass(__FactoryDispatcher__) |
|
|
|
|
|
|
|
|
def initialize(context): |
|
|
context.registerClass( |
|
|
ZopePageTemplate, |
|
|
permission='Add Page Templates', |
|
|
constructors=(manage_addPageTemplateForm, |
|
|
manage_addPageTemplate), |
|
|
icon='www/opt.png', |
|
|
) |
|
|
try: |
|
|
# hook OpenPT up CMF's brain |
|
|
import FSOPT |
|
|
FSOPT.initialize(context) |
|
|
except ImportError: |
|
|
pass |
|