We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Allow to migrate to Volto after updating a site to Plone 6
When Plone is updated to 6 the upgrade-view (@@plone-upgrade) should display a link to the migration to Volto.
See https://github.com/plone/plone.volto/issues/55 for the migration itself.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `Products/CMFPlone/browser/admin.py`
Content:
```
1 from AccessControl import getSecurityManager
2 from AccessControl.Permissions import view as View
3 from OFS.interfaces import IApplication
4 from Products.CMFCore.permissions import ManagePortal
5 from Products.CMFPlone.factory import _DEFAULT_PROFILE
6 from Products.CMFPlone.factory import addPloneSite
7 from plone.base.interfaces import INonInstallable
8 from plone.base.interfaces import IPloneSiteRoot
9 from Products.CMFPlone.utils import get_installer
10 from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
11 from Products.GenericSetup import BASE, EXTENSION
12 from Products.GenericSetup import profile_registry
13 from Products.GenericSetup.upgrade import normalize_version
14 from ZPublisher.BaseRequest import DefaultPublishTraverse
15 from collections import OrderedDict
16 from plone.i18n.locales.interfaces import IContentLanguageAvailability
17 from plone.keyring.interfaces import IKeyManager
18 from plone.protect.authenticator import check as checkCSRF
19 from plone.protect.interfaces import IDisableCSRFProtection
20 from urllib import parse
21 from ZODB.broken import Broken
22 from zope.component import adapts
23 from zope.component import getAllUtilitiesRegisteredFor
24 from zope.component import getUtility
25 from zope.component import queryMultiAdapter
26 from zope.component import queryUtility
27 from zope.i18n.interfaces import IUserPreferredLanguages
28 from zope.i18n.locales import locales, LoadLocaleError
29 from zope.interface import Interface
30 from zope.interface import alsoProvides
31 from zope.publisher.browser import BrowserView
32 from zope.publisher.interfaces import IRequest
33 from zope.schema.interfaces import IVocabularyFactory
34 
35 import logging
36 import pkg_resources
37 
38 
39 try:
40     pkg_resources.get_distribution("plone.volto")
41     HAS_VOLTO = True
42 except pkg_resources.DistributionNotFound:
43     HAS_VOLTO = False
44 LOGGER = logging.getLogger('Products.CMFPlone')
45 
46 
47 class AppTraverser(DefaultPublishTraverse):
48     adapts(IApplication, IRequest)
49 
50     def publishTraverse(self, request, name):
51         if name == 'index_html':
52             view = queryMultiAdapter(
53                 (self.context, request), Interface, 'plone-overview')
54             if view is not None:
55                 return view
56         return DefaultPublishTraverse.publishTraverse(self, request, name)
57 
58 
59 class Overview(BrowserView):
60     has_volto = HAS_VOLTO
61 
62     def sites(self, root=None):
63         if root is None:
64             root = self.context
65 
66         result = []
67         secman = getSecurityManager()
68         candidates = (
69             obj for obj in root.values() if not isinstance(obj, Broken)
70         )
71         for obj in candidates:
72             if obj.meta_type == 'Folder':
73                 result = result + self.sites(obj)
74             elif IPloneSiteRoot.providedBy(obj):
75                 if secman.checkPermission(View, obj):
76                     result.append(obj)
77             elif obj.getId() in getattr(root, '_mount_points', {}):
78                 result.extend(self.sites(root=obj))
79         return result
80 
81     def outdated(self, obj):
82         # Try to pick the portal_migration as an attribute
83         # (Plone 5 unmigrated site root) or as an item
84         mig = (
85             getattr(obj, "portal_migration", None)
86             or obj.get('portal_migration', None)
87         )
88         if mig is not None:
89             return mig.needUpgrading()
90         return False
91 
92     def can_manage(self):
93         secman = getSecurityManager()
94         return secman.checkPermission(ManagePortal, self.context)
95 
96     def upgrade_url(self, site, can_manage=None):
97         if can_manage is None:
98             can_manage = self.can_manage()
99         if can_manage:
100             return site.absolute_url() + '/@@plone-upgrade'
101         else:
102             return self.context.absolute_url() + '/@@plone-root-login'
103 
104 
105 class RootLoginRedirect(BrowserView):
106     """ @@plone-root-login
107 
108     This view of the Zope root forces authentication via the root
109     acl_users and then redirects elsewhere.
110     """
111 
112     def __call__(self, came_from=None):
113         if came_from is not None:
114             # see if this is a relative url or an absolute
115             if len(parse.urlparse(came_from)[1]) == 0:
116                 # No host specified, so url is relative.  Get an absolute url.
117                 # Note: '\\domain.org' is not recognised as host,
118                 # which is good.
119                 came_from = parse.urljoin(
120                     self.context.absolute_url() + '/', came_from,
121                 )
122             elif not came_from.startswith(self.context.absolute_url()):
123                 # Note: we cannot use portal_url.isURLInPortal here, because we
124                 # are not in a Plone portal, but in the Zope root.
125                 came_from = None
126         if came_from is None:
127             came_from = self.context.absolute_url()
128         self.request.response.redirect(came_from)
129 
130 
131 class RootLogout(BrowserView):
132     """ @@plone-root-logout """
133 
134     logout = ViewPageTemplateFile('templates/plone-admin-logged-out.pt')
135 
136     def __call__(self):
137         response = self.request.response
138         realm = response.realm
139         response.setStatus(401)
140         response.setHeader('WWW-Authenticate', 'basic realm="%s"' % realm, 1)
141         response.setBody(self.logout())
142         return
143 
144 
145 class FrontPage(BrowserView):
146 
147     index = ViewPageTemplateFile('templates/plone-frontpage.pt')
148 
149 
150 class AddPloneSite(BrowserView):
151 
152     # Profiles that are installed by default,
153     # but can be removed later.
154     default_extension_profiles = (
155         'plone.app.caching:default',
156         'plonetheme.barceloneta:default',
157     )
158     # Let's have a separate list for Volto.
159     volto_default_extension_profiles = (
160         'plone.app.caching:default',
161         # We could choose to not install Barceloneta:
162         'plonetheme.barceloneta:default',
163         'plone.volto:default',
164         'plone.volto:default-homepage'
165     )
166 
167     def profiles(self):
168         base_profiles = []
169         extension_profiles = []
170         if HAS_VOLTO and not self.request.get('classic'):
171             selected_extension_profiles = self.volto_default_extension_profiles
172         else:
173             selected_extension_profiles = self.default_extension_profiles
174 
175         # profiles available for install/uninstall, but hidden at the time
176         # the Plone site is created
177         not_installable = [
178             'Products.CMFPlacefulWorkflow:CMFPlacefulWorkflow',
179         ]
180         utils = getAllUtilitiesRegisteredFor(INonInstallable)
181         for util in utils:
182             not_installable.extend(util.getNonInstallableProfiles())
183 
184         for info in profile_registry.listProfileInfo():
185             if info.get('type') == EXTENSION and \
186                info.get('for') in (IPloneSiteRoot, None):
187                 profile_id = info.get('id')
188                 if profile_id not in not_installable:
189                     if profile_id in selected_extension_profiles:
190                         info['selected'] = 'selected'
191                     extension_profiles.append(info)
192 
193         def _key(v):
194             # Make sure implicitly selected items come first
195             selected = v.get('selected') and 'automatic' or 'manual'
196             return '{}-{}'.format(selected, v.get('title', ''))
197         extension_profiles.sort(key=_key)
198 
199         for info in profile_registry.listProfileInfo():
200             if info.get('type') == BASE and \
201                info.get('for') in (IPloneSiteRoot, None):
202                 base_profiles.append(info)
203 
204         return dict(
205             base=tuple(base_profiles),
206             default=_DEFAULT_PROFILE,
207             extensions=tuple(extension_profiles),
208         )
209 
210     def browser_language(self):
211         language = 'en'
212         pl = IUserPreferredLanguages(self.request)
213         if pl is not None:
214             languages = pl.getPreferredLanguages()
215             for httplang in languages:
216                 parts = (httplang.split('-') + [None, None])[:3]
217                 if parts[0] == parts[1]:
218                     # Avoid creating a country code for simple languages codes
219                     parts = [parts[0], None, None]
220                 try:
221                     locale = locales.getLocale(*parts)
222                     language = locale.getLocaleID().replace('_', '-').lower()
223                     break
224                 except LoadLocaleError:
225                     # Just try the next combination
226                     pass
227         return language
228 
229     def grouped_languages(self, default='en'):
230         util = queryUtility(IContentLanguageAvailability)
231         available = util.getLanguages(combined=True)
232         languages = dict(util.getLanguageListing())
233 
234         # Group country specific versions by language
235         grouped = OrderedDict()
236         for langcode, data in available.items():
237             lang = langcode.split('-')[0]
238             language = languages.get(lang, lang)  # Label
239 
240             struct = grouped.get(lang, {'label': language, 'languages': []})
241 
242             langs = struct['languages']
243             langs.append({
244                 'langcode': langcode,
245                 'label': data.get('native', data.get('name')),
246             })
247 
248             grouped[lang] = struct
249 
250         # Sort list by language, next by country
251         data = sorted(grouped.values(), key=lambda k: k['label'])
252         for item in data:
253             item['languages'] = sorted(
254                 item['languages'], key=lambda k: k['label'].lower())
255         return data
256 
257     def timezones(self):
258         tz_vocab = getUtility(
259             IVocabularyFactory,
260             'plone.app.vocabularies.CommonTimezones'
261         )(self.context)
262 
263         grouped = OrderedDict()
264         tz_values = [it.value for it in tz_vocab]
265         for value in tz_values:
266             splitted = value.split('/')
267             group = splitted.pop(0)
268             label = '/'.join(splitted)
269 
270             entries = grouped.get(group, [])
271             entries.append({'label': label or group, 'value': value})
272             grouped[group] = entries
273 
274         return grouped
275 
276     def __call__(self):
277         context = self.context
278         form = self.request.form
279         submitted = form.get('form.submitted', False)
280         if submitted:
281             site_id = form.get('site_id', 'Plone')
282 
283             # CSRF protect. DO NOT use auto CSRF protection for adding a site
284             alsoProvides(self.request, IDisableCSRFProtection)
285 
286             # check if keyring is installed on root, disable CSRF protection
287             # if it is because it is not installed until a plone site
288             # is created
289             if queryUtility(IKeyManager) is None:
290                 LOGGER.info('CSRF protection disabled on initial site '
291                             'creation')
292             else:
293                 # we have a keymanager, check csrf protection manually now
294                 checkCSRF(self.request)
295             site = addPloneSite(
296                 context, site_id,
297                 title=form.get('title', ''),
298                 profile_id=form.get('profile_id', _DEFAULT_PROFILE),
299                 extension_ids=form.get('extension_ids', ()),
300                 setup_content=form.get('setup_content', False),
301                 default_language=form.get('default_language', 'en'),
302                 portal_timezone=form.get('portal_timezone', 'UTC')
303             )
304             self.request.response.redirect(site.absolute_url())
305             return ''
306 
307         return self.index()
308 
309 
310 class Upgrade(BrowserView):
311 
312     def upgrades(self):
313         pm = getattr(self.context, 'portal_migration')
314         return pm.listUpgrades()
315 
316     def versions(self):
317         pm = getattr(self.context, 'portal_migration')
318         result = {}
319         result['instance'] = pm.getInstanceVersion()
320         result['fs'] = pm.getFileSystemVersion()
321         result['equal'] = result['instance'] == result['fs']
322         instance_version = normalize_version(result['instance'])
323         fs_version = normalize_version(result['fs'])
324         result['instance_gt'] = instance_version > fs_version
325         result['instance_lt'] = instance_version < fs_version
326         result['corelist'] = pm.coreVersions()
327         return result
328 
329     def __call__(self):
330         form = self.request.form
331         submitted = form.get('form.submitted', False)
332         if submitted:
333             # CSRF protect. DO NOT use auto CSRF protection for upgrading sites
334             alsoProvides(self.request, IDisableCSRFProtection)
335 
336             pm = getattr(self.context, 'portal_migration')
337             report = pm.upgrade(
338                 REQUEST=self.request,
339                 dry_run=form.get('dry_run', False),
340             )
341             return self.index(
342                 report=report,
343             )
344 
345         return self.index()
346
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/Products/CMFPlone/browser/admin.py b/Products/CMFPlone/browser/admin.py
--- a/Products/CMFPlone/browser/admin.py
+++ b/Products/CMFPlone/browser/admin.py
@@ -343,3 +343,16 @@
             )
 
         return self.index()
+
+    def can_migrate_to_volto(self):
+        if not HAS_VOLTO:
+            return False
+        pm = getattr(self.context, 'portal_migration')
+        if pm.getInstanceVersion() < "6005":
+            return False
+        try:
+            from plone.volto.browser import migrate_to_volto
+        except ImportError:
+            return False
+        installer = get_installer(self.context, self.request)
+        return not installer.is_product_installed("plone.volto")

{"golden_diff": "diff --git a/Products/CMFPlone/browser/admin.py b/Products/CMFPlone/browser/admin.py\n--- a/Products/CMFPlone/browser/admin.py\n+++ b/Products/CMFPlone/browser/admin.py\n@@ -343,3 +343,16 @@\n             )\n \n         return self.index()\n+\n+    def can_migrate_to_volto(self):\n+        if not HAS_VOLTO:\n+            return False\n+        pm = getattr(self.context, 'portal_migration')\n+        if pm.getInstanceVersion() < \"6005\":\n+            return False\n+        try:\n+            from plone.volto.browser import migrate_to_volto\n+        except ImportError:\n+            return False\n+        installer = get_installer(self.context, self.request)\n+        return not installer.is_product_installed(\"plone.volto\")\n", "issue": "Allow to migrate to Volto after updating a site to Plone 6\nWhen Plone is updated to 6 the upgrade-view (@@plone-upgrade) should display a link to the migration to Volto.\r\nSee https://github.com/plone/plone.volto/issues/55 for the migration itself.\n", "before_files": [{"content": "from AccessControl import getSecurityManager\nfrom AccessControl.Permissions import view as View\nfrom OFS.interfaces import IApplication\nfrom Products.CMFCore.permissions import ManagePortal\nfrom Products.CMFPlone.factory import _DEFAULT_PROFILE\nfrom Products.CMFPlone.factory import addPloneSite\nfrom plone.base.interfaces import INonInstallable\nfrom plone.base.interfaces import IPloneSiteRoot\nfrom Products.CMFPlone.utils import get_installer\nfrom Products.Five.browser.pagetemplatefile import ViewPageTemplateFile\nfrom Products.GenericSetup import BASE, EXTENSION\nfrom Products.GenericSetup import profile_registry\nfrom Products.GenericSetup.upgrade import normalize_version\nfrom ZPublisher.BaseRequest import DefaultPublishTraverse\nfrom collections import OrderedDict\nfrom plone.i18n.locales.interfaces import IContentLanguageAvailability\nfrom plone.keyring.interfaces import IKeyManager\nfrom plone.protect.authenticator import check as checkCSRF\nfrom plone.protect.interfaces import IDisableCSRFProtection\nfrom urllib import parse\nfrom ZODB.broken import Broken\nfrom zope.component import adapts\nfrom zope.component import getAllUtilitiesRegisteredFor\nfrom zope.component import getUtility\nfrom zope.component import queryMultiAdapter\nfrom zope.component import queryUtility\nfrom zope.i18n.interfaces import IUserPreferredLanguages\nfrom zope.i18n.locales import locales, LoadLocaleError\nfrom zope.interface import Interface\nfrom zope.interface import alsoProvides\nfrom zope.publisher.browser import BrowserView\nfrom zope.publisher.interfaces import IRequest\nfrom zope.schema.interfaces import IVocabularyFactory\n\nimport logging\nimport pkg_resources\n\n\ntry:\n    pkg_resources.get_distribution(\"plone.volto\")\n    HAS_VOLTO = True\nexcept pkg_resources.DistributionNotFound:\n    HAS_VOLTO = False\nLOGGER = logging.getLogger('Products.CMFPlone')\n\n\nclass AppTraverser(DefaultPublishTraverse):\n    adapts(IApplication, IRequest)\n\n    def publishTraverse(self, request, name):\n        if name == 'index_html':\n            view = queryMultiAdapter(\n                (self.context, request), Interface, 'plone-overview')\n            if view is not None:\n                return view\n        return DefaultPublishTraverse.publishTraverse(self, request, name)\n\n\nclass Overview(BrowserView):\n    has_volto = HAS_VOLTO\n\n    def sites(self, root=None):\n        if root is None:\n            root = self.context\n\n        result = []\n        secman = getSecurityManager()\n        candidates = (\n            obj for obj in root.values() if not isinstance(obj, Broken)\n        )\n        for obj in candidates:\n            if obj.meta_type == 'Folder':\n                result = result + self.sites(obj)\n            elif IPloneSiteRoot.providedBy(obj):\n                if secman.checkPermission(View, obj):\n                    result.append(obj)\n            elif obj.getId() in getattr(root, '_mount_points', {}):\n                result.extend(self.sites(root=obj))\n        return result\n\n    def outdated(self, obj):\n        # Try to pick the portal_migration as an attribute\n        # (Plone 5 unmigrated site root) or as an item\n        mig = (\n            getattr(obj, \"portal_migration\", None)\n            or obj.get('portal_migration', None)\n        )\n        if mig is not None:\n            return mig.needUpgrading()\n        return False\n\n    def can_manage(self):\n        secman = getSecurityManager()\n        return secman.checkPermission(ManagePortal, self.context)\n\n    def upgrade_url(self, site, can_manage=None):\n        if can_manage is None:\n            can_manage = self.can_manage()\n        if can_manage:\n            return site.absolute_url() + '/@@plone-upgrade'\n        else:\n            return self.context.absolute_url() + '/@@plone-root-login'\n\n\nclass RootLoginRedirect(BrowserView):\n    \"\"\" @@plone-root-login\n\n    This view of the Zope root forces authentication via the root\n    acl_users and then redirects elsewhere.\n    \"\"\"\n\n    def __call__(self, came_from=None):\n        if came_from is not None:\n            # see if this is a relative url or an absolute\n            if len(parse.urlparse(came_from)[1]) == 0:\n                # No host specified, so url is relative.  Get an absolute url.\n                # Note: '\\\\domain.org' is not recognised as host,\n                # which is good.\n                came_from = parse.urljoin(\n                    self.context.absolute_url() + '/', came_from,\n                )\n            elif not came_from.startswith(self.context.absolute_url()):\n                # Note: we cannot use portal_url.isURLInPortal here, because we\n                # are not in a Plone portal, but in the Zope root.\n                came_from = None\n        if came_from is None:\n            came_from = self.context.absolute_url()\n        self.request.response.redirect(came_from)\n\n\nclass RootLogout(BrowserView):\n    \"\"\" @@plone-root-logout \"\"\"\n\n    logout = ViewPageTemplateFile('templates/plone-admin-logged-out.pt')\n\n    def __call__(self):\n        response = self.request.response\n        realm = response.realm\n        response.setStatus(401)\n        response.setHeader('WWW-Authenticate', 'basic realm=\"%s\"' % realm, 1)\n        response.setBody(self.logout())\n        return\n\n\nclass FrontPage(BrowserView):\n\n    index = ViewPageTemplateFile('templates/plone-frontpage.pt')\n\n\nclass AddPloneSite(BrowserView):\n\n    # Profiles that are installed by default,\n    # but can be removed later.\n    default_extension_profiles = (\n        'plone.app.caching:default',\n        'plonetheme.barceloneta:default',\n    )\n    # Let's have a separate list for Volto.\n    volto_default_extension_profiles = (\n        'plone.app.caching:default',\n        # We could choose to not install Barceloneta:\n        'plonetheme.barceloneta:default',\n        'plone.volto:default',\n        'plone.volto:default-homepage'\n    )\n\n    def profiles(self):\n        base_profiles = []\n        extension_profiles = []\n        if HAS_VOLTO and not self.request.get('classic'):\n            selected_extension_profiles = self.volto_default_extension_profiles\n        else:\n            selected_extension_profiles = self.default_extension_profiles\n\n        # profiles available for install/uninstall, but hidden at the time\n        # the Plone site is created\n        not_installable = [\n            'Products.CMFPlacefulWorkflow:CMFPlacefulWorkflow',\n        ]\n        utils = getAllUtilitiesRegisteredFor(INonInstallable)\n        for util in utils:\n            not_installable.extend(util.getNonInstallableProfiles())\n\n        for info in profile_registry.listProfileInfo():\n            if info.get('type') == EXTENSION and \\\n               info.get('for') in (IPloneSiteRoot, None):\n                profile_id = info.get('id')\n                if profile_id not in not_installable:\n                    if profile_id in selected_extension_profiles:\n                        info['selected'] = 'selected'\n                    extension_profiles.append(info)\n\n        def _key(v):\n            # Make sure implicitly selected items come first\n            selected = v.get('selected') and 'automatic' or 'manual'\n            return '{}-{}'.format(selected, v.get('title', ''))\n        extension_profiles.sort(key=_key)\n\n        for info in profile_registry.listProfileInfo():\n            if info.get('type') == BASE and \\\n               info.get('for') in (IPloneSiteRoot, None):\n                base_profiles.append(info)\n\n        return dict(\n            base=tuple(base_profiles),\n            default=_DEFAULT_PROFILE,\n            extensions=tuple(extension_profiles),\n        )\n\n    def browser_language(self):\n        language = 'en'\n        pl = IUserPreferredLanguages(self.request)\n        if pl is not None:\n            languages = pl.getPreferredLanguages()\n            for httplang in languages:\n                parts = (httplang.split('-') + [None, None])[:3]\n                if parts[0] == parts[1]:\n                    # Avoid creating a country code for simple languages codes\n                    parts = [parts[0], None, None]\n                try:\n                    locale = locales.getLocale(*parts)\n                    language = locale.getLocaleID().replace('_', '-').lower()\n                    break\n                except LoadLocaleError:\n                    # Just try the next combination\n                    pass\n        return language\n\n    def grouped_languages(self, default='en'):\n        util = queryUtility(IContentLanguageAvailability)\n        available = util.getLanguages(combined=True)\n        languages = dict(util.getLanguageListing())\n\n        # Group country specific versions by language\n        grouped = OrderedDict()\n        for langcode, data in available.items():\n            lang = langcode.split('-')[0]\n            language = languages.get(lang, lang)  # Label\n\n            struct = grouped.get(lang, {'label': language, 'languages': []})\n\n            langs = struct['languages']\n            langs.append({\n                'langcode': langcode,\n                'label': data.get('native', data.get('name')),\n            })\n\n            grouped[lang] = struct\n\n        # Sort list by language, next by country\n        data = sorted(grouped.values(), key=lambda k: k['label'])\n        for item in data:\n            item['languages'] = sorted(\n                item['languages'], key=lambda k: k['label'].lower())\n        return data\n\n    def timezones(self):\n        tz_vocab = getUtility(\n            IVocabularyFactory,\n            'plone.app.vocabularies.CommonTimezones'\n        )(self.context)\n\n        grouped = OrderedDict()\n        tz_values = [it.value for it in tz_vocab]\n        for value in tz_values:\n            splitted = value.split('/')\n            group = splitted.pop(0)\n            label = '/'.join(splitted)\n\n            entries = grouped.get(group, [])\n            entries.append({'label': label or group, 'value': value})\n            grouped[group] = entries\n\n        return grouped\n\n    def __call__(self):\n        context = self.context\n        form = self.request.form\n        submitted = form.get('form.submitted', False)\n        if submitted:\n            site_id = form.get('site_id', 'Plone')\n\n            # CSRF protect. DO NOT use auto CSRF protection for adding a site\n            alsoProvides(self.request, IDisableCSRFProtection)\n\n            # check if keyring is installed on root, disable CSRF protection\n            # if it is because it is not installed until a plone site\n            # is created\n            if queryUtility(IKeyManager) is None:\n                LOGGER.info('CSRF protection disabled on initial site '\n                            'creation')\n            else:\n                # we have a keymanager, check csrf protection manually now\n                checkCSRF(self.request)\n            site = addPloneSite(\n                context, site_id,\n                title=form.get('title', ''),\n                profile_id=form.get('profile_id', _DEFAULT_PROFILE),\n                extension_ids=form.get('extension_ids', ()),\n                setup_content=form.get('setup_content', False),\n                default_language=form.get('default_language', 'en'),\n                portal_timezone=form.get('portal_timezone', 'UTC')\n            )\n            self.request.response.redirect(site.absolute_url())\n            return ''\n\n        return self.index()\n\n\nclass Upgrade(BrowserView):\n\n    def upgrades(self):\n        pm = getattr(self.context, 'portal_migration')\n        return pm.listUpgrades()\n\n    def versions(self):\n        pm = getattr(self.context, 'portal_migration')\n        result = {}\n        result['instance'] = pm.getInstanceVersion()\n        result['fs'] = pm.getFileSystemVersion()\n        result['equal'] = result['instance'] == result['fs']\n        instance_version = normalize_version(result['instance'])\n        fs_version = normalize_version(result['fs'])\n        result['instance_gt'] = instance_version > fs_version\n        result['instance_lt'] = instance_version < fs_version\n        result['corelist'] = pm.coreVersions()\n        return result\n\n    def __call__(self):\n        form = self.request.form\n        submitted = form.get('form.submitted', False)\n        if submitted:\n            # CSRF protect. DO NOT use auto CSRF protection for upgrading sites\n            alsoProvides(self.request, IDisableCSRFProtection)\n\n            pm = getattr(self.context, 'portal_migration')\n            report = pm.upgrade(\n                REQUEST=self.request,\n                dry_run=form.get('dry_run', False),\n            )\n            return self.index(\n                report=report,\n            )\n\n        return self.index()\n", "path": "Products/CMFPlone/browser/admin.py"}], "after_files": [{"content": "from AccessControl import getSecurityManager\nfrom AccessControl.Permissions import view as View\nfrom OFS.interfaces import IApplication\nfrom Products.CMFCore.permissions import ManagePortal\nfrom Products.CMFPlone.factory import _DEFAULT_PROFILE\nfrom Products.CMFPlone.factory import addPloneSite\nfrom plone.base.interfaces import INonInstallable\nfrom plone.base.interfaces import IPloneSiteRoot\nfrom Products.CMFPlone.utils import get_installer\nfrom Products.Five.browser.pagetemplatefile import ViewPageTemplateFile\nfrom Products.GenericSetup import BASE, EXTENSION\nfrom Products.GenericSetup import profile_registry\nfrom Products.GenericSetup.upgrade import normalize_version\nfrom ZPublisher.BaseRequest import DefaultPublishTraverse\nfrom collections import OrderedDict\nfrom plone.i18n.locales.interfaces import IContentLanguageAvailability\nfrom plone.keyring.interfaces import IKeyManager\nfrom plone.protect.authenticator import check as checkCSRF\nfrom plone.protect.interfaces import IDisableCSRFProtection\nfrom urllib import parse\nfrom ZODB.broken import Broken\nfrom zope.component import adapts\nfrom zope.component import getAllUtilitiesRegisteredFor\nfrom zope.component import getUtility\nfrom zope.component import queryMultiAdapter\nfrom zope.component import queryUtility\nfrom zope.i18n.interfaces import IUserPreferredLanguages\nfrom zope.i18n.locales import locales, LoadLocaleError\nfrom zope.interface import Interface\nfrom zope.interface import alsoProvides\nfrom zope.publisher.browser import BrowserView\nfrom zope.publisher.interfaces import IRequest\nfrom zope.schema.interfaces import IVocabularyFactory\n\nimport logging\nimport pkg_resources\n\n\ntry:\n    pkg_resources.get_distribution(\"plone.volto\")\n    HAS_VOLTO = True\nexcept pkg_resources.DistributionNotFound:\n    HAS_VOLTO = False\nLOGGER = logging.getLogger('Products.CMFPlone')\n\n\nclass AppTraverser(DefaultPublishTraverse):\n    adapts(IApplication, IRequest)\n\n    def publishTraverse(self, request, name):\n        if name == 'index_html':\n            view = queryMultiAdapter(\n                (self.context, request), Interface, 'plone-overview')\n            if view is not None:\n                return view\n        return DefaultPublishTraverse.publishTraverse(self, request, name)\n\n\nclass Overview(BrowserView):\n    has_volto = HAS_VOLTO\n\n    def sites(self, root=None):\n        if root is None:\n            root = self.context\n\n        result = []\n        secman = getSecurityManager()\n        candidates = (\n            obj for obj in root.values() if not isinstance(obj, Broken)\n        )\n        for obj in candidates:\n            if obj.meta_type == 'Folder':\n                result = result + self.sites(obj)\n            elif IPloneSiteRoot.providedBy(obj):\n                if secman.checkPermission(View, obj):\n                    result.append(obj)\n            elif obj.getId() in getattr(root, '_mount_points', {}):\n                result.extend(self.sites(root=obj))\n        return result\n\n    def outdated(self, obj):\n        # Try to pick the portal_migration as an attribute\n        # (Plone 5 unmigrated site root) or as an item\n        mig = (\n            getattr(obj, \"portal_migration\", None)\n            or obj.get('portal_migration', None)\n        )\n        if mig is not None:\n            return mig.needUpgrading()\n        return False\n\n    def can_manage(self):\n        secman = getSecurityManager()\n        return secman.checkPermission(ManagePortal, self.context)\n\n    def upgrade_url(self, site, can_manage=None):\n        if can_manage is None:\n            can_manage = self.can_manage()\n        if can_manage:\n            return site.absolute_url() + '/@@plone-upgrade'\n        else:\n            return self.context.absolute_url() + '/@@plone-root-login'\n\n\nclass RootLoginRedirect(BrowserView):\n    \"\"\" @@plone-root-login\n\n    This view of the Zope root forces authentication via the root\n    acl_users and then redirects elsewhere.\n    \"\"\"\n\n    def __call__(self, came_from=None):\n        if came_from is not None:\n            # see if this is a relative url or an absolute\n            if len(parse.urlparse(came_from)[1]) == 0:\n                # No host specified, so url is relative.  Get an absolute url.\n                # Note: '\\\\domain.org' is not recognised as host,\n                # which is good.\n                came_from = parse.urljoin(\n                    self.context.absolute_url() + '/', came_from,\n                )\n            elif not came_from.startswith(self.context.absolute_url()):\n                # Note: we cannot use portal_url.isURLInPortal here, because we\n                # are not in a Plone portal, but in the Zope root.\n                came_from = None\n        if came_from is None:\n            came_from = self.context.absolute_url()\n        self.request.response.redirect(came_from)\n\n\nclass RootLogout(BrowserView):\n    \"\"\" @@plone-root-logout \"\"\"\n\n    logout = ViewPageTemplateFile('templates/plone-admin-logged-out.pt')\n\n    def __call__(self):\n        response = self.request.response\n        realm = response.realm\n        response.setStatus(401)\n        response.setHeader('WWW-Authenticate', 'basic realm=\"%s\"' % realm, 1)\n        response.setBody(self.logout())\n        return\n\n\nclass FrontPage(BrowserView):\n\n    index = ViewPageTemplateFile('templates/plone-frontpage.pt')\n\n\nclass AddPloneSite(BrowserView):\n\n    # Profiles that are installed by default,\n    # but can be removed later.\n    default_extension_profiles = (\n        'plone.app.caching:default',\n        'plonetheme.barceloneta:default',\n    )\n    # Let's have a separate list for Volto.\n    volto_default_extension_profiles = (\n        'plone.app.caching:default',\n        # We could choose to not install Barceloneta:\n        'plonetheme.barceloneta:default',\n        'plone.volto:default',\n        'plone.volto:default-homepage'\n    )\n\n    def profiles(self):\n        base_profiles = []\n        extension_profiles = []\n        if HAS_VOLTO and not self.request.get('classic'):\n            selected_extension_profiles = self.volto_default_extension_profiles\n        else:\n            selected_extension_profiles = self.default_extension_profiles\n\n        # profiles available for install/uninstall, but hidden at the time\n        # the Plone site is created\n        not_installable = [\n            'Products.CMFPlacefulWorkflow:CMFPlacefulWorkflow',\n        ]\n        utils = getAllUtilitiesRegisteredFor(INonInstallable)\n        for util in utils:\n            not_installable.extend(util.getNonInstallableProfiles())\n\n        for info in profile_registry.listProfileInfo():\n            if info.get('type') == EXTENSION and \\\n               info.get('for') in (IPloneSiteRoot, None):\n                profile_id = info.get('id')\n                if profile_id not in not_installable:\n                    if profile_id in selected_extension_profiles:\n                        info['selected'] = 'selected'\n                    extension_profiles.append(info)\n\n        def _key(v):\n            # Make sure implicitly selected items come first\n            selected = v.get('selected') and 'automatic' or 'manual'\n            return '{}-{}'.format(selected, v.get('title', ''))\n        extension_profiles.sort(key=_key)\n\n        for info in profile_registry.listProfileInfo():\n            if info.get('type') == BASE and \\\n               info.get('for') in (IPloneSiteRoot, None):\n                base_profiles.append(info)\n\n        return dict(\n            base=tuple(base_profiles),\n            default=_DEFAULT_PROFILE,\n            extensions=tuple(extension_profiles),\n        )\n\n    def browser_language(self):\n        language = 'en'\n        pl = IUserPreferredLanguages(self.request)\n        if pl is not None:\n            languages = pl.getPreferredLanguages()\n            for httplang in languages:\n                parts = (httplang.split('-') + [None, None])[:3]\n                if parts[0] == parts[1]:\n                    # Avoid creating a country code for simple languages codes\n                    parts = [parts[0], None, None]\n                try:\n                    locale = locales.getLocale(*parts)\n                    language = locale.getLocaleID().replace('_', '-').lower()\n                    break\n                except LoadLocaleError:\n                    # Just try the next combination\n                    pass\n        return language\n\n    def grouped_languages(self, default='en'):\n        util = queryUtility(IContentLanguageAvailability)\n        available = util.getLanguages(combined=True)\n        languages = dict(util.getLanguageListing())\n\n        # Group country specific versions by language\n        grouped = OrderedDict()\n        for langcode, data in available.items():\n            lang = langcode.split('-')[0]\n            language = languages.get(lang, lang)  # Label\n\n            struct = grouped.get(lang, {'label': language, 'languages': []})\n\n            langs = struct['languages']\n            langs.append({\n                'langcode': langcode,\n                'label': data.get('native', data.get('name')),\n            })\n\n            grouped[lang] = struct\n\n        # Sort list by language, next by country\n        data = sorted(grouped.values(), key=lambda k: k['label'])\n        for item in data:\n            item['languages'] = sorted(\n                item['languages'], key=lambda k: k['label'].lower())\n        return data\n\n    def timezones(self):\n        tz_vocab = getUtility(\n            IVocabularyFactory,\n            'plone.app.vocabularies.CommonTimezones'\n        )(self.context)\n\n        grouped = OrderedDict()\n        tz_values = [it.value for it in tz_vocab]\n        for value in tz_values:\n            splitted = value.split('/')\n            group = splitted.pop(0)\n            label = '/'.join(splitted)\n\n            entries = grouped.get(group, [])\n            entries.append({'label': label or group, 'value': value})\n            grouped[group] = entries\n\n        return grouped\n\n    def __call__(self):\n        context = self.context\n        form = self.request.form\n        submitted = form.get('form.submitted', False)\n        if submitted:\n            site_id = form.get('site_id', 'Plone')\n\n            # CSRF protect. DO NOT use auto CSRF protection for adding a site\n            alsoProvides(self.request, IDisableCSRFProtection)\n\n            # check if keyring is installed on root, disable CSRF protection\n            # if it is because it is not installed until a plone site\n            # is created\n            if queryUtility(IKeyManager) is None:\n                LOGGER.info('CSRF protection disabled on initial site '\n                            'creation')\n            else:\n                # we have a keymanager, check csrf protection manually now\n                checkCSRF(self.request)\n            site = addPloneSite(\n                context, site_id,\n                title=form.get('title', ''),\n                profile_id=form.get('profile_id', _DEFAULT_PROFILE),\n                extension_ids=form.get('extension_ids', ()),\n                setup_content=form.get('setup_content', False),\n                default_language=form.get('default_language', 'en'),\n                portal_timezone=form.get('portal_timezone', 'UTC')\n            )\n            self.request.response.redirect(site.absolute_url())\n            return ''\n\n        return self.index()\n\n\nclass Upgrade(BrowserView):\n\n    def upgrades(self):\n        pm = getattr(self.context, 'portal_migration')\n        return pm.listUpgrades()\n\n    def versions(self):\n        pm = getattr(self.context, 'portal_migration')\n        result = {}\n        result['instance'] = pm.getInstanceVersion()\n        result['fs'] = pm.getFileSystemVersion()\n        result['equal'] = result['instance'] == result['fs']\n        instance_version = normalize_version(result['instance'])\n        fs_version = normalize_version(result['fs'])\n        result['instance_gt'] = instance_version > fs_version\n        result['instance_lt'] = instance_version < fs_version\n        result['corelist'] = pm.coreVersions()\n        return result\n\n    def __call__(self):\n        form = self.request.form\n        submitted = form.get('form.submitted', False)\n        if submitted:\n            # CSRF protect. DO NOT use auto CSRF protection for upgrading sites\n            alsoProvides(self.request, IDisableCSRFProtection)\n\n            pm = getattr(self.context, 'portal_migration')\n            report = pm.upgrade(\n                REQUEST=self.request,\n                dry_run=form.get('dry_run', False),\n            )\n            return self.index(\n                report=report,\n            )\n\n        return self.index()\n\n    def can_migrate_to_volto(self):\n        if not HAS_VOLTO:\n            return False\n        pm = getattr(self.context, 'portal_migration')\n        if pm.getInstanceVersion() < \"6005\":\n            return False\n        try:\n            from plone.volto.browser import migrate_to_volto\n        except ImportError:\n            return False\n        installer = get_installer(self.context, self.request)\n        return not installer.is_product_installed(\"plone.volto\")\n", "path": "Products/CMFPlone/browser/admin.py"}]}