source: serversideredirectplugin/0.11/tracserversideredirect/plugin.py

Last change on this file was 15264, checked in by Ryan J Ollos, 8 years ago

Remove unnecessary svn:mime-type on py files

svn:mime-type was set to "plain" for many files.

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Id URL Author Date Rev
File size: 8.8 KB
Line 
1# -*- coding: utf-8 -*-
2""" ServerSideRedirectPlugin for Trac
3
4    Copyright (c) 2008 Martin Scharrer <martin@scharrer-online.de>
5    This is Free Software under the BSD or GPL v3 or later license.
6    $Id: plugin.py 15264 2016-02-11 04:22:34Z rjollos $
7"""
8
9import re
10
11from genshi.builder import tag
12from trac.core import *
13from trac.mimeview.api import Context
14from trac.web.api import IRequestHandler, IRequestFilter, RequestDone
15from trac.wiki.api import IWikiMacroProvider
16from trac.wiki.model import WikiPage
17
18from tracextracturl.extracturl import extract_url
19
20MACRO = re.compile(r'.*\[\[[rR]edirect\((.*)\)\]\]')
21
22
23class ServerSideRedirectPlugin(Component):
24    """This Trac plug-in implements a server sided redirect functionality.
25The user interface is the wiki macro `Redirect` (alternatively `redirect`).
26
27== Description ==
28Website: https://trac-hacks.org/wiki/ServerSideRedirectPlugin
29
30`$Id: plugin.py 15264 2016-02-11 04:22:34Z rjollos $`
31
32This plug-in allow to place a redirect macro at the start of any wiki
33page which will cause an server side redirect when the wiki page is
34viewed.
35
36This plug-in is compatible (i.e. can be used) with the client side
37redirect macro TracRedirect but doesn't depend on it. Because the
38redirect is caused by the server (using a HTTP redirect request to the
39browser) it is much faster and less noticeable for the user. The
40back-link feature of TracRedirect can also be used for server side
41redirected pages because both generate the same URL attributes.
42
43To edit a redirecting wiki page access its URL with `?action=edit`
44appended. To view the page either use `?action=view`, which will print
45the redirect target (if TracRedirect isn't active, which will redirect
46the wiki using client side code), or `?redirect=no` which disables
47redirection of both the ServerSideRedirectPlugin and TracRedirect
48plug-in.
49
50Direct after the redirect target is added (or modified) Trac will
51automatically reload it, as it does with all wiki pages. This plug-in
52will detect this and not redirect but display the wiki page with the
53redirect target URL printed to provide feedback about the successful
54change. However, further visits will trigger the redirect.
55
56== Usage Examples ==
57The following 'macro' at the begin of the wiki page will cause a
58redirect to the ''!OtherWikiPage''.
59{{{
60[[redirect(OtherWikiPage)]]
61[[Redirect(OtherWikiPage)]]
62}}}
63Any other [TracLinks TracLink] can be used:
64{{{
65[[redirect(wiki:OtherWikiPage)]]
66[[Redirect(wiki:OtherWikiPage)]]
67[[redirect(source:/trunk/file.py)]]
68[[Redirect(source:/trunk/file.py)]]
69[[redirect(http://www.example.com/)]]
70[[Redirect(http://www.example.com/)]]
71}}}
72    """
73    implements(IRequestHandler, IRequestFilter, IWikiMacroProvider)
74
75    def expand_macro(self, formatter, name, content):
76        """Print redirect notice after edit."""
77
78        target = extract_url(self.env, formatter.context, content)
79        if not target:
80            target = formatter.context.req.href.wiki(content)
81
82        return tag.div(
83            tag.strong('This page redirects to: '),
84            tag.a(content, href=target),
85            class_='system-message',
86            id='notice'
87        )
88
89    def get_macros(self):
90        """Provide but do not redefine the 'redirect' macro."""
91        get = self.env.config.get
92        if get('components', 'redirect.*') == 'enabled' or \
93                get('components', 'redirect.redirect.*') == 'enabled' or \
94                get('components',
95                    'redirect.redirect.tracredirect') == 'enabled':
96            return ['Redirect']
97        else:
98            return ['redirect', 'Redirect']
99
100    def get_macro_description(self, name):
101        if name == 'Redirect':
102            return self.__doc__
103        else:
104            return "See macro `Redirect`."
105
106    # IRequestHandler methods
107
108    def match_request(self, req):
109        """Only handle request when selected from `pre_process_request`."""
110        return False
111
112    def process_request(self, req):
113        """Redirect to pre-selected target."""
114        target = self._get_redirect(req)
115        if target:
116            # Check for self-redirect:
117            if target and target == req.href(req.path_info):
118                message = tag.div('Please ',
119                                  tag.a("change the redirect target",
120                                        href=target + "?action=edit"),
121                                  ' to another page.',
122                                  class_="system-message")
123                data = {
124                    'title': "Page redirects to itself!",
125                    'message': message,
126                    'type': 'TracError'
127                }
128                req.send_error(data['title'], status=409,
129                               env=self.env, data=data)
130                raise RequestDone
131
132            # Check for redirect pair, i.e. A->B, B->A
133            redirected_from = req.args.get('redirectedfrom', '')
134            if target and target == req.href.wiki(redirected_from):
135                message = tag.div(
136                    'Please change the redirect target from either ',
137                    tag.a("this page",
138                          href=req.href(req.path_info, action="edit")),
139                    ' or ',
140                    tag.a("the redirecting page",
141                          href=target + "?action=edit"),
142                    '.', class_="system-message")
143                data = {
144                    'title': "Redirect target redirects back to this page!",
145                    'message': message,
146                    'type': 'TracError'
147                }
148                req.send_error(data['title'], status=409,
149                               env=self.env, data=data)
150                raise RequestDone
151
152            # Add back link information for internal links:
153            if target and target[0] == '/':
154                redirectfrom = "redirectedfrom=" + req.path_info[6:]
155                # anchor should be the last in url
156                # according to http://trac.edgewall.org/ticket/8072
157                tgt, query, anchor = self.split_link(target)
158                if not query:
159                    query = "?" + redirectfrom
160                else:
161                    query += "&" + redirectfrom
162                target = tgt + query + anchor
163            req.redirect(target)
164            raise RequestDone
165        raise TracError("Invalid redirect target!")
166
167    def _get_redirect(self, req):
168        """Checks if the request should be redirected."""
169        if req.path_info == '/' or req.path_info == '/wiki':
170            wiki = 'WikiStart'
171        elif not req.path_info.startswith('/wiki/'):
172            return False
173        else:
174            wiki = req.path_info[6:]
175
176        wp = WikiPage(self.env, wiki, req.args.get('version'))
177
178        if not wp.exists:
179            return None
180
181        # Check for redirect "macro":
182        m = MACRO.match(wp.text)
183        if not m:
184            return False
185        wikitarget = m.groups()[0]
186        ctxt = Context.from_request(req)
187        redirect_target = extract_url(self.env, ctxt, wikitarget)
188        if not redirect_target:
189            redirect_target = req.href.wiki(wikitarget)
190        return redirect_target
191
192    # IRequestFilter methods
193
194    def pre_process_request(self, req, handler):
195        from trac.wiki.web_ui import WikiModule
196        if not isinstance(handler, WikiModule):
197            return handler
198
199        args = req.args
200        if not req.path_info.startswith('/wiki/') and \
201                not req.path_info == '/wiki' and not req.path_info == '/':
202            self.log.debug("SSR: no redirect: Path is not a wiki path")
203            return handler
204        if req.method != 'GET':
205            self.log.debug("SSR: no redirect: No GET request")
206            return handler
207        if 'action' in args:
208            self.log.debug("SSR: no redirect: action=" + args['action'])
209            return handler
210        if 'version' in args:
211            self.log.debug("SSR: no redirect: version=...")
212            return handler
213        if 'redirect' in args and args['redirect'].lower() == 'no':
214            self.log.debug("SSR: no redirect: redirect=no")
215            return handler
216        if req.environ.get('HTTP_REFERER', '').find('action=edit') != -1:
217            self.log.debug("SSR: no redirect: HTTP_REFERER includes "
218                           "action=edit")
219            return handler
220        if self._get_redirect(req):
221            self.log.debug("SSR: redirect!")
222            return self
223        self.log.debug("SSR: no redirect: No redirect macro found.")
224        return handler
225
226    def post_process_request(self, req, template, data, content_type):
227        return template, data, content_type
228
229    # Internal methods
230
231    def split_link(self, target):
232        """Split a target along "?" and "#" in `(path, query, fragment)`."""
233        query = fragment = ''
234        idx = target.find('#')
235        if idx >= 0:
236            target, fragment = target[:idx], target[idx:]
237        idx = target.find('?')
238        if idx >= 0:
239            target, query = target[:idx], target[idx:]
240        return target, query, fragment
Note: See TracBrowser for help on using the repository browser.