Leaderboard
Popular Content
Showing content with the highest reputation on 12/15/2018 in all areas
-
Pages At Bottom Keeps selected pages at the bottom of their siblings. A "bottom page" will stay at the bottom even if it is drag-sorted to a different location or another page is drag-sorted below it (after Page List is refreshed the bottom page will still be at the bottom). Newly added sibling pages will not appear below a bottom page. The module also prevents the API methods $pages->sort() and $pages->insertAfter() from affecting the position of bottom pages. Note: the module only works when the sort setting for children on the parent page/template is "Manual drag-n-drop". Why? Because you want some pages to always be at the bottom of their siblings for one reason or another. And someone requested it. ? Usage Install the Pages At Bottom module. Select one or more pages to keep at the bottom of their siblings. If you select more than one bottom page per parent then their sort order in the page list will be the same as the sort order in the module config. https://github.com/Toutouwai/PagesAtBottom https://modules.processwire.com/modules/pages-at-bottom/9 points
-
4 points
-
Last week's version 3.0.121 was the first release candidate for our next master version. Several issue reports were covered this week for minor things, and they are all included in this week's dev branch version 3.0.122. Please consider this version the RC2 (release candidate 2) for our next master. There's nothing major to report, but if you'd like to see the list of changes relative to last week's version, please see the dev branch commit log at: https://github.com/processwire/processwire/commits/dev If you have a chance, please upgrade to version 3.0.122 and let us know if you run into any issues. Thus far things are going smoothly and it seems like we are very close to the next master version, perhaps as soon as next week. Since I don't have much more to report than the above, we'll skip doing a blog post this week. But after last week's post, a couple of people asked me about ProMailer (as mentioned, which we use for the weekly email distribution), so I'll be sure to write more on that soon. Attached is a screenshot from the message editor screen that reveals several aspects of the message sending part at least. Thanks for reading and I hope that you all have a great weekend!3 points
-
ping: @ryan This one would be really useful: https://github.com/processwire/processwire/pull/117 Also this is not an issue, it would be really helpful to have this enhanced debugInfo integrated into the current dev branch, before it becomes the new stable. It doesn't affect any other method or param, - only enhances the __debugInfo() method with useful informations. This would be really helpful for debug and support cases, if someone encounters a problem and ask here in the forums for help, we can get a good insight / overview in his/her current images system by simply asking to show us that info.3 points
-
Not sure where I originally saw it while lurking around the forums, but someone, somewhere at some time was asking about styling Uikit checkboxes as toggle-style switches, much like the ones at the bottom of this post asking me if I want to be notified of replies. So here is my humble offering, rough and ready, which can be thrown in at the bottom of your Uikit css as a starting point. Everything is based on ems and rems, so you can increase size as you desire by altering the single instance of font-size. It only targets single instances of labels within a specific field to a) try to limit unintended consequences and b) because in those cases it often seems more user-friendly to have an on/off binary switch rather than a checkbox. It's still totally a checkbox, just styled differently. .uk-form-controls-text label:only-of-type input.uk-checkbox { font-size:.8rem; margin-top:0; position:relative; -webkit-appearance:none; outline:none; width:4em; height:2.4em; border:2px solid #D6D6D6; border-radius: 3em; box-shadow:inset 5em 0 0 0 #DDD; flex-shrink: 0; } .uk-form-controls-text label:only-of-type input.uk-checkbox:after { content:""; position:absolute; top:.25em; left:.25em; background:#FFF; width:1.6em; height:1.6em; border-radius:50%; transition:all 250ms ease 20ms; box-shadow:.05em .25em .5em rgba(0,0,0,0.2); } .uk-form-controls-text label:only-of-type input.uk-checkbox:checked { background-color: transparent; box-shadow:inset 5em 0 0 0 #4ed164; border-color:#67bba5; } .uk-form-controls-text label:only-of-type input.uk-checkbox:checked:after { left:1.85em; box-shadow:0 0 1em rgba(0,0,0,0.2); } label:only-of-type input.uk-checkbox:checked + span { color:#008a00; transition:all 250ms ease 20ms; } .InputfieldCheckbox .InputfieldContent label:only-of-type {display:flex;} label:only-of-type input.uk-checkbox + span { color:#c3c3c3; display:flex; align-items:center; line-height:1.1; } /* Below is only necessary if you want the optional "tick" after items when selected */ label:only-of-type input.uk-checkbox + span:after { flex-shrink:0; margin-left:.5em;width:2em; opacity:0; content:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 250 250'%3E%3Ccircle cx='125' cy='125' r='125' fill='%23231F20' opacity='.1'/%3E%3Cpath fill='%230B8B44' d='M95.823 139.432l-32.492-32.56-31.872 31.883-.008-.008 63.72 63.732L218.549 79.116 187.494 47.52z'/%3E%3C/svg%3E"); } label:only-of-type input.uk-checkbox:checked + span:after { opacity:1; transition: opacity 250ms ease 150ms; }2 points
-
I'd also love to see more/better debugInfo for other PW objects as well, as per: https://github.com/processwire/processwire-issues/issues/5752 points
-
Beta testers needed ?. Please send me a PM if you'd like to help. An active subscription/current license is required. I'm happy (and frankly relieved ?) to announce Media Manager v 012 is ready for beta testing. Please accept my apologies for the delay. With this version: Only ProcessWire 3.x (preferably the latest master) is supported from now on. Currently, only AdminThemeUiKit is supported. (we may support Reno theme in the future). Changelog ProcessMediaManager (Media Manager Library/back-end) New UI: Responsive and closely matches ProcessWire theme (UiKit) Media thumbs shown in their natural orientation Upload Anywhere: Upload directly to Media Manager Library using drag and drop New edit media page in modal (natural ProcessWire page) (respects locked status and Media Manager edit permission if present) Create image variations in natural ProcessWire page during editing Add, edit, display and access custom ‘columns’ (fields) added to media manager templates (e.g. add text field to media-manager-image and access value using Media Manager API) Hide media types menu items if media type not in use Global setting for allowed media types (e.g. image and video only) Hide ‘All’ menu item New filters dashboard Updated and freshened up upload’s dashboard Option to upload ZIP archives as is (to save as documents) vs. to decompress them (recursively) Preview PDF documents in ‘gallery mode’ (if browser supports it) Selection of media is now identical to how images thumbs are selected in ProcessWire FieldtypeMediaManager Enable Upload Anywhere (for InputfieldMediaManager use) Allow users to edit media displayed in a Media Manager Inputfield Change whether to use text or icons for action ‘buttons’ Custom fields: Display and edit ‘columns’ (fields) added to media manager templates InputfieldMediaManager New UI: Responsive and closely matches ProcessWire theme (UiKit) Media thumbs shown in their natural orientation Upload Anywhere: Upload directly to Media Manager Library using drag and drop AND simultaneously insert into the Media Manager field on the page being edited Inputfield Media Manager markup updated/refreshed using Ajax when media is added or edited. No need for a page reload (so you won’t lose unsaved changes on the page) New edit media page in modal (natural ProcessWire page) (respects locked status and Media Manager edit permission if present) If a media in a Media Manager Inputfield is unpublished or trashed, it is automatically removed from all Media Manager Inputfields present on the page (including its variations) and the fields refreshed using Ajax View values of custom columns (not all Fieldtypes supported for viewing although all supported for accessing using the API). These are refreshed on the Inputfield using Ajax once editing is complete. Better support for repeaters Preview PDF documents in ‘gallery mode’ (if browser supports it) Selection of media is now identical to how images thumbs are selected in ProcessWire Inserting image variations Media Manager API (working in the front-end and manipulation via the API) Access media in a Media Manager field on a given page (see examples below) In the example below, we access custom fields (columns) added to the image media template (media-manager-image). In this case, two fields have been added. A URL field named links and a text field called headline. You can add any field you want. Please note though that not all fields can be displayed in the backend (for practicality). In the frontend, there are no such restrictions and you can access any of the fields as shown below. Your MediaManager object gives you access to the fields. For instance: $m->name_of_your_field. Below, we have $m->links and $m->headline. Please note that $m->media still returns the first media in the MediaManager object. In case you have added more than one media to your media field, e.g. more than one image added to one MediaManager object (done via editing a Media), you can access all of them using: $m->mediaField; In the case of image media, $m->mediaField returns a Pageimages object which can be iterated. This assumes, of course, that your media_manager_image field accepts more than one image (the default). The default setting for number of files accepted for other media types is 1, hence $m->mediaField returns a Pagefile object. You can edit these fields (media_manager_audio, media_manager_document and media_manager_video) to accept multiple files is you wish, in which case $m->mediaField will return a Pagefiles object which you can loop through, access using first(), last(), eq(n), etc. // get the media manager field on the page $mediaContent = $page->media_manager_tests; $tbody = '<tbody>'; $tbody .= $mediaContent->each(function($m) { if($m->typeLabel !='image') return; $thumb = $m->media->height(130); $img = "<img src='{$thumb->url}' alt='{$m->media->description}' height='130'>"; $tds = "<td>$img</td>" . "<td>{$m->headline}</td>" . "<td><a href='{$m->links}'>{$m->title}</a></td>"; $row = "<tr>{$tds}</tr>"; return $row; }); $tbody .= '</tbody>'; $content .= "<div class='uk-overflow-auto uk-width-1-1'>". "<table id='news-outlets' role='presentation' class='uk-table uk-table-divider uk-table-hover uk-table-justify uk-table uk-table-middle uk-table-responsive'>". "<thead>" . "<tr>" . "<th class='uk-width-small'>Preview</th>" . "<th class='uk-table-expand'>Summary</th>" . "<th class='uk-width-medium'>Link</th>" . "</tr>" . "</thead>" . "{$tbody}". "</table></div>"; Output of the above code2 points
-
Hi @bernhard! Gutenberg in Wordpress is a dual misconception: Wordpress is known as a blog platform. Yes, you can extend it massively and use it for many different projects but in the end it's mostly about operating with text. It's usually enriched with images and embeds but it's still an article, news, etc. So many authors for so many years just copied and pasted their texts, added few media and with 2-3 clicks published it. Now, doing this simple task with Gutenberg is insanely uneffective: instead one simple menu in editor there are several (hidden) menus everywhere (in every block). And EVERYTHING is a block now - a title, a paragraph, an image, a quote and so on (imagine the amount of additional code it generates!). You have to do absurdly much more things to get the same result and you have to spend much more time to do the simplest task. So maybe Gutenberg is for designers rather? If it's true and if this was the idea (to simplify building content) - there are so many so much better WP site builders and themes (to do so) FOR YEARS! Anybody who wants a one-page site or portfolio just picks up a theme and few plugins and voila. The core blogging mechanism stays safe and ready when necessary. I can understand that Matt, Automattic and the dev-deciders want to turn with WP elsewhere and change the character/purpose of the platform. Their choice (though the style they're doing that is embarrassing). But cutting out all those (millions?) of websites using WP for TEXT PUBLISHING and trying to convince the whole world that Gutenberg is better tool to write an article than TinyMCE or CKEditor is as funny as idiotic. From practical point of view, Gutenberg is incompatible with so many plugins and themes (used stably for years!) that it broke hundreds (thousands?) of websites. Blank sites, errors, bugs everywhere AND in many cases people have no idea where is a problem and what to fix. At my site (mentioned above) I have 50 plugins. Half of them doesn't work or generates errors with Gutenberg. Just take a look at the reviews of Gutenberg (and their dates!) to catch the disaster happening there. I know perfectly from my own experience how such a "click click drag-and-drop magic" approach can be tempting. Probably even using PW at some point (it's still ahead of me!). But it's the whole different way and phillosophy of creating (fast) content. There are all those Wix, Weebly, Spacesqare and Mobirise builders for that. WordPress as it was thought in the beginning shoudn't go that way (that 30% of web share will fall quickly).2 points
-
Great module! It is everything I needed for "group by" queries.1 point
-
1 point
-
AdminOnSteroids has a nice feature to make pages stick to the top of the page tree. Is there an easy way to do the opposite—make a page stick to the bottom of the page tree? What I’d like to use it for: Usually I have a test page in my PW setup. This is handy because the main content of a page is often created using a Repeater Matrix field. If I add a new type to this field, I can test it on the test page (which usually uses the basic page template). In the page tree, I usually position this test page as the last page before the 404 page. 404 page, the admin section and trash are always at the bottom of the fist level of the page tree. I’d like to see my test page work the same: It should be positioned before the 404 page. And when I add a new page directly under home (with the first level of the page tree being manually sortable), I’d like the new page to be inserted just before my test page instead of just before the 404 page.1 point
-
Hello Everyone! I'm new here, just signed up. I've discovered PW few days ago (what a shame...) and I'm angry for myself that I lost several years of my life... I'm a homegrown webdeveloper since 2005, started from pure HTML, then e107, Textpattern and from 2008 I work(ed) only with WordPress. I've made over a hundred of sites. Most of them are simple blogs but my main site is quite large (with over a 10k entries and over 100k of media). It's outdated a little bit so I was planning to modify and refresh it lately but then... Gutenberg happened. Gutenberg as an editor is unintuitive, buggy, produces massive problems and it's incompatibile with most of the things I'm using (it seems to be the same story for hundreds of websites built on WP). And though the whole WP community is frustrated and angry, it's been forced with WP 5.0 as the beginning of rebuilding WP as a platform into... hmm... next Wix? I was trying to discuss - my comments were deleted and my account has been blocked (as many others). After 8 years of developing WP... Well... I've frozen the site for updates and started looking elswhere. I have few advanced demands so tried Drupal, then played a little with Craft, Codeigniter and Laravel (I'm not a programmer per se). And after few recommendations I've finally found You. Guys... I'm so thrilled and emotional. I can't stop reading and following tutorials on localhost. PW is THE TOOL I was searching my whole life! I'm going to study everything very carefully and next year I'm going to rebuild slowly (there's no rush) my main site with PW. Back to Gutenberg: I understand where it aims to be and what it tries to accomplish BUT this is wrong on so many levels. As far as I can see, a barebone PW installation is far more flexible and useful then WP with 30 plugins. And obviously you know it perfectly. So I'm begging you: no Gutenberg here... ? Cheers to Ryan and Everyone! Greetings from Poland!1 point
-
Or just put that in the settings: { "files.associations": { "*.module": "php" } }1 point
-
.uk-form-controls-text label:only-of-type input.uk-checkbox { font-size: .8rem; margin-top: 0; position: relative; -webkit-appearance: none; outline: none; width: 4em; height: 2.4em; border: 2px solid #D6D6D6; border-radius: 3em; box-shadow: inset 5em 0 0 0 #DDD; flex-shrink: 0; &:after { content: ""; position: absolute; top: .25em; left: .25em; background: #FFF; width: 1.6em; height: 1.6em; border-radius: 50%; transition: all 250ms ease 20ms; box-shadow: .05em .25em .5em rgba(0, 0, 0, 0.2); } } .uk-form-controls-text label:only-of-type input.uk-checkbox:checked { background-color: transparent; box-shadow: inset 5em 0 0 0 #4ed164; border-color: #67bba5; &:after { left: 1.85em; box-shadow: 0 0 1em rgba(0, 0, 0, 0.2); } } label:only-of-type input.uk-checkbox:checked+span { color: #008a00; transition: all 250ms ease 20ms; } .InputfieldCheckbox .InputfieldContent label:only-of-type { display: flex; } label:only-of-type input.uk-checkbox+span { color: #c3c3c3; display: flex; align-items: center; line-height: 1.1; }1 point
-
Writing reusable markup generation functions Hello there, I've been working with ProcessWire for a while now, and I've been writing some helper functions to generate markup and reduce the amount of repetitive code in my templates. In this tutorial I want to explain how to write small, reusable functions and combine them to accomplish bigger tasks. Note that this is the follow-up to my last post, Building a reusable function to generate responsive images. In that tutorial, I demonstrated a pretty large function that generates multiple image variations for responsive images, as well as the corresponding markup. In this post, I'll split this function into multiple smaller functions that can be utilized for other purposes as well. This will be more beginner-orientated than the last one, I hope there's some interest in this anyway ? Note that for my purposes, I prefer to have those functions as static methods on a namespaced object, so the following code examples will be placed in a simple Html class. However, you can use those as normal functions just as well. class Html { // code goes here } Edit: Those functions use some syntax exclusive to PHP 7.1 and above, they won't work in PHP 7.0 and lower. Thanks for @Robin S for pointing that out. Seperation of concerns To split up the original function, we need to analyze all the individual tasks it performs: Generate several image variations in different sizes. Generate the corresponding srcset attribute markup according to the specification. Generate the sizes attribute markup based on the passed queries. Automatically include the description as the alt attribute. Generate the markup for all attributes (including the ones passed to the function). Generate the markup for the complete img tag. The first three of those tasks are very specifically concerned with generating responsive images. Generating the alt attribute is relevant to any img tag, not just responsive images. Finally, generating the attributes and HTML markup is relevant to all HTML markup that one wants to generate. Therefore, this is how a hierarchy between those functions could look like. Generate responsive image Generate image markup Generate any HTML tag markup Generate an HTML start tag Generate HTML attributes markup Generate an HTML end tag Those bullet points are the tasks I want to turn into individual functions, each accepting arguments as general as they can be, facilitating code reuse. I'll start writing those out from the ground up. Generating attributes markup HTML attributes are a list of property-value pairs, where the value is wrapped in quotation marks (") and assigned to the property with an equals-sign (=). Each pair is separated by a space. There are also standalone/empty attributes that don't have a value, for example: <input id="name" class="form-control" disabled> Since the input format consists of key-value pairs, it makes sense to use an associative array as the argument to the attributes functions. public static function attributes( array $attributes ): string { $attr_string = ''; foreach ($attributes as $attr => $val) { $attr_string .= ' ' . $attr . '="' . $val . '"'; } return $attr_string; } However, this still needs to support standalone attributes. Those attributes are also known as boolean attributes, since their presence indicates a true value, their absence the opposite. Since all other values in the markup are strings or integers, we can differentiate between those based on the type of the value in the associative array. If it's a boolean, we'll treat it as a standalone attribute and only include it if the value is also true. public static function attributes( array $attributes, bool $leading_space = false ): string { $attr_string = ''; foreach ($attributes as $attr => $val) { if (is_bool($val)) { if ($val) { $attr_string .= " $attr"; } } else { $attr_string .= ' ' . $attr . '="' . $val . '"'; } } if (!$leading_space) { $attr_string = ltrim($attr_string, ' '); } return $attr_string; } Of course, this means that if a value in the array is boolean false, this array item will be left out. This is by design, as it allows the caller to use expressions in the array declaration. For example: echo Html::attributes([ 'id' => 'name', 'class' => 'form-control', 'disabled' => $this->isDisabled() ]); This way, if isDisabled returns true, the disabled attribute will be included, and left out if it doesn't. Note that I also included a $leading_space argument for convenience. Generating start tags, end tags and complete HTML elements The start tag is identified by the element name and the attributes it contains. The end tag only needs a name. Those functions are trivial: public static function startTag( string $element, ?array $attributes = [] ): string { $attribute_string = self::attributes($attributes, true); return "<{$element}{$attribute_string}>"; } public static function endTag(string $element): string { return "</{$element}>"; } Of course, the startTag function builds on the existing function to generate the attributes. Note that a start tag is identical with a standalone tag (i.e. a void HTML element such as the img tag). At this point, it's also trivial to write a function that builds a complete element, including start and end tag as well as the enclosed content. public static function element( string $element, ?string $content = null, array $attributes = [], $self_closing = false ): string { if ($self_closing) { return self::startTag($element, $attributes); } else { return self::startTag($element, $attributes) . $content . self::endTag($element); } } Note that while this function does take several arguments, all except the first have reasonable default values, so usually the caller will only have to pass two or three of them. Some examples: echo Html::startTag('hr'); // <hr> echo Html::element('a', 'My website', ['href' => 'http://herebedragons.world']); // <a href="http://herebedragons.world">My website</a> Image tags Those functions make for a solid foundation to build any type of HTML element markup. Based on the type, the functions can accept more specific arguments to be easier to use. For example, the previous link example could be simplified by writing a link function that accepts a link text and an href value, since those are needed for any link: public static function link( string $url, ?string $text = null, array $attributes = [] ): string { // use url as text if no text was passed $text = $text ?? $url; $attributes['href'] = $url; return self::element('a', $text, $attributes); } Anyway, for our image markup function, we'll take a Pageimage object as an argument instead, since most images we will use in a ProcessWire template will come from the ProcessWire API. Since all ProcessWire image fields have a description field by default, we can use that description as the alt attribute, which is good practice for accessibility. public static function image(Pageimage $img, array $attributes = []): string { $attributes['src'] = $img->url(); // use image description as alt text, unless specified in $attributes if (empty($attributes['alt']) && !empty($img->description())) { $attributes['alt'] = $img->description(); } return self::selfClosingElement('img', $attributes); } Pretty simple. Note that the alt attribute can still be manually overridden by the caller by including it in the $attributes array. Responsive images Now, the responsive image function can be shortened by building on this function in turn. Optimally, the three distinct tasks this performs (see above) should be separated into their own functions as well, however in practice I haven't seen much need for this. Also, this post is plenty long already, so ... public static function imageResponsive( Pageimage $img, ?int $standard_width = 0, ?int $standard_height = 0, ?array $attributes = [], ?array $sizes_queries = [], array $variant_factors = [0.25, 0.5, 0.75, 1, 1.5, 2] ): string { // use inherit dimensions of the passed image if standard width/height is empty if (empty($standard_width)) { $standard_width = $img->width(); } if (empty($standard_height)) { $standard_height = $img->height(); } $suffix = 'auto_srcset'; // if $attributes is null, default to an empty array $attributes = $attributes ?? []; // get original image for resizing $original_image = $img->getOriginal() ?? $img; // the default image for the src attribute $default_image = $original_image->size( $standard_width, $standard_height, ['upscaling' => false, 'suffix' => $suffix] ); // build the srcset attribute string, and generate the corresponding widths $srcset = []; foreach ($variant_factors as $factor) { // round up, srcset doesn't allow fractions $width = ceil($standard_width * $factor); $height = ceil($standard_height * $factor); // we won't upscale images if ($width <= $original_image->width() && $height <= $original_image->height()) { $current_image = $original_image->size($width, $height, ['upscaling' => false, 'suffix' => $suffix]); $srcset[] = $current_image->url() . " {$width}w"; } } $attributes['srcset'] = implode(', ', $srcset); // build the sizes attribute string if ($sizes_queries) { $attributes['sizes'] = implode(', ', $sizes_queries); } return self::image($default_image, $attributes); } See my last post for details. Since then, I made some changed to the function I outlined here (thanks to @horst for pointing out some pitfalls with my approach). Most notably: The generated images now include a prefix so they can be removed by a cleanup script more easily. The function now accepts a width and a height parameter so that the aspect ratio of the generated images is fixed (reasons for this change are explained here). To get the original functionality back, I also wrote two helper functions that takes only a width/height and fill in the missing parameter based on the aspect ratio of the passed image. The helper functions look like this: public static function imageResponsiveByWidth( Pageimage $img, ?int $standard_width = 0, ?array $attributes = [], ?array $sizes_queries = [], array $variant_factors = [0.25, 0.5, 0.75, 1, 1.5, 2] ): string { // automatically fill the height parameter based // on the aspect ratio of the passed image if (empty($standard_width)) { $standard_width = $img->width(); } $factor = $img->height() / $img->width(); $standard_height = ceil($factor * $standard_width); return self::imageResponsive( $img, $standard_width, $standard_height, $attributes, $sizes_queries, $variant_factors ); } Conclusion This approach was born out of necessity, since pure PHP templating makes for some messy code. Of course, another approach would be to use a template engine in the first place. However, I didn't want the overhead of installing Twig or Blades for my smaller projects, so for those small to medium-sized projects, I found some helper functions to generate markup and clean up my code to be a helpful addition. A small disclaimer, I update those functions pretty frequently while developing with ProcessWire, so it's possible some errors made their way into the versions I posted here that I haven't discovered yet. If you want to use some of the included code in your own projects, make sure to properly test it. I'm also working on a small library including those and some other helpers I wrote, I'll post a Github link once it's in a usable stage. So this post got way longer than I intended, I hope that some of you still made your way through it and enjoyed it a bit ? If you see some problems or possible improvements to those methods and the general approach, I'd be happy to hear them! Complete code for reference <?php use \Processwire\Pageimage; class Html { /** * Build a simple element tag with the passed element. * * @param string $element The element/tag name as a string. * @param ?string $content The content of the element (what goes between the tags). * @param ?array $attributes Optional attributes for the element. * @param boolean $self_closing Whether the element is self-closing (i.e. no end tag). $content is ignored if true. * @return string The HTML element markup. */ public static function element( string $element, ?string $content = null, array $attributes = [], $self_closing = false ): string { if ($self_closing) { return self::startTag($element, $attributes); } else { return self::startTag($element, $attributes) . $content . self::endTag($element); } } /** * Builds a start tag for an element (or a self-closing/void element). * * @param string $element * @param array $attributes * @return string The HTML start tag markup. */ public static function startTag( string $element, ?array $attributes = [] ): string { $attribute_string = self::attributes($attributes, true); return "<{$element}{$attribute_string}>"; } /** * Build an end tag for an element. * * @param string $element The HTML end tag markup. * @return void */ public static function endTag(string $element): string { return "</{$element}>"; } /** * Build an HTML attribute string from an array of attributes. Attributes set * to (bool) true will be included as standalone (no attribute value) and left * out if set to (bool) false. * * @param array $attributes Attributes in attribute => value form. * @param bool $leading_space Whether to include a leading space in the attribute string. * @return string The attributes as html markup. */ public static function attributes( array $attributes, bool $leading_space = false ): string { $attr_string = ''; foreach ($attributes as $attr => $val) { if (is_bool($val)) { if ($val) { $attr_string .= " $attr"; } } else { $attr_string .= ' ' . $attr . '="' . $val . '"'; } } if (!$leading_space) { $attr_string = ltrim($attr_string, ' '); } return $attr_string; } /** * Image Functions. */ /** * Build a simple image tag from a Processwire Pageimage object. * * @param Pageimage $img The image to use. * @param array $attributes Optional attributes for the element. * @return string */ public static function image(Pageimage $img, array $attributes = []): string { $attributes['src'] = $img->url(); // use image description as alt text, unless specified in $attributes if (empty($attributes['alt']) && !empty($img->description())) { $attributes['alt'] = $img->description(); } return self::selfClosingElement('img', $attributes); } /** * Builds a responsive image element including different resolutions * of the passed image and optionally a sizes attribute build from * the passed queries. * * @param Pageimage $img The base image. Must be passed in the largest size available. * @param int|null $standard_width The standard width for the generated image. Use NULL to use the inherent size of the passed image. * @param int|null $standard_height The standard height for the generated image. Use NULL to use the inherent size of the passed image. * @param array|null $attributes Optional array of html attributes. * @param array|null $sizes_queries The full queries and sizes for the sizes attribute. * @param array|null $variant_factors The multiplication factors for the alternate resolutions. * @return string */ public static function imageResponsive( Pageimage $img, ?int $standard_width = 0, ?int $standard_height = 0, ?array $attributes = [], ?array $sizes_queries = [], array $variant_factors = [0.25, 0.5, 0.75, 1, 1.5, 2] ): string { // use inherit dimensions of the passed image if standard width/height is empty if (empty($standard_width)) { $standard_width = $img->width(); } if (empty($standard_height)) { $standard_height = $img->height(); } $suffix = 'auto_srcset'; // if $attributes is null, default to an empty array $attributes = $attributes ?? []; // get original image for resizing $original_image = $img->getOriginal() ?? $img; // the default image for the src attribute $default_image = $original_image->size( $standard_width, $standard_height, ['upscaling' => false, 'suffix' => $suffix] ); // build the srcset attribute string, and generate the corresponding widths $srcset = []; foreach ($variant_factors as $factor) { // round up, srcset doesn't allow fractions $width = ceil($standard_width * $factor); $height = ceil($standard_height * $factor); // we won't upscale images if ($width <= $original_image->width() && $height <= $original_image->height()) { $current_image = $original_image->size($width, $height, ['upscaling' => false, 'suffix' => $suffix]); $srcset[] = $current_image->url() . " {$width}w"; } } $attributes['srcset'] = implode(', ', $srcset); // build the sizes attribute string if ($sizes_queries) { $attributes['sizes'] = implode(', ', $sizes_queries); } return self::image($default_image, $attributes); } /** * Shortcut for the responsiveImage function that only takes a width parameter. * Height is automatically generated based on the aspect ratio of the passed image. * * @param Pageimage $img The base image. Must be passed in the largest size available. * @param int|null $standard_width The standard width for this image. Use NULL to use the inherent size of the passed image. * @param array|null $attributes Optional array of html attributes. * @param array|null $sizes_queries The full queries and sizes for the sizes attribute. * @param array|null $variant_factors The multiplication factors for the alternate resolutions. * @return string */ public static function imageResponsiveByWidth( Pageimage $img, ?int $standard_width = 0, ?array $attributes = [], ?array $sizes_queries = [], array $variant_factors = [0.25, 0.5, 0.75, 1, 1.5, 2] ): string { // automatically fill the height parameter based // on the aspect ratio of the passed image if (empty($standard_width)) { $standard_width = $img->width(); } $factor = $img->height() / $img->width(); $standard_height = ceil($factor * $standard_width); return self::imageResponsive( $img, $standard_width, $standard_height, $attributes, $sizes_queries, $variant_factors ); } /** * Shortcut for the responsiveImage function that only takes a height parameter. * Width is automatically generated based on the aspect ratio of the passed image. * * @param Pageimage $img The base image. Must be passed in the largest size available. * @param int|null $standard_height The standard height for this image. Use NULL to use the inherent size of the passed image. * @param array|null $attributes Optional array of html attributes. * @param array|null $sizes_queries The full queries and sizes for the sizes attribute. * @param array|null $variant_factors The multiplication factors for the alternate resolutions. * @return string */ public static function imageResponsiveByHeight( Pageimage $img, ?int $standard_height = 0, ?array $attributes = [], ?array $sizes_queries = [], array $variant_factors = [0.25, 0.5, 0.75, 1, 1.5, 2] ): string { // automatically fill the width parameter based // on the aspect ratio of the passed image if (empty($standard_height)) { $standard_height = $img->height(); } $factor = $img->width() / $img->height(); $standard_width = ceil($factor * $standard_height); return self::imageResponsive( $img, $standard_width, $standard_height, $attributes, $sizes_queries, $variant_factors ); } }1 point
-
CKEditor is a nasty beast when it comes to configuring it, but I will try to help ? First things first, how to configure it: https://github.com/processwire/processwire/blob/dev/wire/modules/Inputfield/InputfieldCKEditor/README.md#custom-editor-css-file You probably did this but to make things clear: the field's type is Textarea, while the inputfield's type is CKEditor. So far so good ? On the Details tab: content type is recommended to be Markup/HTML and it should be. "Markup/HTML (Content Type)" option can be configured as required, as they do not affect the CSS we are dealing with. The things in question be configured on the Input tab, under the CKEditor settings, so here come the interesting bits. Available core toolbar items are listed there, and as an example, here is what I often use: PWImage, Format, Styles, RemoveFormat, Bold, Italic, NumberedList, BulletedList, PWLink, Unlink, Table, SpecialChar, Sourcedialog Format and Styles are needed to make those dropdowns show up. If you do not have too many CKEditor fields on the edit page then I recommend setting them to "Regular Editor". We have a thread of discussing settings, BTW: https://processwire.com/talk/topic/17862-share-your-ckeditor-settings/ Turning ACF and HTML Purifier on is generally recommended, however, they might make things harder in terms of keeping those HTML bits intact when saving the page. In the case of ACF, you need to deal with "extra allowed content" so that your code actually gets saved as intended. Here are some forum topics to tackle some issues that may arise: https://processwire.com/talk/topic/19519-ckeditor-and-extra-allowed-content/ https://processwire.com/talk/topic/19918-modify-ckeditor-image-markup-via-hook/?do=findComment&comment=172616 https://processwire.com/talk/topic/12773-ckeditor-does-not-reapply-styles/#entry116174 Normally I turn ACF on, but keep HTML Purifier off in some cases, as I usually use "Regular Editor" instead of "Inline" and have trusted users. However, HTML Purifier is good for making sure only valid code is saved, so it is a trade off not to turn it on. When I do use HTML Purifier, then I sometimes temporarily turn it off during development but I make sure to turn in on for the production site, even for trusted users. If you run into troubles, try turning it on and off to see what it changes in the source. Sadly, HTML Purifier is not yet configurable in ProcessWire. You can give this request a thumb's up though: https://github.com/processwire/processwire-requests/issues/226 Setting up Format Tags is self explanatory, usually I use at least the minimum of p;h2;h3;h4;h5 (h1 is always generated by the template file so no need to include it). Extra Allowed Content is very important, normally I use this: p(*)[*]{*};div(*)[*]{*};li(*)[*]{*};ul(*)[*]{*};i(*)[*]{*};tr(*)[*]{*};*[id];*(class); *[id] and *(class) are somewhat redundant as they allow any IDs and any classes while (*)[*] also allow any of those in the case of the element they are applied to. Now we are getting to the bits you are probably concerned with: Custom Editor CSS File (regular mode) - which can be used to style the HTML of content editor itself as opposed to the frontend Custom Editor JS Styles Set - this is the file you need when you want to define styes for the Style toolbar dropdown Custom Config Options - the notes of the field read: these settings can also be set in one of these files (e.g.) ...config.js and ...config-body.js and this is what I normally use. To configure these options properly this is what you need to tripple check: Make sure: you did create the directory /site/modules/InputfieldCKEditor/ and copied the file called mystyles.js from /wire/modules/Inputfield/InputfieldCKEditor/ to its counterpart: /site/modules/InputfieldCKEditor/ Make sure: files in the directory /site/modules/InputfieldCKEditor/ are named exactly as the ones configured in the options. In the case of Custom Editor JS Styles Set, make sure that in the JS file the function parameter reflects the name you defined in the admin. The default is "mystyles", defined this way: mystyles:/site/modules/InputfieldCKEditor/site_styles.js which means that in the JS file you need CKEDITOR.stylesSet.add( 'mystyles', [... but if you want to use a different string, then replace "mystyles" both in the admin's configuration filed and the accompanying JS file, something like: cke_extra:/site/modules/InputfieldCKEditor/my_extra.js and CKEDITOR.stylesSet.add( 'cke_extra', [... Regarding the configuration files themselves, here is an example for config-body.js: CKEDITOR.editorConfig = function (config) { // Define changes to default configuration here. For example: // config.uiColor = '#AADC6E'; //config.enterMode = CKEDITOR.ENTER_BR; // stops new <p> tags from being added, adds <br /> instead. config.protectedSource.push(/<i[a-z0-9\=\'\"_\- ]*><\/i>/g); // needed for empty <i> tags (icons) config.protectedSource.push(/@/g); // needed for empty <i> tags (icons) /* If this one is enabled: * config.allowedContent = true; * then it is not possible to stop adding widht and height to images */ config.disallowedContent = 'img[width,height]'; // to stop adding widht and height to images }; CKEDITOR.dtd.$removeEmpty['i'] = 0; // needed for empty <i> tags (icons) Regarding mystyles:/site/modules/InputfieldCKEditor/mystyles.js here is one approach for UIkit 3: CKEDITOR.stylesSet.add( 'cke_extra', [ { name: 'Normal Paragraph', element: 'p', attributes: {'class': 'site-paragraph'} }, { name: 'Clear runaround', element: 'p', attributes: {'class': 'uk-clearfix'} }, { name: 'Warning Paragraph', element: 'p', attributes: {'class': 'uk-alert-warning', 'data-uk-alert': ''} }, { name: 'Success Paragraph', element: 'p', attributes: {'class': 'uk-alert-success', 'data-uk-alert': ''} }, { name: '1/2 left align', element: 'img', attributes: {'class': 'align_left uk-float-left uk-width-1-2@s site-body-img'} }, { name: '1/2 right align', element: 'img', attributes: {'class': 'align_right uk-float-right uk-width-1-2@s site-body-img'} }, { name: '1/3 left align', element: 'img', attributes: {'class': 'align_left uk-float-left uk-width-1-3 site-body-img'} }, { name: '1/3 right align', element: 'img', attributes: {'class': 'align_right uk-float-right uk-width-1-3 site-body-img'} }, { name: '1/4 left align', element: 'img', attributes: {'class': 'align_left uk-float-left uk-width-1-4 site-body-img'} }, { name: '1/4 right align', element: 'img', attributes: {'class': 'align_right uk-float-right uk-width-1-4 site-body-img'} }, { name: 'center image', element: 'img', attributes: {'class': 'align_center uk-align-center site-body-img'} }, ]); This method above defines classes for the Style toolbar dropdown in a way that more than one class or attribute is applied to the selected element. For example align_left is applied because the default ProcessWire CKEditor class for left align is this (see this post). However, to make sure that in the frontend the same element is also aligned to the left, I also apply uk-float-left uk-width-1-2@s to it. This is just a dead simple approach but you can make it more sophisticated. If you want to turn CKEditor into a WYSIWYG editor then you need do some extra work, because you need to make sure your contents.css defines the classes and styles also used on the fronted. I have not ventured too far in this direction though. Normally I use UIkit 3 on the frontend and not in the admin so I simply mimic the most important subset of the frontend's styles in contents.css. I will need to improve on this practice but only after I have switched from the Reno Admin Theme to Admin Theme UIkit. I hope I cleared up a few things for you. I you want even more tips form others, I recommend using google to search like this: https://www.google.hu/search?q=CKEditor+site%3Aprocesswire.com%2Ftalk&oq=CKEditor1 point
-
@Leftfield Welcome to the community. The easiest and most performant way is just to make page tree reflects your desirable URL structure. But often such structure looks messy. You can change it by using URL segments and rewriting page paths for your category and post templates. For example, for blogs, I use such a structure home/blog/ /authors /categories /cat1 /cat2 /posts /post1 /post2 I got such URLs site.com/blog/category/cat1 site.com/blog/posts/post1 etc. But I want to have such URLs and I want that every post can be listed in several categories, but for preventing contend duplication I want that it has only one accessible URL site.com/cat1/ site.com/cat1/post1/ site.com/cat2/post2/ To make it work you have to: 1. Enable URLs segments for your home page. 2. Create to Page Reference fields and add it to your post template blog_section - Page reference field ( Page field value type set to Single page, selector 'template=blog_category') - this one we will use for building URL for the current post. blog_categories - Page reference field ( Page field value type set to Multiple pages, selector 'template=blog_category') 2. In your ready.php $pages->addHookAfter('Page(template=blog-category)::path', function ($e) { $page = $e->object; $e->replace = true; $e->return = "/" . $page->name . "/"; }); $pages->addHookAfter('Page(template=post)::path', function ($e) { $page = $e->object; if ($page->blog_section) { $slug = $page->blog_section->name; } else { $slug = $page->parent('template=blog')->name; //fallback } $e->replace = true; $e->return = "/" . $slug . "/" . $page->name . "/"; }); 3. In your home.php if ($input->urlSegment3) throw new Wire404Exception(); if ($input->urlSegment2) { $post_name = $input->urlSegment2; $match = $pages->get($sanitizer->selectorValue($post_name)); if (!$match->id) throw new Wire404Exception(); echo $match->render(); return $this->halt(); } if ($input->urlSegment1) { $category_name = $input->urlSegment1; $match = $pages->get($sanitizer->selectorValue($category_name)); if (!$match->id) throw new Wire404Exception(); echo $match->render(); return $this->halt(); } It's not tested and there can be some errors, but I hope you get a general idea.1 point
-
Something I've done that is quite useful is setup a Less parsing library like this one that accepts variables and then I pass the variables on to the rendering pipeline, this keeps my styles files clean from php code: Pardon the dirty code: <?php $variables = array( 'main-color' => $home->main_color , 'body-background' => $home->color_main_background, 'icon-colors' => $home->color_icons, 'menu-icon-color' => $home->color_menu, 'footer-gradient' => $home->color_footer_gradient, 'go-top-gradient' => $home->color_got_top_gradient, 'slide-background-color'=> $home->color_slides_background, 'slide-hover-background-color'=> $home->color_slides_hover_background, 'h2-titles-color'=> $home->color_h2, 'headings-font'=> $home->headings_font, 'footer-background' => $home->color_footer_background ); //Set up main file path and directory of imports. $less_files = array( $mainCssFile => $config->paths->templates . 'css/' ); $options = array( 'cache_dir' => $config->paths->assets . 'cache/less/', 'output' => $config->paths->templates. 'css/build.css', 'relativeUrls' => false, "strictMath" => "on"); try{ $css_file_name = Less_Cache::Get( $less_files, $options, $variables); }catch(Exception $e){ $log->save("less", $e); } ?>1 point
-
It's linked to in the GitHub issue that szabesz mentioned, but in case you missed it, there is a hook here for preventing referenced pages from being trashed... ...and from being trashed or directly deleted...1 point
-
It's easy using RockFinder. See the example in the docs: https://gitlab.com/baumrock/RockFinder#custom-sql-aggregations-groupings-distincts1 point
-
The module in the PW version 2.7.2 create a table with a TEXT (64Kb) field and in PW version 3.0.x it use a MEDIUMTEXT (16Mb) field. @gebeer your are trying to insert 23,536 bytes which could be fine if the string was composed with single-byte characters. But in your case it look like your string is encoded in UTF-8/Multi-byte, so we can assume that each character in your string take 1 to 4 bytes of space and thus the database server truncate your string because the TEXT field can only contain 65 535 chars (using single-byte characters) and your string contain (assuming here) 94 144 bytes (multi-byte characters). It could explain your issue. For testing, you could try to disable SQL STRICT MODE. I hope it make sense.1 point
-
@gebeer, there was a change in Session-DB-Handler in the past, but don't remember exactly when. Maybe you compare the current 3.x Module against the 2.7.x one?1 point
-
Thank you. My level isn't high either. But I think this project is not big enough for teamwork. Not even sure it's a project.1 point
-
WillyC's solution is a good way to go if you have this need. But want to mention that the whole idea of multiple routes to a page kind of goes against the grain of the ProcessWire philosophy. By design, there is only one URL to any given page. This is different from systems that disconnect their data from URLs (Drupal, EE, etc.). ProcessWire considers pages like files on a file system. We are trying to embrace the way that the web is addressed rather than counter it. I understand the desire to make a shorter URL for a given page, and that's a fine reason to implement a solution like this (and I've done it myself too). But the reason you don't see things like this outlined in the documentation is because I don't think it's a best practice, whether in ProcessWire or on the web in general. So if someone uses multiple routes, I would suggest it only to solve a specific need after a site is produced… not as something to build around from the start.1 point