diff --git a/addons/payment_acquirer_adyen/controllers/main.py b/addons/payment_acquirer_adyen/controllers/main.py index 2ea9fb00c51..706b26e163d 100644 --- a/addons/payment_acquirer_adyen/controllers/main.py +++ b/addons/payment_acquirer_adyen/controllers/main.py @@ -19,7 +19,7 @@ class AdyenController(http.Controller): @website.route([ '/payment/adyen/return/', - ], type='http', auth='public') + ], type='http', auth='public', methods=['POST']) def adyen_return(self, **post): """ Paypal IPN. """ _logger.info('Beginning Adyen form_feedback with post data %s', pprint.pformat(post)) # debug diff --git a/addons/product/product.py b/addons/product/product.py index 3e8aecc8e08..e6ea84b9c85 100644 --- a/addons/product/product.py +++ b/addons/product/product.py @@ -529,12 +529,15 @@ class product_product(osv.osv): pricelist_ids = self.pool.get('product.pricelist').name_search( cr, uid, pricelist, operator='=', context=context, limit=1) pricelist = pricelist_ids[0][0] if pricelist_ids else pricelist + + qtys = map(lambda x: (x, quantity, partner), ids) + price = self.pool.get('product.pricelist').price_get_multi(cr,uid, [pricelist], + qtys, context=context) for id in ids: try: - price = self.pool.get('product.pricelist').price_get(cr,uid,[pricelist], id, quantity, partner=partner, context=context)[pricelist] + res[id] = price[id][pricelist] except: price = 0.0 - res[id] = price for id in ids: res.setdefault(id, 0.0) return res diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py index c430e9168f9..7d14c4deacf 100644 --- a/addons/website/models/ir_http.py +++ b/addons/website/models/ir_http.py @@ -2,6 +2,7 @@ import logging import traceback +import werkzeug import werkzeug.routing import openerp diff --git a/addons/website/models/website.py b/addons/website/models/website.py index 738c8d460ae..962a4bf5479 100644 --- a/addons/website/models/website.py +++ b/addons/website/models/website.py @@ -77,6 +77,9 @@ def urlplus(url, params): for k, v in params.iteritems() )) +def quote_plus(value): + return urllib.quote_plus(value.encode('utf-8') if isinstance(value, unicode) else str(value)) + class website(osv.osv): def _get_menu_website(self, cr, uid, ids, context=None): # IF a menu is changed, update all websites @@ -181,10 +184,12 @@ class website(osv.osv): return self.pool['website'].browse(cr, uid, 1, context=context) def preprocess_request(self, cr, uid, ids, request, context=None): + # TODO FP: is_website_publisher and editable in context should be removed + # for performance reasons (1 query per image to load) but also to be cleaner + # I propose to replace this by a group 'base.group_website_publisher' on the + # view that requires it. Access = request.registry['ir.model.access'] - is_website_publisher = ( - Access.check_groups(cr, uid, 'base.group_website_publisher') - and Access.check(cr, uid, 'ir.ui.view', 'write', False, context)) + is_website_publisher = Access.check(cr, uid, 'ir.ui.view', 'write', False, context) lang = request.context['lang'] is_master_lang = lang == request.website.default_lang_code @@ -224,6 +229,7 @@ class website(osv.osv): slug=slug, res_company=request.website.company_id, user_id=user.browse(cr, uid, uid), + quote_plus=quote_plus, ) context.update( diff --git a/addons/website/security/ir.model.access.csv b/addons/website/security/ir.model.access.csv index c5ccffd697e..86d258edc5c 100644 --- a/addons/website/security/ir.model.access.csv +++ b/addons/website/security/ir.model.access.csv @@ -17,3 +17,5 @@ access_website_qweb_field_html,access_website_qweb_field_html,model_website_qweb access_website_qweb_field_image,access_website_qweb_field_image,model_website_qweb_field_image,,0,0,0,0 access_website_qweb_field_monetary,access_website_qweb_field_monetary,model_website_qweb_field_monetary,,0,0,0,0 access_website_ir_ui_view,access_website_ir_ui_view,model_ir_ui_view,base.group_website_designer,1,1,1,1 +access_seo_public,access_seo_public,model_website_seo_metadata,base.group_public,1,0,0,0 +access_seo_designer,access_seo_designer,model_website_seo_metadata,base.group_website_designer,1,1,1,1 diff --git a/addons/website/static/src/css/editor.css b/addons/website/static/src/css/editor.css index f8034b52c49..487306be5dc 100644 --- a/addons/website/static/src/css/editor.css +++ b/addons/website/static/src/css/editor.css @@ -150,10 +150,10 @@ div.oe_menu_buttons { right: -8px; } -ul.oe_menu_editor .icon-home { +ul.oe_menu_editor .fa-home { display: none; } -ul.oe_menu_editor > li:first-child > div > span > .icon-home { +ul.oe_menu_editor > li:first-child > div > span > .fa-home { display: block; } ul.oe_menu_editor .oe_menu_placeholder { @@ -220,6 +220,13 @@ ul.oe_menu_editor .disclose { background-color: #ffd9dd !important; } +.hover-edition-button { + position: absolute; + top: 0; + left: 0; + z-index: 42; +} + /* ---- SNIPPET EDITOR ---- */ #oe_snippets { position: fixed; diff --git a/addons/website/static/src/css/editor.sass b/addons/website/static/src/css/editor.sass index cb0876d4212..7de48fdc611 100644 --- a/addons/website/static/src/css/editor.sass +++ b/addons/website/static/src/css/editor.sass @@ -120,9 +120,9 @@ div.oe_menu_buttons right: -8px ul.oe_menu_editor - .icon-home + .fa-home display: none - > li:first-child > div > span > .icon-home + > li:first-child > div > span > .fa-home display: block .oe_menu_placeholder outline: 1px dashed #4183C4 @@ -181,6 +181,12 @@ ul.oe_menu_editor outline: 1px solid red !important background-color: #ffd9dd !important +.hover-edition-button + position: absolute + top: 0 + left: 0 + z-index: 42 + /* ---- SNIPPET EDITOR ---- */ #oe_snippets diff --git a/addons/website/static/src/css/website.css b/addons/website/static/src/css/website.css index 78de8be5803..d62a3b9b371 100644 --- a/addons/website/static/src/css/website.css +++ b/addons/website/static/src/css/website.css @@ -406,29 +406,33 @@ ul.nav-stacked > li > a { } /* Parallax Theme */ -.parallax_quote, [data-snippet-id="slider"] { - background: center center no-repeat fixed; - background-size: contain; -} -.parallax_quote .carousel-indicators li, [data-snippet-id="slider"] .carousel-indicators li { +div.carousel[data-snippet-id="slider"] .carousel-indicators li { border: 1px solid grey; } -.parallax_quote .carousel-indicators .active, [data-snippet-id="slider"] .carousel-indicators .active { +div.carousel[data-snippet-id="slider"] .carousel-indicators .active { background-color: grey; } .parallax { position: relative; background-size: 100%; + display: table; + width: 100%; + min-height: 100px; } .parallax.oe_small { - height: 200px; + min-height: 200px; } .parallax.oe_medium { - height: 300px; + min-height: 300px; } .parallax.oe_big { - height: 450px; + min-height: 450px; +} +.parallax > div { + display: table-cell; + vertical-align: middle; + padding: 32px 0; } /* Background */ diff --git a/addons/website/static/src/css/website.sass b/addons/website/static/src/css/website.sass index a559efdca75..8329ef09070 100644 --- a/addons/website/static/src/css/website.sass +++ b/addons/website/static/src/css/website.sass @@ -304,9 +304,7 @@ ul.nav-stacked > li > a /* Parallax Theme */ -.parallax_quote, [data-snippet-id="slider"] - background: center center no-repeat fixed - background-size: contain +div.carousel[data-snippet-id="slider"] .carousel-indicators li border: 1px solid grey @@ -316,12 +314,19 @@ ul.nav-stacked > li > a .parallax position: relative background-size: 100% + display: table + width: 100% + min-height: 100px &.oe_small - height: 200px + min-height: 200px &.oe_medium - height: 300px + min-height: 300px &.oe_big - height: 450px + min-height: 450px + > div + display: table-cell + vertical-align: middle + padding: 32px 0 /* Background */ diff --git a/addons/website/static/src/js/website.editor.js b/addons/website/static/src/js/website.editor.js index b5caac71d74..a834f5f8162 100644 --- a/addons/website/static/src/js/website.editor.js +++ b/addons/website/static/src/js/website.editor.js @@ -17,6 +17,11 @@ website.form(this.pathname, 'POST'); }); + $(document).on('submit', '.cke_editable form', function (ev) { + // Disable form submition in editable mode + ev.preventDefault(); + }); + $(document).on('hide.bs.dropdown', '.dropdown', function (ev) { // Prevent dropdown closing when a contenteditable children is focused if (ev.originalEvent @@ -68,57 +73,6 @@ link_dialog(editor); }, null, null, 500); - var previousSelection; - $(editor.element.$).on('mouseenter', 'img', function () { - if (previousSelection) { return; } - var selected = new CKEDITOR.dom.element(this); - if (!selected.is('img') - || selected.data('cke-realelement') - || selected.isReadOnly() - || selected.data('oe-model') === 'ir.ui.view') { - return; - } - - // display button - var $el = $((previousSelection = selected).$); - var $btn = $('') - .insertAfter($el) - .click(function (e) { - e.preventDefault(); - e.stopPropagation(); - image_dialog(editor, selected); - }); - - var position = $el.position(); - // http://bugs.jquery.com/ticket/5445#comment:6 - // if the image has margin: x auto for centering, FF & IE - // will report top/left as the "correct" value and - // marginTop/marginLeft at "0px", webkit browsers will - // report top/left as 0-ish and set marginTop and - // marginLeft - var image_top = position.top + parseInt($el.css('marginTop'), 10); - var image_left = position.left + parseInt($el.css('marginLeft'), 10); - $btn.css({ - position: 'absolute', - top: $el.height() / 2 + image_top - $btn.outerHeight() / 2, - left: $el.width() / 2 + image_left - $btn.outerWidth() / 2, - }); - }).on('mouseleave', 'img', function (e) { - if (!previousSelection) { return; } - var $previous = $(previousSelection.$); - var $button = $previous.next('button'); - $button.css('visibility', 'hidden'); - var el = document.elementFromPoint(e.clientX, e.clientY); - $button.css('visibility', ''); - if (el === this) { return; } - $button.remove(); - previousSelection = null; - }); - editor.on('destroy', function () { - if (!previousSelection) { return; } - $('.image-edit-button').remove(); - }); - //noinspection JSValidateTypes editor.addCommand('link', { exec: function (editor) { @@ -416,8 +370,8 @@ if (item.header) { menu.append(''); } else { - menu.append(_.str.sprintf('
  • %s
  • ', - item.id, item.active ? '' : '-empty', item.name)); + menu.append(_.str.sprintf('
  • %s
  • ', + item.id, item.active ? '' : '-check', item.name)); } }); // Adding Static Menus @@ -458,6 +412,7 @@ this.rte = new website.RTE(this); this.rte.on('change', this, this.proxy('rte_changed')); this.rte.on('rte:ready', this, function () { + self.setup_hover_buttons(); self.trigger('rte:ready'); }); @@ -555,7 +510,20 @@ }); }, cancel: function () { - website.reload(); + new $.Deferred(function (d) { + var $dialog = $(openerp.qweb.render('website.editor.discard')).appendTo(document.body); + $dialog.on('click', '.btn-danger', function () { + d.resolve(); + }).on('hidden.bs.modal', function () { + d.reject(); + }); + d.always(function () { + $dialog.remove(); + }); + $dialog.modal('show'); + }).then(function () { + website.reload(); + }) }, new_page: function (ev) { ev.preventDefault(); @@ -568,6 +536,97 @@ } }); }, + + /** + * Creates a "hover" button for image and link edition + * + * @param {String} label the button's label + * @param {Function} editfn edition function, called when clicking the button + * @param {String} [classes] additional classes to set on the button + * @returns {jQuery} + */ + make_hover_button: function (label, editfn, classes) { + return $(openerp.qweb.render('website.editor.hoverbutton', { + label: label, + classes: classes, + })).hide().appendTo(document.body).click(function (e) { + e.preventDefault(); + e.stopPropagation(); + editfn.call(this, e); + }); + }, + /** + * For UI clarity, during RTE edition when the user hovers links and + * images a small button should appear to make the capability clear, + * as not all users think of double-clicking the image or link. + */ + setup_hover_buttons: function () { + var editor = this.rte.editor; + var $link_button = this.make_hover_button(_t("Edit Link"), function () { + var sel = new CKEDITOR.dom.element(previous); + editor.getSelection().selectElement(sel); + link_dialog(editor); + $link_button.hide(); + previous = null; + }, 'btn-xs'); + var $image_button = this.make_hover_button(_t("Edit Image"), function () { + image_dialog(editor, new CKEDITOR.dom.element(previous)); + $image_button.hide(); + previous = null; + }); + + // previous is the state of the button-trigger: it's the + // currently-ish hovered element which can trigger a button showing. + // -ish, because when moving to the button itself ``previous`` is + // still set to the element having triggered showing the button. + var previous; + $(editor.element.$).on('mouseover', 'a, img', function (e) { + // Back from edit button -> ignore + if (previous && previous === this) { return; } + + var selected = new CKEDITOR.dom.element(this); + if (selected.data('oe-model') === 'ir.ui.view' + || selected.data('cke-realelement') + || selected.isReadOnly()) { + return; + } + + previous = this; + var $selected = $(this); + var position = $selected.offset(); + if ($selected.is('img')) { + var image_top = position.top + parseInt($selected.css('marginTop'), 10); + var image_left = position.left + parseInt($selected.css('marginLeft'), 10); + $link_button.hide(); + // center button on image + $image_button.show().offset({ + top: $selected.outerHeight() / 2 + + image_top + - $image_button.outerHeight() / 2, + left: $selected.outerWidth() / 2 + + image_left + - $image_button.outerWidth() / 2, + }); + } else { + $image_button.hide(); + // put button below link, horizontally centered + $link_button.show().offset({ + top: $selected.outerHeight() + + position.top, + left: $selected.outerWidth() / 2 + + position.left + - $link_button.outerWidth() / 2 + }) + } + }).on('mouseleave', 'a, img', function (e) { + var current = document.elementFromPoint(e.clientX, e.clientY); + if (current === $link_button[0] || current === $image_button[0]) { + return; + } + $image_button.add($link_button).hide(); + previous = null; + }); + } }); var blocks_selector = _.keys(CKEDITOR.dtd.$block).join(','); @@ -1415,14 +1474,11 @@ var output = []; for(var i=0; i appears when focusing RTE in FF, ignore - continue; - } - if (node.className.indexOf('image-edit-button') !== -1) { - // [Edit] button added and removed on image hover, don't count - // as making parent node dirty - continue; + if (node.nodeType === document.ELEMENT_NODE) { + if (node.nodeName === 'BR' && node.getAttribute('type') === '_moz') { + //
    appears when focusing RTE in FF, ignore + continue; + } } output.push(node); diff --git a/addons/website/static/src/js/website.snippets.animation.js b/addons/website/static/src/js/website.snippets.animation.js index 1dbb2d7426e..7d3b84f2254 100644 --- a/addons/website/static/src/js/website.snippets.animation.js +++ b/addons/website/static/src/js/website.snippets.animation.js @@ -106,6 +106,7 @@ $(window).scroll(); }; img.src = this.$target.css("background-image").replace(/url\(['"]*|['"]*\)/g, ""); + $(window).scroll(); } }); diff --git a/addons/website/static/src/js/website.snippets.editor.js b/addons/website/static/src/js/website.snippets.editor.js index c083aeea70f..ef1afe88d47 100644 --- a/addons/website/static/src/js/website.snippets.editor.js +++ b/addons/website/static/src/js/website.snippets.editor.js @@ -301,7 +301,11 @@ $toInsert = $snippet.find('.oe_snippet_body').clone(); $toInsert.removeClass('oe_snippet_body'); - $toInsert.attr('data-snippet-id', snipped_id); + if (!$toInsert.data('snippet-id')) { + $toInsert.attr('data-snippet-id', snipped_id); + } else { + snipped_id = $toInsert.data('snippet-id'); + } } else if( action === 'mutate' ){ if (!$snippet.data('selector')) { @@ -936,7 +940,8 @@ * function called just before save vue */ clean_for_save: function () { - this.$target.find(".row:empty").remove(); + this.$target.removeAttr('contentEditable') + .find('*').removeAttr('contentEditable'); }, }); @@ -1104,7 +1109,7 @@ }, on_clone: function () { var $clone = this.$target.clone(false); - var _class = $clone.attr("class").replace(/\s*(col-md-|col-lg-offset-|col-md-offset-)([0-9-]+)/g, ''); + var _class = $clone.attr("class").replace(/\s*(col-lg-offset-|col-md-offset-)([0-9-]+)/g, ''); _class += ' col-md-1'; $clone.attr("class", _class); this.$target.after($clone); @@ -1143,13 +1148,7 @@ website.snippet.editorRegistry.slider = website.snippet.editorRegistry.resize.extend({ drop_and_build_snippet: function() { - var id = 0; - $(".carousel").each(function () { - var _id = +$(this).attr("id").replace(/^[^a-z]+/i, ''); - if (id <= _id) { - id = _id + 1; - } - }); + var id = $(".carousel").length; this.id = "myCarousel" + id; this.$target.attr("id", this.id); this.$target.find(".carousel-control").attr("href", "#myCarousel" + id); @@ -1174,8 +1173,6 @@ if(!this.$target.find(".item.active").length) { this.$target.find(".item:first").addClass("active"); } - this.$target.removeAttr('contentEditable') - .find('*').removeAttr('contentEditable'); }, onFocus: function () { this._super(); @@ -1186,13 +1183,14 @@ this.$target.carousel('cycle'); }, start : function () { + var self = this; this._super(); this.id = this.$target.attr("id"); this.$inner = this.$target.find('.carousel-inner'); this.$indicators = this.$target.find('.carousel-indicators'); - this.$editor.find(".js_add").on('click', _.bind(this.on_add_slide, this)); - this.$editor.find(".js_remove").on('click', _.bind(this.on_remove_slide, this)); + this.$editor.find(".js_add").on('click', function () {self.on_add_slide(); return false;}); + this.$editor.find(".js_remove").on('click', function () {self.on_remove_slide(); return false;}); this.rebind_event(); }, @@ -1318,6 +1316,8 @@ this.$target.on('snippet-style-change snippet-style-preview', function () { self.$target.data("snippet-view").set_values(); }); + this.$target.attr('contentEditable', 'false'); + this.$target.find('> div > .oe_structure').attr('contentEditable', 'true'); }, scroll: function () { var self = this; diff --git a/addons/website/static/src/js/website.tour.banner.js b/addons/website/static/src/js/website.tour.banner.js index 41c12f7a592..75838ada953 100644 --- a/addons/website/static/src/js/website.tour.banner.js +++ b/addons/website/static/src/js/website.tour.banner.js @@ -103,7 +103,7 @@ }, { stepId: 'activate-text-block-title', - element: '#wrap [data-snippet-id=three-columns] .text-center[data-snippet-id=colmd]', + element: '#wrap [data-snippet-id=three-columns]:first .text-center[data-snippet-id=colmd]', placement: 'top', title: "Edit an Area", content: "Select any area of the page to modify it. Click on this subtitle.", @@ -115,11 +115,11 @@ }, { stepId: 'remove-text-block-title', - element: '.oe_snippet_remove:last', + element: '.ui-draggable.oe_active .oe_snippet_remove', placement: 'top', reflex: true, title: "Delete the Title", - content: "From this toolbar you can move, duplicate or delete the selected zone. Click on the cross to delete the title.", + content: "From this toolbar you can move, duplicate or delete the selected zone. Click on the trash can to delete the title.", }, { stepId: 'save-changes', diff --git a/addons/website/static/src/js/website.tour.js b/addons/website/static/src/js/website.tour.js index 3d46e51c28d..a14a55bded5 100644 --- a/addons/website/static/src/js/website.tour.js +++ b/addons/website/static/src/js/website.tour.js @@ -38,8 +38,23 @@ self.moveToNextStep(); }); }; - } else { - step.onShow = step.triggers; + } else if (step.triggers) { + step.onShow = (function () { + var executed = false; + return function () { + if (!executed) { + step.triggers(); + executed = true; + } + }; + }()); + } else if (step.reflex) { + step.onShow = function () { + $(step.element).one('click', function () { + self.moveToNextStep(); + }); + }; + delete step.reflex; } return step; })); @@ -84,17 +99,18 @@ } }, moveToNextStep: function () { - this.moveToStep(this.currentStepIndex() + 1); + var nextStepIndex = this.currentStepIndex() + 1; + this.moveToStep(nextStepIndex); }, stop: function () { this.tour.end(); }, redirect: function (url) { url = url || new website.UrlParser(window.location.href); - if (this.startPath && url.pathname !== this.startPath) { - var newUrl = this.startPath + (url.search ? (url.search + "&") : "?") + this.id + "=true"; - window.location.replace(newUrl); - } + var path = (this.startPath && url.pathname !== this.startPath) ? this.startPath : url.pathname; + var search = url.addToSearch(this.id + "=true"); + var newUrl = path + search; + window.location.replace(newUrl); }, ended: function () { return this.tourStorage.getItem(this.id+'_end') === "yes"; @@ -146,6 +162,18 @@ this.origin = a.origin; this.search = a.search; this.hash = a.hash; + this.addToSearch= function (query) { + var querystring = _.filter(this.search.split('?'), function (str) { + return str; + }); + if (querystring.length > 0) { + var queries = querystring[0].split("&"); + queries.push(query); + return "?" + _.uniq(queries).join("&"); + } else { + return "?"+query; + } + }; }, }); diff --git a/addons/website/static/src/xml/website.editor.xml b/addons/website/static/src/xml/website.editor.xml index 8f749374485..be80e71afc0 100644 --- a/addons/website/static/src/xml/website.editor.xml +++ b/addons/website/static/src/xml/website.editor.xml @@ -6,7 +6,7 @@ + class="btn">Discard @@ -170,4 +170,35 @@ + + + + + + + + diff --git a/addons/website/static/src/xml/website.menu.xml b/addons/website/static/src/xml/website.menu.xml index 2276076550a..9277b0d4bc2 100644 --- a/addons/website/static/src/xml/website.menu.xml +++ b/addons/website/static/src/xml/website.menu.xml @@ -2,16 +2,16 @@
  • - + - + -
    @@ -32,11 +32,11 @@
    - right indent for multi-level menus + right indent for multi-level menus
    diff --git a/addons/website/views/snippets.xml b/addons/website/views/snippets.xml index 085d26803bc..031fedcee4d 100644 --- a/addons/website/views/snippets.xml +++ b/addons/website/views/snippets.xml @@ -711,10 +711,11 @@
  • Very Fast
  • -
    -
    +
    +
    @@ -722,51 +723,57 @@ Parallax Slider
    -
    -