class WeakCaching(BaseCaching): """Weak caching operation. A subclass of the generic BaseCaching operation to help make the UI approachable by mortals """ # Type metadata classProvides(ICachingOperationType) title = _(u"Weak caching") description = _( u"Cache in browser but expire immediately and enable 304 " u"responses on subsequent requests. 304's require configuration " u"of the 'Last-modified' and/or 'ETags' settings. If Last-Modified " u"header is insufficient to ensure freshness, turn on ETag " u"checking by listing each ETag components that should be used to " u"to construct the ETag header. " u"To also cache public responses in Zope memory, set 'RAM cache' to True. " ) prefix = 'plone.app.caching.weakCaching' sort = 3 # Configurable options options = ('etags', 'lastModified', 'ramCache', 'vary', 'anonOnly') # Default option values maxage = 0 smaxage = etags = vary = None lastModified = ramCache = anonOnly = False
class StrongCaching(BaseCaching): """Strong caching operation. A subclass of the generic BaseCaching operation to help make the UI approachable by mortals """ # Type metadata classProvides(ICachingOperationType) title = _(u"Strong caching") description = _( u"Cache in browser and proxy (default: 24 hrs). " u"Caution: Only use for stable resources " u"that never change without changing their URL, or resources " u"for which temporary staleness is not critical.") prefix = 'plone.app.caching.strongCaching' sort = 1 # Configurable options options = ('maxage', 'smaxage', 'etags', 'lastModified', 'ramCache', 'vary', 'anonOnly') # Default option values maxage = 86400 smaxage = etags = vary = None lastModified = ramCache = anonOnly = False
class ModerateCaching(BaseCaching): """Moderate caching operation. A subclass of the generic BaseCaching operation to help make the UI approachable by mortals """ # Type metadata classProvides(ICachingOperationType) title = _(u"Moderate caching") description = _( u"Cache in browser but expire immediately (same as 'weak caching'), " u"and cache in proxy (default: 24 hrs). " u"Use a purgable caching reverse proxy for best results. " u"Caution: If proxy cannot be purged, or cannot be configured " u"to remove the 's-maxage' token from the response, then stale " u"responses might be seen until the cached entry expires. ") prefix = 'plone.app.caching.moderateCaching' sort = 2 # Configurable options options = ('smaxage', 'etags', 'lastModified', 'ramCache', 'vary', 'anonOnly') # Default option values maxage = 0 smaxage = 86400 etags = vary = None lastModified = ramCache = anonOnly = False
def processImport(self): profile = self.request.form.get('profile', None) snapshot = self.request.form.get('snapshot', True) if not profile: self.errors['profile'] = _(u'You must select a profile to import.') if self.errors: IStatusMessage(self.request).addStatusMessage( _(u'There were errors.'), 'error') return portal_setup = getToolByName(self.context, 'portal_setup') # Create a snapshot if snapshot: snapshotId = 'plone.app.caching.beforeimport.{0}'.format( datetime.datetime.now().isoformat().replace(':', '.')) portal_setup.createSnapshot(snapshotId) # Import the new profile portal_setup.runAllImportStepsFromProfile( 'profile-{0}'.format(profile) ) IStatusMessage(self.request).addStatusMessage( _(u'Import complete.'), 'info')
class NoCaching(object): """A caching operation that tries to keep the response out of all caches. """ implements(ICachingOperation) adapts(Interface, IHTTPRequest) # Type metadata classProvides(ICachingOperationType) title = _(u"No caching") description = _(u"Use this operation to keep the response " u"out of all caches.") prefix = 'plone.app.caching.noCaching' sort = 4 options = () def __init__(self, published, request): self.published = published self.request = request def interceptResponse(self, rulename, response): return None def modifyResponse(self, rulename, response): doNotCache(self.published, self.request, response)
def processImport(self): profile = self.request.form.get('profile', None) snapshot = self.request.form.get('snapshot', True) if not profile: self.errors['profile'] = _(u"You must select a profile to import.") if self.errors: IStatusMessage(self.request).addStatusMessage( _(u"There were errors."), "error") return portal_setup = getToolByName(self.context, 'portal_setup') # Create a snapshot if snapshot: snapshotId = "plone.app.caching.beforeimport.%s" % ( datetime.datetime.now().isoformat().replace(':', '.')) portal_setup.createSnapshot(snapshotId) # Import the new profile portal_setup.runAllImportStepsFromProfile("profile-%s" % profile) IStatusMessage(self.request).addStatusMessage(_(u"Import complete."), "info")
def title(self): if self.rulesetName: return _(u'Edit ${operation} options for Ruleset: ${ruleset}', mapping={'operation': self.operation.title, 'ruleset': self.ruleset.title}) else: return _(u'Edit ${operation} options', mapping={'operation': self.operation.title})
def title(self): if self.rulesetName: return _(u'Edit ${operation} options for Ruleset: ${ruleset}', mapping={ 'operation': self.operation.title, 'ruleset': self.ruleset.title }) else: return _(u'Edit ${operation} options', mapping={'operation': self.operation.title})
def processPurge(self): if self.ramCache is None: IStatusMessage(self.request).addStatusMessage(_(u"RAM cache not installed."), "error") if self.errors: IStatusMessage(self.request).addStatusMessage(_(u"There were errors."), "error") return self.ramCache.invalidateAll() IStatusMessage(self.request).addStatusMessage(_(u"Cache purged."), "info")
def processPurge(self): if self.ramCache is None: IStatusMessage(self.request).addStatusMessage( _(u'RAM cache not installed.'), 'error') if self.errors: IStatusMessage(self.request).addStatusMessage( _(u'There were errors.'), 'error') return self.ramCache.invalidateAll() IStatusMessage(self.request).addStatusMessage( _(u'Cache purged.'), 'info')
def processPurge(self): if self.ramCache is None: IStatusMessage(self.request).addStatusMessage( _(u'RAM cache not installed.'), 'error') if self.errors: IStatusMessage(self.request).addStatusMessage( _(u'There were errors.'), 'error') return self.ramCache.invalidateAll() IStatusMessage(self.request).addStatusMessage(_(u'Cache purged.'), 'info')
def cancel(self, action): IStatusMessage(self.request).addStatusMessage(_(u"Edit cancelled."), type="info") self.request.response.redirect( "%s/@@caching-controlpanel#detailed-settings" % self.context.absolute_url()) return ''
def cancel(self, action): IStatusMessage(self.request).addStatusMessage(_(u'Edit cancelled.'), type='info') self.request.response.redirect( '{0}/@@caching-controlpanel#detailed-settings'.format( self.context.absolute_url(), ), ) return ''
def save(self, action): data, errors = self.extractData() if errors: self.status = self.formErrorsMessage return self.applyChanges(data) IStatusMessage(self.request).addStatusMessage(_(u"Changes saved."), "info") self.request.response.redirect("%s/@@caching-controlpanel#detailed-settings" % self.context.absolute_url())
def cancel(self, action): IStatusMessage(self.request).addStatusMessage( _(u'Edit cancelled.'), type='info') self.request.response.redirect( '{0}/@@caching-controlpanel#detailed-settings'.format( self.context.absolute_url(), ), ) return ''
def clear(self, action): for key in self.getContent().keys(): assert key.startswith("%s.%s." % (self.operation.prefix, self.rulesetName,)) if key in self.registry.records: del self.registry.records[key] IStatusMessage(self.request).addStatusMessage(_(u"Ruleset-specific settings removed."), type="info") self.request.response.redirect("%s/@@caching-controlpanel#detailed-settings" % self.context.absolute_url()) return ''
def save(self, action): data, errors = self.extractData() if errors: self.status = self.formErrorsMessage return self.applyChanges(data) IStatusMessage(self.request).addStatusMessage(_(u"Changes saved."), "info") self.request.response.redirect( "%s/@@caching-controlpanel#detailed-settings" % self.context.absolute_url())
def save(self, action): data, errors = self.extractData() if errors: self.status = self.formErrorsMessage return self.applyChanges(data) IStatusMessage(self.request).addStatusMessage(_(u'Changes saved.'), 'info') self.request.response.redirect( '{0}/@@caching-controlpanel#detailed-settings'.format( self.context.absolute_url(), ), ) return ''
class NoCaching(object): """A caching operation that tries to keep the response out of all caches. """ title = _(u'No caching') description = _(u'Use this operation to keep the response ' u'out of all caches.') prefix = 'plone.app.caching.noCaching' sort = 4 options = () def __init__(self, published, request): self.published = published self.request = request def interceptResponse(self, rulename, response): return None def modifyResponse(self, rulename, response): doNotCache(self.published, self.request, response)
def save(self, action): data, errors = self.extractData() if errors: self.status = self.formErrorsMessage return self.applyChanges(data) IStatusMessage(self.request).addStatusMessage( _(u'Changes saved.'), 'info') self.request.response.redirect( '{0}/@@caching-controlpanel#detailed-settings'.format( self.context.absolute_url(), ), ) return ''
class WeakCaching(BaseCaching): """Weak caching operation. A subclass of the generic BaseCaching operation to help make the UI approachable by mortals """ title = _(u'Weak caching') description = _( u'Cache in browser but expire immediately and enable 304 ' u'responses on subsequent requests. 304\'s require configuration ' u'of the \'Last-modified\' and/or \'ETags\' settings. If ' u'Last-Modified header is insufficient to ensure freshness, turn on ' u'ETag checking by listing each ETag components that should be used ' u'to construct the ETag header. To also cache public responses in ' u'Zope memory, set \'RAM cache\' to True.') prefix = 'plone.app.caching.weakCaching' sort = 3 # Configurable options options = ('etags', 'lastModified', 'ramCache', 'vary', 'anonOnly') # Default option values maxage = 0 smaxage = etags = vary = None lastModified = ramCache = anonOnly = False
def clear(self, action): for key in self.getContent().keys(): assert key.startswith("%s.%s." % ( self.operation.prefix, self.rulesetName, )) if key in self.registry.records: del self.registry.records[key] IStatusMessage(self.request).addStatusMessage( _(u"Ruleset-specific settings removed."), type="info") self.request.response.redirect( "%s/@@caching-controlpanel#detailed-settings" % self.context.absolute_url()) return ''
def clear(self, action): for key in self.getContent().keys(): key_suffix = '{0}.{1}.'.format( self.operation.prefix, self.rulesetName, ) assert key.startswith(key_suffix) if key in self.registry.records: del self.registry.records[key] IStatusMessage(self.request).addStatusMessage( _(u'Ruleset-specific settings removed.'), type='info') self.request.response.redirect( '{0}/@@caching-controlpanel#detailed-settings'.format( self.context.absolute_url(), ), ) return ''
def processSave(self): form = self.request.form # Form data enabled = form.get('enabled', False) enableCompression = form.get('enableCompression', False) contentTypesMap = form.get('contenttypes', {}) templatesMap = form.get('templates', {}) operations = form.get('operations', {}) purgingEnabled = form.get('purgingEnabled', False) cachingProxies = tuple(form.get('cachingProxies', ())) purgedContentTypes = tuple(form.get('purgedContentTypes', ())) virtualHosting = form.get('virtualHosting', False) domains = tuple(form.get('domains', ())) ramCacheMaxEntries = form.get('ramCacheMaxEntries', None) ramCacheMaxAge = form.get('ramCacheMaxAge', None) ramCacheCleanupInterval = form.get('ramCacheCleanupInterval', None) # Settings operationMapping = {} contentTypeRulesetMapping = {} templateRulesetMapping = {} # Process mappings and validate for ruleset, operation in operations.items(): if not ruleset or not operation: continue if isinstance(ruleset, unicode): # should be ASCII ruleset = ruleset.encode('utf-8') if isinstance(operation, unicode): # should be ASCII operation = operation.encode('utf-8') ruleset = ruleset.replace('-', '.') operationMapping[ruleset] = operation for ruleset, contentTypes in contentTypesMap.items(): if not ruleset: continue if isinstance(ruleset, unicode): # should be ASCII ruleset = ruleset.encode('utf-8') ruleset = ruleset.replace('-', '.') for contentType in contentTypes: if not contentType: continue if isinstance(contentType, unicode): # should be ASCII contentType = contentType.encode('utf-8') if contentType in contentTypeRulesetMapping: self.errors.setdefault('contenttypes', {})[ruleset] = \ _(u"Content type ${contentType} is already mapped to the rule ${ruleset}.", mapping={'contentType': self.contentTypesLookup.get(contentType, {}).get('title', contentType), 'ruleset': contentTypeRulesetMapping[contentType]}) else: contentTypeRulesetMapping[contentType] = ruleset for ruleset, templates in templatesMap.items(): if not ruleset: continue if isinstance(ruleset, unicode): # should be ASCII ruleset = ruleset.encode('utf-8') ruleset = ruleset.replace('-', '.') for template in templates: template = template.strip() if not template: continue if isinstance(template, unicode): # should be ASCII template = template.encode('utf-8') if template in templateRulesetMapping: self.errors.setdefault('templates', {})[ruleset] = \ _(u"Template ${template} is already mapped to the rule ${ruleset}.", mapping={'template': template, 'ruleset': templateRulesetMapping[template]}) else: templateRulesetMapping[template] = ruleset # Validate purging settings for cachingProxy in cachingProxies: if not _isuri(cachingProxy): self.errors['cachingProxies'] = _(u"Invalid URL: ${url}", mapping={'url': cachingProxy}) for domain in domains: if not _isuri(domain): self.errors['domain'] = _(u"Invalid URL: ${url}", mapping={'url': domain}) # RAM cache settings try: ramCacheMaxEntries = int(ramCacheMaxEntries) except (ValueError, TypeError,): self.errors['ramCacheMaxEntries'] = _(u"An integer is required.") else: if ramCacheMaxEntries < 0: self.errors['ramCacheMaxEntries'] = _(u"A positive number is required.") try: ramCacheMaxAge = int(ramCacheMaxAge) except (ValueError, TypeError,): self.errors['ramCacheMaxAge'] = _(u"An integer is required.") else: if ramCacheMaxAge < 0: self.errors['ramCacheMaxAge'] = _(u"A positive number is required.") try: ramCacheCleanupInterval = int(ramCacheCleanupInterval) except (ValueError, TypeError,): self.errors['ramCacheCleanupInterval'] = _(u"An integer is required.") else: if ramCacheMaxAge < 0: self.errors['ramCacheCleanupInterval'] = _(u"A positive number is required.") # Check for errors if self.errors: IStatusMessage(self.request).addStatusMessage(_(u"There were errors."), "error") return # Save settings self.settings.enabled = enabled self.settings.operationMapping = operationMapping self.ploneSettings.enableCompression = enableCompression self.ploneSettings.templateRulesetMapping = templateRulesetMapping self.ploneSettings.contentTypeRulesetMapping = contentTypeRulesetMapping self.ploneSettings.purgedContentTypes = purgedContentTypes self.purgingSettings.enabled = purgingEnabled self.purgingSettings.cachingProxies = cachingProxies self.purgingSettings.virtualHosting = virtualHosting self.purgingSettings.domains = domains self.ramCache.update(ramCacheMaxEntries, ramCacheMaxAge, ramCacheCleanupInterval) IStatusMessage(self.request).addStatusMessage(_(u"Changes saved."), "info")
def processSave(self): form = self.request.form # Form data enabled = form.get('enabled', False) contentTypesMap = form.get('contenttypes', {}) templatesMap = form.get('templates', {}) operations = form.get('operations', {}) purgingEnabled = form.get('purgingEnabled', False) cachingProxies = tuple(form.get('cachingProxies', ())) purgedContentTypes = tuple(form.get('purgedContentTypes', ())) virtualHosting = form.get('virtualHosting', False) domains = tuple(form.get('domains', ())) ramCacheMaxEntries = form.get('ramCacheMaxEntries', None) ramCacheMaxAge = form.get('ramCacheMaxAge', None) ramCacheCleanupInterval = form.get('ramCacheCleanupInterval', None) # Settings operationMapping = {} contentTypeRulesetMapping = {} templateRulesetMapping = {} # Process mappings and validate for ruleset, operation in operations.items(): if not ruleset or not operation: continue if isinstance(ruleset, unicode): # should be ASCII ruleset = ruleset.encode('utf-8') if isinstance(operation, unicode): # should be ASCII operation = operation.encode('utf-8') ruleset = ruleset.replace('-', '.') operationMapping[ruleset] = operation for ruleset, contentTypes in contentTypesMap.items(): if not ruleset: continue if isinstance(ruleset, unicode): # should be ASCII ruleset = ruleset.encode('utf-8') ruleset = ruleset.replace('-', '.') for contentType in contentTypes: if not contentType: continue if isinstance(contentType, unicode): # should be ASCII contentType = contentType.encode('utf-8') if contentType in contentTypeRulesetMapping: self.errors.setdefault('contenttypes', {})[ruleset] = \ _(u"Content type ${contentType} is already mapped to the rule ${ruleset}.", mapping={ 'contentType': self.contentTypesLookup.get(contentType, {}).get('title', contentType), # noqa 'ruleset': contentTypeRulesetMapping[contentType]}) else: contentTypeRulesetMapping[contentType] = ruleset for ruleset, templates in templatesMap.items(): if not ruleset: continue if isinstance(ruleset, unicode): # should be ASCII ruleset = ruleset.encode('utf-8') ruleset = ruleset.replace('-', '.') for template in templates: template = template.strip() if not template: continue if isinstance(template, unicode): # should be ASCII template = template.encode('utf-8') if template in templateRulesetMapping: self.errors.setdefault('templates', {})[ruleset] = \ _(u"Template ${template} is already mapped to the rule ${ruleset}.", mapping={ 'template': template, 'ruleset': templateRulesetMapping[template]}) else: templateRulesetMapping[template] = ruleset # Validate purging settings for cachingProxy in cachingProxies: if not _isuri(cachingProxy): self.errors['cachingProxies'] = _( u"Invalid URL: ${url}", mapping={'url': cachingProxy}) # noqa for domain in domains: if not _isuri(domain): self.errors['domain'] = _(u"Invalid URL: ${url}", mapping={'url': domain}) # RAM cache settings try: ramCacheMaxEntries = int(ramCacheMaxEntries) except ( ValueError, TypeError, ): self.errors['ramCacheMaxEntries'] = _(u"An integer is required.") else: if ramCacheMaxEntries < 0: self.errors['ramCacheMaxEntries'] = _( u"A positive number is required.") try: ramCacheMaxAge = int(ramCacheMaxAge) except ( ValueError, TypeError, ): self.errors['ramCacheMaxAge'] = _(u"An integer is required.") else: if ramCacheMaxAge < 0: self.errors['ramCacheMaxAge'] = _( u"A positive number is required.") try: ramCacheCleanupInterval = int(ramCacheCleanupInterval) except ( ValueError, TypeError, ): self.errors['ramCacheCleanupInterval'] = _( u"An integer is required.") else: if ramCacheMaxAge < 0: self.errors['ramCacheCleanupInterval'] = _( u"A positive number is required.") # Check for errors if self.errors: IStatusMessage(self.request).addStatusMessage( _(u"There were errors."), "error") return # Save settings self.settings.enabled = enabled self.settings.operationMapping = operationMapping self.ploneSettings.templateRulesetMapping = templateRulesetMapping self.ploneSettings.contentTypeRulesetMapping = contentTypeRulesetMapping self.ploneSettings.purgedContentTypes = purgedContentTypes self.purgingSettings.enabled = purgingEnabled self.purgingSettings.cachingProxies = cachingProxies self.purgingSettings.virtualHosting = virtualHosting self.purgingSettings.domains = domains self.ramCache.update(ramCacheMaxEntries, ramCacheMaxAge, ramCacheCleanupInterval) IStatusMessage(self.request).addStatusMessage(_(u"Changes saved."), "info")
def processSave(self): form = self.request.form # Form data enabled = form.get('enabled', False) contentTypesMap = form.get('contenttypes', {}) templatesMap = form.get('templates', {}) operations = form.get('operations', {}) purgingEnabled = form.get('purgingEnabled', False) cachingProxies = tuple(form.get('cachingProxies', ())) purgedContentTypes = tuple(form.get('purgedContentTypes', ())) virtualHosting = form.get('virtualHosting', False) domains = tuple(form.get('domains', ())) ramCacheMaxEntries = form.get('ramCacheMaxEntries', None) ramCacheMaxAge = form.get('ramCacheMaxAge', None) ramCacheCleanupInterval = form.get('ramCacheCleanupInterval', None) # Settings operationMapping = {} contentTypeRulesetMapping = {} templateRulesetMapping = {} # Process mappings and validate for ruleset, operation in operations.items(): if not ruleset or not operation: continue ruleset = ruleset.replace('-', '.') operationMapping[ruleset] = operation for ruleset, contentTypes in contentTypesMap.items(): if not ruleset: continue ruleset = ruleset.replace('-', '.') for contentType in contentTypes: if not contentType: continue if contentType in contentTypeRulesetMapping: self.errors.setdefault( 'contenttypes', {}, )[ruleset] = _( u'Content type ${contentType} is already mapped to ' u'the rule ${ruleset}.', mapping={ 'contentType': self.contentTypesLookup.get( contentType, {}, ).get( 'title', contentType, ), 'ruleset': contentTypeRulesetMapping[contentType], }, ) else: contentTypeRulesetMapping[contentType] = ruleset for ruleset, templates in templatesMap.items(): if not ruleset: continue ruleset = ruleset.replace('-', '.') for template in templates: template = template.strip() if not template: continue if template in templateRulesetMapping: self.errors.setdefault( 'templates', {}, )[ruleset] = _( u'Template ${template} is already mapped to the rule ' u'${ruleset}.', mapping={ 'template': template, 'ruleset': templateRulesetMapping[template], }, ) else: templateRulesetMapping[template] = ruleset # Validate purging settings for cachingProxy in cachingProxies: if not _isuri(cachingProxy): self.errors['cachingProxies'] = _(u'Invalid URL: ${url}', mapping={'url': cachingProxy}) # noqa for domain in domains: if not _isuri(domain): self.errors['domain'] = _( u'Invalid URL: ${url}', mapping={'url': domain}, ) # RAM cache settings try: ramCacheMaxEntries = int(ramCacheMaxEntries) except (ValueError, TypeError,): self.errors['ramCacheMaxEntries'] = _(u'An integer is required.') else: if ramCacheMaxEntries < 0: self.errors['ramCacheMaxEntries'] = _( u'A positive number is required.', ) try: ramCacheMaxAge = int(ramCacheMaxAge) except (ValueError, TypeError,): self.errors['ramCacheMaxAge'] = _(u'An integer is required.') else: if ramCacheMaxAge < 0: self.errors['ramCacheMaxAge'] = _( u'A positive number is required.', ) try: ramCacheCleanupInterval = int(ramCacheCleanupInterval) except (ValueError, TypeError,): self.errors['ramCacheCleanupInterval'] = _( u'An integer is required.', ) else: if ramCacheMaxAge < 0: self.errors['ramCacheCleanupInterval'] = _( u'A positive number is required.', ) # Check for errors if self.errors: IStatusMessage(self.request).addStatusMessage( _(u'There were errors.'), 'error') return # Save settings self.settings.enabled = enabled self.settings.operationMapping = operationMapping self.ploneSettings.templateRulesetMapping = templateRulesetMapping self.ploneSettings.contentTypeRulesetMapping = contentTypeRulesetMapping # noqa self.ploneSettings.purgedContentTypes = purgedContentTypes self.purgingSettings.enabled = purgingEnabled self.purgingSettings.cachingProxies = cachingProxies self.purgingSettings.virtualHosting = virtualHosting self.purgingSettings.domains = domains self.ramCache.update( ramCacheMaxEntries, ramCacheMaxAge, ramCacheCleanupInterval, ) if not enabled and purgingEnabled: IStatusMessage(self.request).addStatusMessage( _(u'Purging is still enabled while caching is disabled!'), 'warning', ) IStatusMessage(self.request).addStatusMessage( _(u'Changes saved.'), 'info', )
def processPurge(self): urls = self.request.form.get('urls', []) sync = self.request.form.get('synchronous', True) if not urls: self.errors['urls'] = _(u"No URLs or paths entered.") if self.errors: IStatusMessage(self.request).addStatusMessage( _(u"There were errors."), "error") return purger = getUtility(IPurger) serverURL = self.request['SERVER_URL'] def purge(url): if sync: status, xcache, xerror = purger.purgeSync(url) log = url if xcache: log += " (X-Cache header: " + xcache + ")" if xerror: log += " -- " + xerror self.purgeLog.append(log) else: purger.purgeAsync(url) self.purgeLog.append(url) portal_url = getToolByName(self.context, 'portal_url') portal = portal_url.getPortalObject() portalPath = portal.getPhysicalPath() proxies = self.purgingSettings.cachingProxies for inputURL in urls: if not inputURL.startswith(serverURL): # not in the site if '://' in inputURL: # Full URL? purge(inputURL) else: # Path? for newURL in getURLsToPurge(inputURL, proxies): purge(newURL) continue physicalPath = relativePath = None try: physicalPath = self.request.physicalPathFromURL(inputURL) except ValueError: purge(inputURL) continue if not physicalPath: purge(inputURL) continue relativePath = physicalPath[len(portalPath):] if not relativePath: purge(inputURL) continue obj = portal.unrestrictedTraverse(relativePath, None) if obj is None: purge(inputURL) continue for path in getPathsToPurge(obj, self.request): for newURL in getURLsToPurge(path, proxies): purge(newURL)
def processPurge(self): urls = self.request.form.get('urls', []) sync = self.request.form.get('synchronous', True) if not urls: self.errors['urls'] = _(u"No URLs or paths entered.") if self.errors: IStatusMessage(self.request).addStatusMessage(_(u"There were errors."), "error") return purger = getUtility(IPurger) serverURL = self.request['SERVER_URL'] def purge(url): if sync: status, xcache, xerror = purger.purgeSync(url) log = url if xcache: log += " (X-Cache header: " + xcache + ")" if xerror: log += " -- " + xerror self.purgeLog.append(log) else: purger.purgeAsync(url) self.purgeLog.append(url) portal_url = getToolByName(self.context, 'portal_url') portal = portal_url.getPortalObject() portalPath = '/'.join(portal.getPhysicalPath()) proxies = self.purgingSettings.cachingProxies for inputURL in urls: if not inputURL.startswith(serverURL): # not in the site if '://' in inputURL: # Full URL? purge(inputURL) else: # Path? for newURL in getURLsToPurge(inputURL, proxies): purge(newURL) continue physicalPath = relativePath = None try: physicalPath = self.request.physicalPathFromURL(inputURL) except ValueError: purge(inputURL) continue if not physicalPath: purge(inputURL) continue relativePath = physicalPath[len(portalPath):] if not relativePath: purge(inputURL) continue obj = portal.unrestrictedTraverse(relativePath, None) if obj is None: purge(inputURL) continue for path in getPathsToPurge(obj, self.request): for newURL in getURLsToPurge(path, proxies): purge(newURL)
def processSave(self): form = self.request.form # Form data enabled = form.get('enabled', False) contentTypesMap = form.get('contenttypes', {}) templatesMap = form.get('templates', {}) operations = form.get('operations', {}) purgingEnabled = form.get('purgingEnabled', False) cachingProxies = tuple(form.get('cachingProxies', ())) purgedContentTypes = tuple(form.get('purgedContentTypes', ())) virtualHosting = form.get('virtualHosting', False) domains = tuple(form.get('domains', ())) ramCacheMaxEntries = form.get('ramCacheMaxEntries', None) ramCacheMaxAge = form.get('ramCacheMaxAge', None) ramCacheCleanupInterval = form.get('ramCacheCleanupInterval', None) # Settings operationMapping = {} contentTypeRulesetMapping = {} templateRulesetMapping = {} # Process mappings and validate for ruleset, operation in operations.items(): if not ruleset or not operation: continue ruleset = ruleset.replace('-', '.') operationMapping[ruleset] = operation for ruleset, contentTypes in contentTypesMap.items(): if not ruleset: continue ruleset = ruleset.replace('-', '.') for contentType in contentTypes: if not contentType: continue if contentType in contentTypeRulesetMapping: self.errors.setdefault( 'contenttypes', {}, )[ruleset] = _( u'Content type ${contentType} is already mapped to ' u'the rule ${ruleset}.', mapping={ 'contentType': self.contentTypesLookup.get( contentType, {}, ).get( 'title', contentType, ), 'ruleset': contentTypeRulesetMapping[contentType], }, ) else: contentTypeRulesetMapping[contentType] = ruleset for ruleset, templates in templatesMap.items(): if not ruleset: continue ruleset = ruleset.replace('-', '.') for template in templates: template = template.strip() if not template: continue if template in templateRulesetMapping: self.errors.setdefault( 'templates', {}, )[ruleset] = _( u'Template ${template} is already mapped to the rule ' u'${ruleset}.', mapping={ 'template': template, 'ruleset': templateRulesetMapping[template], }, ) else: templateRulesetMapping[template] = ruleset # Validate purging settings for cachingProxy in cachingProxies: if not _isuri(cachingProxy): self.errors['cachingProxies'] = _( u'Invalid URL: ${url}', mapping={'url': cachingProxy}) # noqa for domain in domains: if not _isuri(domain): self.errors['domain'] = _( u'Invalid URL: ${url}', mapping={'url': domain}, ) # RAM cache settings try: ramCacheMaxEntries = int(ramCacheMaxEntries) except ( ValueError, TypeError, ): self.errors['ramCacheMaxEntries'] = _(u'An integer is required.') else: if ramCacheMaxEntries < 0: self.errors['ramCacheMaxEntries'] = _( u'A positive number is required.', ) try: ramCacheMaxAge = int(ramCacheMaxAge) except ( ValueError, TypeError, ): self.errors['ramCacheMaxAge'] = _(u'An integer is required.') else: if ramCacheMaxAge < 0: self.errors['ramCacheMaxAge'] = _( u'A positive number is required.', ) try: ramCacheCleanupInterval = int(ramCacheCleanupInterval) except ( ValueError, TypeError, ): self.errors['ramCacheCleanupInterval'] = _( u'An integer is required.', ) else: if ramCacheMaxAge < 0: self.errors['ramCacheCleanupInterval'] = _( u'A positive number is required.', ) # Check for errors if self.errors: IStatusMessage(self.request).addStatusMessage( _(u'There were errors.'), 'error') return # Save settings self.settings.enabled = enabled self.settings.operationMapping = operationMapping self.ploneSettings.templateRulesetMapping = templateRulesetMapping self.ploneSettings.contentTypeRulesetMapping = contentTypeRulesetMapping # noqa self.ploneSettings.purgedContentTypes = purgedContentTypes self.purgingSettings.enabled = purgingEnabled self.purgingSettings.cachingProxies = cachingProxies self.purgingSettings.virtualHosting = virtualHosting self.purgingSettings.domains = domains self.ramCache.update( ramCacheMaxEntries, ramCacheMaxAge, ramCacheCleanupInterval, ) if not enabled and purgingEnabled: IStatusMessage(self.request).addStatusMessage( _(u'Purging is still enabled while caching is disabled!'), 'warning', ) IStatusMessage(self.request).addStatusMessage( _(u'Changes saved.'), 'info', )
class EditForm(form.Form): """General edit form for operations. This is not registered as a view directly. Instead, we parameterise it manually and return it from the ``publishTraverse()`` method in ``controlpanel.py`` This form can be used in two slightly different ways: to edit "global" settings for an operation, or to edit "ruleset-specific" overrides. The latter mode is invoked when ``rulesetName`` and ``ruleset`` are set. The form fields are built from the records in registry corresponding to the operation's ``options`` list, taking the ``prefix`` into account. See ``plone.caching`` for a detailed explanation of how the naming scheme works. If a global record cannot be found, the option is ignored, i.e. no field is rendered for it. If we are editing ruleset-specific options and a particular ruleset- specific option does not exist, we take the global option field as a basis, and create a new record on the fly in ``applyChanges()``. The only other complication comes from the fact that we need to clone the persistent fields for two purposes: * Every record's field has the same name -- "value". We need to give it a different name in the form, so we clone the field and set a new name. * When we create a new ruleset-specific record, we also need a clone of the field. The ``cloneField()`` method takes care of this for us. Once the fields have been set up, the form operations on a dictionary context (as returned by ``getContent()``), where the keys are the record names. """ template = ViewPageTemplateFile('edit.pt') # Keep the ZPublisher happy - would normally be done by the ZCML # registration __name__ = 'cache-operation-edit' def __init__( self, context, request, operationName, operation, rulesetName=None, ruleset=None, ): self.context = context self.request = request self.operationName = operationName self.operation = operation self.rulesetName = rulesetName self.ruleset = ruleset def update(self): self.registry = getUtility(IRegistry) # If we were using plone.z3cform, this would be done with # z2.switch_on() if not IFormLayer.providedBy(self.request): alsoProvides(self.request, IFormLayer) self.request.set('disable_border', True) # Create fields for records we actually have. Where applicable, fall # back on the global record if a ruleset-specific record does not # yet exist - it will be created later in applyChanges() fields = [] prefix = self.operation.prefix for option in self.operation.options: newField = None fieldName = '{0}.{1}'.format(prefix, option) if self.rulesetName: rulesetFieldName = '{0}.{1}.{2}'.format( prefix, self.rulesetName, option, ) if rulesetFieldName in self.registry.records: newField = self.cloneField( self.registry.records[rulesetFieldName].field) newField.__name__ = rulesetFieldName elif fieldName in self.registry.records: newField = self.cloneField( self.registry.records[fieldName].field) newField.__name__ = rulesetFieldName else: if fieldName in self.registry.records: newField = self.cloneField( self.registry.records[fieldName].field) newField.__name__ = fieldName if newField is not None: fields.append(newField) self.fields = field.Fields(*fields) # Set up widgets and actions as normal super(EditForm, self).update() # Plonify the buttons self.actions['save'].addClass('context') self.actions['cancel'].addClass('standalone') self.actions['clear'].addClass('destructive') # Hide 'clear' action if we're not editing a ruleset if not self.rulesetName: del self.actions['clear'] # Context @memoize def getContent(self): """Operate on a dictionary context that contains the values for all options for which we actually have records. """ context = {} prefix = self.operation.prefix options = self.operation.options for option in options: recordName = '{0}.{1}'.format( prefix, option, ) # If a ruleset-specific record does not exist, we can fall back on # a global record, since the per-ruleset records will be created # as necessary in applyChanges() if self.rulesetName: rulesetRecordName = '{0}.{1}.{2}'.format( prefix, self.rulesetName, option, ) if rulesetRecordName in self.registry.records: context[rulesetRecordName] = self.registry[ rulesetRecordName] elif recordName in self.registry.records: context[rulesetRecordName] = self.registry[recordName] else: if recordName in self.registry.records: context[recordName] = self.registry[recordName] return context def applyChanges(self, data): """Save changes in the given data dictionary to the registry. """ for key, value in data.items(): # Lazily create per-ruleset records if necessary if key not in self.registry.records: # This should only ever happen if we have a not-yet-creted # ruleset-specific record assert self.rulesetName in key # Strip the ruleset name out, leaving the original key - this # must exist, otherwise getContent() would not have put it in # the data dictionary globalKey = self.operation.prefix + \ key[len(self.operation.prefix) + len(self.rulesetName) + 1:] assert globalKey in self.registry.records # Create a new record with a FieldRef field = self.registry.records[globalKey].field self.registry.records[key] = Record(FieldRef(globalKey, field), value) else: self.registry[key] = value def cloneField(self, field): # XXX: The field may sometimes not have data for reasons known only # to Jim. try: field._p_activate() except AttributeError: pass clone = field.__class__.__new__(field.__class__) clone.__dict__.update(field.__dict__) for name, attr in field.__dict__.items(): if IField.providedBy(attr): clone.__dict__[name] = self.cloneField(attr) return clone # Display @property def title(self): if self.rulesetName: return _(u'Edit ${operation} options for Ruleset: ${ruleset}', mapping={ 'operation': self.operation.title, 'ruleset': self.ruleset.title }) else: return _(u'Edit ${operation} options', mapping={'operation': self.operation.title}) @property def description(self): return self.operation.description # Buttons/actions @button.buttonAndHandler(_(u'Save'), name='save') def save(self, action): data, errors = self.extractData() if errors: self.status = self.formErrorsMessage return self.applyChanges(data) IStatusMessage(self.request).addStatusMessage(_(u'Changes saved.'), 'info') self.request.response.redirect( '{0}/@@caching-controlpanel#detailed-settings'.format( self.context.absolute_url(), ), ) return '' @button.buttonAndHandler(_(u'Cancel'), name='cancel') def cancel(self, action): IStatusMessage(self.request).addStatusMessage(_(u'Edit cancelled.'), type='info') self.request.response.redirect( '{0}/@@caching-controlpanel#detailed-settings'.format( self.context.absolute_url(), ), ) return '' @button.buttonAndHandler(_(u'Delete settings (use defaults)'), name='clear') def clear(self, action): for key in self.getContent().keys(): key_suffix = '{0}.{1}.'.format( self.operation.prefix, self.rulesetName, ) assert key.startswith(key_suffix) if key in self.registry.records: del self.registry.records[key] IStatusMessage(self.request).addStatusMessage( _(u'Ruleset-specific settings removed.'), type='info') self.request.response.redirect( '{0}/@@caching-controlpanel#detailed-settings'.format( self.context.absolute_url(), ), ) return ''
class BaseCaching(object): """A generic caching operation class that can do pretty much all the usual caching operations based on options settings. For UI simplicity, it might be easier to subclass this in your custom operations to set a few default operations. Generic options (Default value for each is None): ``maxage`` is the maximum age of the cached item, in seconds.. ``smaxage`` is the maximum age of the cached item in proxies, in seconds. ``etags'' is a list of etag components to use when constructing an etag. ``lastModified`` is a boolean indicating whether to set a Last-Modified header and turn on 304 responses. ``ramCache`` is a boolean indicating whether to turn on RAM caching for this item. Etags are only required if the URL is not specific enough to ensure uniqueness. ``vary`` is a string to add as a Vary header value in the response. """ implements(ICachingOperation) adapts(Interface, IHTTPRequest) # Type metadata classProvides(ICachingOperationType) title = _(u"Generic caching") description = _( u"Through this operation, all standard caching functions " u"can be performed via various combinations of the optional " u"parameter settings. For most cases, it's probably easier " u"to use one of the other simpler operations (Strong caching, " u"Moderate caching, Weak caching, or No caching).") prefix = 'plone.app.caching.baseCaching' options = ('maxage', 'smaxage', 'etags', 'lastModified', 'ramCache', 'vary', 'anonOnly') # Default option values maxage = smaxage = etags = vary = None lastModified = ramCache = anonOnly = False def __init__(self, published, request): self.published = published self.request = request def interceptResponse(self, rulename, response, class_=None): options = lookupOptions(class_ or self.__class__, rulename) etags = options.get('etags') or self.etags anonOnly = options.get('anonOnly', self.anonOnly) ramCache = options.get('ramCache', self.ramCache) lastModified = options.get('lastModified', self.lastModified) # Add the ``anonymousOrRandom`` etag if we are anonymous only if anonOnly: if etags is None: etags = ['anonymousOrRandom'] elif 'anonymousOrRandom' not in etags: etags = tuple(etags) + ('anonymousOrRandom', ) etag = getETagAnnotation(self.published, self.request, keys=etags) lastModified = getLastModifiedAnnotation(self.published, self.request, lastModified=lastModified) # Check for cache stop request variables if cacheStop(self.request, rulename): return None # Check if this should be a 304 response if not isModified(self.request, etag=etag, lastModified=lastModified): return notModified(self.published, self.request, response, etag=etag, lastModified=lastModified) # Check if this is in the ram cache if ramCache: context = getContext(self.published) portal_state = getMultiAdapter((context, self.request), name=u'plone_portal_state') if portal_state.anonymous(): cached = fetchFromRAMCache(self.request, etag=etag, lastModified=lastModified) if cached is not None: return cachedResponse(self.published, self.request, response, *cached) return None def modifyResponse(self, rulename, response, class_=None): options = lookupOptions(class_ or self.__class__, rulename) maxage = options.get('maxage', self.maxage) smaxage = options.get('smaxage', self.smaxage) etags = options.get('etags') or self.etags anonOnly = options.get('anonOnly', self.anonOnly) ramCache = options.get('ramCache', self.ramCache) vary = options.get('vary', self.vary) # Add the ``anonymousOrRandom`` etag if we are anonymous only if anonOnly: if etags is None: etags = ['anonymousOrRandom'] elif 'anonymousOrRandom' not in etags: etags = tuple(etags) + ('anonymousOrRandom', ) etag = getETagAnnotation(self.published, self.request, etags) lastModified = getLastModifiedAnnotation(self.published, self.request, options['lastModified']) # Check for cache stop request variables if cacheStop(self.request, rulename): # only stop with etags if configured if etags: etag = "%s%d" % (time.time(), random.randint(0, 1000)) return setCacheHeaders(self.published, self.request, response, etag=etag) # XXX: should there be an else here? Last modified works without extra headers. # Are there other config options? # Do the maxage/smaxage settings allow for proxy caching? proxyCache = smaxage or (maxage and smaxage is None) # Check if the content can be cached in shared caches public = True if ramCache or proxyCache: if etags is not None: if 'userid' in etags or 'anonymousOrRandom' in etags or 'roles' in etags: context = getContext(self.published) portal_state = getMultiAdapter((context, self.request), name=u'plone_portal_state') public = portal_state.anonymous() public = public and visibleToRole(self.published, role='Anonymous') if proxyCache and not public: # This is private so keep it out of both shared and browser caches maxage = smaxage = 0 setCacheHeaders(self.published, self.request, response, maxage=maxage, smaxage=smaxage, etag=etag, lastModified=lastModified, vary=vary) if ramCache and public: cacheInRAM(self.published, self.request, response, etag=etag, lastModified=lastModified)
def processPurge(self): urls = self.request.form.get('urls', []) sync = self.request.form.get('synchronous', True) if not urls: self.errors['urls'] = _(u'No URLs or paths entered.') if self.errors: IStatusMessage(self.request).addStatusMessage( _(u'There were errors.'), 'error') return if six.PY3: urls = [ x.decode('utf8') if isinstance(x, six.binary_type) else x for x in urls ] purger = getUtility(IPurger) serverURL = self.request['SERVER_URL'] def purge(url): if sync: status, xcache, xerror = purger.purgeSync(url) log = url if xcache: log += ' (X-Cache header: ' + xcache + ')' if xerror: log += ' -- ' + xerror if not str(status).startswith('2'): log += ' -- WARNING status ' + str(status) self.purgeLog.append(log) else: purger.purgeAsync(url) self.purgeLog.append(url) portal_url = getToolByName(self.context, 'portal_url') portal = portal_url.getPortalObject() portalPath = portal.getPhysicalPath() proxies = self.purgingSettings.cachingProxies for inputURL in urls: if not inputURL.startswith(serverURL): # not in the site if '://' in inputURL: # Full URL? purge(inputURL) else: # Path? for newURL in getURLsToPurge(inputURL, proxies): purge(newURL) continue physicalPath = relativePath = None try: physicalPath = self.request.physicalPathFromURL(inputURL) except ValueError: purge(inputURL) continue if not physicalPath: purge(inputURL) continue relativePath = physicalPath[len(portalPath):] if not relativePath: purge(inputURL) continue obj = portal.unrestrictedTraverse(relativePath, None) if obj is None: purge(inputURL) continue for path in getPathsToPurge(obj, self.request): for newURL in getURLsToPurge(path, proxies): purge(newURL)
def cancel(self, action): IStatusMessage(self.request).addStatusMessage(_(u"Edit cancelled."), type="info") self.request.response.redirect("%s/@@caching-controlpanel#detailed-settings" % self.context.absolute_url()) return ''