2 # -*- coding: iso-8859-15 -*-
4 "MediaWiki communication functions"
8 from authkit.users import UsersReadOnly, md5
9 import formencode, formencode.national
11 log = logging.getLogger(__name__)
13 import wradmin.model as model
14 import wradmin.model.validators
21 return model.validators.GermanBool().to_python(value)
24 def to_unsigned(value):
25 "Requires a positive number"
26 return formencode.validators.Int(min=0).to_python(value)
30 "Parses a date in the form 'yyy-mm-dd'"
31 return model.validators.DateConverter().to_python(value)
35 "Formats to coordinates '47.076207 N 11.453553 E' to the (latitude, longitude) tuplet."
36 return model.validators.Geo().to_python(value)
40 """Line 2237 of includes/Title.php says: $this->mTextform = str_replace( '_', ' ', $dbkey );
41 No not check for None because a missing title is an error"""
42 return value.replace(u'_', u' ')
45 def to_tristate(value):
46 """Does the following conversion:
48 u'Ja' -> (True, False)
49 u'Teilweise' -> (True, True)
50 u'Nein' -> (False, True)"""
51 return model.validators.GermanTristate().to_python(value)
55 return formencode.validators.Email().to_python(value)
59 return formencode.validators.URL().to_python(value)
63 return model.validators.AustrianPhoneNumber(messages={'phoneFormat': u"Telefonnummer %%(value)s muss das Format 0123/456789 oder +43/123/456789 haben"}).to_python(value)
66 def to_phone_info(value):
67 return model.validators.PhoneInfo(messages={'phoneInfo': u"Bitte verwenden Sie ein Format wie '0512/123456 (Schnee Alm)'."}).to_python(value)
70 def to_valuecommentlist(value):
71 """A value-comment list looks like one of the following lines:
73 value (optional comment)
75 value1; value2 (optional comment)
76 value1 (optional comment1); value2 (optional comment2); value3 (otional comment3)
77 value1 (optional comment1); value2 (optional comment2); value3 (otional comment3)
78 This function returns the value-comment list as list of tuples. If no comment is present, None is specified."""
79 return model.validators.ValueCommentList().to_python(value)
82 def conv(fnct, value, fieldname):
83 "Like one of the to_xxx functions (e.g. to_bool), but adds the field name to the error message"
84 try: return fnct(value)
85 except formencode.Invalid, e: raise formencode.Invalid(_(u"Conversion error in field '%s': %s") % (fieldname, unicode_e(e)), e.value, e.state)
88 def unicode_e(exception):
89 """Does "unicode(exception)" as it should be. This is a workaround for bug http://bugs.python.org/issue2517
90 that is not fixed in python 2.5.2.
91 Details of bug: "unicode(Exception(u'\xe4'))" raises an UnicodeEncodeError exception."""
92 if exception.message: return unicode(exception.message)
93 return unicode(exception)
96 def wikipage_to_wrsleddingcache1_2(wiki_page):
97 """Converts a sledding route wiki page (wradmin.model.page_table)
98 to a sledding route wrsleddingcache database record (version 1.2) (wradmin.model.wrsleddingcache1_2_table)."""
99 sl = model.WrSleddingCache1_2()
100 sl.page_id = wiki_page.page_id
101 sl.page_title = to_title(wiki_page.page_title)
104 wikitext = wiki_page.old_text
105 regexp = re.compile(u"\{\{(Rodelbahnbox[^\}]*)\}\}", re.DOTALL)
106 match = regexp.search(wikitext)
108 raise Exception(u"No 'Rodelbahnbox' found")
111 # Process Rodelbahnbox
112 for property in box.split('|'):
113 property = property.strip()
114 if property == u'Rodelbahnbox': continue
115 key_value = property.split('=')
116 if len(key_value) != 2:
117 raise Exception(u"Property '%s' has unexpected format" % key_value)
118 key = key_value[0].strip()
119 value = key_value[1].strip()
120 if key == u'Rodelbahnnummer': pass
121 elif key == u'Länge': sl.length = conv(to_unsigned, value, u'Länge')
122 elif key == u'Gehzeit': sl.walktime = conv(to_unsigned, value, u'Gehzeit')
123 elif key == u'Höhe oben': sl.height_top = conv(to_unsigned, value, u'Höhe oben')
124 elif key == u'Höhe unten': sl.height_bottom = conv(to_unsigned, value, u'Höhe unten')
125 elif key == u'Aufstieg getrennt': sl.walkup_separate = conv(to_bool, value, u'Aufstieg getrennt')
126 elif key == u'Lift': sl.lift = conv(to_bool, value, u'Lift')
127 elif key == u'Beleuchtung': sl.night_light = conv(to_bool, value, u'Beleuchtung')
128 elif key == u'Rodelverleih': sl.sledge_rental = conv(to_bool, value, u'Rodelverleih')
129 elif key == u'Öffentliche Anreise': sl.public_transport = conv(to_bool, value, u'Öffentliche Anreise')
130 elif key == u'Bild': sl.image = value
131 elif key == u'Position': (sl.position_latitude, sl.position_longitude) = conv(to_geo, value, u'Position') # '47.583333 N 15.75 E'
132 elif key == u'Auskunft': sl.information = conv(to_phone_info, value, u'Auskunft')
133 elif key == u'In Übersichtskarte': sl.show_in_overview = conv(to_bool, value, u'In Übersichtskarte')
134 elif key == u'Aufnahmedatum': sl.creation_date = conv(to_date, value, u'Aufnahmedatum') # '2006-03-15'
135 elif key == u'Lawinengefahr':
136 if not value in [u'kaum', u'selten', u'gelegentlich', u'häufig']: raise formencode.Invalid(u"No valid value for 'Lawinengefahr': '%s'" % value, value, None)
137 else: raise formencode.Invalid(u"Unbekannte Eigenschaft der Rodelbahnbox: '%s' (mit Wert '%s')" % (key, value), value, None)
138 sl.under_construction = None
140 # Match Forumlink (e.g. {{Forumlink|68}})
141 match = re.search(u"\{\{Forumlink\|(\d+)\}\}", wikitext)
142 if match: sl.forum_id = match.group(1)
147 def wikipage_to_wrsleddingcache(wiki_page):
148 """Converts a sledding route wiki page (wradmin.model.page_table)
149 to a sledding route wrsleddingcache database record (wradmin.model.wrsleddingcache_table).
150 Raises a RuntimeError if the format is not OK
151 sledding_wiki is a column of tabe "page".
152 Returns the WrSleddingCache class"""
153 sl = model.WrSleddingCache()
154 sl.page_id = wiki_page.page_id
155 sl.page_title = to_title(wiki_page.page_title)
156 errors = [] # List of errors with localized messages
159 wikitext = wiki_page.old_text
160 regexp = re.compile(u"\{\{(Rodelbahnbox[^\}]*)\}\}", re.DOTALL)
161 match = regexp.search(wikitext)
163 raise RuntimeError(_(u"No 'Rodelbahnbox' found"))
166 # Process Rodelbahnbox
167 for property in box.split('|'):
168 property = property.strip()
169 if property == u'Rodelbahnbox': continue
170 key_value = property.split('=')
171 if len(key_value) != 2:
172 raise RuntimeError(_(u"Property '%s' has unexpected format") % key_value)
173 key = key_value[0].strip()
174 value = key_value[1].strip()
175 if key in [u'Rodelbahnnummer', u'Lift']:
176 errors.append(_("Property '%s' is not supported anymore, see %s.") % (key, 'http://www.winterrodeln.org/wiki/Vorlage:Rodelbahnbox'))
177 elif key == u'Position': (sl.position_latitude, sl.position_longitude) = conv(to_geo, value, u'Position') # '47.583333 N 15.75 E'
178 elif key == u'Position oben': (sl.top_latitude, sl.top_longitude) = conv(to_geo, value, u'Position oben') # '47.583333 N 15.75 E'
185 | Schwierigkeit = mittel
187 | Betreiber = Max Mustermann
188 | Öffentliche Anreise = Mittelmäßig
190 | Aufstieg getrennt = Ja
191 | Aufstiegshilfe = Gondel (unterer Teil)
192 | Beleuchtungsanlage = Ja
193 | Beleuchtungstage = 3 (Montag, Mittwoch, Freitag)
194 | Rodelverleih = Ja (Talstation Serlesbahnan)
195 | Gütesiegel = Tiroler Naturrodelbahn-Gütesiegel 2009 mittel
196 | Webauskunft = http://www.nösslachhütte.at/page9.php
197 | Telefonauskunft = +43-664-5487520 (Mitterer Alm)
198 | Bild = Rodelbahn_Mitterer_Alm_04.jpg
199 | In Übersichtskarte = Ja
202 sa.Column("top_elevation", types.Integer),
203 sa.Column("bottom_latitude", types.Float),
204 sa.Column("bottom_longitude", types.Float),
205 sa.Column("bottom_elevation", types.Integer),
206 sa.Column("length", types.Integer),
207 sa.Column("difficulty", types.Integer),
208 sa.Column("avalanches", types.Integer),
209 sa.Column("operator", types.Unicode(255)),
210 sa.Column("public_transport", types.Integer),
211 sa.Column("walkup_time", types.Integer),
212 sa.Column("walkup_separate", types.Float),
213 sa.Column("walkup_separate_comment", types.Unicode(255)),
214 sa.Column("lift", types.Boolean),
215 sa.Column("lift_details", types.Unicode(255)),
216 sa.Column("night_light", types.Float),
217 sa.Column("night_light_days", types.Integer),
218 sa.Column("night_light_days_comment", types.Unicode(255)),
219 sa.Column("sled_rental", types.Boolean),
220 sa.Column("cachet", types.Unicode(255)),
221 sa.Column("information_web", types.Unicode(255)),
222 sa.Column("information_phone", types.Unicode(255)),
223 sa.Column("image", types.Unicode(255)),
224 sa.Column("show_in_overview", types.Boolean),
225 sa.Column("forum_id", types.Integer),
226 sa.Column("under_construction", types.Boolean),
235 elif key == u'Bild': sl.image = value
237 elif key == u'Länge': sl.length = conv(to_unsigned, value, u'Länge')
238 elif key == u'Gehzeit': sl.walktime = conv(to_unsigned, value, u'Gehzeit')
239 elif key == u'Höhe oben': sl.height_top = conv(to_unsigned, value, u'Höhe oben')
240 elif key == u'Höhe unten': sl.height_bottom = conv(to_unsigned, value, u'Höhe unten')
241 elif key == u'Aufstieg getrennt':
242 tristate = conv(to_tristate, value, u'Aufstieg getrennt')
243 if tristate == (True, False): sl.walkup_separate = 1.0
244 elif tristate == (True, True): sl.walkup_separate = 0.5
245 elif tristate == (False, True): sl.walkup_separate = 0.0
246 elif key == u'Aufstiegshilfe':
247 valuecommentlist = conv(to_valuecommentlist, value, u'Aufstiegshilfe')
248 lift = len(valuecommentlist) > 0
249 for value, comment in valuecommentlist:
251 if len(valuecommentlist) != 1: raise formencode.Invalid('"Nein" kann mit keiner anderen Aufstiegshilfe kombiniert werden.', value, None)
253 elif not value in [u'Sessellift', u'Gondel', u'Linienbus', u'Taxi', u'Sonstige']:
254 raise formencode.Invalid(u'"%s" ist keine gültige Aufstiegshilfe.' % value, value, None)
256 sl.lift_detail = model.validators.ValueCommentList().from_python(valuecommentlist)
257 elif key == u'Beleuchtung': sl.night_light = conv(to_bool, value, u'Beleuchtung')
258 elif key == u'Rodelverleih': sl.sledge_rental = conv(to_bool, value, u'Rodelverleih')
259 elif key == u'Öffentliche Anreise': sl.public_transport = conv(to_bool, value, u'Öffentliche Anreise')
260 elif key == u'Auskunft': sl.information = conv(to_phone_info, value, u'Auskunft')
261 elif key == u'In Übersichtskarte': sl.show_in_overview = conv(to_bool, value, u'In Übersichtskarte')
262 elif key == u'Aufnahmedatum': sl.creation_date = conv(to_date, value, u'Aufnahmedatum') # '2006-03-15'
263 elif key == u'Lawinengefahr':
264 if not value in [u'kaum', u'selten', u'gelegentlich', u'häufig']: raise formencode.Invalid(u"No valid value for 'Lawinengefahr': '%s'" % value, value, None)
265 else: raise formencode.Invalid(u"Unbekannte Eigenschaft der Rodelbahnbox: '%s' (mit Wert '%s')" % (key, value), value, None)
266 sl.under_construction = None
268 # Match Forumlink (e.g. {{Forumlink|68}})
269 match = re.search(u"\{\{Forumlink\|(\d+)\}\}", wikitext)
270 if match: sl.forum_id = match.group(1)
278 class MediaWikiUsers(UsersReadOnly):
279 def __init__(self, data=None, encrypt=None):
280 UsersReadOnly.__init__(self, data, encrypt)
282 # Initialize class fields
287 self.user_ids = {} # MediaWiki user_id field of the database
288 self.real_names = {} # Real names of the users
289 self.emails = {} # E-Mail addresses of the users
292 con = model.meta.engine.connect()
293 sql = "SELECT user_id, user_name, user_real_name, user_password, user_email FROM user, user_groups WHERE ug_user=user_id AND ug_group='beauftragte'"
294 result = con.execute(sql)
296 user_id, username, real_name, password, email = row
297 username = username.lower()
301 self.usernames.append(username)
302 self.passwords[username] = password
303 self.roles[username] = role
304 self.groups[username] = group
305 self.user_ids[username] = user_id
306 self.real_names[username] = real_name
307 self.emails[username] = email
309 log.info("%d users loaded from the MediaWiki database" % len(self.usernames))
312 def user_has_password(self, username, password):
314 Passwords are case sensitive.
315 Returns ``True`` if the user has the password specified, ``False`` otherwise.
316 Raises an exception if the user doesn't exist.
318 See http://www.winterrodeln.org/trac/wiki/MediaWikiAuthorization
320 pwd = self.user_password(username)
321 # Example: pwd = ':B:d25b2886:41e46c952790b1b442aac4f24f7ea7a8'
322 pwd_parts = pwd.split(':') # password_parts = ['', 'B', 'd25b2886', '41e46c952790b1b442aac4f24f7ea7a8']
323 if len(pwd_parts) == 4 and pwd_parts[1] == 'B':
324 salt, pwd_md5 = tuple(pwd_parts[2:4]) # salt = 'd25b2886'; pwd_md5 = '41e46c952790b1b442aac4f24f7ea7a8'
326 raise AuthKitError("Password in the MediaWiki database format has an unexpected format ('%s' instead of e.g. ':B:d25b2886:41e46c952790b1b442aac4f24f7ea7a8')" % pwd)
327 # log.info("user: '%s'; md5 of salt+' '+entered_pwd: '%s'; md5-part of DB-pwd: %s" % (username, md5(salt + '-' + md5(password)), pwd_md5))
328 return md5(salt + '-' + md5(password)) == pwd_md5