Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation on 10/24/2018 in all areas

  1. 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 ); } }
    6 points
  2. A little self promotion: I just added "date" and "time" subfields to my DatetimeAdvanced replacement for PW's core Datetime field. That lets you search like this: $today = date('y-m-d'); $result = $users->find("birth_date.date=$today"); Also works for in-memory (i.e. PageArray) searches.
    4 points
  3. I agree that would be nice - perhaps in a future version and maybe using @tpr's FilterBox Utility - for now, the best option is browsing and Toggle All and then CTRL+F to use your browser find in page functionality. The first version of the API Explorer is now available and includes some config settings for determining what is shown in the tables for each object/class's method. The default is to show the "description" column and not to have the full doc comment block toggleable. This results in much smaller sizes for these panels and of course if you have your code editor links configured properly you can always click the line number to go to the method and read the doc comment that way. Also, since the previews posted yesterday, the API Explorer now also includes Core Classes (as well as API variables) so you have access to things like wireArray and Pageimage methods. It also now displays all properties for each object/class which is especially useful for $page Please take this for a spin and give me your feedback. I will most certainly be tweaking it myself lots over the next few days, but I'd still love your inout to guide my decisions.
    3 points
  4. It seems that you have $config->pagefileSecure = true; $config->pagefileSecurePathPrefix = '-'; in your config, is that correct? I had the same and to fix it you have to use the disk-filepath instead of an URL because the URL is protected via .htaccess. So I used $page->getUnformatted($fieldName)->first()->filename instead. Sorry, I did not read your comment correctly. If you use images in a textarea I think you could replace the relative image url with a textformatter.
    2 points
  5. @bernhard I've finally had a chance to try out your module tonight for a project where we're loading pages into a large data table (200+ rows) and were hitting a wall. Using RockFinder I now have the initial page load down to ~2 seconds, down from ~7+ seconds! This is a fantastic module, Bernhard. It looks like it's really well thought out and has some really powerful features for building queries. I love how it extends PW's native selectors and allows you to return standard objects, making it easier to substitute this in for a regular $pages->find. Thank you for making this! I think I can answer my own question now... The main issue with creating Page objects is that page instantiation requires a trip back to the database. The initial $pages->find converts a selector into a SQL query which returns an array of matching page IDs. Then those IDs are used to go back to the database and get the pages (or pull them from cache if they're already loaded). Then for any page field requested that isn't auto-join, an additional database query is required. If you're looping through a lot of pages, that's a lot of DB queries! It seems like there might be a way to provide the functionality of RockFinder in the native PW core, as an option when loading pages. You would still end up with Page objects in the end (which in my case would be a huge boon since I like to extend the Page class with a custom class and methods for each template), but we could skip that second trip to the database (getting pages by IDs) if we could just tell PW which fields we wanted it to get up front. After that, any additional fields we didn't specify could be loaded with another trip to the DB, as they are now. That being said, I'm sure @ryan has a good reason for that second trip to the DB. But it seems like there must be a way that we could improve the speed of native pages, even if it is a hidden/advanced option with some caveats. One minor complaint: I noticed is that the module seems to fail silently and return nothing when it can't find one of the fields. It would be good to throw an exception to make this easier to debug. Edit: Another thought... Is there a reason not to use WireData and WireArray for the objects returned from RockFinder, in place of an StdObject? This would allow you to use WireArray's built in sorting and selecting features on the result set: $results = $this->database->query($this->getSql()); if($array) { $objects = $results->fetchAll(\PDO::FETCH_ASSOC); } else { $objects = $results->fetchAll(\PDO::FETCH_CLASS, '\ProcessWire\WireData'); $objects = (new WireArray())->import($objects); }
    2 points
  6. Just use `<?php foreach(): ?>` and `<?php endforeach; ?>` - I very much doubt hosting companies will remove support for that. I wouldn't bother using a Template Language. It's just overhead that's not often worth it.
    2 points
  7. Recently introduced, unintentional bug ? I have just committed a fix, although no version number bump just yet because I am working on other changes.
    1 point
  8. Hi Adrian - is the gap between the dump heading and the dump output deliberate? I think it would look better without a gap.
    1 point
  9. I think this is something that @tpr needs to take care of in AOS, but if someone know better, please correct me. I actually think I am OK with the horizontal scroll in this case. I'd rather be able to read it all and I don't want to go with line break either. If others think this is annoying, please let me know and I'll reconsider. Over the last few years I have come to really dislike forced new window/tab links unless there is a chance of data loss which I guess in this situation is possible if you're editing a page at the time, so I have introduced a config option under the Misc section to let you guys decide. This is the same as the option for the PW Info panel. I have also added it to the Captain Hook panel as well. So guys can choose what you prefer. I have added this to the latest version - also added to the Captain Hook panel as well. Sorry, I don't get what you are talking about It's a link to automatically insert that code into the Console panel so you can easily dump it to get the value. Maybe it will make more sense when it's done ?
    1 point
  10. It's working for me here... <?php namespace ProcessWire; class TestModule extends WireData implements Module { public static function getModuleInfo() { return array( 'title' => "Test Module", 'version' => 1, 'autoload' => true, ); } public function init() { $this->addHookAfter('Pagefile::url', function(HookEvent $event) { if(!$event->object instanceof Pagefile) { return; } $file = $event->object; $fileBasename = $file->basename; $event->return = $fileBasename . '?lorem=ipsum'; }); $this->pages->addHookAfter('save', function(HookEvent $event) { $page = $event->arguments(0); if($page->hasField('image')) { // Output formatting will probably be off, but get unformatted value to be sure $pageimages = $page->getUnformatted('image'); // Get first image URL $url = $pageimages->first()->url; // Dump URL with Tracy Debugger bd($url, 'url'); } }); } } You shouldn't use $this as the second argument to addHookAfter() if your hook function is a closure, but I'm guessing that is just an error in the demo code you posted and not in your actual module. Your issue might be due to output formatting being off in the Pages::save() hook which means an Images field will return a Pageimages object which doesn't have a URL property. So if the field holds only a single Pageimage you would get that with first().
    1 point
  11. https://github.com/processwire/processwire/blob/master/wire/config.php#L777 https://github.com/processwire/processwire/blob/master/wire/config.php#L788 But I think that the best route is to update your URLs and make redirects in Jumplinks.
    1 point
  12. You're right... I was not counting some of the pages involved. There are at least 2 to 3 times that many. Also, I am counting the entire time from request to first response (not using a debug timer)
    1 point
  13. How about adding a full screen button to the API Explorer panel? Like it is with the Console panel.
    1 point
  14. Thank you for building the API Explorer. I just checked it out and looks very helpful indeed. Clicking the line number even opens the file directly in my VSCode editor. Nice! This sounds good to me.
    1 point
  15. Thx adrian, I still have the bug on the AOS language switcher z-index. Any news on this? On the config var the description column is very large, leading to a horizontal scrollbar. Maybe it would make sense to use text-overflow: ellipsis; here? Is it intended that the links to the api ref opens in the same window? I know we can middle-click, but I wonder if it was better to open it in a new tab by default? Sorry, I don't get what you are talking about ?
    1 point
  16. @Ralf Which PHP version are you using? "self::new" is a syntax that threw an error prior to PHP 7. Since PW minimum requirements is PHP 5.3 it is a bug introduced in version 3.0.117. Like @adrian said, reverting to 3.0.116 or upgrading to PHP >=7.0 should fix it. Anyway, I would also like to use this comment to continue a discussion that started in the comments sections of the blog post about the newly introduced WireArray::new() static method. I posted a pretty long comment about that which never got published. (As a side node, that is highly frustrating, taking into account I took quite some time researching background information to the comment I made. So here is going to be a shorter version of that post) The discussion started with @teppo and @bernhard asking, why a new concept of WireArray::new() was introduced to initialise a new WireArray with data. More intuitive would be if you could just initialise it using new WireArray() and pass your data to the constructor. That's how it works for most other objects in ProcessWire too. To that @ryan replied: Now, I do understand why this is necessary, but just like Teppo and Bernhard I'm not 100% happy with it. It seems like a small thing we shouldn't care too much about, but I came to love ProcessWire because I usually do things I learned them using plain PHP and most of the time they just work the way I expect it. This new WireArray::new syntax is something I would intuitevly do wrong, then wonder why it doesn't work, then search the documentation, and finally ask myself, why I can't just use new WireArray(). And that's a workflow that's slowing me as a developer down, a workflow I'm accustomed to from Wordpress, not from ProcessWire. So although it doesn't seem like a big thing, is there a chance we could make new WireArray() work for initialising WireArray objects with data? As far as I could see there are four classes derived from WireArray and implementing their own constructor. Would it be possible to refactor those?
    1 point
  17. Thanks for this, very cool! When you toggle all and then try to collapse one object/class by clicking on the down arrow, the first click doesn't collapse, had to click it again. Nothing to do with Tracy, but I noticed that some paths in $config are different, like $config->paths: ProcessWire\Paths #3457 _root protected => "C:/inetpub/wwwroot/" (19) data protected => array (206) wire => "wire/" (5) site => "site/" (5) modules => "wire/modules/" (13) ... fieldTemplates => "site/templates/fields/" (22) adminTemplates => "C:/inetpub/wwwroot/wire/modules/AdminTheme/AdminThemeDefault/" (61) sessions => "C:/inetpub/wwwroot/site/assets/sessions/" (40) and $config->urls ProcessWire\Paths #cc50 _root protected => "/" data protected => array (206) wire => "wire/" (5) site => "site/" (5) modules => "wire/modules/" (13) ... adminTemplates => "/wire/modules/AdminTheme/AdminThemeDefault/" (43) AdminThemeDefault => "wire/modules/AdminTheme/AdminThemeDefault/" (42) (adminTemplates has the slash at the beginning). Is this how it should be?
    1 point
  18. Thanks to everyone who was helping me the other night. A little update on what I ended up doing with my helper module so far.... For the site settings, I ended up just making a configurable module with only one field containing a load of defaults stored as a simple JSON array. I thought this would be far easier for my specific purpose as it means that it's easily customisable with new data on a site-by-site basis without having to update the module interface or add new fields. I've hooked the $page object to access this data on the front end, so I can now do <?=$page->siteSettings['company_name'];?> etc in my templates which is cool. I've also set some dependencies in the config for the modules we regularly use. I have also used a hook to inject an overlay 'edit mode' icon that displays on the front end whenever an editor is logged into the site, as we've found some people don't realise when they're logged in and can use the front-end editing on their site. The little cog icon also links back to the PW dashboard. Really starting to get the hang of how the PW modules work now, so will be adding in lots more useful stuff we tend to use for every project in due course. ?
    1 point
  19. @adrian Yes, this module works flawless, and I had an error in my own hook, so there IS a way to hide the "page" navigation item already. Don't know, why it worked before, and now it did not. Also I don't know why I did not tried this module, because I was aware of it. However, I still think my pull-request is a valid option, for changing the navigation.
    1 point
  20. @adrian Thank you for the latest additions and your continous great work on this essential module. I can't wait for the API explorer panel. This is great and would save me a lot of time going to ProcessWires API reference page and look for what I am searching. One thing that would be extremely useful would be a find-as-you-type-filter, to find what you are looking for quickly, similar to AdminOnSteroids filter function for AdminDataTables or modules.
    1 point
  21. You are getting into a looping issue because you are hooking into every page saved. I also think you have to hook into Pages which then has the need info in its arguments. wire()->addHookAfter('Pages::saved', function($event) { $page = $event->arguments(0); if($page->template == "user"){ $savedUser = $page; $userpage = wire('pages')->find("parent=Abos, name=".$savedUser->name); if ( count($userpage) == 0 ) { $page = new Page(); $page->template = 'Abos'; $page->parent = wire('pages')->get('/Abos'); $page->title = $savedUser->name; $page->save(); } } });
    1 point
  22. I can see it on my install (although I installed it before upgrading to PW 3.0.117). What Theme are you using?
    1 point
  23. Hi everyone, @dab has kindly sponsored support for multi-language subjects and content which I have just added to v1.3.0 Here is my test email content which will hopefully show you how it works. Subject English Subject ==#es== Spanish Subject ==#fr== French Subject Body English body ==#es== Spanish body ==#fr== French body ==sidebar== Sidebar english ==sidebar==#es== Sidebar spanish ==sidebar==#fr== Sidebar french There is also a new "Auto Activate Languages" checkbox that you'll probably want to check in the module config settings. Please let me know if you have any problems with this new functionality. Cheers, Adrian
    1 point
  24. Thanks! That sounds good and is what I was hoping for ?. I wasn't sure if there are any limits when it comes to the API features if I skip the module and just create a class. BTW … what do you mean by installability? Just being able to install something isn't worth anything on its own, is it? Do I miss something here?
    1 point
  25. Why is JSON bad? It doesn't get much more convenient than PWs way of translating strings. You edit them right inside PW, under Setup > Languages > click on a language, then choose the language, and select a JSON and hit "edit" - you'll see text input fields for each string. No need to directly touch and edit the JSON file with a text editor.
    1 point
  26. You can also use the following code in your site/ready.php: <?php if($page->template->hasField('name-of-your-ckeditor-field')){ $string = $page->name-of-your-ckeditor-field; //create string replace for blockquote $string = str_replace('<blockquote>', '<blockquote class="blockquote">', $string); //here you can add further manipulations.... //.............. //finally set the value back with manipulations $page->name-of-your-ckeditor-field = $string; } Replace "name-of-your-ckeditor-field" with the name of your field. In this case you can use the default blockquote button from CKEditor. Best regards
    1 point
×
×
  • Create New...