diff --git a/addons/web_kanban/static/src/js/kanban.js b/addons/web_kanban/static/src/js/kanban.js
index 1d97522e011..239a703d276 100644
--- a/addons/web_kanban/static/src/js/kanban.js
+++ b/addons/web_kanban/static/src/js/kanban.js
@@ -62,6 +62,14 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
},
load_kanban: function(data) {
this.fields_view = data;
+
+ // use default order if defined in xml description
+ var default_order = this.fields_view.arch.attrs.default_order,
+ unsorted = !this.dataset._sort.length;
+ if (unsorted && default_order) {
+ this.dataset.set_sort(default_order.split(','));
+ }
+
this.$el.addClass(this.fields_view.arch.attrs['class']);
this.$buttons = $(QWeb.render("KanbanView.buttons", {'widget': this}));
if (this.options.$buttons) {
diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py
index 8e6846dd7a8..6ffb267c214 100644
--- a/addons/website/models/ir_http.py
+++ b/addons/website/models/ir_http.py
@@ -24,6 +24,7 @@ class ir_http(orm.AbstractModel):
_inherit = 'ir.http'
rerouting_limit = 10
+ geo_ip_resolver = None
def _get_converters(self):
return dict(
@@ -53,6 +54,18 @@ class ir_http(orm.AbstractModel):
request.website_multilang = request.website_enabled and func and func.routing.get('multilang', True)
+ if not request.session.has_key('geoip'):
+ record = {}
+ if self.geo_ip_resolver is None:
+ try:
+ import GeoIP
+ self.geo_ip_resolver = GeoIP.open('/usr/share/GeoIP/GeoIP.dat', GeoIP.GEOIP_STANDARD)
+ except ImportError:
+ self.geo_ip_resolver = False
+ if self.geo_ip_resolver:
+ record = self.geo_ip_resolver.record_by_addr(request.httprequest.remote_addr) or {}
+ request.session['geoip'] = record
+
if request.website_enabled:
if func:
self._authenticate(func.routing['auth'])
diff --git a/addons/website_crm_partner_assign/controllers/main.py b/addons/website_crm_partner_assign/controllers/main.py
index 7bec48c140a..9ae7a43289f 100644
--- a/addons/website_crm_partner_assign/controllers/main.py
+++ b/addons/website_crm_partner_assign/controllers/main.py
@@ -1,15 +1,6 @@
# -*- coding: utf-8 -*-
-import logging
import re
import werkzeug
-
-_logger = logging.getLogger(__name__)
-try:
- import GeoIP
-except ImportError:
- GeoIP = None
- _logger.warn("Please install GeoIP python module to use events localisation.")
-
from openerp import SUPERUSER_ID
from openerp.addons.web import http
from openerp.addons.web.http import request
@@ -20,12 +11,6 @@ from openerp.tools.translate import _
class WebsiteCrmPartnerAssign(http.Controller):
_references_per_page = 40
- def _get_current_country_code(self):
- if not GeoIP:
- return False
- GI = GeoIP.open('/usr/share/GeoIP/GeoIP.dat', 0)
- return GI.country_code_by_addr(request.httprequest.remote_addr)
-
@http.route([
'/partners',
'/partners/page/
',
@@ -52,7 +37,7 @@ class WebsiteCrmPartnerAssign(http.Controller):
# group by grade
grade_domain = list(base_partner_domain)
if not country and not country_all:
- country_code = self._get_current_country_code()
+ country_code = request.session['geoip'].get('country_code')
if country_code:
country_ids = country_obj.search(request.cr, request.uid, [('code', '=', country_code)], context=request.context)
if country_ids:
diff --git a/addons/website_event/controllers/main.py b/addons/website_event/controllers/main.py
index 16a6bd24edf..2e9aa4616d2 100644
--- a/addons/website_event/controllers/main.py
+++ b/addons/website_event/controllers/main.py
@@ -19,28 +19,18 @@
#
##############################################################################
-import logging
import time
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
import werkzeug.urls
-from openerp import SUPERUSER_ID
from openerp import http
from openerp import tools
from openerp.http import request
from openerp.tools.translate import _
from openerp.addons.website.models.website import slug
-_logger = logging.getLogger(__name__)
-
-try:
- import GeoIP
-except ImportError:
- GeoIP = None
- _logger.warn("Please install GeoIP python module to use events localisation.")
-
class website_event(http.Controller):
@http.route(['/event', '/event/page/'], type='http', auth="public", website=True)
def events(self, page=1, **searches):
@@ -214,10 +204,6 @@ class website_event(http.Controller):
event = Event.browse(request.cr, request.uid, event_id, context=context)
return request.redirect("/event/%s/register?enable_editor=1" % slug(event))
- def get_visitors_country(self):
- GI = GeoIP.open('/usr/share/GeoIP/GeoIP.dat', 0)
- return {'country_code': GI.country_code_by_addr(request.httprequest.remote_addr), 'country_name': GI.country_name_by_addr(request.httprequest.remote_addr)}
-
def get_formated_date(self, event):
start_date = datetime.strptime(event.date_begin, tools.DEFAULT_SERVER_DATETIME_FORMAT).date()
end_date = datetime.strptime(event.date_end, tools.DEFAULT_SERVER_DATETIME_FORMAT).date()
@@ -225,21 +211,19 @@ class website_event(http.Controller):
@http.route('/event/get_country_event_list', type='http', auth='public', website=True)
def get_country_events(self ,**post):
- if not GeoIP:
- return ""
+ cr, uid, context, event_ids = request.cr, request.uid, request.context,[]
country_obj = request.registry['res.country']
event_obj = request.registry['event.event']
- cr, uid, context,event_ids = request.cr, request.uid, request.context,[]
- country_code = self.get_visitors_country()['country_code']
+ country_code = request.session['geoip'].get('country_code')
result = {'events':[],'country':False}
if country_code:
- country_ids = country_obj.search(request.cr, request.uid, [('code', '=', country_code)], context=request.context)
- event_ids = event_obj.search(request.cr, request.uid, ['|', ('address_id', '=', None),('country_id.code', '=', country_code),('date_begin','>=', time.strftime('%Y-%m-%d 00:00:00')),('state', '=', 'confirm')], order="date_begin", context=request.context)
+ country_ids = country_obj.search(cr, uid, [('code', '=', country_code)], context=context)
+ event_ids = event_obj.search(cr, uid, ['|', ('address_id', '=', None),('country_id.code', '=', country_code),('date_begin','>=', time.strftime('%Y-%m-%d 00:00:00')),('state', '=', 'confirm')], order="date_begin", context=context)
if not event_ids:
- event_ids = event_obj.search(request.cr, request.uid, [('date_begin','>=', time.strftime('%Y-%m-%d 00:00:00')),('state', '=', 'confirm')], order="date_begin", context=request.context)
- for event in event_obj.browse(request.cr, request.uid, event_ids, context=request.context)[:6]:
+ event_ids = event_obj.search(cr, uid, [('date_begin','>=', time.strftime('%Y-%m-%d 00:00:00')),('state', '=', 'confirm')], order="date_begin", context=context)
+ for event in event_obj.browse(cr, uid, event_ids, context=context)[:6]:
if country_code and event.country_id.code == country_code:
- result['country'] = country_obj.browse(request.cr, request.uid, country_ids[0], context=request.context)
+ result['country'] = country_obj.browse(cr, uid, country_ids[0], context=context)
result['events'].append({
"date": self.get_formated_date(event),
"event": event,
diff --git a/addons/website_mail_group/models/mail_group.py b/addons/website_mail_group/models/mail_group.py
index 804785b66f9..8e790b2b8d5 100644
--- a/addons/website_mail_group/models/mail_group.py
+++ b/addons/website_mail_group/models/mail_group.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
from openerp.osv import osv
+from openerp.tools.safe_eval import safe_eval as eval
class MailGroup(osv.Model):
@@ -10,9 +11,14 @@ class MailGroup(osv.Model):
res = super(MailGroup, self).message_get_email_values(cr, uid, id, notif_mail=notif_mail, context=context)
group = self.browse(cr, uid, id, context=context)
base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url')
- res['headers'].update({
+ try:
+ headers = eval(res.get('headers', '{}'))
+ except Exception:
+ headers = {}
+ headers.update({
'List-Archive': '<%s/groups/%s>' % (base_url, group.id),
'List-Subscribe': '<%s/groups>' % (base_url),
'List-Unsubscribe': '<%s/groups>' % (base_url),
})
+ res['headers'] = '%s' % headers
return res
diff --git a/addons/website_sale/static/src/js/website.tour.shop.js b/addons/website_sale/static/src/js/website.tour.shop.js
index c39a8b2f9e8..7fc37a48b5b 100644
--- a/addons/website_sale/static/src/js/website.tour.shop.js
+++ b/addons/website_sale/static/src/js/website.tour.shop.js
@@ -47,16 +47,14 @@
popover: { next: _t("Continue") },
},
{
- element: '.product_price .oe_currency_value',
+ element: '.product_price .oe_currency_value:visible',
sampleText: '20.50',
placement: 'left',
title: _t("Change the price"),
content: _t("Edit the price of this product by clicking on the amount."),
},
-
-
{
- waitNot: '.product_price .oe_currency_value:containsExact(1.00)',
+ waitNot: '.product_price .oe_currency_value:visible:containsExact(1.00)',
element: '#wrap img.product_detail_img',
placement: 'top',
title: _t("Update image"),
diff --git a/addons/website_sale/views/templates.xml b/addons/website_sale/views/templates.xml
index fb5a83de1bd..21bab48a8b3 100644
--- a/addons/website_sale/views/templates.xml
+++ b/addons/website_sale/views/templates.xml
@@ -408,7 +408,7 @@
-
+
+
+
+
Product not available
diff --git a/setup/debian/changelog b/debian/changelog
similarity index 100%
rename from setup/debian/changelog
rename to debian/changelog
diff --git a/setup/debian/compat b/debian/compat
similarity index 100%
rename from setup/debian/compat
rename to debian/compat
diff --git a/setup/debian/control b/debian/control
similarity index 100%
rename from setup/debian/control
rename to debian/control
diff --git a/setup/debian/copyright b/debian/copyright
similarity index 100%
rename from setup/debian/copyright
rename to debian/copyright
diff --git a/setup/debian/openerp-server.conf b/debian/openerp-server.conf
similarity index 100%
rename from setup/debian/openerp-server.conf
rename to debian/openerp-server.conf
diff --git a/setup/debian/openerp.init b/debian/openerp.init
similarity index 100%
rename from setup/debian/openerp.init
rename to debian/openerp.init
diff --git a/setup/debian/openerp.install b/debian/openerp.install
similarity index 100%
rename from setup/debian/openerp.install
rename to debian/openerp.install
diff --git a/setup/debian/openerp.logrotate b/debian/openerp.logrotate
similarity index 100%
rename from setup/debian/openerp.logrotate
rename to debian/openerp.logrotate
diff --git a/setup/debian/openerp.postinst b/debian/openerp.postinst
similarity index 100%
rename from setup/debian/openerp.postinst
rename to debian/openerp.postinst
diff --git a/setup/debian/openerp.postrm b/debian/openerp.postrm
similarity index 100%
rename from setup/debian/openerp.postrm
rename to debian/openerp.postrm
diff --git a/setup/debian/rules b/debian/rules
similarity index 100%
rename from setup/debian/rules
rename to debian/rules
diff --git a/setup/debian/run_all_with_tests.sh b/debian/run_all_with_tests.sh
similarity index 100%
rename from setup/debian/run_all_with_tests.sh
rename to debian/run_all_with_tests.sh
diff --git a/odoo.py b/odoo.py
index ba4178fbbcc..ad34f9ddc1a 100755
--- a/odoo.py
+++ b/odoo.py
@@ -72,7 +72,6 @@ def cmd_setup_git():
# alias
run('git','config','alias.st','status')
# merge bzr style
- run('git','config','merge.ff','no')
run('git','config','merge.commit','no')
# pull let me choose between merge or rebase only works in git > 2.0, use an alias for 1
run('git','config','pull.ff','only')
diff --git a/openerp/addons/base/ir/ir_ui_view_view.xml b/openerp/addons/base/ir/ir_ui_view_view.xml
index 23c5275cc3b..61b5af9b06d 100644
--- a/openerp/addons/base/ir/ir_ui_view_view.xml
+++ b/openerp/addons/base/ir/ir_ui_view_view.xml
@@ -28,6 +28,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/openerp/service/server.py b/openerp/service/server.py
index c0dc15caab7..576d55e1cd7 100644
--- a/openerp/service/server.py
+++ b/openerp/service/server.py
@@ -130,14 +130,14 @@ class AutoReload(object):
self.handler = EventHandler(self)
self.notifier = pyinotify.Notifier(self.wm, self.handler, timeout=0)
mask = pyinotify.IN_MODIFY | pyinotify.IN_CREATE # IN_MOVED_FROM, IN_MOVED_TO ?
- for path in openerp.modules.modules.ad_paths:
+ for path in openerp.modules.module.ad_paths:
_logger.info('Watching addons folder %s', path)
self.wm.add_watch(path, mask, rec=True)
def process_data(self, files):
xml_files = [i for i in files if i.endswith('.xml')]
for i in xml_files:
- for path in openerp.modules.modules.ad_paths:
+ for path in openerp.modules.module.ad_paths:
if i.startswith(path):
# find out wich addons path the file belongs to
# and extract it's module name
diff --git a/setup/setup.cfg b/setup.cfg
similarity index 66%
rename from setup/setup.cfg
rename to setup.cfg
index 7693a84fc3a..40062a73157 100644
--- a/setup/setup.cfg
+++ b/setup.cfg
@@ -7,4 +7,4 @@ formats=gztar
formats=rpm
[bdist_rpm]
-install_script=setup_rpm.sh
+install_script=setup/setup_rpm.sh
diff --git a/setup/windows/Makefile b/setup/windows/Makefile
index 603951b6cd0..6378e17401b 100644
--- a/setup/windows/Makefile
+++ b/setup/windows/Makefile
@@ -1,10 +1,9 @@
include Makefile.version
-include Makefile.python
SERVER_DIRECTORY=../server
FILES_DIRECTORY=files
-LAUNCH_PY2EXE=/cygdrive/c/python${PYTHON_VERSION}/python.exe setup.py py2exe
+LAUNCH_PY2EXE=/cygdrive/c/python26/python.exe setup.py py2exe
MAKENSIS_ARGUMENTS=/DVERSION=$(VERSION)
@@ -27,7 +26,7 @@ server_clean:
server: server_clean
(cd $(SERVER_DIRECTORY)/win32 && $(LAUNCH_PY2EXE))
(cd $(SERVER_DIRECTORY) && $(LAUNCH_PY2EXE))
- rm -rf `/usr/bin/find ${SERVER_DIRECTORY}/dist/openerp/addons -maxdepth 1 -mindepth 1 -not -name "web_*" -and -not -name "base" -and -not -name "web" -type d | xargs rm -rf`
+# rm -rf `/usr/bin/find ${SERVER_DIRECTORY}/dist/openerp/addons -maxdepth 1 -mindepth 1 -not -name "web*" -and -not -name "base" -type d | xargs rm -rf`
(cd $(SERVER_DIRECTORY) && $(LAUNCH_MAKENSIS))
cp $(SERVER_DIRECTORY)/openerp-*.exe $(FILES_DIRECTORY)/openerp-server-setup-$(VERSION).exe
diff --git a/setup/windows/contrib.nsh b/setup/windows/contrib.nsh
new file mode 100644
index 00000000000..ba93b6fa5ef
--- /dev/null
+++ b/setup/windows/contrib.nsh
@@ -0,0 +1,329 @@
+# Source: http://nsis.sourceforge.net/CharToASCII
+!include LogicLib.nsh
+
+!ifndef CHAR_TO_ASCII_NSH
+
+!define CharToASCII "!insertmacro CharToASCII"
+
+!macro CharToASCII AsciiCode Character
+ Push "${Character}"
+ Call CharToASCII
+ Pop "${AsciiCode}"
+!macroend
+
+Function CharToASCII
+ Exch $0 ; given character
+ Push $1 ; current character
+ Push $2 ; current Ascii Code
+
+ StrCpy $2 1 ; right from start
+Loop:
+ IntFmt $1 %c $2 ; Get character from current ASCII code
+ ${If} $1 S== $0 ; case sensitive string comparison
+ StrCpy $0 $2
+ Goto Done
+ ${EndIf}
+ IntOp $2 $2 + 1
+ StrCmp $2 255 0 Loop ; ascii from 1 to 255
+ StrCpy $0 0 ; ASCII code wasn't found -> return 0
+Done:
+ Pop $2
+ Pop $1
+ Exch $0
+FunctionEnd
+
+!endif ; CHAR_TO_ASCII_NSH
+
+# Source: http://nsis.sourceforge.net/Base64
+!ifndef BASE64_NSH
+!define BASE64_NSH
+
+!define BASE64_ENCODINGTABLE "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
+!define BASE64_ENCODINGTABLEURL "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
+
+!define BASE64_PADDING "="
+
+VAR OCTETVALUE
+VAR BASE64TEMP
+
+!define Base64_Encode "!insertmacro Base64_Encode"
+!define Base64_URLEncode "!insertmacro Base64_URLEncode"
+
+!macro Base64_Encode _cleartext
+ push $R0
+ push $R1
+ push $R2
+ push $0
+ push $1
+ push $2
+ push $3
+ push $4
+ push $5
+ push $6
+ push $7
+ push `${_cleartext}`
+ push `${BASE64_ENCODINGTABLE}`
+ Call Base64_Encode
+ Pop $BASE64TEMP
+ Pop $7
+ Pop $6
+ Pop $5
+ Pop $4
+ Pop $3
+ Pop $2
+ Pop $1
+ Pop $0
+ pop $R2
+ pop $R1
+ pop $R0
+ Push $BASE64TEMP
+!macroend
+
+!macro Base64_URLEncode _cleartext
+ push $R0
+ push $R1
+ push $R2
+ push $0
+ push $1
+ push $2
+ push $3
+ push $4
+ push $5
+ push $6
+ push $7
+ push `${_cleartext}`
+ push `${BASE64_ENCODINGTABLEURL}`
+ Call Base64_Encode
+ Pop $BASE64TEMP
+ Pop $7
+ Pop $6
+ Pop $5
+ Pop $4
+ Pop $3
+ Pop $2
+ Pop $1
+ Pop $0
+ pop $R2
+ pop $R1
+ pop $R0
+ Push $BASE64TEMP
+!macroend
+
+Function Base64_Encode
+ pop $R2 ; Encoding table
+ pop $R0 ; Clear Text
+ StrCpy "$R1" "" # The result
+
+ StrLen $1 "$R0"
+ StrCpy $0 0
+
+ ${WHILE} $0 < $1
+ # Copy 3 characters, and for each character push their value.
+ StrCpy $OCTETVALUE 0
+
+ StrCpy $5 $0
+ StrCpy $4 "$R0" 1 $5
+ ${CharToASCII} $4 "$4"
+
+ IntOp $OCTETVALUE $4 << 16
+
+ IntOp $5 $5 + 1
+ ${IF} $5 < $1
+ StrCpy $4 "$R0" 1 $5
+ ${CharToASCII} $4 "$4"
+
+ IntOp $4 $4 << 8
+ IntOp $OCTETVALUE $OCTETVALUE + $4
+
+ IntOp $5 $5 + 1
+ ${IF} $5 < $1
+ StrCpy $4 "$R0" 1 $5
+ ${CharToASCII} $4 "$4"
+
+ IntOp $OCTETVALUE $OCTETVALUE + $4
+ ${ENDIF}
+ ${ENDIF}
+
+ # Now take the 4 indexes from the encoding table, based on 6bits each of the octet's value.
+ IntOp $4 $OCTETVALUE >> 18
+ IntOp $4 $4 & 63
+ StrCpy $5 "$R2" 1 $4
+ StrCpy $R1 "$R1$5"
+
+ IntOp $4 $OCTETVALUE >> 12
+ IntOp $4 $4 & 63
+ StrCpy $5 "$R2" 1 $4
+ StrCpy $R1 "$R1$5"
+
+ StrCpy $6 $0
+ StrCpy $7 2
+
+ IntOp $6 $6 + 1
+ ${IF} $6 < $1
+ IntOp $4 $OCTETVALUE >> 6
+ IntOp $4 $4 & 63
+ StrCpy $5 "$R2" 1 $4
+ StrCpy $R1 "$R1$5"
+ IntOp $7 $7 - 1
+ ${ENDIF}
+
+ IntOp $6 $6 + 1
+ ${IF} $6 < $1
+ IntOp $4 $OCTETVALUE & 63
+ StrCpy $5 "$R2" 1 $4
+ StrCpy $R1 "$R1$5"
+ IntOp $7 $7 - 1
+ ${ENDIF}
+
+ # If there is any padding required, we now write that here.
+ ${IF} $7 > 0
+ ${WHILE} $7 > 0
+ StrCpy $R1 "$R1${BASE64_PADDING}"
+ IntOp $7 $7 - 1
+ ${ENDWHILE}
+ ${ENDIF}
+
+ IntOp $0 $0 + 3
+ ${ENDWHILE}
+
+ Push "$R1"
+FunctionEnd
+
+
+!define Base64_Decode "!insertmacro Base64_Decode"
+!define Base64_URLDecode "!insertmacro Base64_URLDecode"
+
+!macro Base64_Decode _encodedtext
+ push `${_encodedtext}`
+ push `${BASE64_ENCODINGTABLE}`
+ Call Base64_Decode
+!macroend
+
+!macro Base64_URLDecode _encodedtext
+ push `${_encodedtext}`
+ push `${BASE64_ENCODINGTABLEURL}`
+ Call Base64_Decode
+!macroend
+
+Function base64_Decode
+ ; Stack: strBase64table strEncoded
+ Push $9 ; Stack: $9 strBase64table strEncoded ; $9 = strDecoded
+ Exch 2 ; Stack: strEncoded strBase64table $9
+ Exch ; Stack: strBase64table strEncoded $9
+ Exch $0 ; Stack: $0 strEncoded $9 ; $0 = strBase64table
+ Exch ; Stack: strEncoded $0 $9
+ Exch $1 ; Stack: $1 $0 $9 ; $1 = strEncoded
+
+ Push $2 ; strBase64table.length
+ Push $3 ; strEncoded.length
+ Push $4 ; strBase64table.counter
+ Push $5 ; strEncoded.counter
+ Push $6 ; strBase64table.char
+ Push $7 ; strEncoded.char
+
+ Push $R0 ; 6bit-group.counter
+ Push $R1 ; 6bit-group.a
+ Push $R2 ; 6bit-group.b
+ Push $R3 ; 6bit-group.c
+ Push $R4 ; 6bit-group.d
+
+ Push $R5 ; bit-group.tempVar.a
+ Push $R6 ; bit-group.tempVar.b
+
+ Push $R7 ; 8bit-group.A
+ Push $R8 ; 8bit-group.B
+ Push $R9 ; 8bit-group.C
+
+ StrCpy $9 "" ; Result string
+
+ StrLen $2 "$0" ; Get the length of the base64 table into $2
+ StrLen $3 "$1" ; Get the length of the encoded text into $3
+ IntOp $3 $3 - 1 ; Subtract one as the StrCpy offset is zero-based
+
+ StrCpy $R0 4 ; Initialize the 6bit-group.counter
+
+ ${ForEach} $5 0 $3 + 1 ; Loop over the encoded string
+ StrCpy $7 $1 1 $5 ; Grab the character at the loop counter's index
+
+ ${If} $7 == "${BASE64_PADDING}" ; If it's the padding char
+ Push 0 ; Push value 0 (no impact on decoded string)
+ ${Else} ; Otherwise
+ ${ForEach} $4 0 $2 + 1 ; Loop over the base64 lookup table
+ StrCpy $6 $0 1 $4 ; Grab the character at this loop counter's index
+ ${If} $6 S== $7 ; If that character matches the encoded string character
+ ${ExitFor} ; Exit this loop early
+ ${EndIf}
+ ${Next}
+ Push $4 ; Push the lookup's index to the stack
+ ${EndIf}
+
+ IntOp $R0 $R0 - 1 ; Decrease the 6bit-group counter
+
+ ${If} $R0 = 0 ; If that counter reaches zero
+ ; Pop the index values off the stack to variables
+ Pop $R4
+ Pop $R3
+ Pop $R2
+ Pop $R1
+
+ ; The way the base64 decoding works is like this...
+ ; Normal ASCII has 8 bits, base64 has 6 bits.
+ ; Those 8 bits need to be presented as 6 bits somehow
+ ; Turns out you can easily do that by taking their common multiple: 24
+ ; This results in 3 8bit characters per each 4 6bit characters:
+ ; AAAAAAAA BBBBBBBB CCCCCCCC
+ ; aaaaaabb bbbbcccc ccdddddd
+
+ ; So to go back to AAAAAAAA, you need:
+ ; aaaaaa shifted two bits to the left
+ ; the two left-most bits of bbbbbb,
+ ; which you can do by shifting it four bits to the right
+ IntOp $R5 $R1 << 2
+ IntOp $R6 $R2 >> 4
+ IntOp $R5 $R5 | $R6
+ IntFmt $R7 "%c" $R5 ; IntFmt turns the resulting 8bit value to a character
+
+ ; For BBBBBBBB, you need:
+ ; the four least significant bits of bbbbbb
+ ; which you can get by binary OR'ing with 2^4-1 = 15
+ ; the four most significant bits of cccccc
+ ; which you can get by just shifting it two bits to the right
+ IntOp $R5 $R2 & 15
+ InTop $R5 $R5 << 4
+ IntOp $R6 $R3 >> 2
+ IntOp $R5 $R5 | $R6
+ IntFmt $R8 "%c" $R5
+
+ ; For CCCCCCCC, the procedure is entirely similar.
+ IntOp $R5 $R3 & 3
+ IntOp $R5 $R5 << 6
+ IntOp $R5 $R5 | $R4
+ IntFmt $R9 "%c" $R5
+
+ StrCpy $9 "$9$R7$R8$R9" ; Tack it all onto the result
+ StrCpy $R0 4 ; Reset the 6bit-group counter
+ ${EndIf}
+ ${Next}
+
+ ; Done. Now let's restore the user's variables
+ Pop $R9
+ Pop $R8
+ Pop $R7
+ Pop $R6
+ Pop $R5
+ Pop $R4
+ Pop $R3
+ Pop $R2
+ Pop $R1
+ Pop $R0
+ Pop $7
+ Pop $6
+ Pop $5
+ Pop $4
+ Pop $3
+ Pop $2
+ Pop $1
+ Pop $0
+ Exch $9 ; Stack: strDecoded
+FunctionEnd
+!endif ;BASE64_NSH
\ No newline at end of file