Jump to content
thirty bees forum

Jeffrey de Bruijn

Members
  • Posts

    24
  • Joined

  • Last visited

Everything posted by Jeffrey de Bruijn

  1. Hi, I have migrated to a new server, with the migration I took the chance to upgrade OS and PHP versions. I am now on PHP 8.1.14, I have "fixed" the installation with core updater. It seems like the PayPal module is not working any more though, with the only option I use (Website Payments Plus) am getting a forbidden iframe with the following error: Failed to load resource: the server responded with a status of 403 () Refused to set the document's base URI to 'https://www.paypalobjects.com/web/res/956/47fb6d7...' because it violates the following Content Security Policy directive: "base-uri 'self' https://*.paypal.com". Is this related to the version or was the plugin broken already and I didn't notice? Since PayPal deprecated Website Payments Standard and has a new SDK out, is there a viable alternative plugin to use?
  2. I am attempting something different, but I am stuck. In order to keep the benefit of public key authentication via SSH, I am using SSH port forwarding this way: ssh -L 9999:fqdn.server.domain:443 user@fqdn.server.domain And, by resolving the domain name to 127.0.0.1 by adding this line to the /etc/hosts file 127.0.0.1 server.domain It is possible to reach the thirtybees website through the SSH port forwarding. All it takes is to get the following url https://server.domain:9999 Unfortunately it seems thirtybees redirects the request with code 302. I am certain there is no code 302 in my virtual host configuration so I am certain it is thirtybees doing the redirect. Is there any way to disable that redirect? wget https://server.domain:9999/ --2023-04-01 21:29:03-- https://server.domain:9999/ Resolving server.domain (server.domain)... 127.0.0.1 Connecting to server.domain (server.domain)|127.0.0.1|:9999... connected. HTTP request sent, awaiting response... 302 Found Location: https://server.domain/ [following] --2023-04-01 21:29:04-- https://server.domain/ Connecting to server.domain (server.domain)|127.0.0.1|:443... failed: Connection refused.
  3. I wasn't aware the recaptcha module would add a captcha on the admin login page. However, I still do not believe recaptcha is a suitable solution. Locking the folder with a password is another thing I never considered because the password should be shared across all admin users, and the chances of it leaking are higher. It still seems better than nothing, though a rate-limit on attempts and/or 2FA is the gold-standard way to handle this problem nowadays, is there really no such implementation for tb?
  4. Hi, I am concerned about the security of the backoffice login page: I do not believe there is any sort of application-level rate limiting on the login page. Anybody could reach that page and attempt to log in, and thirty bees would not stop an attacker from attempting hundreds of logins per minute. There is no 2-factor authentication available either. This is why I would limit access to the backoffice route to a small number of trusted IP addresses, using apache's require IP directive. Recently however, I have moved to another country and the ISP here do not provide static IP addresses. The IP address I am given changes rather frequently and I am unable to use the require IP directive to protect the backoffice login page. Am I mistaken? Are there application-level mitigations for bruteforce attacks on the backoffice login page? How do I make sure attackers cannot attempt logins like that?
  5. I'm stupid and there was a missing trailing / in the italian url. <a href="/it/contenuto/termini-e-condizioni">link on italian language</a> Problem solved.
  6. Hi, I have a cms page on a multilanguage site (english and italian). In the cms page content I insert a link using html like so <a href="/en/content/terms-and-conditions">link on english (default) language</a> And this link resolves correctly to `https://example.com/en/content/terms-and-conditions` If I do the same thing on the secondary language <a href="it/contenuto/termini-e-condizioni">link on italian language</a> The address wrongly resolves to `https://example.com/en/content/current-page/it/contenuto/termini-e-condizioni` What am I doing wrong?
  7. I tried and from my limited testing it seems to be working as intended (thank you) but I really don't understand the logic: How does this work? `$product.id_product_attribute` is true as long as the product has one attribute? What does the `id_product_` refer to? And why does the "method" `.combinations` not work?
  8. Hi, I am modifying the Niara theme and in the `<span class="price product-price">` of `product-list-item.tpl` I want to add a label to products that have combinations. I have seen that in `product.tpl` there is this line: `{if isset($combinations) && $combinations}`, I have tried add this: `{if isset($product.combinations) && $product.combinations}From{/if}` but it does not seem to work, is there some similar method? Note that I am purposely avoiding the "Advanced EU Compliance" module as it seems to cause some problems I do not want to debug. I am aware that module has an option like this.
  9. HTMLpurifier strips picture tags too. The following <picture> <source type="image/avif" srcset="/themes/themename/img/test.avif" class="picture-responsive" alt="Responsive image"> <img src="/themes/themename/img/test.jpeg" class="img-responsive" alt="Responsive image"> </picture> Becomes <img src="/themes/themename/img/test.jpeg" class="img-responsive" alt="Responsive image"> I think this is a bit excessive. I read on the project's website that there should be a whitelist, how can I manage that whitelist in thirty bees?
  10. I indeed do not follow. I only follow that yes, having to clear the CDN cache manually is not as immediate. What do you mean with users having to load a minified file? Users generally do not even touch CSS nor JS and their developers do it for them. The current situation with the default theme is even more complex as users are asked to compile the entire SASS directory to CSS. If a user is expected to be able to do that I imagine that they are also able to minify their CSS.
  11. Because I do not have a CDN to try and I want to understand how it works beforehand. A CDN is not strictly to serve stylesheets and js but for other static content like images and videos too. Why would thirty bees need to force js and css caching if users want to serve images and videos via a CDN? My CSS is fine, thank you very much. Dropping it may be a bad idea, but the repository has a load of issues open that are not being addressed. CSS and JS are constantly changing and if a library that manipulates this kind of code does not keep up, it is obviously going to have issues; look at how much work it is being done on actively maintained projects such as csso and terser just for minifying. That is a good point, thanks for making me notice that. Who exactly develops themes if not developers? I'm sorry but even users who do not develop their own themes use themes somebody else develops for them. I do not understand what your point is with this statement. Wrong assumption, not doing that.
  12. That does not sound simple at all 🤣 I do not want to change and implement a new library, I am not good enough to do that and have people depend on my php skills. In my opinion, just dropping the library might be better. No need to rely on third party libraries that may get abandoned and break, plus developers have their own preferences too. For example I work in LESS and CoffeeScript and I already compile these to CSS and JavaScript, minifying is built in my tooling, I do not need another tool to minify my code. That said, I am still confused: does enabling media servers without CCC work? If so, what is handed to the CDN?
  13. Hello, I need clarifications regarding the media servers functionality and CCC Media servers are said to be used only with CCC enabled but it is very confusing to me: which of the many toggles of CCC are required for using media servers? Smart cache for CSS and JS is handled by mrclay/minify. This script supposedly minifies and combines CSS and JS files into a single CSS and single JS, the problem is that this is a very outdated script which does not support modern JS and also breaks my CSS. I want to minify and combine CSS and JS on my own (with csso and terser, actively maintained scripts) and provide these files to my CDN (media servers). Why is this not possible?
  14. Yes this is also an acceptable solution. In my experience, people do not trust that contact forms actually send any messages, some form of confirmation is better than none. As a system administrator I think that reducing the volume of emails sent is generally a good idea. If hosting a shop on a shared hosting account, most hosts limit the emails sent on a hourly/daily basis to a very low amount, I have seen as low as 50 per hour. Large shops may run into these limitations and the above override can help them.
  15. I would like to raise a concern about the contact form. The default behavior of the contact form: customer reaches the contact form, submits a message and the form sends the customer a copy of the message at the provided email address. The problem: this behavior cannot be changed in the backoffice and it can easily be exploited. A malicious individual, human or robot, can provide the email address of another person and enter the message to send to the provided address, effectively turning the mail server to an open relay. This causes the domain of the shop to be blacklisted as spam/malicious domain and hurts email deliverability/reputation. Proposed solution: provide the shop owner the ability to disable "sending a copy of the message" to the customer so that the copy of the email is only sent to the domain of the shop owner. Hotfix solution: provide an override anyone can apply to their shop Unacceptable solution: utilizing a captcha, specifically google's recaptcha, is not a viable solution because it is not a service available in all countries and in Europe it may be required to be loaded as an opt-in non-strictly-necessary cookie (bots are not going to accept optional cookies). Edit: hotfix Here is the code of my override if anybody needs it. Save it as ContactController.php in override/controllers/front and then delete cache/class_index.php. With this override, the contact form will no longer send a copy of the email to the email provided by the visitor. <?php class ContactController extends ContactControllerCore { public function postProcess() { if (Tools::isSubmit('submitMessage')) { $extension = ['.txt', '.rtf', '.doc', '.docx', '.pdf', '.zip', '.png', '.jpeg', '.gif', '.jpg']; $fileAttachment = Tools::fileAttachment('fileUpload'); $message = Tools::getValue('message'); // Html entities is not usefull, iscleanHtml check there is no bad html tags. if (!($from = Tools::convertEmailToIdn(trim(Tools::getValue('from')))) || !Validate::isEmail($from)) { $this->errors[] = Tools::displayError('Invalid email address.'); } elseif (!$message) { $this->errors[] = Tools::displayError('The message cannot be blank.'); } elseif (!Validate::isCleanHtml($message)) { $this->errors[] = Tools::displayError('Invalid message'); } elseif (!($idContact = (int) Tools::getValue('id_contact')) || !(Validate::isLoadedObject($contact = new Contact($idContact, $this->context->language->id)))) { $this->errors[] = Tools::displayError('Please select a subject from the list provided. '); } elseif (!empty($fileAttachment['name']) && $fileAttachment['error'] != 0) { $this->errors[] = Tools::displayError('An error occurred during the file-upload process.'); } elseif (!empty($fileAttachment['name']) && !in_array(mb_strtolower(substr($fileAttachment['name'], -4)), $extension) && !in_array(mb_strtolower(substr($fileAttachment['name'], -5)), $extension)) { $this->errors[] = Tools::displayError('Bad file extension'); } else { $customer = $this->context->customer; if (!$customer->id) { $customer->getByEmail($from); } $idOrder = (int) $this->getOrder(); if (!(( ($idCustomerThread = (int) Tools::getValue('id_customer_thread')) && (int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue( (new DbQuery()) ->select('ct.`id_customer_thread`') ->from('customer_thread', 'ct') ->where('ct.`id_customer_thread` = '.(int) $idCustomerThread) ->where('ct.`id_shop` = '.(int) $this->context->shop->id) ->where('ct.`token` = \''.pSQL(Tools::getValue('token')).'\'') ) ) || ( $idCustomerThread = CustomerThread::getIdCustomerThreadByEmailAndIdOrder($from, $idOrder) )) ) { $fields = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS( (new DbQuery()) ->select('ct.`id_customer_thread`, ct.`id_contact`, ct.`id_customer`, ct.`id_order`, ct.`id_product`, ct.`email`') ->from('customer_thread', 'ct') ->where('ct.`email` = \''.pSQL($from).'\'') ->where('ct.`id_shop` = '.(int) $this->context->shop->id) ->where('('.($customer->id ? 'id_customer = '.(int) $customer->id.' OR ' : '').' id_order = '.(int) $idOrder.')') ); $score = 0; foreach ($fields as $key => $row) { $tmp = 0; if ((int) $row['id_customer'] && $row['id_customer'] != $customer->id && $row['email'] != $from) { continue; } if ($row['id_order'] != 0 && $idOrder != $row['id_order']) { continue; } if ($row['email'] == $from) { $tmp += 4; } if ($row['id_contact'] == $idContact) { $tmp++; } if (Tools::getValue('id_product') != 0 && $row['id_product'] == Tools::getValue('id_product')) { $tmp += 2; } if ($tmp >= 5 && $tmp >= $score) { $score = $tmp; $idCustomerThread = $row['id_customer_thread']; } } } $oldMessage = Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue( (new DbQuery()) ->select('cm.`message`') ->from('customer_message', 'cm') ->leftJoin('customer_thread', 'cc', 'cm.`id_customer_thread` = cc.`id_customer_thread`') ->where('cc.`id_customer_thread` = '.(int) $idCustomerThread) ->where('cc.`id_shop` = '.(int) $this->context->shop->id) ->orderBy('cm.`date_add` DESC') ); if ($oldMessage == $message) { $this->context->smarty->assign('alreadySent', 1); $contact->email = ''; $contact->customer_service = 0; } if ($contact->customer_service) { if ((int) $idCustomerThread) { $ct = new CustomerThread($idCustomerThread); $ct->status = 'open'; $ct->id_lang = (int) $this->context->language->id; $ct->id_contact = (int) $idContact; $ct->id_order = (int) $idOrder; if ($idProduct = (int) Tools::getValue('id_product')) { $ct->id_product = $idProduct; } $ct->update(); } else { $ct = new CustomerThread(); if (isset($customer->id)) { $ct->id_customer = (int) $customer->id; } $ct->id_shop = (int) $this->context->shop->id; $ct->id_order = (int) $idOrder; if ($idProduct = (int) Tools::getValue('id_product')) { $ct->id_product = $idProduct; } $ct->id_contact = (int) $idContact; $ct->id_lang = (int) $this->context->language->id; $ct->email = $from; $ct->status = 'open'; $ct->token = Tools::passwdGen(12); $ct->add(); } if ($ct->id) { $cm = new CustomerMessage(); $cm->id_customer_thread = $ct->id; $cm->message = $message; if (isset($fileAttachment['rename']) && !empty($fileAttachment['rename']) && rename($fileAttachment['tmp_name'], _PS_UPLOAD_DIR_.basename($fileAttachment['rename']))) { $cm->file_name = $fileAttachment['rename']; @chmod(_PS_UPLOAD_DIR_.basename($fileAttachment['rename']), 0664); } $cm->ip_address = (int) ip2long(Tools::getRemoteAddr()); $length = ObjectModel::getDefinition('CustomerMessage', 'user_agent')['size']; $cm->user_agent = substr($_SERVER['HTTP_USER_AGENT'], 0, $length); if (!$cm->add()) { $this->errors[] = Tools::displayError('An error occurred while sending the message.'); } } else { $this->errors[] = Tools::displayError('An error occurred while sending the message.'); } } if (!count($this->errors)) { $varList = [ '{order_name}' => '-', '{attached_file}' => '-', '{message}' => Tools::nl2br(stripslashes($message)), '{email}' => $from, '{product_name}' => '', ]; if (isset($fileAttachment['name'])) { $varList['{attached_file}'] = $fileAttachment['name']; } $idProduct = (int) Tools::getValue('id_product'); if (isset($ct) && Validate::isLoadedObject($ct) && $ct->id_order) { $order = new Order((int) $ct->id_order); $varList['{order_name}'] = $order->getUniqReference(); $varList['{id_order}'] = (int) $order->id; } if ($idProduct) { $product = new Product((int) $idProduct); if (Validate::isLoadedObject($product) && isset($product->name[$this->context->language->id])) { $varList['{product_name}'] = $product->name[$this->context->language->id]; } } // Here disable sending copy of email to customer https://forum.thirtybees.com/topic/5433-contact-form-exploit-concern if (!empty($contact->email)) { if (!Mail::Send($this->context->language->id, 'contact', Mail::l('Message from contact form') . ' [no_sync]', $varList, $contact->email, $contact->name, null, null, $fileAttachment, null, _PS_MAIL_DIR_, false, null, null, $from)) { $this->errors[] = Tools::displayError('An error occurred while sending the message.'); } } } if (count($this->errors) > 1) { array_unique($this->errors); } elseif (!count($this->errors)) { $this->context->smarty->assign('confirmation', 1); } } } } }
  16. Hello, This is what I've done in order to create a staging environment which is a copy for the live website. Please, assume that the configuration of the web server is correct (out of scope). The operative system utilized is VzLinux # Remove current data rm -rf /var/www/staging.domain.tld/html # Copy the live website files to the staging domain cp -R /var/www/domain.tld/html /var/www/staging.domain.tld chown -R apache:apache /var/www/staging.domain.tld/html # Backup the database of the live website mysqldump livedb > domain.tld.backup.sql # Create the database mysql DROP DATABASE stagingdb; # Unnecessary if the database does not exist already CREATE DATABASE stagingdb; # For staging.domain.tld CREATE USER 'stagingdbuser'@'localhost' IDENTIFIED BY 'my-very-secret-password'; # Unnecessary if the user already exists GRANT ALL ON stagingdb.* TO 'stagingdbuser'@'localhost' WITH GRANT OPTION; FLUSH PRIVILEGES; EXIT; # Import the backup of the live website's database mysql stagingdb < domain.tld.backup.sql # Change domain to utilize from domain.tld to staging.domain.tld mysql -D stagingdb -e "UPDATE tb_configuration SET value='staging.domain.tld' WHERE name='PS_SHOP_DOMAIN';" mysql -D stagingdb -e "UPDATE tb_configuration SET value='staging.domain.tld' WHERE name='PS_SHOP_DOMAIN_SSL';" mysql -D stagingdb -e "UPDATE tb_shop_url SET domain='staging.domain.tld' WHERE id_shop='1';" mysql -D stagingdb -e "UPDATE tb_shop_url SET domain_ssl='staging.domain.tld' WHERE id_shop='1';" # Edit configuration file to use the new database vim /var/www/staging.domain.tld/html/config/settings.inc.php define('_DB_SERVER_', 'localhost'); define('_DB_NAME_', 'stagingdb'); define('_DB_USER_', 'stagingdbuser'); define('_DB_PASSWD_', 'my-very-secret-password'); # Remove cache files, do not delete index.php files rm -rf /var/www/staging.domain.tld/html/cache/smarty/compile/* rm -rf /var/www/staging.domain.tld/html/cache/smarty/cache/* After this process it is possible to log to the backoffice with the same credentials as the live website. I have run into a problem: I have noticed that the .htaccess file contains domain.tld instead of staging.domain.tld. So I went in the Preferences -> SEO & URLs menu and tried to regenerate the .htaccess file by, for example, pressing on the save button in the SET SHOP URL section. It is at this point that I get the following error Before being able to use this tool, you need to - Create a blank .htaccess in your root directory. - Give it write permissions (CHMOD 666 on Unix system). The /var/www/staging.domain.tld/html/.htaccess file already exists with the default 644 permissions and the correct owner (apache:apache). I tried changing permissions to 666 but it does not make any difference. Removing the file seems to make the content of it disappear from the .HTACCESS FILE section below, so thirty bees seems to be looking at the correct file. What do you think is happening? EDIT: solution It was SELinux. I forgot to do the following: semanage fcontext -a -t httpd_sys_rw_content_t "/var/www/staging.domain.tld/html(/.*)?" restorecon -R -v /var/www/
  17. So this is also used in the front office, than yes it makes a lot of sense to prevent users from entering anything that isn't simple text. And I think I also understand that the use in the backoffice is to prevent users that aren't knowledgeable from entering dangerous code. Thank you for the explanation. In this situation, I'm trying to create a FAQ page that is a CMS page with the code above. I entered the markup in the editor because the sample pages that come with the clean thirty bees installation suggest that this is how it should be done: the default "about-us" page comes with markup for a 3-column layout and the only way to create these 3 columns seems to be by entering markup in the editor. So I did not expect HTMLpurifier to also purify code added by the shop owner. I am interested in that suggestion of yours, and I welcome the suggestion that I am not doing something wrong because I most certainly am not an expert of thirty bees. So thank you for suggesting that course of action. I thought about how to go about this problem by using a dedicated template file for this specific page. This is what I would do: Override CmsController.php to load a specific template for the FAQ page Write the code above in the template, replace the content lines with the smarty {l s="content"} Manage the translations from the translations menu in the backoffice The content of the page in the CMS menu will be left blank However, assuming that the questions of the FAQ were to be single paragraphs, the answers would be multiple paragraphs, with links, and subject to frequent changes (so likely a variable paragraph count). In a template then I'd have to decide in advance the number of paragraphs and paragraph structure (because of the presence of links) and editing this content-now-template becomes quite a bit more complex compared to just editing the CMS page content in the CMS menu. Is there a better way to handle this, without disabling HTMLPurifier, that I am not aware of?
  18. But is this assuming that the shop owner could enter malicious code, or is it to prevent somebody else from entering malicious code? I don't understand that. If it is there for "safety" there must be a case in which safety could be compromised, and I don't understand which that case could be.
  19. I know what it does, but what is the point of it in thirty bees?
  20. Hello, I am running a basic thirty bees install with Niara as theme, Niara is based on Bootstrap and comes with the vendor collapse and transition javascript plugins, so the accordion example found here should work fine. I go ahead and edit the CMS page I want to edit, paste this code in <div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true"> <div class="panel panel-default"> <div class="panel-heading" role="tab" id="headingOne"> <h4 class="panel-title"> <a role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseOne" aria-expanded="true" aria-controls="collapseOne"> Collapsible Group Item #1 </a> </h4> </div> <div id="collapseOne" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="headingOne"> <div class="panel-body"> Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. </div> </div> </div> <div class="panel panel-default"> <div class="panel-heading" role="tab" id="headingTwo"> <h4 class="panel-title"> <a class="collapsed" role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo"> Collapsible Group Item #2 </a> </h4> </div> <div id="collapseTwo" class="panel-collapse collapse" role="tabpanel" aria-labelledby="headingTwo"> <div class="panel-body"> Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. </div> </div> </div> <div class="panel panel-default"> <div class="panel-heading" role="tab" id="headingThree"> <h4 class="panel-title"> <a class="collapsed" role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseThree" aria-expanded="false" aria-controls="collapseThree"> Collapsible Group Item #3 </a> </h4> </div> <div id="collapseThree" class="panel-collapse collapse" role="tabpanel" aria-labelledby="headingThree"> <div class="panel-body"> Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. </div> </div> </div> </div> And as soon as I save the changes, the code that is actually saved is this: <div class="panel-group" id="accordion"> <div class="panel panel-default"> <div class="panel-heading" id="headingOne"> <h4 class="panel-title"><a href="#collapseOne"> Collapsible Group Item #1 </a></h4> </div> <div id="collapseOne" class="panel-collapse collapse in"> <div class="panel-body">Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS.</div> </div> </div> <div class="panel panel-default"> <div class="panel-heading" id="headingTwo"> <h4 class="panel-title"><a class="collapsed" href="#collapseTwo"> Collapsible Group Item #2 </a></h4> </div> <div id="collapseTwo" class="panel-collapse collapse"> <div class="panel-body">Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS.</div> </div> </div> <div class="panel panel-default"> <div class="panel-heading" id="headingThree"> <h4 class="panel-title"><a class="collapsed" href="#collapseThree"> Collapsible Group Item #3 </a></h4> </div> <div id="collapseThree" class="panel-collapse collapse"> <div class="panel-body">Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS.</div> </div> </div> </div> A lot of attributes are stripped from the html tags which makes the accordion not work at all. How do I work around this issue? EDIT: Disabling the Use HTMLPurifier Library option solves the issue. What exactly is the purpose of HTMLPurifier? What kind of security vulnerability is it supposed to protect against?
  21. Hello, The default subject heading in the Niara theme is created with <option value="0">{l s='-- Choose --'}</option> I do not like this. All other subject headings are built with a smarty foreach loop. Given that I know the ID of the subject heading I want as default, how can I get smarty to output the contact.name of the contact I want? I tried the following which does not work. <option value="2" selected="selected">{from=$contacts item=contact $key=2 $smarty.get.contact.name}</option> Option value is set to the ID of the contact I want, how do I tell smarty to: select the contact with id=2 from contacts and give me the contact name? EDIT: This works {foreach from=$contacts item=contact} <option value="{$contact.id_contact|intval}"{if (isset($smarty.request.id_contact) && $smarty.request.id_contact == $contact.id_contact) || (!isset($smarty.request.id_contact) && $contact.id_contact == 2)} selected="selected"{/if}>{$contact.name|escape:'html':'UTF-8'}</option> {/foreach} With the exception that the contact description is now shown on the default P.S. the code clearly suggests that the id.contact can be set, but how? With a specific url? EDIT 2: To link to a specific contact, add ?id_contact=N to the url, where N is the ID of the contact you want to link to. The following example assumes the contact with ID number 3 is the first additional contact added to thirty bees: domain.tld/en/contact-us?id_contact=3
  22. Hi again, I am still learning: I would like to add a product with combinations to the catalog and I find the pricing settings confusing. I'm doing this: 1. Create new product 2. In the Prices tab I set Unit price (tax excl.) 0 per g (I assign g to the unit field) 3. Add a combination with Impact on unit price increase of 5 4. Add a combination with Impact on unit price increase of 10 With this setup, the unit price is not displayed in the product page. I looked at product.tpl and I see this smarty line: {if !empty($product->unity) && $product->unit_price_ratio > 0.000000} I am guessing that !empty($product->unity) evaluates to true, because I assigned a unit $product->unit_price_ratio > 0.000000 evaluates to false because the base ratio remains 0 My understanding, as somebody who is new to thirty bees and does not know anything of smarty, is that the && evaluates to false and it is not re-evaluated when the default combination is automatically selected thus the unit price is never shown. Am I correct in this assumption? Why is the "$product->unit_price_ratio > 0.000000" in the condition? Am I setting up prices in a way that shouldn't be done? I think thirty bees expects the base price to be set and then combinations used to modify the base price, but calculating prices that way is not very straight-forward. P.S. Where can I find a list of all thirty bees variables that can be passed to smarty? EDIT: I was wrong and I am even more confused: changing {if !empty($product->unity) && $product->unit_price_ratio > 0.000000} to {if !empty($product->unity)} Does not make a difference, the unit price is still not displayed when the "base" unit price in the prices tab is set to 0 per g. The complete if statement in product.tpl is the following: {if !empty($product->unity) && $product->unit_price_ratio > 0.000000} {math equation="pprice / punit_price" pprice=$productPrice punit_price=$product->unit_price_ratio assign=unit_price} <p class="unit-price"><span id="unit_price_display">{convertPrice price=$unit_price}</span> {l s='per'} {$product->unity|escape:'html':'UTF-8'}</p> {hook h="displayProductPriceBlock" product=$product type="unit_price"} {/if} $productPrice is divided by $product->unit_price_ratio and the result is given to the variable unit_price (where is this declared?) which is displayed in the paragraph tag. $product->unit_price_ratio must be 0 according to my settings above, but it also should be 5 for the first and default combination. This makes no sense, why would the product price be divided by the unit price? But then what is $product->unit_price_ratio? What variable am I manipulating in the backoffice when I assign the 0 in the Unit price (tax excl.) setting in the Prices tab?
  23. Currently, whenever a customer starts a return request, the only way to find out that such request was started is to look in the backoffice. I would like to be notified by email when a new return request is started. Surprisingly (to me at least), nobody seems to have asked this question before on this forum. Is there a way to achieve this behavior, one that I am missing?
  24. I am resurrecting this thread because I also would like to completely disable the contact form. I do not want to utilize a captcha to reduce spam but I want the contact form functionality gone from thirty bees. For security reasons I completely separate receiving emails from customers and thirty bees. I do not use the customer service features in the backoffice and exclusively communicate with the customers via email hosted on another machine. It also is my understanding that nobody actually likes nor trusts contact forms. I googled and found this page on stackoverflow, this "small module" solution is used in prestashop 1.6 and I have tried it however thirty bees will not see the module and I can't even get to the point of installing it. <?php class ContactPageDisabler extends Module { public function __construct() { $this->name = 'contactpagedisabler'; $this->tab = 'front_office_features'; $this->version = '1.0'; $this->author = 'whatever'; parent::__construct(); $this->displayName = $this->l('Contact page disabler'); $this->description = $this->l('Disables contact page.'); } public function install() { return parent::install() && $this->registerHook('actionDispatcher'); } // hook runs just after controller has been instantiated public function hookActionDispatcher($params) { if ($params['controller_type'] === 1 && $params['controller_class'] === 'ContactController') { Tools::redirect('pagenotfound'); // redirect contact page to 404 page } } } This is not working, I can't figure out how I could rename the file controllers/front/ContactController.php to controllers/front/ContactController.php.disabled and it would take it down, however that counts as modifying core files and it is not recommended to maintain changes across updates. How do I achieve the same outcome in a safer way / preserving core files? Update 26-01-2022: Currently utilizing an override to redirect the page defined in controllers/front/ContactController.php to error 404: I have created the file override/controllers/front/ContactController.php containing the following code, then deleting the /cache/class_index.php file to clear the cache. <?php // The override file must contain the class named ContactController // which must extend the class ContactControllerCore // defined in controllers/front/ContactController.php class ContactController extends ContactControllerCore { public function init(){ // where is this function defined? Tools::redirect('pagenotfound'); // redirect contact page to 404 page } } The documentation says this regarding overrides: However, I have no idea what the public function init() exactly does. My concern is that bots and users with bad intentions could access the contact form and manage to send emails through my server through it, compromising the reputation of my domain name and IP address. Such an incident has been reported on this forum before, though it was mitigated with a captcha. Do you believe my solution will be able to completely prevent anybody from accessing the contact form?
×
×
  • Create New...