Jump to content


Popular Content

Showing content with the highest reputation on 05/14/2020 in all areas

  1. 9 points
    We recently relaunched the website of IBIS Backwaren, a brand for international pastries and bakery products. Concept, design and implementation by schwarzdesign, built with ProcessWire. As always, we aimed to deliver a clean website design with a focus on content and fast loading times. Here's a list of modules used on this site, followed by a couple of interesting features included. The site is bilingual, though currently only the German version is available. The English version will be released at a later date. ProFields FormBuilder WireMailSMTP Cache Control MarkupSitemap Hanna Code Automatically link page titles Unique image variations Product database and product collections The website features a product database with all the products IBIS distributes. The product template is rather extensive, including fields for product categories and attributes, nutrition facts, certifications etc. We make heavy use of page reference fields, e.g. for product categories and attributes such as vegan or gluten-free products. Nutrition facts are stored in a Textareas field. There are multiple ways for visitors to explore products. One is a classic product finder with filters for product category and attributes. But there are also individual product collections (Produktwelten) which can be regularly updated by the client. Product collections include a list of related products. This allows the marketing department to quickly set up targeted landing pages. Recipes and custom forms Another approach to marketing and activating fans is the recipe section. Each recipe uses at least one IBIS product to give visitors some ideas. Of course, recipes and products are cross-linked to allow for exploration and discovery. There's a recipe submission form which allows you visitors to send in their own recipes, as well as a couple of other forms. All forms on the site are built with the Form Builder module. We made use of the form submissions to pages feature, which automatically creates a new (unpublished) page for new recipe submissions. Those can be reviewed by the staff and published directly. We also use a hook to automatically transform the plaintext fields for ingredients and instructions into HTML lists. Multi-brand IBIS uses different brands for different sets of products. Some landing pages as well as the gluten-free selection are branded differently. To accomodate this, we create an additional "brand" template with an override for the default logo. Each page has a brand selection field to allow switching the logo / branding for that page. Since brands are represented by normal pages, then client can create additional brands on their own.
  2. 5 points
    Developing custom fields for ProcessWire is great! And it's easier than many might think once you get the basic concepts. But it's hard to learn those concepts by reading the code and doing some trial and error... That's why I think we need a good tutorial about that topic. It took me quite long, but now I feel knowledgeable enough to write such a tutorial. I also have the idea (or the need) for some new fields that might be helpful to the community. What I don't have is time 😄 So I thought to share the workload and share my knowledge while the development of the module (and testing, writing docs, etc) could be done by someone else under my supervision (hope that does not sound scary 😄 ). What do you think? I'm happy to hear your opinions - we are in the PUB 😉 Have a great week and happy coding!
  3. 5 points
    I don't know why the limit is hardcoded, but I can offer a workaround for this. If you want to modify a core module without having to worry about updates, just copy the entire module folder / file from the core to your site's module directory (/site/modules/). Then execute Modules -> Refresh through the CMS. This will create a notice about the duplicate module asking you to select which module file you use. There you can select the copied module. Once whatever is not working is fixed in the core, just change that setting back and delete the duplicated module. You still have to check for changes inside the core module when you update the core, in case your older copied version is not compatible anymore. In this case, just copy the module again and reapply your changes.
  4. 4 points
    "Markup Regions pw vs data-pw different behavior" I have not yet experienced such a behavior. Maybe you are mixing up "Boolean action attributes (inner HTML)" with "Action attributes with value (outer HTML)"? Regarding the differences between these two, see my explanation below. +1 Here is my simplified docs for markup regions, we can also call it "cheat sheet". I wrote it some time ago: Defining markup regions Basic region Wrapping tags do appear in the final markup. Children tags are preserved if not explicitly replaced. <div id="hello"> OR <div data-pw-id="hello"> data-pw-... They are removed from the final output and thus not visible to front-end markup, while id="..." is not removed! Placeholder region Only the inner HTML will be used and the wrapping tags won't appear in the final markup. <pw-region id="hello">...</pw-region> Good for groupping tags in the <head> eg.: <pw-region id="site_scripts"> Optional region It will be automatically removed from the document markup if nothing populates it, so it is a region which should be empty by default. <div id='hello' data-pw-optional></div> Examples: an <ul>, which [according to HTML5 specs] is required to have one or more <li> elements within it, otherwise it's invalid HTML. a sidebar which is only needed on pages populated with widgets /* -------------------------------------------------------------------------------------------------- */ Populating markup regions Available action attributes: data-pw-replace: replaces a region’s markup data-pw-append: appends markup to a region data-pw-prepend: prepends markup to a region data-pw-before: inserts markup before a region data-pw-after: inserts markup after a region Boolean action attributes (inner HTML) Only applies the inner HTML to the region. TARGET CODE: <div id='hello'> <h2> Hello World </h2> </div> <p data-pw-id="hello" data-pw-append> This text will APPEND to div#hello </p> <p data-pw-id="hello" data-pw-prepend> This text will PREPEND to div#hello </p> <p data-pw-id="hello" data-pw-before> This will insert this text BEFORE div#hello </p> <p data-pw-id="hello" data-pw-after> This will insert this text AFTER div#hello. </p> RESULTS: This will insert this text BEFORE div#hello <div id='hello'> This text will PREPEND to div#hello <h2> Hello World </h2> This text will APPEND to div#hello </div> This will insert this text AFTER div#hello Action attributes with value (outer HTML) All of the markup that you specify (the outer HTML) becomes part of the final document markup (except for the pw-* attributes): TARGET CODE: <div id='hello'> <h2> Hello World </h2> </div> <p data-pw-append="hello"> This paragraph will APPEND to div#hello </p> <p data-pw-prepend="hello"> This paragraph will PREPEND to div#hello </p> <p data-pw-before="hello"> This will insert this paragraph BEFORE div#hello </p> <p data-pw-after="hello" class="world"> This will insert this paragraph with class "world" AFTER div#hello. </p> RESULTS: <p> This will insert this paragraph BEFORE div#hello </p> <div id='hello'> <p> This paragraph will PREPEND to div#hello </p> <h2> Hello World </h2> <p> This paragraph will APPEND to div#hello </p> </div> <p class="world"> This will insert this paragraph with class "world" AFTER div#hello. </p> /* ------------------------------------------------ ------------------------------------------------ */ Adding HTML attributes Any HTML attribute you add to the action tag that does not begin with pw- or data-pw- will be added to the originally defined region tag. TARGET CODE: <ul id="foo" class="bar"> <li> First item </li> </ul> ACTION CODE: <ul data-pw-append="foo" title="Hello"> <li> Second item </li> </ul> RESULTS: <ul id="foo" class="bar" title="Hello"> <li> First item </li> <li> Second item </li> </ul> Adding and removing classes Classes from the action tag and the region tag are merged by default. To remove a class by the region action: prepend a minus sign to the class to be removed, eg: class="-foo bar" will result in class="bar"
  5. 2 points
    As an alternative to this approach, my new Cache Control module also includes an optional feature for browser cache busting. You retrieve a version string (by default, a timestamp) through the module that is stored inside the cache. As soon as you clear the cache through the module, this version string gets cleared as well, and a new version string is generated during the next request. This ensures that any assets cached by the browser are cleared alongside all server-side caches. Just throwing it out there 🙂
  6. 2 points
    There is a difference between selectors used with PageFinder... $pages->find($selector), $page->children($selector), etc ...and selectors used in memory... $some_pagearray->find($selector) Any string that strtotime() understands can be used with a PageFinder selector, but with an in-memory selector you can only use timestamps. This is because when FieldtypeDatetime prepares the database query it actually passes the value through strtotime() if it isn't recognised as being an integer (timestamp) or a DateTime object. See here and here. But FieldtypeDatetime isn't involved with in-memory selectors so in such cases if you are working with a time string you'll need to convert it to a timestamp yourself.
  7. 2 points
    Could post a brief code snippet of what you are trying to do? I am assuming you should have something like this: On _main.php: <footer id="footer-region"> ... </footer> On other_template.php <div id="footer-region" pw-append> <script>...</script> </div> @huseyin Just to give my personal opinion, I just really like that with markup regions you can code the markup pretty much like if you were doing normal template output, no string concatenation to fill up a variable to output content, instead of doing that you wrap your markup with the Markup Regions conventions. That would probably be the downside, that you have to understand that maybe not all markup visible in your code, will actually end up in the output markup (depending on how you use it!), and this has sometimes caused me confusion while coding, but nowadays I practically code all my templates with markup regions strategy. So in conclusion, I see it more as a "style" rather than something better or worse.
  8. 2 points
    Our effort seems to be prized.. I see many new members on the forum with their first posts....
  9. 1 point
    Menu Builder As of 29 December 2017 ProcessWire versions earlier than 3.x are not supported Modules Directory Project Page Read Me (How to install, use, etc..) For highly customisable menus, please see this post. If you want a navigation that mirrors your ProcessWire page tree, the system allows you to easily create recursive menus using either vanilla PHP or Soma's great MarkupSimpleNavigation. In some cases, however, you may wish to create menus that: 1. Do not mirror you site's page tree (hirarchies and ancestry); and 2. You can add custom links (external to your site) to. That is primarily where Menu Builder comes in. It is also helpful if you: 3. Prefer creating menus via drag and drop 4. Have a need for menus (or other listings) that will be changing regularly or that you want to allow your admin users to edit. The issue of custom menus is not new here in the forums. The difference is that this module allows you to easily create such menus via drag and drop in the Admin. Actually, you can even use it to just create some list if you wanted to. In the backend, the module uses the jQueryUI plugin nestedSortable by Manuele J Sarfatti for the drag and drop and is inspired in part by the WP Custom Menu feature. Please read the Read Me completely before using this module. For Complex or highly-customised menus, it is recommended to use the getMenuItems() method as detailed in this post. Features Ability to create menus that do not mirror your ProcessWire Page Tree hierarchy/structure Menus can contain both ProcessWire pages and custom links Create menu hierarchies and nesting via drag and drop Easily add CSS IDs and Classes to each menu item on creating the menu items (both custom and from ProcessWire pages) or post creation. Optionally set custom links to open in a new tab Change menu item titles built from ProcessWire pages (without affecting the original page). E.g. if you have a page titled 'About Us' but you want the menu item title to be 'About' Readily view the structure and settings for each menu item Menus stored as pages (note: just the menu, not the items!) Menu items stored as JSON in a field in the menu pages (empty values not stored) Add menu items from ProcessWire pages using page fields (option to choose between PageAutocomplete and AsmSelect [default]) or a Selector (e.g. template=basic-page, limit=20, sort=title). For page fields, you can specify a selector to return only those specified pages for selection in the page field (i.e. asm and autocomplete) For superusers, optionally allow markup in your menu titles, e.g. <span>About</span> Menu settings for nestedSortable - e.g. maxLevels (limit nesting levels) Advanced features (e.g. add pages via selector, menu settings) currently permissible to superadmins only (may change to be permission-based) Delete single or all menu items without deleting the menu itself Lock down menus for editing Highly configurable MarkupMenuBuilder - e.g. can pass menu id, title, name or array to render(); Passing an array means you can conditionally manipulate it before rendering, e.g. make certain menu branches visible only to certain users [the code is up to you!] Optionally grab menu items only (as a Menu object WireArray or a normal array) and use your own code to create custom highly complex menus to meet any need. More... In the backend, ProcessMenuBuilder does the menu creation. For the frontend, menus are displayed using MarkupMenuBuilder. Credits In this module's infancy (way back!), I wanted to know more about ProcessWire modules as well as improve my PHP skills. As they say, what better way to learn than to actually create something? So, I developed this module (instead of writing PW tutorials as promised, tsk, tsk, naughty, naughty!) in my own summer of code . Props to Wanze, Soma, Pete, Antti and Ryan whose modules I studied (read copied ) to help in my module development and to Teppo for his wonderful write-up on the "Anatomy of fields in ProcessWire" that vastly improved my knowledge and understanding of how PW works. Diogo and marcus for idea about using pages (rather than a custom db table), onjegolders for his helpful UI comments, Martijn Geerts, OrganizedFellow, dazzyweb and Mike Anthony for 'pushing me' to complete this module and netcarver for help with the code. Screens
  10. 1 point
    I've seen a couple of questions regarding namespaces and autoloading floating around the forum recently, so I decided to write a little tutorial. In general, I often see people getting confused when they try to wrap their head around namespaces, autoloading, Composer and the mapping of namespaces to directory structures all at once. In fact, those are very much independent, distinct concept, and it is much easier to explain and understand them separately. So this guide is structured as follows: How namespaces work in PHP. How autoloading works in PHP. Conventions for mapping namespaces to directory structures: PSR-4. How autoloading works in Composer and ProcessWire's class loader. How to use the class loader in a ProcessWire module. Feel free to skip the sections you're already familiar with. Namespaces in PHP The purpose of namespaces in PHP is to avoid naming conflicts between classes, functions and constants, especially when you're using external libraries and frameworks. Nothing more. It's important to understand that this has nothing at all to do with autoloading, directory structures or file names. You can put namespaced stuff everywhere you want. You can even have multiple namespaces inside a single file (don't try this at home). Namespaces only exist to be able to use a generic name – for example, ProcessWire's Config class – multiple times in different contexts without getting a naming conflict. Without namespaces, I couldn't use any library that includes a Config class of it's own, because that name is already taken. With namespaces, you can have a distinction between the classes ProcessWire\Config and MoritzLost\Config. You can also use sub-namespaces to further segregate your code into logical groups. For example, I can have two classes MoritzLost\Frontend\Config and MoritzLost\Backend\Config– a class name only needs to be unique within it's namespace. You can declare the namespace for a PHP file using the namespace statement at the top: // file-one.php <?php namespace ProcessWire; // file-two.php <?php namespace MoritzLost\Frontend; This way, all classes, methods and constants defined inside this file are placed in that namespace. All ProcessWire classes live in the ProcessWire namespace. Now to use one of those classes – for example, to instantiate it – you have a couple of options. You can either use it's fully qualified class name or import it into the current namespace. Also, if you are inside a namespaced file, any reference to a class is relative to that namespace. Unless it starts with a backward slash, in this case it's relative to the global namespace. So all of those examples are equivalent: // example-one.php <?php namespace ProcessWire; $page = new Page(); // example-two.php <?php use ProcessWire\Page; $page = new Page(); // example-three.php <?php $page = new ProcessWire\Page(); // example-four.php <?php namespace MoritzLost\Somewhere\Over\The\Rainbow; $page = new \ProcessWire\Page(); The use statement in the second example can be read like this: “Inside this file, all references to Page refer to the class \ProcessWire\Page” How autoloading works Every PHP program starts with one entry file – for ProcessWire, that's usually it's index.php. But you don't want to keep all your code in one file, that would get out of hand quickly. Once you start to split your code into several individual files however, you have to take care of manually including them with require or include calls. That becomes very tedious as well. The purpose of autoloading is to be able to add new code in new files without having to import them manually. This, again, has nothing to do with namespaces, not even something with file locations. Autoloading is a pretty simple concept: If you try to use a class that hasn't been loaded yet, PHP calls upon it's registered autoloaders as a last-ditch attempt to load them before throwing an exception. Let's look at a simple example: // classes.php <?php class A { /** class stuff */ } class B { /** class stuff */ } // index.php <?php spl_autoload_register(function ($class) { include_once 'classes.php'; }); new A(); new B(); This is a complete and functional autoloader. If you don't believe me, go ahead and save those two files (classes.php and index.php) and run the index.php with php -f index.php. Then comment out the include_once call and run it again, then you'll get an error that class A was not found. Now here's what happens when index.php is executed (with the autoloader active): Our anonymous function is added to the autoload queue through spl_autoload_register. PHP tries to instantiate class A, but can't because it's not loaded yet. If there was no autoloader registered, the program would die with a fatal error at this point. But since there is an autoloader ... The autoloader is called. Our autoloader includes classes.php with the class definition. That was a close one! Since the class has been loaded, execution goes back to the index.php which can now proceed to instantiate A and B. If the class was still not loaded at this point, PHP would go back to the original plan and die. One thing to note is that the autoloader will only be called once in this example. That's because both A and B are in the same file and that file is included during the first call to the autoloader. Autoloading works on files, not on classes! The important takeaway is that PHP doesn't know if the autoloader knows where to find the class it asks for or, if there are multiple autoloader, which one can load it. PHP just calls each registered autoloader in turn and checks if the class has been loaded after each one. If the class still isn't loaded after the last autoloader is done, it's error time. What the autoloader actually does is pretty much wild wild west as well. It takes the name of the class PHP is trying to load as an argument, but it doesn't have to do anything with it. Our autoloader ignores it entirely. Instead, it just includes classes.php and says to itself “My job here is done”. If class A was in another file, it wouldn't have worked. This process has two main advantages: Since autoloaders are only called on-demand to load classes just in time, we only include the files we actually need. If in the example above class A and B are not used in some scenarios, the classes.php will not be included, which will result in better performance for larger projects (though this isn't as cut and dry, since autoloading has it's own overhead, so if you load most classes anyway during a single request, it will actually be less efficient). If the autoloader is smart enough to somehow map class names to the files they're located in, we can just let the autoloader handle including the classes we need, without having to worry about jamming include statements everywhere. That brings us to ... PSR-4, namespaces and directory structures As you see, namespaces and autoloading are both pretty limited concepts. And they aren't inherently linked to each other. You can namespace your classes without ever adding an autoloader, and you can autoload classes that are all in the same namespace. But they become useful when you put them together. At the core of all that autoloading talk is a simple idea: By putting classes in files named after their class names, and putting those files in directory hierarchies based on the namespace hierarchy, the autoloader can efficiently find and load those files based on the namespace. All it needs is a list of root namespaces with their corresponding directories. The exact way class names and namespaces are mapped to directory structures and file names is purely conventional. The accepted convention for this is PSR-4. This is a super simple standard which basically just sums up the ideas above: A base namespace is mapped to a specific directory in the file system. When the autoloader is asked to load a class in that namespace (or a sub-namespace of it), it starts looking in that folder. This "base" namespace may include multiple parts – for example, I could use MoritzLost\MyAwesomeLibrary as a base and map that to my source directory. PSR-4 calls this a "namespace prefix". Each sub-namespace corresponds to a sub-directory. So by looking at the namespace, you can follow subdirectories to the location where you expect to find the class file. Finally, the class name is mapped directly to the file name. So MyCoolClass needs to be put inside MyCoolClass.php. This all sounds simple and straightforward - and it absolutely is! It's only once you mash everything together, mix up language features, accepted conventions and proprietary implementations like Composer on top that it becomes hard to grasp in one go. Composer and ProcessWire's class loader Now all that's left is to talk about how Composer and ProcessWire provide autoloading. Composer, of course, is primarily a tool for dependency management. But because most libraries use namespaces and most developers want to have the libraries they're using autoloaded, those topics become a prerequisite to understanding what Composer does in this regard. Composer can use different autoloading mechanisms; for example, you can just give it a static list of files to include for every request, or use the older PSR-0 standard. But most modern libraries use PSR-4 to autoload classes. So all Composer needs to function is a mapping of namespace prefixes to directories. Each library maintains this mapping for it's PSR-4-structured classes through the autoload information in their composer.json. You can do this for your own site to: Just include the autoload information as shown in the documentation and point it to the directory of your class files. Composer collects all that information and uses it to generate a custom file at vendor/autoload.php — that's the one you need to include somewhere whenever you set up Composer in one of your projects. Bells and whistles aside, this file just registers an autoloader function that will use all the information collected from your own and your included libraries' composer.json to locate and include class files on demand. You can read more about how to optimize Composer's autoloader for production usage here. If you want to read up on how to set up Composer for your own sites, read my ProcessWire + Composer integration guide instead. And finally, what does ProcessWire do to handle all this? Turns out, ProcessWire has it's own autoloader implementation that is more or less PSR-4 compliant. You can access it as an API variable ($classLoader or wire('classLoader'), depending on context). Instead of using a static configuration file like Composer, the namespace -> directory mapping is added during the runtime by calling $classLoader->addNamespace. As you would expect, this function accepts a namespace and a directory path. You can use this to register your own custom namespaces. Alternatively, if you have site-specific classes within the ProcessWire namespace, you can just add their location to the class loader using the same method: $classLoader->addNamespace('ProcessWire', '/path/to/your/classes/'). Utilizing custom namespaces and autoloading in ProcessWire modules Now as a final remark, I wanted to give an example of how to use custom namespaces and the class loader in your own modules. I'll use my TrelloWire module as an example: Decide what namespace you're going to use. The main module file should live in the ProcessWire namespace, but if you have other classes in your module, they can and should use a custom namespace to avoid collisions with other modules. TrelloWire uses ProcessWire\TrelloWire, but you can also use something outside the ProcessWire namespace. You need to make sure to add the namespace to the class loader as early as possible. If either you or a user of your module tries to instantiate one of your custom classes before that, it will fail. Good places to start are the constructor of your main module file, or their init or ready methods. Here's a complete example. The module uses only one custom namespaced class: ProcessWire\TrelloWire\TrelloWireApi, located in the src/ directory of the module. But with this setup, I can add more classes whenever I need without having to modify anything else. /** * The constructor registers the TrelloWire namespace used by this module. */ public function __construct() { $namespace = 'ProcessWire\\TrelloWire'; $classLoader = $this->wire('classLoader'); if (!$classLoader->hasNamespace($namespace)) { $srcPath = $this->wire('config')->paths->get($this) . 'src/'; $classLoader->addNamespace($namespace, $srcPath); } } Source Thanks for making it through to the very end! I gotta learn to keep those things short. Anyway, I hope this clears up some questions about namespaces and autoloading. Let me know if I got something wrong, and feel free to add your own tips and tricks!
  11. 1 point
    Maybe give RockGrid and RockFinder2 a try? I'll tag @bernhard, the author to chime in if you need more info.
  12. 1 point
    Thanks for the fix @adrian, I just tested it and it seems to be working! I'm not getting errors any more with both modules I tested before 🙂 Just butting in here: I don't believe you want to use Strict, this may break the debugger for if you visit the site by clicking on a link from somewhere else. Since you can't really use Secure cookies because of the local dev environments like you said, you'll probably be best of with SameSite=Lax to most cookies. This article on web.dev is a good resource for the SameSite changes.
  13. 1 point
    I think I got it... It works now by the following: $termine = $page->children("standort=(standort_reference~=$standort), standort=(standort_alle=1), date=(date>=today, enddate=''), date=(date!='', enddate>=today), sort=date, limit=10"); At least It looks like the output is correct!🙏
  14. 1 point
    Thanks @MoritzLost for figuring that out. I have added a check to prevent that error when a module doesn't extend WireData. Honestly, I think the getting of module settings in PW is a bit of a mess which is why I use 3 different approaches in Tracy because they all return different things for some modules. Let me know if you have any problems with the new version. Thanks @rick for the heads up on that. I took a quick look, but I don't have time to fix things at the moment - I set a lot of cookies in various places in Tracy and I am not using a standard method so it's not easy to change them all and I sometimes set in JS and sometimes in PHP. Obviously I don't want to make them "secure" because I am sure many of us are running local dev setups without https, so I probably need to just set the sameSite rule to Lax, but maybe Strict is better? Not sure yet, but it's on my list to take care of soon.
  15. 1 point
    Some sorts of hypotheses. 1 - Yes, too many bookmarks could cause problems 👇🏼 2 - The potential problem is: the size of the data stored in this session (session are written to disk, and re-write every request) 👇🏼 3 - The practical limit will be the memory that PHP can take on the server 4 - What @MoritzLost explained I hope it give you an idea of the potential issue with a lots of pages.
  16. 1 point
    Hi @adrian, I've been cleanin' up my latest project and noticed this warning: Cookie “tracyClearDumpsRecorderItems” will be soon rejected because it has the “sameSite” attribute set to “none” or an invalid value, without the “secure” attribute. To know more about the “sameSite“ attribute, read https://developer.mozilla.org/docs/Web/HTTP/Cookies I don't know if it means anything. If so, great. If not, please ignore this post. 🙂
  17. 1 point
    Sometimes you have to burst the css-cache, to ensure that visitors that have visited your site earlier, will get an updated version of the css-file.
  18. 1 point
    @adrian I have tested my other modules; I think the error only occurs when the module does NOT extend WireData, since the getArray method comes from there. TextformatterPageTitleLinks extends Textformatter which extends WireData -> RequestInfoPanel is working. ProcessCacheControl extends Process which extends WireData -> RequestInfoPanel is working. TrelloWire extends Wire (instead of WireData) -> RequestInfoPanel displays the error. When I change the class definition to extend WireData instead of Wire, the error disappears. I have tested & confirmed this behaviour with another module I'm working on (currently not publicly available, will probably publish this next week or so). So I think it comes down to modules that don't extend WireData at all, which isn't required (at least according to the docs). I guess it's mostly a cosmetic choice, but for the modules that don't really keep track of data I didn't want to extend WireData. Maybe the RequestInfoPage could check for that case and use getModuleConfig instead?
  19. 1 point
    Nowadays open dev tools and disable the cache in there. No need for hacky cache busting on each request.
  20. 1 point
    That. The main.css under site/templates/assets/css is meant as a convenient place to add custom styling (no sass/less magic involved here). I had meant to change that time() call to an mtime() call as an example of how to bust the cache whenver the file is changed, but I apparently never got around to it. Brushing up my site templates is another point on my growing todo list. The default styling is done in site/templates/assets/css/blog.css, but you won't see that filename in the developer console because the site template uses the AOIM module to combinde and minimize the js and css files.
  21. 1 point
    Just here to say that I'm the kind of person whom prefer to learn by watching videos, so it would be great if someone could create a series about this topic :)
  22. 1 point
    Just a heads up that might hopefully help save someone some time. Browser adblockers don't like "google-analytics" in ajax calls these days which breaks all the ajax calls in this module. I changed the name (not title) of the Admin > Google Analytics page in the PW admin and also changed the PAGE_NAME constant on this line: https://github.com/wanze/ProcessGoogleAnalytics/blob/c97c170485ff91f81d8a67e72b968a1c8e25165e/ProcessGoogleAnalytics.module#L25 In my case I changed them both to "stats" because I didn't want to mess around figuring out whether it was "google" or "analytics" that was causing the problem, but other names should also work.
  23. 1 point
    @MoritzLost - from my initial investigation, it looks like a PW core bug. You are using this (https://processwire.com/blog/posts/new-module-configuration-options/#enter-the-new-moduleconfig-class) approach to defining module settings which I have never used, but it appears that when that approach is used, the getArray() method doesn't exist on the module, hence the error. Do you know of another module that uses that approach that I could test to confirm?
  24. 1 point
    I always use the Markup Regions, until now I haven't needed more complicated system in my projects. Roughly this is my standard configuration: _main.php <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Title</title> <link rel="stylesheet" href="..3rd_party_styles_1.css"> <link rel="stylesheet" href="..3rd_party_styles_2.css"> <pw-region id="styles_area"></pw-region><!--#styles_area--> <link rel="stylesheet" href="..my_main_style.css"> <pw-region id="head_area"></pw-region><!--#head_area--> </head> <body> <header> Header Content Here </header> <div class="container"> <pw-region id="content_area"></pw-region><!--#content_area--> </div> <footer> Footer Content Here </footer> <pw-region id="append_area"></pw-region><!--#append_area--> <script src="..3rd_party_scripts_1.js"></script> <script src="..3rd_party_scripts_2.js"></script> <pw-region id="scripts_area"></pw-region><!--#scripts_area--> <script src="..my_main_scripts.js"></script> </body> </html> template_file.php <?php namespace ProcessWire; $vars_or_arrays = "fill_some_vars_or_arrays_here"; ?> <pw-region id="styles_area"> <style> body { background: #F2F2F2 !important; } h2 { text-transform: uppercase !important; } .classA { background: red; } .classB { font-size: 2rem; } </style> </pw-region> <pw-region id="head_area"> <link rel="dns-prefetch" href="//example.com"> <link rel="preconnect" href="//example.com"> <link rel="prefetch" href="image.png"> </pw-region> <pw-region id="content_area"> Here render your page content from $page or using $pages, $vars_or_arrays, etc. </pw-region> <pw-region id="append_area"> <div class="modal"> <a href="#">close</a> <div class="modal_body"> Content of modal window </div> </div> </pw-region> <pw-region id="scripts_area"> <script src="..3rd_party_scripts_3.js"></script> <script> window.onload = function(event) { alert('Hello World!'); }; </script> </pw-region> Use the regions from the main layout as needed on each page, to add a modal window, run a javascript, add styles, etc. I always use <pw-region> because this element is removed from the final markup if it's not used in the template file.
  25. 1 point
    In case you missed it, GitHub bought npm https://blog.npmjs.org/post/612764866888007680/next-phase-montage https://github.blog/2020-03-16-npm-is-joining-github/
  26. 1 point
    You can create a own logout template/page with this content: <?php $session->logout(); $session->redirect('/');
  27. 1 point
    Good day everybody! Thanks for the module @ryan! Is there an easy way to integrate social login into this?
  28. 1 point
    First a note about my other modules... I have three existing modules that are similar in that they allow restrictions to be placed on repeating inputfields: Limit Repeater, Limit PageTable, Limit Table Restrict Repeater Matrix takes a different approach to the module configuration from those other modules. The module settings for Restrict Repeater Matrix are applied in the field settings rather in a module config screen. I think this new approach is better, but it means that it isn't practical to create different settings for different roles via the admin interface. Instead the module has a hookable method, allowing roles to be targeted and other advanced usages to be achieved via a hook. The result is that the module is more flexible. I intend to transition my other modules to the same approach over the coming weeks, but because this will result in breaking changes I will be releasing the updated modules under new names ("Restrict Repeater", etc) to avoid users upgrading via the Upgrades module without full awareness of the changes. The old modules will be marked as deprecated. Restrict Repeater Matrix A module for ProcessWire CMS/CMF. Allows restrictions and limits to be placed on Repeater Matrix fields. Requires ProcessWire >= v3.0.0 and FieldtypeRepeaterMatrix >= v0.0.5. For any matrix type in a Repeater Matrix field you have the option to: Disable settings for items (cannot change matrix type) Prevent drag-sorting of items Prevent cloning of items Prevent toggling of the published state of items Prevent trashing of items Limit the number of items that may be added to the inputfield. When the limit is reached the "Add new" button for the matrix type will be removed and the matrix type will not be available for selection in the "Type" dropdown of other matrix items. Please note that restrictions and limits are applied with CSS/JS so should not be considered tamper-proof. Usage Install the Restrict Repeater Matrix module. For each matrix type created in the Repeater Matrix field settings, a "Restrictions" fieldset is added at the bottom of the matrix type settings: For newly added matrix types, the settings must be saved first in order for the Restrictions fieldset to appear. Set restrictions for each matrix type as needed. A limit of zero means that no items of that matrix type may be added to the inputfield. Setting restrictions via a hook Besides setting restrictions in the field settings, you can also apply or modify restrictions by hookingRestrictRepeaterMatrix::checkRestrictions. This allows for more focused restrictions, for example, applying restrictions depending on the template of the page being edited or depending on the role of the user. The checkRestrictions() method receives the following arguments: $field This Repeater Matrix field $inputfield This Repeater Matrix inputfield $matrix_types An array of matrix types for this field. Each key is the matrix type name and the value is the matrix type integer. $page The page that is open in ProcessPageEdit The method returns a multi-dimensional array of matrix types and restrictions for each of those types. An example of a returned array: Example hooks Prevent the matrix type "images_block" from being added to "my_matrix_field" in a page with the "basic-page" template: $wire->addHookAfter('RestrictRepeaterMatrix::checkRestrictions', function(HookEvent $event) { $field = $event->arguments('field'); $page = $event->arguments('page'); $type_restrictions = $event->return; if($field->name === 'my_matrix_field' && $page->template->name === 'basic-page') { $type_restrictions['images_block']['limit'] = 0; } $event->return = $type_restrictions; }); Prevent non-superusers from trashing any Repeater Matrix items in "my_matrix_field": $wire->addHookAfter('RestrictRepeaterMatrix::checkRestrictions', function(HookEvent $event) { $field = $event->arguments('field'); $type_restrictions = $event->return; if($field->name === 'my_matrix_field' && !$this->user->isSuperuser()) { foreach($type_restrictions as $key => $value) { $type_restrictions[$key]['notrash'] = true; } } $event->return = $type_restrictions; }); http://modules.processwire.com/modules/restrict-repeater-matrix/ https://github.com/Toutouwai/RestrictRepeaterMatrix
  29. 1 point
    v0.1.4 released. This version adds support for some new features added in Repeater Matrix v0.0.5 - you can disable the settings for matrix types (so items cannot change their matrix type), and when the limit for a type is reached it becomes unavailable for selection in the type dropdown of other matrix items. The module now requires Repeater Matrix >= v0.0.5. If you are using Repeater Matrix v0.0.4 or older then there is no need to upgrade Restrict Repeater Matrix as the new features in v0.1.4 are only applicable to Repeater Matrix v0.0.5.
  30. 1 point
    You can Hook into InputfieldFile::processInputFile. There are other places in that Class you could also hook into, but I think processInputFile works best. Throw the code below in ready.php Please note: Starter code: not much validation going on other than checking if the field the file was uploaded to is 'course_file' You will have to implement other logic yourself. For instance, the code copies the file immediately it is uploaded by ProcessWire Ajax. It doesn't check if the page is actually saved. If a page is not saved and the page is reloaded, as you know, files in file fields are deleted from disk. This code does not delete the corresponding file in your custom directory You might want the Hook to only run if you are in admin. You can add that logic I've purposefully left in verbose and debugging code in there (Tracy stuff) to help you ( maybe and others) understand what's going on. I suggest you test using Tracy Debugger for a better grasp of the file upload process. Delete the debugging stuff when you've got this working as you want :-). wire()->addHookAfter("InputfieldFile::processInputFile", function(HookEvent $event) { // @note: here, events are $input, $pagefile, $n @see: the method // get event we are hooking into // get arguments by index {a bit faster, but less-readable} /* $input = $event->arguments[0]; $pagefile = $event->arguments[1]; $n = $event->arguments[2]; */ // get arguments by name #$input = $event->argumentsByName('input'); $pagefile = $event->argumentsByName('pagefile'); #$n = $event->argumentsByName('n'); // $pagefile->field: The Field object that this file is part of. // limit to a specific field {course_file} if($pagefile->field->name != 'course_files') return; # intercept file // Tracy Debugger calls to see what's going on. Also logs Ajax inputs! #bd($input, 'input'); #bd($n, 'input'); // @see: http://processwire.com/api/ref/pagefile/ // pagefile object bd($pagefile, 'pagefile'); // name of the field uploading to {your 'course_file'} bd($pagefile->field->name, 'field pagefile is part of'); // file-sanitized name of the file we've added, e.g. 'checklist_install.pdf' bd($pagefile->basename, 'name of added file'); // full disk path where the file has been uploaded in this page's files folder... //... in /site/assets/files/1234 where 1234 is this page's ID // ... e.g. "F:/www/mysite/site/assets/files/1234/checklist_install.pdf" bd($pagefile->filename, 'full disk path name of added file'); // $pagefile->page: The Page object that this file is part of bd($pagefile->page->id, 'id of the page file added to'); // full disk path to your custom uploads directory $customDirectory = $this->wire('config')->paths->assets . 'custom_directory/'; bd($customDirectory,'custom directory for files'); # copy file // use ProcessWire's $files API // @see: http://processwire.com/api/ref/files/ $files = $this->wire('files'); // copy the file(s) $files->copy($pagefile->filename,$customDirectory . $pagefile->basename); });
  31. 1 point
    Needed this, thanks! Seems so obvious afterwards.
  32. 1 point
    Just edit the url of the "Admin" page in the pagetree.
  • Create New...