Leaderboard
Popular Content
Showing content with the highest reputation on 09/12/2023 in all areas
-
Hi, What's it all about I'd like to share some thoughts and approaches i took to meet the customers requirements when i built the website. Maybe someone has (or will have) similiar needs and can benefit from it, hopefully. I will focus mostly on technical stuff. And because of that i am not sure, if the showcase section is the place to post it (?). Its more like a testimonial. About the organisation/website https://vdp-ev.de (german language only) The Verband Deutscher Puppentheater e.V. (VDP) is the cultural policy respresentation for professional puppet stages in Germany. The VDP gives advice, shares knowledge and represents currently 140 member stages. They publish a magazine frequently, giving awards, workshops and organize events for their members. As the VDP was founded many years ago, they have lots and lots of contents, each with individual structural needs . So the first goal was to pack the vast of content in a good structure and make sure visitors has access to it as straight as possible. Therefor the grafical presentation is rather plain, not really stand out or comes with fancy stuff ?. Some random screens of the public website: Some random screens of the (secured) members marketplace (explanation s. below): Seeing no way to set skip links, here is a short plain TOC: 1. Building a marketplace for VDP members 1.1 Requirements and conceptual thoughts 1.2 Building the menu 1.3 Keep it maintainable 2. Publish selected members content on the public website 2.1 Clients request 2.2 Starting, first approach 2.3 Solution 1. Building a marketplace for VDP members 1.1 Requirements and conceptual thoughts Before the relaunch, members (they have a special user role) yet could login into the backend and edit their contents (data for their stage + add/edit productions). With help of @adrians AdminRestrictBranch they only see their own stage and productions. Those users have no access to other menu trees. With the relaunch the VDP wants to have something different/more elaborated: A secured member area (e.g. marketplace) where all members can share and swap ideas, informations, leading discussion related to their profession, offer or search for profession related material (like puppets, technical gear, services etc.) and some more. Just like some kind of a simple forum (with maintaining their own entries, s. below), but without the overhead of a standalone forum software. There was a (only grafical) approach how to do this by the former developer. His idea was to establish a secured member frontend where the loggedin user have (frontend) presentations to manage their own marketplace content, their stage and productions and even their profile a least. Well, some kind of reinventing the backend... Beside the fixed budged, i thought of a better way to do this. First, i wanted to seperate the marketplace output from the input strictly. Without real advantage member users shouldn't be forced to learn something they're already familiar with (Backend login and add/edit their stage and productions). Second, all marketplace content should live within the PW ecosystem, e.g. are template base pages that lives in the page tree. At least for administrators to see/edit... (s. below also). So, where to establish the CRUD actions for the members content of the marketplace section for each member user (with that special user role)? It all leads to adapt the PW Backend Menu. The Module CustomAdminMenus by @Robin S seems to give a good start for this. The setup for static pages is rather simple. But for my use case i had to deal with a much more dynamic approach. Luckily the module is hookable... I wanted to have two Menus: Mein Markplatz which displays all CRUD actions for the sections (Figuren, Bühne, Material, Tipps & Tricks a.s.o., s. image above) and Zum Markplatz Portal which links to the members marketplace overview page. 1.2 Building the menu First we get the current user content, populate the dynamic menu and build two menus at least. /** * Build Marketplace Admin Menu * * Backend interface for vdp members to add/edit pages marketplace * sections. * * Each member only will see entries that they created. */ $wire->addHookAfter('CustomAdminMenus::getMenuChildren', function(HookEvent $event) { // The menu number is the first argument $menu_number = $event->arguments(0); // Handle Menu 1 if($menu_number === 1) { // Get the current user $user = $event->wire()->user; // Hide admin menu except for vdp members if ($user->isSuperuser() || !$user->hasRole('theater')) { // Do not show the menu $event->return = false; } else { // init $hints_children = []; $offers_children = []; $blog_children = []; $discussions_children = []; // Get all hints for current user $hints = $event->wire()->pages->findRaw("template=members-marketplace-hint, created_users_id=$user.id", ['title', 'url', 'id']); // Allow to add a new hint // Adapt values to fit your environment $hints_children = [ [ 'icon' => 'plus-circle', 'label' => 'Neuen Eintrag anlegen', 'url' => $event->wire()->config->urls->root.'vdpadm/page/add/?parent_id=4232', 'newtab' => false, ] ]; // Build from results // Adapt values to fit your environment foreach($hints as $hint) { $hints_children[] = [ 'icon' => 'file-text-o', 'label' => $hint['title'], 'url' => $event->wire()->config->urls->root.'vdpadm/page/edit/?id='.$hint['id'], 'newtab' => false, ]; } // ... repeat for other marketplace sections ... // Build the menu $menu1 = [ [ 'icon' => 'thumbs-up', 'label' => 'Tipps und Tricks', 'url' => '', 'newtab' => false, 'children' => $hints_children, ] // ... repeat for other marketplace sections ..., don't forget the `,` ;-) ]; // Return the menu $event->return = $menu1; } } // Handle Menu 2 (Link to marketplace portal) if($menu_number === 2) { // Get the current user $user = $event->wire()->user; // Hide admin menu except for vdp members if ($user->isSuperuser() || !$user->hasRole('theater')) { // Do not show the menu $event->return = false; } else { $menu2 = []; // Return the menu $event->return = $menu2; } } }); FYI: Code is part of a ready.php file which lives in /site (s. also https://processwire.com/docs/modules/hooks/) After that the PW top header looks like this: If you click on Mein Markplatz, you get the verbose list*. *) The member user for the demo has no existing entries, so you see none (only the link to add a new entry). 1.3 Keep it maintainable As an administrator i must be able to identify which entry is related to which member. So i prepend the user name to pages names programmatically. This is done by a little helper hook. All relevant templates has a hidden field named `creator`, which gets populated with the user name on page save. To avoid overriding the value when a adminstrator edits an entry, its packed in a condition. Very important, because otherwise the assignment (entry <> member) gets lost! All entries edited by a administrator would have a new owner ?. Definitely not what we want... Btw. the user role `theater` in this example represents the special role for members mentioned above. /** * Utility hook for `Build Marketplace Admin Menu` * * Field `creator` is a hidden page field, that is populated * on page save with the username of the created user. Only related * to members marketplace pages. * * Prevent superuser to change `created user` while edit/save * the page for administration purposes. * * We need this hook on page save, because we want to show * the username within the admin page sidebar list ($page * only provide the ID of the created user). */ if (!$user->isSuperuser() && $user->hasRole('theater')) { $wire->addHookBefore('Pages::saved', function($event){ $page = $event->arguments(0); if($page->hasField("creator")) { // Get the current user $user = $event->wire()->user; // Populate template field for use in the admin sidebar list $page->creator = $user->name; // Save back to $page $page->save('creator'); } }); } FYI: Code is part of a ready.php file which lives in /site (s. also https://processwire.com/docs/modules/hooks/) The complete ready.php vdp-marketplace-demo-ready.php So this is it, mainly. As there are two ways to go after a successful login (Backend view to edit/add content or Frontend view of the members marketplace overview page) the members must decide where they want to go first. Btw, after the user is logged in, the navigation changes within the member frontend scope. So the user can switch between frontend and backend view each with one single click. 2. Publish selected members content on the public website 2.1 Clients request After the relaunch was online, the customer had another request: They wanted to let members decide if their entries (of particular sections of the member marketplace) should also be integrated/viewable on the public website. Visually identical to the views within the member marketplace. Those (public) pages should be available in the navigation and also be indexable by search engines. While this seems not quite a too difficult task at first glance, it turns out to be a bit more tricky. The reason are the restrictions of page paths when dealing with the (secured)members and public scope. Note: All sections in the members marketplace are applied as blog pages, e.g. each has a parent page and associated children. 2.2 Starting, first approach First, i add a conditional checkbox to all related marketplace templates, which was needed in any case. But than it gets a bit annoying. Having kind of a (public) placeholder template (and page based on it) and then iterating over the member blog entries was a dead end. The `children->url` path of course leads to the member section (at that time i wrote a post about this because i hoped this could be solved somehow) As i have to have a real template/page(s) for this (s. 2.1 why), it isn't possible to hook paths here. I realized, i had to find a complete other approach... 2.3 Solution Finally i set up clones for all relevant members section templates (e.g. parent + child template), built public available pages from it and mirror allowed* member entries to the appropriate public pages on a regular base. To avoid disruption, those public pages are readonly for editors. *) Entries with a particular option set (e.g. Beitrag auch auf der öffentlichen VDP Website anzeigen?, s. picture above) The synchronisation between the members area and the public clones is done by cronjobs for each section. Example sync file for one section (VDP Blog): <?php namespace ProcessWire; /** * Synchronize members < > public blog pages * * This file is meant to get executed by a cronjob * * 1. Synchronize (add, update, delete) * 2. Synchronize (delete) * 3. Log, Mail */ // bootstrap processwire, adapt to your path include("../index.php"); // init $d = array(); $o = array(); $totalcountUpdate = ''; $totalcountNew = ''; $totalcountDeleted = ''; // Get member blog items $members_marketplace_blog_items = $pages->find('template=members-marketplace-blog-item'); // Iterate over member blog items, save `name` foreach($members_marketplace_blog_items as $item) { $d[] = $item->name; } reset($d); /** * 1. Synchronize (add, update, delete) * * Perfom synchronisation based on existing member blog items */ // Iterate over members blog items for ($i=0; $i < count($d); $i++ ) { // Get member blog item by given name from the `d` array. $members_blog_item = $pages->findOne("template=members-marketplace-blog-item, name=$d[$i]"); // Get the related public clone (if exists) $public_blog_item = $pages->findOne("template=blog-vdp-item, name=$members_blog_item->name"); /** * Conditional actions * * 1. if * Update present page * * Verbose: Is yet present in public section && is * allowed to be displayed * * 2. elseif * Add new page * * Verbose: Is not present in public section && is * allowed to be displayed * * 3. elseif * Delete page * * Verbose: Is yet present in public section && is * not allowed to be displayed */ if ($public_blog_item->name == $members_blog_item->name && $members_blog_item->options_members_marketplace_entry_publish_to_public == 1) { $public_blog_item->title = $members_blog_item->title; $public_blog_item->name = $members_blog_item->name; $public_blog_item->headline = $members_blog_item->headline; $public_blog_item->blog_date = $members_blog_item->blog_date; $public_blog_item->page_blog_tags_vdp_blog = $members_blog_item->page_blog_tags_vdp_blog; $public_blog_item->body = $members_blog_item->body; // Save page to get an ID for adding images, s.below $public_blog_item->save(); // Make sure we process an array $public_blog_item->of(false); // Delete images first to avoid duplication $public_blog_item->images_members_marketplace_entry->deleteAll(); // Add images foreach ($members_blog_item->images_members_marketplace_entry as $img) { $public_blog_item->images_members_marketplace_entry->add($img); } // Save page again (just for the sake of it) $public_blog_item->save(); // Save iteration for mail, s. below $totalcountUpdate = $i; } elseif ($public_blog_item->name != $members_blog_item->name && $members_blog_item->options_members_marketplace_entry_publish_to_public == 1) { // Add new page $public_blog_item_add = new Page(); $public_blog_item_add->template = 'blog-vdp-item'; $public_blog_item_add->parent = '/wissenswertes-ueber-puppentheater/vdp-blog/'; $public_blog_item_add->title = $members_blog_item->title; $public_blog_item_add->name = $members_blog_item->name; $public_blog_item_add->headline = $members_blog_item->headline; $public_blog_item_add->blog_date = $members_blog_item->blog_date; $public_blog_item_add->page_blog_tags_vdp_blog = $members_blog_item->page_blog_tags_vdp_blog; $public_blog_item_add->body = $members_blog_item->body; // Save page to get an ID for adding images, s.below $public_blog_item_add->save(); // Make sure we process an array $public_blog_item_add->of(false); // Add images foreach ($members_blog_item->images_members_marketplace_entry as $img) { $public_blog_item_add->images_members_marketplace_entry->add($img); } // Save page again (just for the sake of it) $public_blog_item_add->save(); // Save itereation for mail, s. below $totalcountNew = $i; } elseif ($public_blog_item->name == $members_blog_item->name && $members_blog_item->options_members_marketplace_entry_publish_to_public == 0) { // Delete page $public_blog_item->delete(); } } /** * 2. Synchronize (delete) * * Delete outdated public blog item pages, e.g. do not * exists in the member section any longer. */ // Get all member blog items $_members_marketplace_blog_items = $pages->find('template=members-marketplace-blog-item'); // Iterate over member blog items, save `name` foreach($_members_marketplace_blog_items as $_member_item) { $o[] = $_member_item->name; } reset($o); // Get all public blog items $_public_blog_items = $pages->find("template=blog-vdp-item"); // Delete public blog item if not found in members array. foreach ($_public_blog_items as $_public_blog_item) { if(!in_array($_public_blog_item->name, $o)) { $_public_blog_item->delete(); $totalcountDeleted++; } } /** * 2. Log, Mail (delete) * * Log results in PW and send status mail */ // $log->save("cron", "Update public blog posts - New: $totalcountNew, Updated: $totalcountUpdate, Deleted: $totalcountDeleted"); // $emailTo = '<your-email-address>'; // $emailFrom = '<your-email-address>'; // $emailSubject = "VDP Blog | Cron | balance public to member sections "; // $header = ''; // $header .= "From: ".$emailFrom. "\r\n"; // $header .= "X-Mailer: PHP/" . phpversion(); // $message = ''; // $message .= "Am: ".date('d.m.Y H:i')."\n"; // $message .= "Aktualisierte Blog Beiträge: ".$totalcountUpdate."\n"; // $message .= "Hinzugefügte Blog Beiträge: ".$totalcountNew."\n"; // $message .= "Gelöschte Blog Beiträge: ".$totalcountDeleted."\n"; // mail($emailTo, $emailSubject, $message, $header); die(); ?> FYI: This example codefile can live anywhere, just adapt the path where your PW instance is located Example cronjob sync file: vdp-cronjob-sync-blog.php.zip Have fun ?2 points
-
2nd time this has happened this year, 2nd time I've been brought in to fix it. Two different websites, two different clients of a small marketing business I subcontract for. Directly contributing to loss of revenue due to online ordering being down. Currently managing ~4 WP websites for clients, they're all not a fan. Currently negotiating a contract for a website and an employee at the company said "I f****ing hate WordPress". Feel free to spread the WP love here <31 point
-
Thanks to @Macrura for the idea behind this module. Page Field Info Adds information about options in Page Reference fields. Supports InputfieldSelect and inputfields that extend InputfieldSelect: InputfieldSelect InputfieldRadios InputfieldSelectMultiple InputfieldCheckboxes InputfieldAsmSelect Requires ProcessWire >= 3.0.61 and AdminThemeUikit. Screenshots Field config Example of changes to inputfield Example of info field filled out in Page Edit Installation Install the Page Field Info module. Configuration In the Input tab of the settings for a Page Reference field... Tick the "Add info tooltips to options" checkbox to enable tooltips for the options in the field. Tooltips are not possible for Select or AsmSelect inputfield types so for those types you would want to tick the next option. Tick the "Append info about selected options" checkbox to append information about the selected options to the bottom of the inputfield. If the Page Reference field is a "multiple pages" field then the info for each selected option will be prefixed with the option label (so the user will know what option each line of info relates to). In the "Info field" dropdown select a text field that will contain information about the page, to be used in the tooltips and appended info. Of course this field should be in the template(s) of the selectable pages for the Page Reference field. Hook In most cases the "Info field" will supply the text for the tooltips and appended info, but for advanced usages you can hookPageFieldInfo::getPageInfo() to return the text. For example: $wire->addHookAfter('PageFieldInfo::getPageInfo', function(HookEvent $event) { $page = $event->arguments(0); // The page $inputfield = $event->arguments(1); // InputfieldPage $field = $event->arguments(2); // The Page Reference field $info = $event->return; // Text from the info field, if any // Set some custom text as the $event->return... }); https://github.com/Toutouwai/PageFieldInfo https://modules.processwire.com/modules/page-field-info/1 point
-
There wasn’t so much nesting in my case but I did have an issue I resolved using $pages->parents()->rebuildAll(). Maybe try this first? Otherwise I think you’d need to be more specific regarding your setup.1 point
-
The httpUrl property/method will provide you with what you want, whether referring to a Page, PageImage, or PageFile object. The browser should the full URL when you hover in the status bar area simply because that's what it does, but you are correct in that you need to view source to verify what's being output in code. Typically you don't actually want to hardcode the full URL into the rendered code as it's a bit slower for the client (web visitor's browser) to negotiate the resource. If they're already on your domain, you just refer to the relative path of objects/pages/files on your domain; it's a tiny bit more efficient, and as you've already noticed, the browser automatically identifies/assumes the correct location anyway. If you're running a single website on multiple domains but want to redirect visitors to a specific domain, you could use redirects on the server instead of hardcoded domain-based links, but that doesn't mean you can't do what you're doing either. As for SEO benefits or detriments to this, aside from the speed hit, I'm not sure if there are any. If you plan to generate JSON-LD, Canonical URLs, XML Sitemaps, PDFs, or other potentially downloadable files from your content, that would be one area that could benefit from absolute URLs if you didn't want to do extra work, later on, to adjust the URLs on export. Ultimately though the choice is yours depending on context, needs, and desires - and ProcessWire provides you with that choice! $page->url or $page->httpUrl. To get the current website domain, you'd likely want either $input->httpHostUrl();, or $pages->get(1)->httpUrl; (or $pages->get('/')->httpUrl; they'll both retrieve the home page's absolute path; the home page in ProcessWire always has an ID of 1). Take a gander at the searchable ProcessWire API. For the above, you could find them by searching, "host" (without quotes). For identifying other paths, run a PHP print_r() on $config->urls. ? For reference if anyone else is reading this over and needs it, here's a simplified breakdown of relative vs absolute paths in URLs: https://www.conductor.com/academy/urls/faq/absolute-vs-relative/1 point
-
Hey @Robin S - this has bugged me for quite some and I did ask about it some time ago: https://github.com/nette/tracy/issues/407 It's basically due to htaccess's rules 16 & 17 in combination with the way the Tracy core loads itself at the base url rather than from the root url, eg, it loads: http://mywebsite.com/doesn't-exist/?_tracy_bar=js&v=2.10.3&XDEBUG_SESSION_STOP=1 rather than: http://mywebsite.com/?_tracy_bar=js&v=2.10.3&XDEBUG_SESSION_STOP=1 I have been playing around with fixing that. It seems like everything is working, but it's not well tested yet, so I am not sure if there will be any side effects. Would you mind also testing? /site/modules/TracyDebugger/tracy-2.10.x/src/Tracy/Bar/assets/loader.phtml //$baseUrl = $_SERVER['REQUEST_URI'] ?? ''; $baseUrl = $_SERVER['HTTP_HOST'] ?? ''; /site/modules/TracyDebugger/tracy-2.10.x/src/Tracy/Bar/assets/bar.js //baseUrl = location.href.split('#')[0]; baseUrl = location.hostname; Can you think of any reason why this approach would be problematic? If not, I am tempted to reopen that issue and suggest these changes to @dg1 point
-
1 point
-
Just have a look at httpUrl() ? Or should the link be hardcoded?1 point
-
1 point
-
@ryan Can ProCache be refactored in such a way that SCSSPHP and the other compilers are optional extensions, along with the ability to define our own compilers (ie, DartSass and TailwindCSS CLI)? You mentioned something along those lines in a conversation we had in the ProCache forum and I think this would be a step in the right direction given how fast things in frontend web development move.1 point
-
I'm quite sure you did, but just in case: Did you check if there is a plugin that can fix your issue? ?1 point
-
Here's a different site I was called in to fix. The design has been screwed up because it never quite looks right and the non-designer that edited the pages didn't click the buttons that change the editor size to "Mobile", "Tablet", and "Desktop" to make sure it would look correct across all devices. The semantic HTML was destroyed because, you know, the <h*> tags are to control the size of the text, right? It murdered SEO because tags aren't being used correctly. I love ads and update notifications that have existed there for weeks and also appear to be broken. I'm not doing anything about it because I'm not contracted for it and who knows what it will break. I do like the power that this design editor gives non-developers though, when things don't look right they can just tweak all of these numbers. I mean, who doesn't know and understand the CSS box model? Surely users can rattle off the differences between px, %, em, rem, and vw. Anyway. The amount of work it takes to make sure a page doesn't look like a trainwreck is, and I mean this, a valuable use of everyone's time- just stack the time I'm getting paid to fix it on top of that. I love WordPress, I make a lot of money off of it.1 point
-
@csaggo.com I personally add the three classes in my frontend css on every processwire project. The rules are also for the old editor. .align_left { text-align: left; } .align_center { text-align: center; } .align_right { text-align: right; } When i know, there will be a conflict with other align rules, i set also !important. For uikit, you can add this rule in the site.scss, like here explained: https://getuikit.com/docs/sass#how-to-build the "// 1. Your custom variables and variable overwrites." section in the example code.1 point