Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation on 12/01/2018 in all areas

  1. This week ProcessWire 3.0.120 is on the dev branch. This post takes a look at updates for this version and talks about our work towards the next master version. In addition, we take a look at some more updates and screenshots for the new ProcessWire.com website: https://processwire.com/blog/posts/processwire-3.0.120-and-more-new-site-updates/
    6 points
  2. Great work @ryan! Especially love the new search!!! I also like the results view of the search alot, clean and with the smart ability to filter by kind. Thanks for listening to our feedback about the skyscraper ornaments. Also, love the API search! What stands out to me is that the screenshots all together (with the exemption of the sites pages) look very uniform – mostly text, headings, lists. Very clean and organised, I like that, but visually there is not much to distinguish. Now I get that you are focusing on the content and structure of the site - so it might just be to early for feedback on this topic: I think that especially on the marketing focused pages, focusing on presenting ProcessWire for new users, stakeholders and designers & marketing departments, more images and specially crafted pages would make sense. But I would even argue that for more developer oriented pages, some images would be helpful. I'm a visual person and nice images for blogs, even modules would go a long way in making the experience on processwire.com a pleasing and joyful experience. Now I totally understand that you probably need support from a few UX and UI gurus but I guess, there are a lot of us from the community who would be more than happy to stepp in and offer support. A few examples from pages I stumbled across the last couple of days: Well crafted homepage with gimmicks (feature-slider): https://ora.pm/home or https://craftcms.com Nice visual blog posts: https://ora.pm/blog Visually appealing modules directory (with a nice touch for developers, the ability to sell your own modules): https://statamic.com/marketplace/addons Presenting many features visually (nice menu on the left): https://craftcms.com/features/all Blog, module and tutorials directory: The new Laracast website probably has a good middle-ground with nice graphical touches while still not to heavily reliant on custom visuals: https://laracasts.com/series One last thought, maybe "features" would be a better title for the "about" page because it's less about an organisation and more about a product. This is by no means negativ criticism, I really like the current progress of our new beloved CMSs home and would only like to offer some inspiration and my thoughts in hope that it helps boost PW. Ps. Sorry for the double post on the blog – from now on I will only post here… did not think about it when reading the blog.
    4 points
  3. Hi @bernhard, I appreciate and understand all your comments, but I feel like you are missing the point. There are two concepts/workflows at play, and whilst they aren't mutually exclusive they do serve different purposes. Migrations = manually migrate config and content changes by writing php using the API JSON Export/Import = automagically migrate config (not content!!!) changes you have made in the admin panel Both have pros and cons and in some situations one or the other may not even be viable, therefore the choice is important. I personally wish you could export field definitions as php (like you can with ACF), which is sort of a middle-ground between both these options. To be clear, my feature request was just about updating current core Import/Export to (optionally) enable a more seamless workflow like the below screencast. In it I migrate a new field defined in the admin from one wordpress installation to another in seconds. You cannot do this with Migrations, and with version control and the right logic for syncing this is a completely viable way to migrate config. I am not suggesting this should be the canonical way to migrate config, just one option that sits alongside something like Migrations.
    4 points
  4. Maybe you could if the number of items in the WireArray is fixed, but I'd say it's not the best approach if the number of items is variable or might change at some point. The slices() method divides however many items are in the WireArray into the given number of slices. So if there are 6 items and you do slices(2) you will have two slices of 3 items each. But if later there were 7 items you would have a slice of 4 items and a slice of 3 items, which would not be so good for the desired layout. Instead I think you want something equivalent to array_chunk() which will divide the WireArray into chunks of a given number of items. See here for a WireArray implementation of array_chunk():
    3 points
  5. This is a textformatter module that will automatically replace titles of other pages on your site with links to those pages. For example, if you have a template glossary-term, and mention the exact title of one page using that template in a textfield, the title will be automatically linked to that page (if the textfield uses that formatter). This is good for SEO, and saves you some manual labour. You can configure which templates should get automatically linked, and of course the formatter is only active for the fields you add this formatter to. Note that if you need more manual control over when and where titles are automatically linked, you're probably better of using Autolink from a Glossary by @mr-fan. Features Allows you to limit the automatic links by template. Only includes published & visible pages by default, with an option to include hidden pages. Automatically excludes the current page, with an option to change that behaviour. Allows you to configure the minimum title length for linked pages. Doesn't overwrite existing links, and detects most edge cases (titles inside other tag's attributes, titles inside existing links et c.). Supports multi-language sites. Titles will only be linked if a title in the current language is set. Can add configurable attributes to all automatically created links. This includes the ability to use page fields as replacement patterns for attributes. For example, you can create CSS classes that include the name of the template of the linked page. Extensive options and hooks to change the generated markup completely. Need <mark> elements with a title attribute based on a page field instead of a link? No problem. See the example project below. Prefer oldest or newest page in the case of duplicate titles. Queries the database directly for improved performance. Has options to switch between case sensitive and case insensitive modes, and force case sensitive behaviour even for case insensitive database collations. Allows you to overwrite the module configuration via the API to call the module with different settings for different requirements on the same site. Download & Documentation The module is now available in the modules directory: https://modules.processwire.com/modules/textformatter-page-title-links/ You can download & install it through the backend using the classname TextformatterPageTitleLinks. To install it manually, download or clone the module from the Github repository into your site/modules folder and install through the backend. The complete documentation can be found in the README in the repository. Make sure to check out the module configuration page after installing the module. Requirements PHP 7.1 or higher ProcessWire 3+ (it will probably work in older versions, I haven't tested those though). This is my first module, I hope it may become useful to some of you ? If you find any errors or have some other suggestions or feedback, let me know!
    2 points
  6. Thanks for the feedback. It's a little tricky to demo the site as screenshots as it really changes it from an interactive interface to a static image. Looking at the screenshots is kind of like looking at pictures of a house as opposed to walking around in it. It changes the scale completely to one that doesn't happen interactively, so definitely gives a different vibe than actually using it in the browser. But I'm also not one to go off and disappear for weeks at a time, so want to share what I've got every week, even if the viewing context isn't quite right. Per the earlier posts about the site, I'm not trying to create anything graphically too divergent from what we've got already, just trying to evolve it to the next step, and hopefully a platform/foundation for some of the things you've mentioned, and potentially other people that know how to get there. So I'm a lot more focused on the development side (backend and front-end) than the design side, though also trying to get just enough design going to accommodate the content and various responsive layouts that it displays in. At the same time, I don't want something that's purely a mock-up or placeholder either, because I think phase 1 is replacing the current site and immediately after phase 2 is revisiting the design to make it more visually distinct (which is where we need the designers in the community), then phase 3 updating the Module and Directory sites to be consistent with all of it. I do like additional graphics like you mentioned with those examples, though I don't think some of those approaches (Ora, Statamic, Laracasts) are practical for us. Someone has to create those illustrations. I'm not an illustrator, and I don't think I can hire one every time I want to write a new blog post, add a new module, or add a new tutorial. So I don't feel like this level of graphics/illustration is practical or realistic for the PW site and we instead have to work with what we've got. The only real dynamic visual elements we have to work with are the screenshots submitted to the sites directory. But what might be practical is to have some visual elements/illustrations in the marketing side of the site, where we won't be constantly needing new graphics every week. But if there are visual elements we can add that really help to communicate the message then that's ideal. This will especially be the case with the homepage, which is something i'm not sure I'll even attempt a layout for, but may need a lot of help when it comes to that.
    2 points
  7. Nice one @horst! This is really great. Browser support (webp) is pretty straightforward easy using <picture> and <source>. Or if you use a library like lazyload it can even switch automatically.
    2 points
  8. And as a 3rd option, use the PHP modulo feature: http://php.net/manual/en/language.operators.arithmetic.php 1. Set a variable, eg $i to 1 2. Iterate through your (Wire)array and add 1 to $i each time 3. At each iteration, check if $i%3 == 0 and if so, add your row html Example below selects 6 random service items and splits into 2 columns x 3 rows. For each item that returns $i%2 == 0 (2nd row item) I add the class "col_last". Add classes, html as required for your frontend theme - guessing it's Bootstrap <?php $services = $pages->get('name=services')->pgtb_services->findRandom(6); $i = 1; foreach ($services as $item) : $colLast = ""; if (($i%2) == 0) $colLast = 'col_last'; $i++; ?> <div class="col_half service <?=$colLast?> nobottommargin"> <article class=" boxFeature"> ... </article> </div> <?php endforeach; ?>
    2 points
  9. @Robin S Thanks for the clarification about 'slices' method. I was on mobile, so couldn't test it till now, but the description from the blog post made me think that it would work as array_chunk. @Michkael sorry for confusing. Obviously, slices method is not the best solution in your case. One more example of implementing a chunk
    2 points
  10. Or rather not cast the version to int for use in the query string. I had to make this change in several of my modules after I switched to semantic version numbers.
    2 points
  11. Ah, a quick test showed that I can use this already with my local dev environment: $cai3 = $page->croppable_images->first()->getCrop('thumb100'); // WebP with GD-lib bundled with PHP 7 $im = imagecreatefromjpeg($cai3->filename); imagewebp($im, str_replace('.jpg', '.webp', $cai3->filename)); imagedestroy($im); ?
    2 points
  12. emplate Field Widths Adds a "Field widths" field to Edit Template that allows you to quickly set the widths of inputfields in the template. Since v0.2.0 the module also adds a similar field to the settings of Edit Field for Repeater, FieldsetPage and Repeater Matrix allowing you to quickly set the widths of inputfields within the Repeater/FieldsetPage field, or within each Repeater Matrix type. Note: widths are only saved if the edit form is submitted with the "Field widths" field in an open (non-collapsed) state. Edit template Edit Field: Repeater Edit Field: Repeater Matrix Why? When setting up a new template/repeater or trying out different field layouts I find it a bit slow and tedious to have to open each field individually in a modal just to set the width. This module speeds up the process. Config options You can set the default presentation of the "Field widths" field to collapsed or open. Widths entered into the "Field widths" field are only applied if the edit form is submitted with the field in an open (non-collapsed) state. "Collapsed" is the recommended setting if you think you might also use core inputs for setting field widths in a template context. You can choose Name or Label as the primary identifier shown for the field. The unchosen alternative will become the title attribute shown on hover. You can choose to show the original field width next to the template context field width. https://github.com/Toutouwai/TemplateFieldWidths https://modules.processwire.com/modules/template-field-widths/
    1 point
  13. The language pack is available at https://modules.processwire.com/modules/german/ or in the github repository https://github.com/jmartsch/pw-lang-de/releases/tag/latest The master branch will (try to) be up to date with the most recent stable version of ProcessWire. The dev branch will (try to) be up to date with the most recent dev version of ProcessWire. If you find any missing translations or errors, please create a PR or submit a bug/improvement. I hope we as a community can work together, to update translatations as soon as a new dev branch is pushed. Please let me know if you want to translate a new dev version, so we are not both doing the same task. If you want to help, you can clone my ProcessWire environment for language packs which provides an easy way for translating a language pack. You simply clone it, make changes to the language in ProcessWire and commit the changes back to your (or the german) language pack repository. This is a boilerplate which could work with any language, but right now it is tailored to the german language pack. Then I am able to quickly release an updated stable language pack when a new ProcessWire stable version is released. Big thanks to @Nico Knoll and @yellowled for their initial work on the translations.
    1 point
  14. Edit: Because of the great response to this topic I wrote a guest blogpost: https://processwire.com/blog/posts/building-custom-admin-pages-with-process-modules/ One of the hidden treasures of processwire seems to be the creation of custom admin pages. Technically speaking those pages are ProcessModules - but i guess that's the reason why so many people out there seem to be afraid of building them... it sounds so hard! You've never created a module for ProcessWire? You have never created a plugin for any other CMS? You have no clue about OOP with all its classes, methods and properties? No problem! I'll show you how simple you can start: <?php class CustomAdminPage extends Process { public static function getModuleinfo() { return [ 'title' => 'Custom Admin Page Example', 'summary' => 'Minimalistic ProcessModule to show that nobody has to be afraid of building custom admin pages.', 'href' => 'https://processwire.com/talk/topic/17709-how-to-create-custom-admin-pages-aka-processmodules-yes-its-that-simple/', 'author' => 'Bernhard Baumrock, baumrock.com', 'version' => 1, // page that you want created to execute this module 'page' => [ 'name' => 'customadmin', // your page will be online at /youradmin/setup/customadmin/ 'parent' => 'setup', 'title' => 'Custom Admin Page Example' ], ]; } public function ___execute() { return 'This is the most simple Admin-Page you have ever seen :)'; } } Now save this file as CustomAdminPage.module and place it in your /site/modules folder. After a refresh it will show your module in the modules manager of your site where you can install it: After installation you already have your first very own admin page! Congratulations! Was not too hard, was it? It's as simple as that! Now lets add some more custom HTML. And to show you another nice feature we will add this code to a separate method called executeDemo(). And because everything is so simple we will also add some javascript to this page public function ___executeDemo() { $out = ''; $out .= '<h1>H1 has some special css styling in the admin, thats why it seems to have no effect</h1>'; $out .= '<h2>H2 looks different ;)</h2>'; $out .= '<h3>...and so does H3</h3>'; $out .= '<button onclick="myFunction()">Click me</button>'; $out .= '<script>function myFunction() { alert("this is a demo javascript"); }</script>'; return $out; return ''; } Now thanks to ProcessWire-magic your page will already have its own URL: Just append /demo to your url and see what you get: And of course don't forget to click the button Ok, now that code looks a bit hacky, right? Inputfields and especially InputfieldMarkup for the win! We add another method with some advanced code. To use inputfields we need a form that holds all those inputfields and that makes it possible to handle user input lateron. See somas great tutorial about forms here for a quickstart and more details: public function ___executeAdvanced() { $out = '<h2>A more complex Example</h2>'; $form = wire()->modules->get('InputfieldForm'); $field = wire()->modules->get('InputfieldMarkup'); $field->label = 'Markup Test 1'; $field->value = '<h1>h1</h1><h2>h2</h2><h3>h3</h3><h4>h4</h4>'; $form->add($field); $out .= $form->render(); return $out; } Ok, it get's boring Let's do something more fun and add a chart in a second field and change the fields to 50% screen width (I'm sure you know that already from the GUI template editor)! public function ___executeAdvanced() { $out = '<h2>A more complex Example</h2>'; $form = wire()->modules->get('InputfieldForm'); $field = wire()->modules->get('InputfieldMarkup'); $field->label = 'Markup Test 1'; $field->value = '<h1>h1</h1><h2>h2</h2><h3>h3</h3><h4>h4</h4>'; $field->columnWidth = 50; $form->add($field); $field = wire()->modules->get('InputfieldMarkup'); $field->label = 'Chart Sample'; $field->value = '$chart'; //$field->notes = 'Example code taken from here: http://www.chartjs.org/docs/latest/getting-started/usage.html'; $field->columnWidth = 50; $form->add($field); $out .= $form->render(); return $out; } OK, we are almost there... we only need to add the chart library! To keep everything clean we will put the code for the chart in another method. We will make that method PRIVATE to add some security. Our new Method: private function renderChart() { // prepare chart code wire()->config->scripts->add('https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.1.3/Chart.min.js'); ob_start(); ?> <canvas id="myChart"></canvas> <script> var ctx = document.getElementById("myChart"); var myChart = new Chart(ctx, { type: 'bar', data: { labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"], datasets: [{ label: '# of Votes', data: [12, 19, 3, 5, 2, 3], backgroundColor: [ 'rgba(255, 99, 132, 0.2)', 'rgba(54, 162, 235, 0.2)', 'rgba(255, 206, 86, 0.2)', 'rgba(75, 192, 192, 0.2)', 'rgba(153, 102, 255, 0.2)', 'rgba(255, 159, 64, 0.2)' ], borderColor: [ 'rgba(255,99,132,1)', 'rgba(54, 162, 235, 1)', 'rgba(255, 206, 86, 1)', 'rgba(75, 192, 192, 1)', 'rgba(153, 102, 255, 1)', 'rgba(255, 159, 64, 1)' ], borderWidth: 1 }] }, options: { scales: { yAxes: [{ ticks: { beginAtZero:true } }] } } }); </script> <?php return ob_get_clean(); } Now we just need to call $this->renderChart() in the right place! Here is the complete Module: <?php class CustomAdminPage extends Process { public static function getModuleinfo() { return [ 'title' => 'Custom Admin Page Example', 'summary' => 'Minimalistic ProcessModule to show that nobody has to be afraid of building custom admin pages.', 'href' => 'https://processwire.com/talk/topic/17709-how-to-create-custom-admin-pages-aka-processmodules-yes-its-that-simple/', 'author' => 'Bernhard Baumrock, baumrock.com', 'version' => 1, // page that you want created to execute this module 'page' => [ 'name' => 'customadmin', // your page will be online at /youradmin/setup/customadmin/ 'parent' => 'setup', 'title' => 'Custom Admin Page Example' ], ]; } public function ___execute() { return 'This is the most simple Admin-Page you have ever seen :)'; } public function ___executeDemo() { $out = ''; $out .= '<h1>H1 has some special css styling in the admin, thats why it seems to have no effect</h1>'; $out .= '<h2>H2 looks different ;)</h2>'; $out .= '<h3>...and so does H3</h3>'; $out .= '<button onclick="myFunction()">Click me</button>'; $out .= '<script>function myFunction() { alert("this is a demo javascript"); }</script>'; return $out; return ''; } public function ___executeAdvanced() { $out = '<h2>A more complex Example</h2>'; $form = wire()->modules->get('InputfieldForm'); $field = wire()->modules->get('InputfieldMarkup'); $field->label = 'Markup Test 1'; $field->value = '<h1>h1</h1><h2>h2</h2><h3>h3</h3><h4>h4</h4>'; $field->columnWidth = 50; $form->add($field); $field = wire()->modules->get('InputfieldMarkup'); $field->label = 'Chart Sample'; $field->value = $this->renderChart(); $field->notes = 'Example code taken from here: http://www.chartjs.org/docs/latest/getting-started/usage.html'; $field->columnWidth = 50; $form->add($field); $out .= $form->render(); return $out; } private function renderChart() { // prepare chart code wire()->config->scripts->add('https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.1.3/Chart.min.js'); ob_start(); ?> <canvas id="myChart"></canvas> <script> var ctx = document.getElementById("myChart"); var myChart = new Chart(ctx, { type: 'bar', data: { labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"], datasets: [{ label: '# of Votes', data: [12, 19, 3, 5, 2, 3], backgroundColor: [ 'rgba(255, 99, 132, 0.2)', 'rgba(54, 162, 235, 0.2)', 'rgba(255, 206, 86, 0.2)', 'rgba(75, 192, 192, 0.2)', 'rgba(153, 102, 255, 0.2)', 'rgba(255, 159, 64, 0.2)' ], borderColor: [ 'rgba(255,99,132,1)', 'rgba(54, 162, 235, 1)', 'rgba(255, 206, 86, 1)', 'rgba(75, 192, 192, 1)', 'rgba(153, 102, 255, 1)', 'rgba(255, 159, 64, 1)' ], borderWidth: 1 }] }, options: { scales: { yAxes: [{ ticks: { beginAtZero:true } }] } } }); </script> <?php return ob_get_clean(); } } I hope you enjoyed reading this and it will open up many new possibilities for you! Updates: permissions: https://processwire.com/talk/topic/17709-how-to-create-custom-admin-pages-aka-processmodules-yes-its-that-simple/?do=findComment&comment=174746 tutorial on file uploads: https://processwire.com/talk/topic/17709-how-to-create-custom-admin-pages-aka-processmodules-yes-its-that-simple/?do=findComment&comment=185261 snippet how to use NavJSON: https://processwire.com/talk/topic/17709-how-to-create-custom-admin-pages-aka-processmodules-yes-its-that-simple/?do=findComment&comment=216412
    1 point
  15. PHP has a useful array_chunk function: it is used to split an array into a number of smaller arrays ('chunks') of a size you specify, which are returned to you in a new array (i.e. an array of arrays). ProcessWire doesn't provide a method for WireArrays that is the equivalent of array_chunk, but we can add a new method for this in hook. In /site/init.php... // Add a new 'chunk' method to WireArray, the equivalent of PHP's array_chunk $wire->addHookMethod('WireArray::chunk', function($event) { $wire_array = $event->object; $size = $event->arguments[0]; if( !((int) $size > 0) ) throw new WireException('WireArray::chunk requires an integer $size argument greater than zero'); $array = array(); $count = count($wire_array); for($n = 0; $n < $count; $n += $size) { $array[] = $wire_array->slice($n, $size); } $event->return = $array; }); Now we can use this new chunk() method on any WireArray to return an array of smaller WireArrays. Remember that many array-like objects in PW are WireArrays, including PageArrays, Pageimages and Pagefiles. An example using a PageArray of 'workshop' pages. We are running a series of workshops and there is only time for four workshops per day, so we want to divide the workshops into groups of no more than four and put each group under a heading... // Get all workshop pages $workshops = $pages->find("template=workshop"); // say this returns 12 pages // Split the workshops into PageArrays of no more than 4 pages each $chunked_workshops = $workshops->chunk(4); // an array of 3 PageArrays of 4 pages each foreach($chunked_workshops as $key => $chunk) { // $key is the zero-based index of the array $num = $key + 1; // Output a heading followed by the workshop links echo "<h3>Day $num</h3>"; echo $chunk->each("<p><a href='{url}'>{title}</a></p>"); // $chunk is a PageArray } Another example, this time using images. Say we want to divide the images into groups of three or less - maybe they are to be arranged into rows or we are giving the groups some special styling. // Say this page's 'images' field holds 8 images // Split the images into Pageimages objects of no more than 3 images each // 8 does not divide evenly by 3 so the last Pagesimages object will contain only 2 images $chunked_images = $page->images->chunk(3); foreach($chunked_images as $chunk) { echo "<div class='image-group'>"; // $chunk is a Pageimages object foreach($chunk as $image) { echo "<img src='{$image->size(300, 300)->url}'>"; } echo "</div>"; }
    1 point
  16. 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
  17. 1) Setup the necessary GIT Repos Fork ProcessWire: https://github.com/processwire/processwire Clone your fork to your local dev environment (I named my folder processwire-fork): git clone git@github.com:BernhardBaumrock/processwire.git . To be able to keep the fork in sync with ProcessWire we need to set the upstream, because currently it only points to the fork: git remote -v // result: origin git@github.com:BernhardBaumrock/processwire.git (fetch) origin git@github.com:BernhardBaumrock/processwire.git (push) git remote add upstream https://github.com/processwire/processwire.git Now fetch the upstream: git fetch upstream As you can see we are still on the master branch (of the fork). We want to work on the dev branch. In VSCode you instantly see all the available branches and which branch you are on: But you can also use the console: git checkout origin/dev Ok, as we did not have a local dev branch yet, we get this warning: So we do what git tells us: git checkout -b dev We are almost ready to go and we can check if it is working by simply modifying the index.php file: Nice! Index.php shows up in the git panel on the left side under "changes" and clicking on it we get a nice diff (if you haven't tried VSCode yet, give it a go and see the corresponding forum thread here). 2) Setup a running ProcessWire instance Now that we have a fresh fork of ProcessWire we want to contribute something to the project. But how? We need to install ProcessWire first to test everything, right? But we don't want to copy over all edited files manually, do we? Symlinks to the rescue. We just install ProcessWire in another folder (in my case I use /www/processwire) and then we replace the /wire folder of the running instance by a symlink to the wire folder inside the fork. Let's try that via CMD: If you have your folders on the C:/ drive you might need to run CMD as admin. We are ready for our first contribution! ? This is how my ProcessWire installation looks like: Note these things: We are inside "processwire", not "processwire-fork" There is no .git folder, meaning this instance is not under version control The wire folder is symlinked. And this folder IS under version control, but in another folder (processwire-fork) If you want to contribute to something outside of the wire folder, just symlink these files as well (eg index.php or .htaccess)... 3) Example PR - Coding You might have seen this discussion about FieldtypeDecimal for ProcessWire: https://github.com/processwire/processwire-requests/issues/126 I thought it would make sense to have at least a comment in FieldtypeFloat that you can run into troubles when using FieldtypeFloat in some situations. I then wanted to make a PR and since I don't do that every day it's always a kind of hurdle. That's also a reason why I created this tutorial, as a side note ? Ok, we want to add some comments to FieldtypeFloat, so we open up the forked repository of ProcessWire in VSCode. This is important! We work on the forked folder "processwire-fork" but we TEST everything in the browser from the test-instance in the folder "processwire". This might be a little confusing at first, but we are not working on a local project, we are working on the core. We see that we are on the fork In the folder is a clone of ProcessWire that is NOT installed yet We are on the DEV branch and this branch is not yet uploaded to our git account (the cloud symbol shows this) Important: Before you start your changes you always need to make sure that you have the latest version of ProcessWire in your folder! This is done by pulling a branch from your upstream. If you want to work on different contributions it might make sense to create a separate branch for each modification. See this example: The commands are: git checkout -b FieldtypeFloat-comments git pull upstream dev Using VSCode it might be easier to create new branches or switch between existing ones: Just saying ? Now the changes: We open FieldtypeFloat.php and add our comments. We instantly see our changes in VSCode: Or if you prefer you can also open the diff: The Result: When we are happy with our changes we can commit them (either using the VSCode UI or the command line): git add . git commit -m "your commit message" 4) Submitting the PR First, we need to upload the new branch to our forked repo: git push -u origin FieldtypeFloat-comments See the first command and the comment what happens if you don't define the remote branch. Now head over to your github account and click to create the PR: Just make sure that you send your pull request to the dev branch! Check if everything is correct: And voilà: https://github.com/processwire/processwire/pull/130 That's how I do it. If you know better ways or have any suggestions for improvements please let me know ? -------------------- update ----------------------- 5) Updating the fork If you already have your setup and just want to grab the latest version of ProcessWire into your fork, this is what you have to do: git fetch --all As you can see it grabs the new dev version from the upstream repo. But if you go back to the running PW site's upgrades page you'll see that you are still on the old version. We need to merge the changes into our branch. But before we do that, we make sure we are on our local dev branch: git checkout dev git merge upstream/dev Now head over to your sites admin and you'll see that we merged the changes: You can now continue by creating a new branch and working on it as shown in section 3.
    1 point
  18. https://www.spiria.com After several sites made with ProcessWire, Spiria decided it was time to get rid of its cumbersome Drupal site. To be honest, ProcessWire is still difficult to sell to customers, because this CMS/CMF is not as well known as the most popular ones. The migration to ProcessWire therefore served several purposes: Eliminate the frustrations experienced with Drupal (especially with image management and some structural problems). Allow integrators to learn the CMS during quiet periods, when they are not needed on other projects. Promote the CMS by adopting it. The challenges were many, but by no means insolvent, thanks to the great versatility of this programming framework. Indeed, if ProcessWire can be considered as a CMS in its own right, it also offers all the advantages of a CMF (Content Management Framework). Unlike other solutions, the programmer is not forced to follow the proposed model and can integrate his ways of doing things. The blog The site includes a very active blog where visuals abound. It was essential to cache the various dynamic components. For example, in all sections of the blog, there is a list of recent articles, a list of "short technical news", another list from the same author, a classification by category. In short, these lists evolve independently. ProcessWire's cache system, including its ability to classify by namespace, has significantly improved loading speed. Cache file management has been placed in a "saved" hook in the useful "ready.php" file. Data migration Importing the blog data was complex because at the time the site was designed in Drupal, programmers had not been used the easily translatable "entities", so each article resided in two different "nodes" (pages). We would have liked to use the core ProcessWire import module, but it does not yet take into account multilingual fields. However, we have used this code as a basis for building our own import module. This is one of ProcessWire's great qualities, as a CMF, it is easy to use existing code to design your own solutions. Reproduce the layout The current layout of the site has been reproduced exactly as it serves the company's needs very well. ProcessWire has simplified the work in many ways. Apart from the blog, which is very structured, the other sections of the site are more free, especially the case study section ("Our Work"). The use of page reference fields has particularly helped developers. As everything is a page in ProcessWire, you can create a pseudo relational database within the site itself. The administrator user becomes more aware of the data hierarchy and has better control over the data. Programming architecture The separation between controllers and Twig visualization files facilitates the management of the multiple components of the site. We haven't really explored the "regions" of ProcessWire, because we prefer not to mix these aspects of programming. This greatly facilitates the timely arrival of programmers in our department, used to an MVC structure, because they have a better understanding of what does what. The Search Once again, we were able to simplify what had been done in Drupal. There are two types of searches on the site, the blog search and the more general search on page 404 ( https://www.spiria.com/potato). The Drupal site search was driven by an Apache Solr server in Drupal. We decided to rely on the ease of ProcessWire and the Typeahead library (for the blog), because we didn't need the power of Solr (or Elasticsearch) anyway. Work to improve performance still needs to be done in this area. We would have liked to have seen the excellent search tool offered on the administrative side available on the frontend. We have not yet had time to explore the possibility of harnessing this code from the core of ProcessWire. Our wish here is that the CMS designer, Ryan Cramer, sees this as an opportunity to offer an exciting new feature to his CMS! Powerful modules We have the excellent modules ProCache (static caching), ProFields (fields that greatly improve the functionality of existing fields) and ListerPro (data search and processing tool). As the site is installed on a nginx server, we have ruled out ProCache for the moment and we are satisfied with the use of the cache() function alone. The ProFields fields are a blessing just like ListerPro. This last module is very useful to correct, for example, import errors (we had more than 800 blog articles, some of which date back to 2013). We used a functional field to gather translations of terms that would normally have remained hard coded and difficult to access in the translation interface (an aspect to be improved in ProcessWire, in our opinion). By grouping translations in a single page, site administrators can easily change or correct terms. Language management What remains a very small irritant for us is the management of languages, which is fantastic in many ways. The fact that there is a default language is both a blessing and a problem. For example, in 2013, blog articles were not systematically translated. We experienced the same situation with a customer's site. If the article is only in English, no problem, we only have to not check French as an active language. However, if the article is only in French, we are still required to create the page in English and make tricks in the code, thanks in particular to a checkbox such as "Not present in English" to reproduce the behaviour naturally present for English (or any language deemed by default). Perhaps there is a more elegant solution here that we have not yet discovered. It's not much, but some clients don't see why there are two ways to do it here. In conclusion In any case, ProcessWire's great qualities continue to appeal to programmers, integrators, graphic designers, users and even our UI/UX expert. The solidity of the CMS/CMF, its functionalities all translated into objects/variables ($pages, $page, $config, $sanitizer, $input... the list is long) allows us to systematize our workflow, easily recover code and reduce production costs. Although it is dangerous to offer only a CMS solution to our customers (hammer syndrome that only sees nails), it is tempting to consider ProcessWire as the Swiss Army knife par excellence of Web programming. As mentioned above, the CMF is suitable for all situations, has very good security tools and its designer has successfully improved PHP methods to make programming very pleasant and intuitive. For us, migrating the company's website to this platform was the best tribute we could pay to its designer, @ryan.
    1 point
  19. This week is the Thanksgiving holidays here in the US and it’s one of those weeks where there’s no school for the kids, so it’s a little hard to get work done. I don’t have any major core updates to report this week, so I’m not going to bump the version number up today. However, look for a new dev branch version next week. We will also release a new master version before the end of the year… sometime within the next month. Before releasing the new master version, I’m primarily interested in resolving any issues present in the current dev branch that are not present on the current master branch. Meaning, issues that have arisen due to some recent change only on the dev branch (if there are any). So if you are aware of any issues experienced on the dev version that are not on the master, please let me know. Thanks for your help in testing. Even though it’s been a vacation week, I’ve been waking up early every morning to work on the new PW website. Lots of continuing progress, and I should have another update on that next week along with a new dev branch version of the core. Thanks for all the feedback from last week’s post. Among other things, I caught that folks don’t like the skyscrapers anymore (as a visual element), so I’ve taken them out and agree it’s better without them. I’ll have some updated screenshots next week. Off topic, but was so excited I had to tell someone. I got a computer upgrade this week after 4 or so years of working off the same laptop. A few keys on my laptop keyboard recently stopped working (letters in the word “ProcessWire” — I wore them out), so I’ve been using an external keyboard plugged in. That’s been working alright, but made it hard to see the screen since I can’t sit as close with an external keyboard in front of the laptop. It was getting a little tiresome to work on, the keyboard wasn't repairable without rebuilding the whole laptop (costly), and it was basically time for an upgrade, but computers are expensive and I was resigned to waiting another year. Over these Thanksgiving holidays I found out a family member had bought an iMac a year or so ago and didn’t like it, so they were going back to a PC. I said, “hey why don’t you sell that iMac to me?” We came to an agreement. Now I've got it here and am moving my development environment over to this newer computer, and have been working off it for a couple of days and loving every minute of it. It's going to help out a lot with developing ProcessWire.
    1 point
  20. An update to the hook in the first post for PW v3.0.117 or greater. // Add a new 'chunk' method to WireArray, the equivalent of PHP's array_chunk $wire->addHookMethod('WireArray::chunk', function(HookEvent $event) { /* @var WireArray $wire_array */ $wire_array = $event->object; $size = $event->arguments(0); if( !((int) $size > 0) ) throw new WireException('WireArray::chunk requires an integer $size argument greater than zero'); $chunks = new WireArray(); for($n = 0; $n < count($wire_array); $n += $size) { $chunks->add($wire_array->slice($n, $size)); } $event->return = $chunks; }); This returns the chunks as a WireArray so you have the option of using WireArray methods. So if needed you could do something like: $items = $pages->find("template=foo"); $chunks = $items->chunk(5); $three_random_chunks = $chunks->find("limit=3, sort=random");
    1 point
  21. Thats one aspect I love about how you handle PW and the community! The feeling of beeing left in the dark about the future is common for other nice projects and fules uncertainty about it. The constant high quality updates an Blogposts makes me very comfortable and trustfully in PW! Thanks so much @ryan!!! I totally agree and 100% understand all your explanations above. My hope is that some talented designers and PW enthusiasts can support you on the design an UX side of things like @teppo constantly supports PW with very high quality content trough weekly.pw I myself are not a designer but would love to help in implementing some of the designs and ideas on the frontend side...
    1 point
  22. @arjen the support via lazyload sounds very interesting. This way we only have to care that both fileformats are available on the server. There is no extra markup needed. Only downside is the missing noscript support. So the usefulness depends on the usecase. ?
    1 point
  23. Thanks for letting me know about this - I didn't test the PR that went into 1.0.1 - it seemed like such a simple regex change ? Anyway, regex is now fixed and I have also removed the ancient and broken CURL code for grabbing the Youtube video title and replaced it with wireHttp()
    1 point
  24. Sorry about that - fixed in the latest version.
    1 point
  25. Such a cool wee surprise, love it.
    1 point
  26. I've updated the module from 1.0.0 to 1.0.1 and it doesn't grab the youtube / vimeo thumbnail anymore. I switched it back to 1.0.0 where it properly works.
    1 point
  27. You can use new slices method for that. Take a look at these part of previous week's blog post http://processwire.com/blog/posts/processwire-3.0.119-and-new-site-updates/#new-wirearray-slices-method
    1 point
  28. It needs a simple fix to properly work. You're using cache busting on js & css files by using the module version. You have to replace module's version to 111 otherwise we get this: ProcessCustomUploadNames.js?v=1 instead of ProcessCustomUploadNames.js?v=111
    1 point
  29. Let's say that you have an image called myimage.jpg and it's accessible via http://localhost/mysite/site/assets/files/1020/myimage.jpg. If you rename or delete that image and users keep visiting that link they will obviously view a 404 error page. Instead of displaying the 404 error page you can redirect them to the file's belonging page by adding the below code on top of your 404.php template: $url = $_SERVER["REQUEST_URI"]; $pattern = "@site/assets/files/(\d+)/@"; if (preg_match($pattern, $url, $pageid)) { $fp = $pages->get($pageid[1]); // get the page id from the file path if($fp instanceof RepeaterPage) $fp = $fp->getForPage(); // if the file is inside repeater item get the page where the repeater belongs if($fp->viewable()) $session->redirect($fp->url, false); // redirect only if the page is viewable // false = 302 temporary redirect - true = 301 permanent redirect }
    1 point
  30. Ah, ok. Seems that I have to clarify a bit: with the age of 10, he started with a desktop pc i5 and 16gb ram. From that on, he is more and more interested in retro machines. I'm not totally sure, but I think currently he owns 4-5 (old) Laptops of different decades, an old IMac, 5 mobiles, 3 tablets and 1 C64. The C64 is the most beloved for him, and he works on different hardware parts. Also he has built different audio adapters for DOS Laptops. Unfortunately the Sound Chip of the C64 seems to be broken. Don't ask me! - he told me something that not all 8 channels are working but only 2, and that it is not possible to detect the concrete issue of it with his limited equipment. So, he is more interested in modifying the hardware then in sitting in front of a screen. From time to time I pray, that the C64 is the stop in his retro interest, not that I someday has to look out for a Z4. ?
    1 point
  31. @horst Niiiice! I've just tried on a live server with PHP 7.2 installed. Your code works great. The Webp image is successfully generated from the original JPEG. Here's how I've echoed it: $main_image = $page->image->first->getCrop('grande'); // (using Horst's Croppable Image module in this example) $image = imagecreatefromjpeg($main_image->filename); imagewebp($image, str_replace('.jpg', '.webp', $main_image->filename)); imagedestroy($image); $webp_image = str_replace('.jpg', '.webp', $main_image->url); echo "<img class='responsive-image' src='$webp_image' alt=''>"; Note: for those using the Plesk control panel, webp support for GD seems to be disabled by default.
    1 point
  32. Thanks bernard, great writeup! In case it helps someone else, here's the guides I use(which are very similar) https://akrabat.com/the-beginners-guide-to-contributing-to-a-github-project/ https://akrabat.com/the-beginners-guide-to-rebasing-your-pr/ - the followup
    1 point
  33. GD supports webp: http://php.net/manual/en/image.installation.php
    1 point
  34. Thx arjen, I like tutorials that show things step by step, including possible warnings, how to read and how to fix them. Shortcuts are nice when you have some experience but they can be a pain in tutorials... Having said that, I also have to admit that I'm still just scratching the surface of GIT and I like to do things step-by-step myself ?
    1 point
  35. Yes, imagemagick supports it: https://imagemagick.org/script/webp.php Thanks @horst!!
    1 point
  36. Some thoughts / infos in no special order: it has to be a module because all our image engines are modules. As long as the GD library does not support webp, it cannot be a core fileformat. I haven't tested anything in regard of webp til now. If imagick supports it as outputformat, I can start testing it as a image engine module that extends the core imagick module to provide something like a options variable to return the final variation image as webp. Earliest start can be around christmas time 2018.
    1 point
  37. The problem I have with static json or yaml setup files is that it's not migration. It's just a static set of fields/templates/… without knowledge of what came before or after. I have almost always user generated data associated to a certain setup. So when changing the setup there's often the need for something to happen with said data as well, like when adding a new intro field for blog posts one might want to put the first paragraph of existing posts from the body into the intro. Craft seems to see their addition just as replacement of sharing db dumps and it's essentially that. If environments never concurrently change in terms of content, but just structure then some version controlled file for the structure is better than a db dump, which contains both structure and content. But as soon as the content is also affected by changes it's no longer a solution.
    1 point
  38. Congrats on the new computer, Ryan. I'm assuming that this is related to moving onto the new dev setup, but it almost calls for one of those "yo dawg, we heard you like ..." meme pics, amirite? ?
    1 point
  39. Some people think that anyone who works in any form of IT is made of money, but sadly it's not always the case. I know the feeling about dying laptops. About a year ago, when it was just over 3 years old, the motherboard on my laptop died. Fortunately I was able to track down a replacement part on Ebay, and after carefully removing a lot of screws, swapping over the CPU and heatsink, and convincing Windows that I wasn't running a pirated copy, I managed to get up and going again. My desktop is a bit long in the tooth too, but since I upgraded the hard drive in my laptop and added an external screen and keyboard, the desktop tends to get less use now, although it was a lifesaver when I needed to get online to order the replacement motherboard for the laptop, and do some work while I waited for the part to arrive.
    1 point
  40. I upgraded my Windows desktop earlier in the year for the first time in 6+ years as the motherboard was finally dying and the speed difference was immense. Now I'm the slowest component in my dev environment by a long shot ?
    1 point
  41. Love my iMac and happy to know you love yours too @ryan. Biggest hurdle for me going from Windows to Mac was - really? it's that easy????
    1 point
  42. Congratulations on the new iMac, I'm happy to hear you love it. And as always: thanks for the update
    1 point
  43. Congrats on the new machine! Anytime I add something great to my workflow, it energizes me.
    1 point
  44. @Robin S and anyone else interested - there have been several small updates over the last week. Some reorganization of the form and some tweaks to the pluralize functionality, including now automatic selection of the page field value type and input field type based on the plurality of the field label. Anyway, please update to the latest and let me know what you think of the new tweaks.
    1 point
  45. Please use the new thread for discussion from now on.
    1 point
  46. Did I already say that I love PW? ? I had to write an offer today and I wanted to link to a portfolio website that I've built, but the link was too long for the PDF. Then I wanted to use one of the url shortening services, but it felt wrong to put a link like bit.ly/409weifsd on my offer. I then just enabled UrlSegments on my website's home.php template, added a repeater with fields "title" and "link" and put 5 lines of code to get my very own custom short-links: $shorturl = $sanitizer->text($input->urlSegment(1)); if($shorturl) { $link = @$page->shorturls->findOne("title=$shorturl")->link; if(!$link) throw new Wire404Exception(); $session->redirect($link); } If you want to try it out: https://www.baumrock.com/pw-demo Any suggestions for improvements are welcome ?
    1 point
  47. I'm still fairly new here having switched to using ProcessWire for pretty much every project (hence the frequent questions ? ) from Concrete5. Concrete5 has had Gutenberg-esque block-based front-end editing for nearly 10 years longer than Wordpress. Although a finished site using C5 can look great for a site editor/frontend-only user with various drag-drop layout tools, we were finding c5 development had become very convoluted and was starting to make simple website projects unnecessarily complicated. C5's core weighs in at a hefty filesize too. This is why we started researching for alternatives and landed happily at ProcessWire. I already find WP development unnecessarily convoluted, especially compared to the simplicity of ProcessWire. And with Gutenberg, I can only foresee the same sort of headaches ahead for the WP community that we were finding with C5 - namely conflicts between blocks and the core and frontend UI and your design style and functionality being dictated to by the CMS in order to work in the Gutenberg features. Discovering ProcessWire has been a revelation for us - the clean API and design agnostic approach are making everything from simple website projects to complex web apps a breeze, with the added bonus of super simple frontend editing that not only wows client's used to site builder platforms but requires basically zero onboarding too. I would urge anyone thinking of building out Gutenberg inspired modules for ProcessWire to consider the above comments to ensure that what makes ProcessWire special is retained.
    1 point
  48. I am glad that this project is helping ProcessWire getting more devs on board :). I just want to say that I wouldn't have been able to finish ProcessVue if it wasn't for the amazing ProcessWire community. I believe that the community truly is the biggest selling point for new users (like me). Before trying ProcessWire I used OctoberCMS for a while but when I was stuck I got 0 support from the forums, so...althought the CMS is based on the amazing Laravel framework, I just left! I think that ProcessWire is extremely powerful and flexible and with time will become the tool of choice for frontend developers, the new GraphQL module will also help on this direction. Droves of frontend developers are looking for a CMS like this, they just don't know it exists! The usual keywords they use when looking for a SPAs CMS is "Decoupled CMS" or "Headless CMS", and I believe that that's exactly what ProcessWire is for! Some frontend developers prefer to use NodeJS, but the learning curve is huge if you need it for a non trivial project, and the worst thing of all is that after two weeks ANY js tool you may have used is outdated. See for example how Angular has been replaced with React or Vue, and Gulp with Webpack. That doesn't mean that I am against improvements in this regard, I just feel that it's just too much for us poor frontend devs to cope with! ProcessWire is stable, easy to use and won't change API every week. BTW, after that I migrate ProcessVue to GraphQL I am also planning to add Auth0 login integration with JWT, as I think that login/signup is a common feature in SPAs. I am sure I'll have to annoy @Nurguly Ashyrov and the rest of ProcessWire community for getting it in sync with ProcessWire users, but the result should be quite useful
    1 point
×
×
  • Create New...