Leaderboard
Popular Content
Showing content with the highest reputation on 05/03/2019 in all areas
-
This looks very interesting: https://cloudblogs.microsoft.com/opensource/2019/03/12/microsoft-open-sources-accessibility-insights/ You can find 2 tools: 1 chrome extension for Web, and 1 Windows App for WinProgs: https://accessibilityinsights.io/ And the GitHub Repo: https://github.com/Microsoft/accessibility-insights-web9 points
-
http://www.colororacle.org/6 points
-
ProcessWire 3.0.131 adds support for partial/resumable downloads and http stream delivery, and contains several updates to our comments system, among other updates— https://processwire.com/blog/posts/pw-3.0.131/5 points
-
News Update - 3 May 2019 - Part One The little speckled fella has been very busy on the trail. Despite being the smallest of its kind, she has lofty dreams. We've been working on the API and the Products GUI. In the next post, I'll tell you more about the API. Products Features and GUI Though there are a few issues still pending (aren't there always? sigh), I am relatively pleased with the results. Of course, during beta testing we'll received and incorporate feedback as best as we can. Only UI-Kit theme will be officially supported. Multilingual fields if site is multilingual Ajax powered inputs for fast and convenient editing Four types of products Physical product requiring shipping Physical product not requiring shipping (for collection) Digital product Service/Event (etc) products - e.g. Swimming lessons, Consultancy services, Hotel booking, etc Product Classification (ajax powered) by: Type: e.g. Belt, trousers, etc Brand: Puma, Sanyo, whatever (editors can type in or import brand names + planning to support logos) Categories (aka Collections): Multiple categories, e.g. Men, Girls, Hospitality Tags: Multiple tags can be entered, e.g. sale, amazing, etc Product Variants (consists of an Option (e.g. Colour) and an Option Value (e.g. Red) Add zero or n variants as you wish Live preview as you build variants Each created product variant can be enabled/disabled. Devs can then decide to either show that variant as unavailable or not show them at all. That's up to you :-). Apart from classifications, shipping class, inventory policy, weight and dimensions units, title and description (and this latter one may change), almost all product properties will vary by variant if variants are used Product properties include: Images Colour (more on this below) Downloads (centralised and reusable) Price and Compare Price (aka former price) Inventory policy (whether to track or not) Charge taxes SKU (stock keeping unit) Inventory (quantity) Allow back orders (aka overselling) - @note: this can be set per variant. This is useful if one variant can be restocked faster than others Weight Length Width Height Weight Unit (mg, g, kg, oz, lb, t) Dimensions Unit (mm, cm, m, in, ft ) Shipping Class - can be used for product-based shipping if needed (e.g. bulky goods, light, fragile, small items, perishable, etc) Images (more on this below) Downloads (more on this below) Images Multiple images can be added to both a product and its variants (in case its has some). Images add to the product itself can be used with all variants Images added to a variant are tracked/tagged as belonging to only that variant In some cases, it may not make much sense to add different images for similar variants. For instance, a small red hat and a large red hat could probably share the same images. Although we do not currently support specifying an already uploaded image as belonging to a group of variants, this may change in the future Colour Similar to images, can be set at both product and its variants level Colour saved in RGBA format Downloads Also similar to images, can be populated for both both product and/or its variants Multiple files can be added to a product A file designated for 'whole' product will be available to download irrespective which variant of the product was purchased Conversely, a file or files saved for a variant will only be available to the buyer if they buy that variant of the product The above is useful if you want buyers to be able to download different files of the same product. For instance, recently someone needed to sell two versions of a font as part of one product. This is a solution for such cases. TODO List Lots! e.g. default settings for some properties, e.g. weight unit, shop currency, etc. For the frontend, we are creating a rich language-aware API that you can use to build your shop however you want. There will be no rendering of markup (but see first post in this thread about a separate fully functional frontend shop). In the next post, we talk a bit about the API. Before that, here are some screenshots and a video demo (if you can spare some 20 minutes away from watching, er..., cat videos? ?) Screenshots Video Demo Thanks ?5 points
-
Thanks for sharing this, a wonderful and funny read that also brings up some sentimental points. And, coincidentally, I also read his article about quitting at YouTube which ends with My request to take six months off next year to go backpacking (thru-hiking) on the Pacific Crest Trail was approved a week ago. Didn't think it might be this valuable in my CV?5 points
-
3 points
-
Nice thinking @bernhard! I have one config.php with reads a config.ini file using "parse_ini_file" which is one level up from /public/. No passwords in my Version Control and the same config file for all installs.3 points
-
2 points
-
Just a brief update today. I’m going to give it another week before bumping the core version, as I don’t think there’s enough changes yet to warrant a version bump. For whatever reason, several of my clients have needed integration with Stripe (payments) over the last few weeks. I’d not worked with it before the last month or so, but now all of the sudden am working with it a lot, because that's what my clients have asked for. I’ve found myself working on four different Stripe integrations on existing PW sites, both Stripe Elements and Stripe Checkout. None of these are for sites that have an actual “store” where they would need a cart, but rather just “pay for your reservation”, “buy this book”, “buy this song”, and “make a donation of $10”, “make a recurring donation”, type things. After doing a few of these, I thought it would make a lot of sense to have this built into FormBuilder, which would save us time on this stuff. So this week I built Stripe support into FormBuilder (using the Stripe Elements API). It’s already fully functional, so I will be releasing a new version of FormBuilder with this capability quite soon. To add a Stripe payment input to your form you just add a new field of type “Stripe payment for FormBuilder”, and then it asks you for some info about it (like amount to charge) and then your form works as a payment processor. Stripe has a clever way of making this all work, so that the user never leaves your site, but your site (and FormBuilder) never sees credit card numbers or anything like that, so it’s secure and you don’t have to consider things like PCI compliance. I've also got some other unrelated updates for FormBuilder that I'll be covering soon as well. Have a great weekend!2 points
-
Hi @AndZyk, you could try this: brew install imagemagick brew install pkg-config pecl install imagick For me this installs /Applications/MAMP/bin/php/php7.1.22/lib/php/extensions/no-debug-non-zts-20160303/imagick.so In MAMP I switched to PHP 7.1.22 and edited in corresponding php.ini MAMP_Imagick_MAMPextension=/Applications/MAMP/bin/php/php7.1.22/lib/php/extensions/no-debug-non-zts-20160303/imagick.so Now MAMP should use a newer Imagmagick with WebP-Support. Hope that helps. Gruß Armin2 points
-
Just don't create a fake homepage. Build a custom menu that shows whatever pages you want to appear there below the homepage item. Menu Builder is the perfect module for this.2 points
-
Are you familiar with Textformatter modules? https://processwire.com/talk/topic/10906-ckeditor-absolute-image-path-possible/2 points
-
Frontend built in UIkit 3. Backend is a modified version of what I did for HealthCARE Express. I am using the top level pages to hold information only, so no design rows as children. Children are always sub pages. The page builder area is under a settings page that has children for things like the navigation menus, and themes. (The themes hold the page layouts, color schemes and uikit and theme components.) Here is a video showing adding an intro section to a basic page. (Basic page has an inner page heading row already added.) . Instead of adding a text item, I could have added a code item and echoed text tied to the basic page. I may also make a hook for auto creating the layout page to save that step, and add a link on the front end for adding new rows without needing to visit the backend settings area. I put menus under settings so you can design the top menus, main menu, and footer column menus from one spot, and every widget that reads them gets set at one time. You can also control if they open in the same or new windows, and add off site links this way. I just feel like depending on only hidden and non hidden root pages is not enough. Also, an incoming theme can default to reading a menu named a certain way. Menus are primarily set via selector, so you do not need to edit it them every time you add a service for example, like if you wanted to have a service menu in wordpress. So if I do a page template for a Service, there can still be a rich text box and image field for that service attached to the page template, so there is no way for someone to mess up the design just to edit, and to reduce confusion, but if you want to use the page builder to add rows, you can dig into that settings page and edit the rows for the page. I also have front end editing turned on so you can double click almost anything to edit, and added a small drop down at the top right of the page that loops through all rows on your page so you have a shortcut to edit just that row from the backend. The page builder rows are matched by path. ie. /about/ would match /settings/themes/selectedtheme/bypage/about/ and might match /settings/themes/selectedtheme/bytemplate/basic-page/ also, if the about page was a basic page template. Everything will match the sitewide folder. This type of thing lets you do things like add a call to action on a group of pages by adding that row to the template instead of the page. Or add a css file or javascript file to a group or sitewide. The layouts template holds the layout types, like per page or per template, and layout types can have children like sections, containers, grids, cells, images, text, accordions, php or html code, etc. I have also played with doing a page group layout type so you can group a few pages together and add a row or code snippet to those as well. This output method does not require php eval. I actually just start an output buffer and include. That gets added to a snippet array variable that I can sort and edit until the very end when it is time to output. The first level in the array is 8 slots that works as placeholders, and then each of those 8 slots holds the rows or code snippets for that placeholder. ie. before html, head, body-top, body-header, body-main, body-footer, body-bottom, after html are the default placements code can target. The second level also has weights. In addition to targeting a placeholder, a section will also have a priority drop down of highest, high, medium, low, lowest. That way a bottom call to action can be tied to the body-main spot as low priority, and no matter if it is added to the template, page group, page or sitewide, it will always sink to the bottom. Plugins used: Inputfield Ace Extended, MarkInPageTree, Breadcrumb Dropdowns, Prev/Next Tabs (for switch from row to row), Hanna Code The page builder has almost all of UIkit added so you can add a grid to a section and set the child width by drop downs, or you can use drop downs to set them on the child cell pages individually. The cell pages would allow you to add heading, text, images, accordions, etc. If you add a text item to a cell for example, the first tab is content for the rich text editor, and the second tab is design for things like text alignment per device. (So you can say center on mobile or left align on desktop without writing any code!) . The advanced tab holds an id over ride, extra classes box, and a javascript and css box that will minify on page save and be included into your website. This allows any item to add advanced css or js in a portable way. I will attach some screenshots! https://www.medpracticesuccess.com/1 point
-
I have recently started to integrate Twig in my ProcessWire projects to have a better separation between logic and views as well as have cleaner, smaller template files as opposed to large multi-purpose PHP-templates. Though there is a Twig module, I have opted to initialize twig manually to have more control over the structure and settings of the twig environment. This is certainly not required to get started, but I find that having no "secret sauce" gives the me as a developer more agency over my code structure, and better insights into how the internals of the libraries I'm using work. Framework like Drupal or Craft have their own Twig integration, which comes with some opinionated standards on how to organize your templates. Since Twig is not native to ProcessWire, integrating Twig requires one to build a solid template structure to be able to keep adding pages and partials without repeating oneself or having templates grow to unwieldy proportions. This will be an (opinionated) guide on how to organize your own flexible, extensible template system with ProcessWire and Twig, based on the system I developed for some projects at work. Somehow this post got way too long, so I'm splitting it in two parts. I will cover the following topics: Part 1: Extendible template structures How to initialize a custom twig environment and integrate it into ProcessWire How to build an extendible base template for pages, and overwrite it for different ProcessWire templates with custom layouts and logic How to build custom section templates based on layout regions and Repeater Matrix content sections Part 2: Custom functionality and integrations How to customize and add functionality to the twig environment How to bundle your custom functionality into a reusable library Thoughts on handling translations A drop-in template & functions for responsive images as a bonus However, I will not include a general introduction to the Twig language. If you are unfamiliar with Twig, read the Twig guide for Template Designers and Twig for Developers and then come back to this tutorial. That's a lot of stuff, so let's get started ? Initializing the Twig environment First, we need to install Twig. If you set up your site as described in my tutorial on setting up Composer, you can simply install it as a dependency: composer require "twig/twig:^2.0" I'll initialize the twig environment inside a prependTemplateFile and call the main render function inside the appendTemplateFile. You can use this in your config.php: $config->prependTemplateFile = '_init.php'; $config->appendTemplateFile = '_main.php'; Twig needs two things: A FilesystemLoader to load the templates and an Environment to render them. The FilesystemLoader needs the path to the twig template folder. I'll put my templates inside site/twig: $twig_main_dir = $config->paths->site . 'twig'; $twig_loader = new \Twig\Loader\FilesystemLoader($twig_main_dir); As for the environment, there are a few options to consider: $twig_env = new \Twig\Environment( $twig_loader, [ 'cache' => $config->paths->cache . 'twig', 'debug' => $config->debug, 'auto_reload' => $config->debug, 'strict_variables' => false, 'autoescape' => true, ] ); if ($config->debug) { $twig_env->addExtension(new \Twig\Extension\DebugExtension()); } Make sure to include a cache directory, or twig can't cache the compiled templates. The development settings (debug, auto_reload) will be dependent on the ProcessWire debug mode. I turned strict_variables off, since it's easier to check for non-existing and non-empty fields with some parts of the ProcessWire API. You need to decide on an escaping strategy. You can either use the autoescape function of twig, or use textformatters to filter out HTML tags (you can't use both, as it will double escape entities, which will result in a broken frontend). I'm using twig's inbuilt autoescaping, as it's more secure and I don't have to add the HTML entities filter for every single field. This means pretty much no field should use any entity encoding textformatter. I also added the Debug Extension when $config->debug is active. This way, you can use the dump function, which makes debugging templates much easier. Now, all that's left is to add a few handy global variables that all templates will have access to, and initialize an empty array to hold additional variables defined by the individual ProcessWire templates. // add the most important fuel variables to the environment foreach (['page', 'pages', 'config', 'user', 'languages', 'sanitizer'] as $variable) { $twig_env->addGlobal($variable, wire($variable)); }; $twig_env->addGlobal('homepage', $pages->get('/')); $twig_env->addGlobal('settings', $pages->get('/site-settings/')); // each template can add custom variables to this, it // will be passed down to the page template $variables = []; This includes most common fuel variables from ProcessWire, but not all of them. You could also just iterate over all fuel variables and add them all, but I prefer to include only those I will actually need in my templates. The "settings" variable I'm including is simply one page that holds a couple of common settings specific to the site, such as the site logo and site name. Page templates By default, ProcessWire loads a PHP file in the site/templates folder with the same name of the template of the current page; so for a project template/page, that would be site/templates/project.php. In this setup, those files will only include logic and preprocessing that is required for the current page, while the Twig templates will be responsible for actually rendering the markup. We'll get back to the PHP template file later, but for the moment, it can be empty (it just needs to exist, otherwise ProcessWire won't load the page at all). For our main template, we want to have a base html skeleton that all sites inherit, as well as multiple default regions (header, navigation, content, footer, ...) that each template can overwrite as needed. I'll make heavy use of block inheritance for this, so make sure you understand how that works in twig. For example, here's a simplified version of the html skeleton I used for a recent project: {# site/twig/pages/page.twig #} <!doctype html> <html lang="en" dir="ltr"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>{% block title %}{{ '%s | %s'|format(page.get('title'), homepage.title) }}{% endblock %}</title> <link rel="stylesheet" type="text/css" href="{{ config.urls.site }}css/main.css"> </head> <body class="{{ page.template }}"> {% block navigation %}{{ include("components/navigation.twig") }}{% endblock %} {% block header %}{{ include("components/header.twig") }}{% endblock %} {% block before_content %}{% endblock %} {% block content %} {# Default content #} {% endblock %} {% block after_content %}{% endblock %} {% block footer %}{{ include("components/footer.twig") }}{% endblock %} </body> </html> All layout regions are defined as twig blocks, so each page template can override them individually, without having to touch those it doesn't need. I'll fill the content block with default content soon, but for now this will do. Now, templates for our content types can just extend this base. This is the template for the homepage: {# site/twig/templates/pages/page--home.twig #} {% extends "pages/page.twig" %} {# No pipe-seperated site name on the homepage #} {% block title page.get('title') %} {# The default header isn't used on the homepage #} {% block header %}{% endblock %} {# The homepage has a custom slider instead of the normal header #} {% block before_content %} {{ include('sections/section--homepage-slider.twig', { classes: ['section--homepage-slider'] }) }} {% endblock %} Note that I don't do most of the actual html markup in the page templates, but in individual section templates (e.g. section--homepage-slider.twig) that can be reused across content types. More on this in the next section. We still need to actually render the template. The page template (which will be the entry point for twig) will be loaded in our _main.php, which we defined as the appendTemplateFile earlier, so it will always be included after the template specific PHP file. $template_file = 'pages/page--' . $page->template->name . '.twig'; $twig_template = file_exists($twig_main_dir . '/' . $template_file) ? $template_file : 'pages/page.twig'; echo $twig_env->render($twig_template, $variables); This function checks if a specific template for the current content type exists (e.g. pages/page--home.twig) and falls back to the default page template if it doesn't (e.g. pages/page.twig). This way, if you want a blank slate for a specific content type, you can just write a twig template that doesn't extend page.twig, and you will get a blank page ready to be filled with whatever you want. Note that it passes the $variables we initialized in the _init.php. This way, if you need to do any preprocessing or data crunching for this request, you can do it inside the PHP template and include the results in the $variables array. At this point, you can start building your default components (header, footer, navigation, et c.) and they will be included on every site that extends the base page template. Custom sections Now we've done a great deal of setup but haven't actually written much markup yet. But now that we have a solid foundation, we can add layout components very easily. For most of my projects, I use the brilliant Repeater Matrix module to set up dynamic content sections / blocks (this is not the focus of this tutorial, here's a detailed explanation). The module does have it's own built-in template file structure to render it's blocks, but since it won't work with my Twig setup, I'll create some custom twig templates for this. The approach will be the same as with the page template itself: create a "base" section template that includes some boilerplate HTML for recurring markup (such as a container element to wrap the section content in) and defines sections that can be overwritted by section-specific templates. First, let's create a template that will iterate over our the Repeater Matrix field (here it's called sections) and include the relevant template for each repeater matrix type: {# components/sections.twig #} {% for section in page.sections %} {% set template = 'sections/section--' ~ section.type ~ '.twig' %} {{ include( [template, 'sections/section.twig'], { section: section }, with_context = false ) }} {% endfor %} Note that the array syntax in the include function tells Twig to render the first template that exists. So for a section called downloads, it will look look for the template sections/section--downloads.twig and fallback to the generic sections/section.twig. The generic section template will only include the fields that are common to all sections. In my case, each section will have a headline (section_headline) and a select field to choose a background colour (section_background) : {# sections/section.twig #} {% set section_classes = [ 'section', section.type ? 'section--' ~ section.type, section.section_background.first.value ? 'section--' ~ section.section_background.first.value ] %} <div class="{{ section_classes|join(' ')|trim }}"> <section class="container"> {% block section_headline %} {% if section.section_headline %} <h2 class="section__headline">{{ section.section_headline }}</h2> {% endif %} {% endblock %} {% block section_content %} {{ section.type }} {% endblock %} </section> </div> This section template generates classes based on the section type and background colour (for example: section section--downloads section--green) so that I can add corresponding styling with CSS / SASS. The specific templates for each section will extend this base template and fill the block section_content with their custom markup. For example, for our downloads section (assuming it contains a multivalue files field download_files): {# sections/section--download.twig #} {% extends "sections/section.twig" %} {% block section_content %} <ul class="downloads"> {% for download in section.download_files %} <li class="downloads__row"> <a href="{{ download.file.url }}" download class="downloads__link"> {{ download.description ?: download.basename }} </a> </li> {% endfor %} </ul> {% endblock %} It took some setup, but now every section needs only care about their own unique fields and markup without having to include repetitive boilerplate markup. And it's still completely extensible: If you need, for example, a full-width block, you can just not extend the base section template and get a clean slate. Also, you can always go back to the base template and add more blocks as needed, without having to touch all the other sections. By the way, you can also extend the base section template from everywhere you want, not only from repeater matrix types. Now we only need to include the sections component inside the page template {# pages/page.twig #} {% block content %} {% if page.hasField('sections') and page.sections.count %} {{ include('components/sections.twig' }} {% endif %} {% endblock %} Conclusion This first part was mostly about a clean environment setup and template structure. By now you've probably got a good grasp on how I organize my twig templates into folders, depending on their role and importance: blocks: This contains reusable blocks that will be included many times, such as a responsive image block or a link block. components: This contains special regions such as the header and footer that will probably be only used once per page. sections: This will contain reusable, self-contained sections mostly based on the Repeater Matrix types. They may be used multiple times on one page. pages: High-level templates corresponding to ProcessWire templates. Those contain very little markup, but mostly include the appropriate components and sections. Depending on the size of the project, you could increase or decrease the granularity of this as needed, for example by grouping the sections into different subfolders. But it's a solid start for most medium-sized projects I tackle. Anyway, thanks for reading! I'll post the next part in a couple of days, where I will go over how to add more functionality into your environment in a scalable and reusable way. In the meantime, let me know how you would improve this setup!1 point
-
Thank you @Noboru, I will try this out. ? I am wondering: Can you even open WebP files in apps other than browsers without having the codec installed? As far as I know you cannot simply open a .webp file. After all it is meant for browers. But I haven't tried it much. In my opinion I would not serve WebP content in a .jpg or .png file. I would serve only WebP in a .webp file for browsers that support it. ?1 point
-
Man this is huge! Stripe. I need this yesterday. I probably have 4-6 websites right now that can use this.1 point
-
@wbmnfktr YES. That one. Rote Fabrik. Good times. Oh, and I just discovered another BBC 6 Music Iggy episode today via Twitter: https://www.bbc.co.uk/sounds/play/m0004d2m I love how Iggy doesn't care about time and space and genres. It's either "the good kind, or the other kind", to paraphrase Duke once more...1 point
-
Crazy!! ? You fixed my problem ^_^ First, I just updated to already installed files of PaymentModule and PaymentInvoice to the PW3 versions, which crashed everything ? Than uninstalled both modules, updated the files and reinstalled them. Afterwards, I installed the MollieModule and now it worked fine! ? Thank you so much, now let's see if I can actually get it to work with the vendor files from composer ?1 point
-
@AndZyk I have sent a new commit to github to cover this. Please can you download the 3 new files: core/Pageimage.php core/ImageSizerEngineGD.php modules/Image/ImageSizerEngineIMagick.module If you want to test / debug the webp functionality of both engines in one call, please use code like this in a template file: $options = [ 'forceEngine' => 'ImageSizerEngineGD', 'suffix' => 'gd', 'forceNew' => true, 'webpAdd' => true, 'webpQuality' => 90, ]; $img = $page->images->first->size(150, 150, $options); echo $img->getDebugInfo(); $options = [ 'forceEngine' => 'ImageSizerEngineIMagick', 'suffix' => 'im', 'forceNew' => true, 'webpAdd' => true, 'webpQuality' => 90, ]; $img = $page->images->first->size(150, 150, $options); echo $img->getDebugInfo(); A) it now should run without errors when you request a webp copy and the engine doesn't support it, and B) you can see it in the debug info if the selected engine supports webp.1 point
-
1 point
-
I've missed this completely! Thanks for creating this. A real timesaver not storing this in the DB. And thanks for bringing it to our attention @adrian!1 point
-
1 point
-
Added Support for Servers|Connections without Authentication, currently in a dev-branch: https://github.com/horst-n/WireMailSmtp/tree/allow_without_authentication Please, if someone uses SMTP without authentication, can you try it out with enabling the new option in modules config screen and check the "Test Connection" feature with it?1 point
-
1 point
-
Honestly I have no clue what is happening there. Just out of curiosity, could you try to autoload the module? So in FieldtypePageTableExtended.module try 'autoload' => true (perhaps same for PageTable). Doesn't make that much sense but I have no idea why FieldtypePageTable isn't ready since it is already present. The modal injects its changes via ajax, I guess somehow there could be the problem with your referenced PTE template block. But its just wild guessing.1 point
-
This won't work, you need to use the require command to add dependencies to your project. This will add the dependency to your composer.json and download it. The install command is used to read the composer.lock file and download all the dependencies listed in there, as well as update the autoloader. This is why install doesn't take a library as an argument. @bramwolf Pretty sure that BitPoet is right. That error indicates that the class ProcessWire\PaymentModule doesn't exist. If you installed the PaymentModule through the backend or downloaded it from the module page, you would get the master branch, which doesn't have the correct namespace. So this version of the module would have the \PaymentModule class inside the root namespace, but not inside the Processwire namespace (which is what the PaymentMollie module tries to extend). Install the PW3 branch and you should be fine.1 point
-
1 point
-
Thanks for testing. And yes, this is right. But for me personally, (and for most of my clients), the main goal is to present pages with text and images on the screen, and not to let the visitors save images for later use. I think, if a visitor need a visual copy of a page, he still can save the complete page with his browser, or he can do screenshots. On photographer sites, the use of images independend from viewing the site / page are often prohibited. There it even should not be recognized that images with filetype jpeg in real are weppys. But that is my personal opinion, and maybe thats not a sufficient reason to include this into the PW htaccess. But it also could be done the other way round: send .webp in markup and let the htaccess directives change this to (png|jpg) if a) the browser doesn't support this, or b) if there is no webp file available. This way round, in a 100% perfect site, all newer browsers would get 100% webp with correct file type. Only older browsers would get jpegs or pngs with file type webp. ?1 point
-
This is actually very easy but might be interesting for someone anyhow (and I post it here as a reminder for myself). You can use Postman (https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop?hl=de) to send requests to your ProcessWire installation and test request answers. The problem is that $config->ajax will always return FALSE. To make the $config->ajax work properly you just need to add this key-value-pair to your requests: key: X-Requested-With value: XMLHttpRequest Happy AJAXing PS: This also works inside the admin, see post 31 point
-
Hello there! I wanted to share my new proect with you. It's a small online shop for a local wine dealer: https://www.weinmarkt21.de/ Beware, the site is in german language. I used PW 3, Bootstrap 4 and a very few modules for this site. For the whole shop system I used the Processwire shop module Padloper exclusively. Developing in Processwire was a breeze as always. However I had to put some work into the Padloper stuff to make it fit to the needs of my client. It's a vey good base to start with but you should bring a bit of developing experience with you.1 point
-
I have not used Padloper yet, but I built a custom shop lately and it's a LOT of work ? The problem is not the payment integration, the problem is handling all the user interactions (shopping cart, taxes, product variations etc). So it depends a lot on your scenario. If you only want to collect donations without any other features, then you might not need padloper (but then you could also just use a payment button from one of the providers)1 point
-
Another advantage of storing the template code in the database is you can attach all php/html, js, css, and images/files, into the one page so it is completely portable. So you could import a component page into your theme folder, and everything that is needed is there. And to uninstall you just delete the one page.1 point
-
1 point
-
Pixies live in Zurich, on their Doolittle tour: one of my all-time favorite gigs ever. God I'm old... ?1 point
-
1 point
-
1 point
-
Announcement: I've created a new branch on github with the requested feature by @felix. Please can you and / or anybody else try this out and give some feedback. If everything is working as expected, I will push this to the master branch. Short introduction: You can specify how many and what ever params you want into an array that is called $config->wirmailsmtp. Every valid key of this array will override the key of the stored modules config setting. // example entry in site/config.php $config->wiremailsmtp = array( "smtp_host" => "smtp.example.com", "smtp_port" => 587, "smtp_ssl" => 0, "smtp_start_tls" => 1, "smtp_user" => "yourusersname", "smtp_password" => "youruserspassword", "extra_headers" => array("Organization" => "Horst Nogajski - Fotografie & Webdesign", "X-Header" => "Some Content") ); To see your resulting (merged) settings you can var_dump the output of the method getSettings(): // debug example in a template file $mail = wireMail(); echo "<pre>"; var_dump($mail->getSettings()); I tested it here myself and it seems to work fine. EDIT: I forgott to mention that I removed the required flags from the modules config settings for smtp_host and smtp_port. This way, both settings now may stay empty in the config screen, but can be set via the $config->wiremailsmtp array. The downside would be that the modules config screen isn't that robust anymore in regard of misconfiguration. Is this acceptable, or should there also be a required setting in the modules config screen? This is open for discussion. ?1 point
-
Hi @felix, I don't want to enable plain text files with server & password settings, but going with the already needed and available site/config.php, as @adrian suggested, seems to be fine. (As it already stores the DB-credentials). I try to look into it and implement it this weekend. I think this can be useful for a lot of us.1 point
-
Some fresh sounds I discovered recently. I didn't know I'd like this Synthwave music but... can't get anything done without it. Whole YT Channel: https://www.youtube.com/channel/UCmYTgpKxd-QOJCPDrmaXuqQ Some more on Basecamp. If you loved Stranger Things (Netflix) you will probably like this too. https://synthwavecafe.bandcamp.com/album/stranger-things-tribute1 point
-
Can we make the api a bit more flexible like: $options = [ 'additionalFormats': ['webp'], 'webp' => [ 'quality' => 80 ] ]; This way we can easily add any other formats, which might emerge and have the possibility to not only have a single additional format generated. And as soon as we might generate multiple formats / images per sizing operation I feel like namespacing options like quality might prevent an explosion of possible root level options.1 point
-
Just because it's Monday: <?php /** * ProcessWire module. * * Allow depending modules to be updated from non-standard repositories. * * In absence of this module, your module will continue to work but updates * will point at the regular repository. If you want to avoid that, add * a "requires" entry in your module * * To trigger the custom update routines, add an array "extendedUpdate" to * your getModuleInfo array. It can either contain a "url" entry with the * URL to the JSON data that describes the available update, or a "customMethod" * entry with the name of a public method in your module that returns that * URL. * * If you go the "url" way, the value there is run through wirePopulateStringTags. * You can add placeholders for all of the modules moduleInfo values, the module's * configuration values, the "moduleServiceKey" (PW base version, i.e. pw300, pw280 etc.) * and the module's name as, yeah, you guessed it, "name". * * If you use customMethod, it gets passed an associative array with all these values. * * A simple example for a module using custom updates: * * class TestModule extends WireData implements Module { * public static function getModuleInfo() { * return array( * "title" => _("Test Module"), * "summary" => _("Test for a module with a custom update location"), * "version" => "0.0.1", * "requires" => array("ModuleUpdateCustomUrl"), * "extendedUpdate" => array( * "url" => "https://bitpoet.ddns.net/update/{name}.json?apiVersion={moduleServiceKey}", * // Alternative way to generate the URL through a method in your module: * //"customMethod" => "generateUrl" * ) * ); * } * * public function generateUrl($data) { * return "https://bitpoet.ddns.net/update/" . $data['name'] . ".json?apiVersion=" . $data['moduleServiceKey']; * } * * } * * * A somewhat minimal JSON example response for the test module above: * { * "status":"success", * "class_name":"TestModule", * "module_version":"0.0.2", * "authors":[ * { * "title":"BitPoet" * }, * { * "title":"Anonymous" * } * ], * "pw_versions":[ * { * "name":"2.7" * }, * { * "name":"2.8" * }, * { * "name":"3.0" * } * ], * "download_url":"https://bitpoet.ddns.net/update/TestModule_0.0.2.zip", * "release_state":{ * "title":"Beta" * }, * "summary":"Test for a module with a custom update location", * "module_home":"https://bitpoet.ddns.org/modules/TestModule" * } * * For a complete example, see the live data from the official repo, e.g. * http://modules.processwire.com/export-json/Helloworld/?apikey=pw300 * * The response must either return a property "success" with a true value * or populate an "error" property with a description of what went wrong. * */ class ModuleUpdateCustomUrl extends WireData implements Module { public static function getModuleInfo() { return array( "title" => _("Module Update from Custom Url"), "summary" => _("Extension module that allows modules to be updated from non-standard repos."), "version" => "0.0.4", "autoload" => true, ); } public function init() { $this->addHookBefore("ProcessModule::execute", $this, "hookProcessModuleExecute_customUpdate"); } public function hookProcessModuleExecute_customUpdate(HookEvent $event) { if($this->input->get->update) { $name = $this->sanitizer->name($this->input->get->update); $info = $this->modules->getModuleInfo($name); if(! $info["extendedUpdate"]) return; if(! isset($info["extendedUpdate"]["url"]) && !isset($info["extendedUpdate"]["customMethod"])) { $this->error($this->_('Neither URL nor custom method set in extendedUpdate configuration in module info')); return; } $event->return = $this->downloadDialog($name, $info); $event->replace = true; } } public function downloadDialog($name, $info) { $redirectURL = "./edit?name=$name"; $className = $name; $cfgdata = $this->modules->getModuleConfigData($name); $params = array_merge($info, $cfgdata); $params["moduleServiceKey"] = $this->wire('sanitizer')->name($this->wire('config')->moduleServiceKey); $params["name"] = $name; if(isset($info["extendedUpdate"]["customMethod"])) { $method = $info["extendedUpdate"]["customMethod"]; $module = $this->modules->get($name); $url = $module->$method($params); } else { $url = trim(wirePopulateStringTags($info["extendedUpdate"]["url"], $params)); } $http = $this->wire(new WireHttp()); $data = $http->get($url); if(empty($data)) { $this->error($this->_('Error retrieving data from web service URL') . ' - ' . $http->getError()); return $this->session->redirect($redirectURL); } $data = json_decode($data, true); if(empty($data)) { $this->error($this->_('Error decoding JSON from web service')); return $this->session->redirect($redirectURL); } if($data['status'] !== 'success') { $this->error($this->_('Error reported by web service:') . ' ' . $this->wire('sanitizer')->entities($data['error'])); return $this->session->redirect($redirectURL); } $form = $this->buildDownloadConfirmForm($data); return $form->render(); } public function buildDownloadConfirmForm($data) { $warnings = array(); $authors = ''; foreach($data['authors'] as $author) $authors .= $author['title'] . ", "; $authors = rtrim($authors, ", "); $compat = ''; $isCompat = false; $myVersion = substr($this->wire('config')->version, 0, 3); foreach($data['pw_versions'] as $v) { $compat .= $v['name'] . ", "; if(version_compare($v['name'], $myVersion) >= 0) $isCompat = true; } $compat = trim($compat, ", "); if(!$isCompat) $warnings[] = $this->_('This module does not indicate compatibility with this version of ProcessWire. It may still work, but you may want to check with the module author.'); $form = $this->wire('modules')->get('InputfieldForm'); $form->attr('action', './download/'); $form->attr('method', 'post'); $form->attr('id', 'ModuleInfo'); $markup = $this->wire('modules')->get('InputfieldMarkup'); $form->add($markup); $installed = $this->modules->isInstalled($data['class_name']) ? $this->modules->getModuleInfoVerbose($data['class_name']) : null; $moduleVersionNote = ''; if($installed) { $installedVersion = $this->formatVersion($installed['version']); if($installedVersion == $data['module_version']) { $note = $this->_('Current installed version is already up-to-date'); $installedVersion .= ' - ' . $note; $this->message($note); $this->session->redirect("./edit?name=$data[class_name]"); } else { if(version_compare($installedVersion, $data['module_version']) < 0) { $this->message($this->_('An update to this module is available!')); } else { $moduleVersionNote = " <span class='ui-state-error-text'>(" . $this->_('older than the one you already have installed!') . ")</span>"; } } } else { $installedVersion = $this->_x('Not yet', 'install-table'); } $table = $this->wire('modules')->get('MarkupAdminDataTable'); $table->setEncodeEntities(false); $table->row(array($this->_x('Class', 'install-table'), $this->wire('sanitizer')->entities($data['class_name']))); $table->row(array($this->_x('Version', 'install-table'), $this->wire('sanitizer')->entities($data['module_version']) . $moduleVersionNote)); $table->row(array($this->_x('Installed?', 'install-table'), $installedVersion)); $table->row(array($this->_x('Authors', 'install-table'), $this->wire('sanitizer')->entities($authors))); $table->row(array($this->_x('Summary', 'install-table'), $this->wire('sanitizer')->entities($data['summary']))); $table->row(array($this->_x('Release State', 'install-table'), $this->wire('sanitizer')->entities($data['release_state']['title']))); $table->row(array($this->_x('Compatibility', 'install-table'), $this->wire('sanitizer')->entities($compat))); // $this->message("<pre>" . print_r($data, true) . "</pre>", Notice::allowMarkup); $installable = true; if(!empty($data['requires_versions'])) { $requiresVersions = array(); foreach($data['requires_versions'] as $name => $requires) { list($op, $ver) = $requires; $label = $ver ? $this->sanitizer->entities("$name $op $ver") : $this->sanitizer->entities($name); if($this->modules->isInstalled("$name$op$ver") || in_array($name, $data['installs'])) { // installed $requiresVersions[] = "$label <i class='fa fa-fw fa-thumbs-up'></i>"; } else if($this->modules->isInstalled($name)) { // installed, but version isn't adequate $installable = false; $info = $this->modules->getModuleInfo($name); $requiresVersions[] = $this->sanitizer->entities($name) . " " . $this->modules->formatVersion($info['version']) . " " . "<span class='ui-state-error-text'>" . $this->sanitizer->entities("$op $ver") . " " . "<i class='fa fa-fw fa-thumbs-down'></i></span>"; } else { // not installed at all $requiresVersions[] = "<span class='ui-state-error-text'>$label <i class='fa fa-fw fa-thumbs-down'></i></span>"; $installable = false; } } $table->row(array($this->_("Requires"), implode('<br />', $requiresVersions))); if(!$installable) $this->error("Module is not installable because not all required dependencies are currently met."); } if(!empty($data['installs'])) { $installs = $this->sanitizer->entities(implode("\n", $data['installs'])); $table->row(array($this->_("Installs"), nl2br($installs))); } $links = array(); $moduleName = $this->wire('sanitizer')->entities1($data['name']); if($data['module_home']) { $moduleURL = $this->wire('sanitizer')->entities($data['forum_url']); $links[] = "<a target='_blank' href='$moduleURL'>" . $this->_('More Information') . "</a>"; } if($data['project_url']) { $projectURL = $this->wire('sanitizer')->entities($data['project_url']); $links[] = "<a target='_blank' href='$projectURL'>" . $this->_('Project Page') . "</a>"; } if($data['forum_url']) { $forumURL = $this->wire('sanitizer')->entities($data['forum_url']); $links[] = "<a target='_blank' href='$forumURL'>" . $this->_('Support Page') . "</a>"; } if(count($links)) $table->row(array($this->_x('Links', 'install-table'), implode(' / ', $links))); if($data['download_url']) { $downloadURL = $this->wire('sanitizer')->entities($data['download_url']); $table->row(array($this->_x('ZIP file', 'install-table'), $downloadURL)); $warnings[] = $this->_('Ensure that you trust the source of the ZIP file above before continuing!'); } else { $warnings[] = $this->_('This module has no download URL specified and must be installed manually.'); } foreach($warnings as $warning) { $table->row(array($this->_x('Please Note', 'install-table'), "<strong class='ui-state-error-text'> $warning</strong>")); } $markup->value = $table->render(); if($installable && $data['download_url']) { $btn = $this->wire('modules')->get('InputfieldSubmit'); $btn->attr('id+name', 'godownload'); $btn->value = $this->_("Download and install"); $btn->icon = 'cloud-download'; $btn->value .= " ($data[module_version])"; $form->add($btn); $this->session->ProcessModuleDownloadURL = $data['download_url']; $this->session->ProcessModuleClassName = $data['class_name']; } else { $this->session->remove('ProcessModuleDownloadURL'); $this->session->remove('ProcessModuleClassName'); } $btn = $this->wire('modules')->get('InputfieldButton'); $btn->attr('name', 'cancel'); $btn->href ="./edit?name=$data[class_name]"; $btn->value = $this->_("Cancel"); $btn->icon = 'times-circle'; $btn->class .= ' ui-priority-secondary'; $form->add($btn); $form->description = $this->wire('sanitizer')->entities($data['title']); return $form; } /** * Format a module version number from 999 to 9.9.9 * * @param string $version * @return string * */ protected function formatVersion($version) { return $this->wire('modules')->formatVersion($version); } } Created with massive theft of code from ProcessModule.module I'm going to toy around with this in our local environment and see if I can stitch together a nice workflow with the local git repo and a post-receive hook (once I find the time).1 point
-
Thank you for the hint, that seems to be it. Here is a screenshot of my local ImageMagick installed with Homebrew on MacOS: But even when I uninstall the core module IMagick Image Sizer, I cannot create a WebP image with ImageSizerEngineGD. Our hoster doesn't seem to support WebP either unfortunately. Not so easy to use WepP at the moment. ?0 points