Leaderboard
Popular Content
Showing content with the highest reputation on 03/31/2023 in all areas
-
There are several updates on the dev branch this week (commit log), including issue fixes, feature additions and minor class improvements. One of the updates I'd planned to add this week was moving InputfieldTinyMCE into the core. However, I noticed that TinyMCE was up to version 6.4.1 now and we were still running 6.2.0, so I decided instead to upgrade ours to the latest and test it out for another week in its own repository. If all continues to work well, I'll likely commit it to the core in 3.0.215. If you have a chance to test the latest version of InputfieldTinyMCE, please do, and open an issue report if you run into any trouble. Last week the Wire Request Blocker module was released in the ProDevTools board and this week we have version 2, which includes several new additions: Added support for blocking groups. Added configurable settings for immediate block (rather than just a strike) for URLs and user agents. Added support for using RequestBlocker in other applications (like we use it here in IP.Board). Added a feature were you can manually test URLs or user agent strings to see how they match your rules. Added a configuration setting so you can choose whether or not to use a log file. Added a section to the docs on how to block URLs from your .htaccess file. As I wrote this post, the processwire.com site is getting hounded with dozens of IPs trying to locate backup or database zip/rar/tar/gz files, using every possible combination of filenames and extensions you can think of, including those that include the term "processwire". Remember to never leave backup files or DB dump files accessible by URL lying around on your server, because they will get eventually found. Adding these rules (below) to WireRequestBlocker's URL matching rules seems to mostly stopped those DB/backup hunting bots: /ba=/backups/|/backup/|/bak/|/back/ .txt=credentials.txt|backup.txt|password.txt|passwords.txt .sql=.sql.gz|.sql.tar|backup.sql|dump.sql|db.sql|database.sql|mysql.sql|.com.sql .tar=.tar.gz|.tar.sql|dump.tar|backup.tar|bak.tar|website.tar|backup.tar|www.tar .zip=backup.zip|bak.zip|.com.zip|well-known.zip|index.zip|public_html.zip|website.zip|dump.zip|wallet.zip|application.zip .rar=bak.rar|website.rar|backup.rar|www.rar .gz=website.gz|bak.gz|backup.gz|.com.gz /old/ WireRequestBlocker only knows its rules and doesn't know who's real and who's a bot, so be careful not to hit URLs containing those strings on this site or it might hit you with nothing but 403's for a few hours. ? Next week is Spring Break here, so I'll likely be on a reduced schedule with kids home from school. Thanks for reading, have a great weekend! +75 more blocks (not shown)10 points
-
I'm also reusing generic fields in my templates with renamed labels. Keeps the DB cleaner. I use custom page classes, a great feature that was introduced in 3.0.152 to map those generic field names to more meaningful properties. This way I get intellisense in my editor and don't have to look up the mappings of generic field names to meaningful ones. Here's an example page class: <?php namespace ProcessWire; use RockMigrations\MagicPage; /** * Custom page class that provides properties/methods for pages with template glossary-item * * */ class GlossaryItemPage extends DefaultPage { use MagicPage; /** * holds the glossary term * * @var string $term */ public $term; public $hide; public $exclude; public $tooltip; public $meaning; public $description; // set custom properties in loaded public function loaded() { $this->set('term', $this->title); $this->set('hide', $this->checkbox); $this->set('exclude', $this->checkbox2); $this->set('tooltip', $this->text); $this->set('meaning', $this->text2); $this->set('description', $this->rte); } /** * Magic Page hook for clearing cache for terms from module Glossary * */ public function onSaved() { $this->wire->cache->delete(Glossary::CACHE_NAME); } } The property->fieldname mapping happens in the loaded() method. you can comment the property definitions so you get some meaningful info with code intellisense. In the template where I want to use pages with template glossary-item, I define the type for those pages to get intellisense: /** @var GlossaryItemPage $p */ <?= $p->term ?> Some notes on the page class code: The GlossaryItemPage extends DefaultPage. My DefaultPage class is a base class for all other page classes which holds generic methods that I want to have available in all page classes I'm using @bernhard's fabulous RockMigrations module which, apart from migrations, provides MagicPages. This makes it super easy to add commonly used hooks to your page classes. I have a Glossary module installed which handles migrations and logic for the glossary. In the migrations for that module I define the custom field labels in the template context: 'glossary-item' => [ 'fields' => [ 'title' => [ 'label' => 'Name of the Term', ], 'checkbox' => [ 'label' => 'Hide', ], 'checkbox2' => [ 'label' => 'Exclude this term from parsing', ], 'text' => [ 'label' => 'Text Shown in the CSS Tooltip', ], 'text2' => [ 'label' => 'Meaning', ], 'rte' => [ 'label' => 'Description of the Term', ] All in all this is a very structured approach. It surely takes some extra time to setup. But this pays back, especially for larger, more complex projects. It took me quite some time as PW developer to come to this kind of setup. I started out with very simple procedural code. I wish I had all these tools available when I started out developing with PW that we have now (page classes, migrations etc.) thanks to this great community. Everyone here has their own approach and workflow. So you will surely get some inspiration.5 points
-
A module for ProcessWire CMS to integrate a user registration/login functionality based on the FrontendForms module. This module creates pages and templates during the installation for faster development. The intention for the development of such a module was to provide a ready-to-use solution for user management, which can be installed and put into operation in a few minutes. It works out of the box, but it provides a lot of configuration settings in the backend: Highlights "One-click" integration of an user login/registration system without the hazzle of creating all pages and forms by hand "One-click" switch between login or login and registration option Double opt-in with activation link on new registrations Option for automatic sending of reminder mails, if account activation is still pending Option for automatic deletion of unverified accounts after a certain time to prevent unused accounts Option to use TFA-Email if installed for higher security on login Mutli-language Select if you want to login with username and password or email and password Select the desired roles for newly created users Select, which fields of the user template should be displayed on the registration and profile form (beside the mandatory fields). Fields and order can be changed via drag and drop functionality Offer users the possibility to delete their account in the members area using a deletion link with time expiration Customize the texts of the emails which will be send by this module Usage of all the benefits of FrontendForms (fe. CAPTCHA, various security settings,...) Support for SeoMaestro if installed Lock accounts if suspicious login attempts were made Support of Ajax form submission This module runs on top of the FrontendForms module, so please download and install this module first. UPDATE 03.11.2023: The module is now available inside the Processwire modules directory: Frontendloginregister This module is early Beta stage, so please do not use it on live sites at the moment. If you discover any issues, please report them directly on GitHub ?. Thanks!2 points
-
Hey @kongondo I just realized you were redoing this - wow! If I could suggest two places to look for inspiration from the UI side. WP is nigh unusable out of the box for a number of clients in terms of both media and filtering and the two favorite plugins I deploy to handle these have always been Admin Columns Pro on the filtering side and WP Media Folder on the media management side: https://www.admincolumns.com/what-is-admin-columns/ Admin Columns Pro is just pure magic for clients. Being able to edit-in-place on the post/page list, add images from the grid, batch commands from the grid - this plugin alone does so much for wordpress it's a no-brainer first install. But the filtering columns at the top of the list that conform to custom posts and pages (like if you are using ACF or Pods) is really intuitive and awesome AJAX. Like I said, clients love how much it streamlines their work. I personally have gotten used to listers, but the experience of listers on Processwire is a lot more like NSP Code's advanced ordering WP plugin where you are able to filter and sort hierarchical lists and then create set list pages for them: https://www.nsp-code.com/premium-plugins/ This sort of thing is fine for developers to put together special lists - and I actually use lister pro to provide clients with worklists that detect content that is incomplete (missing author, card image, unpublished, too short, etc.) but as other have mentioned, the lister interface for some reason is hard to grasp. I always had the same problem trying to train people on using Advanced Post Types Order. https://www.joomunited.com/wordpress-products/wp-media-folder I tried a bunch of media managers for wordpress and this is the one that folks seemed to like the best, and I like that it produces its own metalayer without disturbing the file system away - so if for some reason they uninstall it everything revert to the big perpetual stew that is WP media. Not saying that you need to duplicate anything here, but I saw others posting about things that clients have liked from a UX perspective and so here ya go!2 points
-
As a follow up, I dropped that plan after I read about chatGPT plugins, specifically the retrieval plugin and then watched This seems a better way to go. But, then again, after watching I realised what a big effort it is to initially collect the data before we can provide it to chatGPT via the retrieval plugin. Definitely not something that I would be up to on my own. But at least I learned something ? Apart from that, the more I read about the company openAI the more I realise that I don't want to support them. Being a proponent of the open source spirit, I'll better look deeper into really open projects like https://github.com/nomic-ai/gpt4all and see how I can get a fine tuned model for programming with PW. Like @gornycreative said earlier, I also think the future lies in more specialized models. Anyways, still a long way to go until we have chatPW ?2 points
-
Im currently using this setup to manage this exact case you mention, my folder structure is looking like this for all my environments: site ├── config.php └── config-dev.php My config.php has the usual stuff and a constant definition to detect my current environment like this: <?php # All the usual processwire configurations # Have my database setup with env vars: $config->dbHost = $_ENV['dbHost']; $config->dbName = $_ENV['dbName']; $config->dbUser = $_ENV['dbUser']; $config->dbPass = $_ENV['dbPass']; if ($_ENV['dbHostReader']) { $config->dbReader = [ 'host' => $_ENV['dbHostReader'] ]; } $config->dbPort = '3306'; /*------------------------------------*\ Detect environment \*------------------------------------*/ switch ($_SERVER['HTTP_HOST']) { case 'www.site.com': define('ENV', 'PRODUCTION'); break; case 'staging.site.com': define('ENV', 'STAGING'); break; default: define('ENV', 'LOCAL'); break; } Since config-dev.php is present on all my environments, it will prevent config.php from running, so I manually call it and then override the settings I need like this: # I call the normal config.php so that the usual stuff is loaded as always require_once 'config.php'; # In case env vars failed if (!$config->dbHost) die('Missing env vars'); if ('LOCAL' === ENV) { $config->moduleInstall('download', true); // tracy config for dev development $config->tracy = [ 'outputMode' => 'development', 'guestForceDevelopmentLocal' => true, 'forceIsLocal' => true, // use this only on local dev!!!! ]; /** * Disable all HTTPS requirements? */ $config->noHTTPS = true; } if ('STAGING' === ENV) { # Staging settings... } This way I can use the exact same codebase on all my environments, hope this helps2 points
-
For very complex templates I (miss)use the Auto Template Stubs module, to manually copy/paste a list of all used fields to the template head comments, so if there are a lot of generic fieldnames (as flydev recommended above), I don't have to look them up all the time. The module creates the whole info in separate files, very easy to copy & paste. If you need fields inside a Repeater, they're also available in separate files. Looks something like this then: /** * Template: contact (Contact) * * @property string $title Page Title * @property string $text_ml_01 Headline * @property string $textarea_ml_01 Body * @property Pageimages $images_01 Images * @property Pagefiles $files_ml_01 Files */2 points
-
Modules Manager 2 provides an easy to use interface to download, update, install, uninstall and configure modules. It is meant to provide an optimized alternative to the core ProcessModule dashboard. Maybe @ryan agrees to merge it to the core at some point when it is finished and polished. Features: Seamlessly download, update, install, uninstall or delete modules Live-Search (aka find as you type) for module names Live-Search (aka find as you type) for categories Browse new and unkown modules from the modules directory on modules.processwire.com Choose your favorite layout (cards, reduced cards, table) Modern UIKit design (therefore only works with AdminThemeUikit) Caches the module list from modules.processwire.com directory locally. What is Modules Manager 2? Why a new module manager? Some people including myself think that the actual module installation in ProcessWire could be improved in some places. Make it easy for ProcessWire beginners and power users Offer better discoverbility to find the right module Make it easier and faster for powerusers to manage modules A manager that list all official modules is a feature, that many other frameworks/CMS's like ModX, WordPress or PrestaShop have by default. What are the disadvantages of the actual core module interface? Installation of a module is not very user-friendly: You have to be aware where to get new modules, search for a module, copy or remember the module name or URL, go back to your ProcessWire installation, paste the module name(URL, click on "get module info" and finally install the module It only displays installed modules, not the ones that are available in the modules directory Uninstalling a module requires you to go to the module detail page, click a checkbox and then submit the change. After that you have to go back to the module overview page. It only displays installed modules, not the ones that are available in the modules directory, so it makes discovering modules hard BETA software Use this module at your own risk. I am not responsible for any damage or unexpected behaviour. Some things might not work fully, please see the TODO list for details. I need your feedback and help This module is still in development and I am happy to discuss with you and get some feedback. What do you like? What is missing? What could make the process even easier? Ask, suggest or provide pull requests. You can download the module at https://modules.processwire.com/modules/modules-manager2/ or from Github: https://github.com/jmartsch/processwire-modules-manager21 point
-
Introducing ProcessDiagnostics and it's helper module suite. (Is this ProcessWire's first community-created module suite?) Description This suite adds a page under the setup menu that displays information about your installation. Each section's data is provided by a specialist diagnostic helper module but it is all collected and displayed by ProcessDiagnostics. The ProcessDiagnostics module itself does not encode any knowledge about what makes up a good or bad setting in PW - (that's done by the helper modules) - but it does the following... Gather the diagnosics (thanks to PW's hook system) Display the collected diagnostics Provide helper functions for describing some common things Dispatch actions to diagnostic provider modules (again thanks to PW's hook system) And eventually it will: Allow control of the verbosity of the output Allow the output to be emailed to a sysop Store the results between visits to the page Detect differences between results at set times Send a notification on detection of changes Although I am curating the collection, anyone is welcome to fork the repo, make changes in a topic branch, and submit pull requests. I've already had submissions from horst and Nico. Diagnostic Providers The current diagnostic providers include... DiagnosePhp - Simple diagnostics about the PHP envirnoment on the server DiagnoseModules - An ajax based module version checker by @Nico DiagnoseImagehandler - Lets you know about GD + Imagick capabilities by @horst DiagnoseDatabase - Checks each DB table and lets you know what engine and charset are in use DiagnoseWebserver - Checks the webserver setup DiagnoseFilesystem - Looks at how your directory and files are configured and warns of permission issues (currently incomplete) There is also a bare bones demonstration diagnostic module... DiagnoseExample - minimal example to get module authors started. Translations English & German (thank you @Manfred62!) Help translating this suite to other languages is always welcome. On The Net Check out Nico's blog post about this suite on supercode.co!1 point
-
1 point
-
I've just added a small PR that makes PW load config-local.php additionally to config.php if the file exists: https://github.com/processwire/processwire/pull/2671 point
-
Test the solution below if it fit your needs : if ($user->hasRole('testrole')) { // $ap = $user->editable_pages[0]->id; //there is always only one page // $wire->addHookBefore('Page(id!=' . $ap . '|1023)::listable', function ($event) { //1058 is the id of the parent page // $event->return = false; // }); // ? // get the parent id of the user's editable page $parent_id = $user->editable_pages[0]->parent_id; //there is always only one page $this->addHookBefore('ProcessPageList::execute', function (HookEvent $event) { // hide root (/) page in list (optional) $event->object->showRootPage = false; // limit number of pages shown - that's the trick to avoid pagination in the lister $event->object->limit = 100; // you might need to adjust this value }); $this->addHookAfter('ProcessPageList::execute', function (HookEvent $event) use ($parent_id) { // run only on ajax requests if ($this->config->ajax) { // manipulate the json returned $json = json_decode($event->return, true); // remove any pages that are not editable or addable and not the parent page foreach ($json['children'] as $key => $child) { $p = $this->pages->get($child['id']); if (!$p['editable'] && !$p['addable'] && $p['id'] !== $parent_id) { // remove the page from the json unset($json['children'][$key]); } } // set the json back to the event $json['children'] = array_values($json['children']); $event->return = json_encode($json); } }); }1 point
-
gpt4all looks amazing, I was watching some video on it last night. There are a number of KB fed GPT driven chatbots appearing on the market. These tools combined with NLP tools from AWS and call routing node with Twilio could eventually create phone accessible intelligent chatbots that could serve as digital virtual assistants for your office, or as a natural language support call bot. I can't really get over how exciting this potential is. Soon these bots will be earning proficiency licenses and will be able to answer diagnostic and therapeutic calls - which is going to be its own problem. If you have not looked at the suite of Amazon's NLP stack, it is really impressive. Lex (process audio text requests) -> Comprehend (tokenized/tag intents, tone, and pass through) -> Transcribe -> Translate (English Output) -> Prompt GPT (Live question request and intent. tone notes) -> Polly (Process GPT response as audio reply) All of this could be driven by Twilio branching nodes to even only focus on GPT instances that are trained in a given department - ultimately this will be a neat way to save on processing resources if you have specialty nodes that can refer to each other when a question goes beyond their expertise or more general help is needed. You could even branch these dialogs to a logged live transcript that could alert a live person if a frustrated or angry sentiment flag gets passed. I'm sure it is only a matter of time before these services offer GPT integration services themselves. I hope open versions will always remain, even if the 'Community Editions' lag behind the commercial version.1 point
-
Agreed! Great thoughts! I've thought about this before. I am keen to work on it. A $mediaManager variable will make it very easy to interact with MM in the frontend. This will make it easier to directly use media items in the frontend. For instance, directly build a photo gallery using images from the library without the need to add these to a page first. It will also make it easier to manage media items using the API, import items into the library, etc. I have updated the the MM Next plans post.1 point
-
@kongondo Just want to let you know I'm super happy development on the module continues. The functionality of the current version is great, it's just a bit unpolished compared to file managers from other CMS. As for new features: I agree with the others, folders and simplified search (+ advanced filtering) are definitely my top two wishes too. Also easier API access to render media in the frontend would be a nice to have. If I remember correctly it was a bit more complicated to use than the usual ProcessWire API. Super excited for the updates, thank you!1 point
-
I don't think you can do that in a selector, but you could add a hidden "owner" field to the "inventory" template and then populate that on page save with the value from the last repeater item. So if "owners" is the repeater field and "owner" is a Page Reference field within the repeater that stores a user... $pages->addHookAfter('saveReady', function(HookEvent $event) { /* @var Page $page */ $page = $event->arguments(0); if($page->template == 'inventory') { $page->owner = $page->owners->last()->owner; } }); Then you can easily find pages like this: $myInventoryPage = $pages->get("template=inventory, owner=$user");1 point
-
If folks are looking to add the full gamut of emojis, be sure to check out https://emojipedia.org/ You have to scroll down a bit on more complex multitone emojis, but shortcodes are also included.1 point
-
We've got just a few core updates on the dev branch this week, but next week we're looking at finally merging in the InputfieldTinyMCE module! This week I also wrapped up the WireRequestBlocker module that was mentioned in last week post, and the v1 beta is now posted in the ProDevTools download thread. I've been running it here on processwire.com this week and it's been doing a good job of keeping out the vulnerability scanners and bots. For more details on this new module please see the new Wire Request Blocker page that I just posted. Thanks and have a great weekend!1 point
-
Hey @ryan sounds like a great module ? Wouldn't it be nice if the module's config screen showed that information? So it would be in a safe space without additional steps to do ?1 point
-
1 point
-
Template Access Log is a straightforward module that logs changes made to template level access settings: the useRoles option, or applicable roles and/or role-specific permissions. This module is primarily intended for use cases where an audit log is needed, and (at least for now) it just logs data to a log file template_access_log.txt and provides no admin view (apart from what can be found from the logs section in admin). Data is logged as JSON: 2023-03-18 18:42:05 admin https://example.com/processwire/setup/template/save {"template":"basic-page","template_id":29,"use_roles":1,"permissions":{"view":[37,1061,1062,1125],"edit":[1062],"add":[1061,1062],"create":[]},"permissions_changed":{"edit":[-1061]}} This is something that I needed for some projects, so thought I'd share it here in case someone else has use for it as well. I may add more features later, but at the moment it's already doing pretty much everything it needs to for my use case(s) ? GitHub: https://github.com/teppokoivula/TemplateAccessLog Packagist: https://packagist.org/packages/teppokoivula/template-access-log1 point
-
1 point
-
I am proud to announce my very first module: GooglePlaceDetails. I was in the need to include some google place reviews for a clients website. It turned out that no such review widget was available for ProcessWire. So I made my own solution which i want to share with you. Google Place Details for ProcessWire Modules Directory: https://processwire.com/modules/google-place-details/ Github: https://github.com/StefanThumann/GooglePlaceDetails What it does Google Place Details offers the possibility to send requests to the Google Maps API to receive information about a certain place. A typical use case would be to display the reviews of a place on your website. But you can receive any other information that the API offers. Before you start You need three things: A Google API Key The Place ID A project with a billing account activated You can set up all of those by using Googles quick start widget here: https://developers.google.com/maps/third-party-platforms/quick-start-widget-users How to install Copy this directory to /site/modules In your admin, go to Modules > Refresh, then Modules > New Click the "Install" button next to the Google Place Details module Fill out the API Key and Place ID fields in the module settings and you are ready to go. Module settings and field descriptions API Key This field is required and must contain your generated Google API key. Place ID This field is required. You can put the ID of any place into this field. Fields to include in request Specify a comma-separated list of place data types to return. Leave empty to load all default fields. For an overview of the available fields see: https://developers.google.com/maps/documentation/places/web-service/details Review Sorting Chose your sorting criteria. "Most relevant" is used by default. Preview Place Details If checked the place details can be previewed for debugging/development purpose on module page submit. Usage example Load the module in a page context: $module = $modules->get('GooglePlaceDetails'); Call a function to load data $module->getPlaceDetails(); This function fetches the data in realtime, on every page request and returns a php array containing the full response from the Google server. See the frontend example at the end of this document to see how to extract data from the array in a working example. Place details answer example The place details answer will be in JSON format and looks like this (depending of the fields you included in your request) { "html_attributions": [], "result": { "name": "Google Workplace 6", "rating": 4, "reviews": [ { "author_name": "Luke Archibald", "author_url": "https://www.google.com/maps/contrib/113389359827989670652/reviews", "language": "en", "profile_photo_url": "https://lh3.googleusercontent.com/a-/AOh14GhGGmTmvtD34HiRgwHdXVJUTzVbxpsk5_JnNKM5MA=s128-c0x00000000-cc-rp-mo", "rating": 1, "relative_time_description": "a week ago", "text": "Called regarding paid advertising google pages to the top of its site of a scam furniture website misleading and taking peoples money without ever sending a product - explained the situation, explained I'd spoken to an ombudsman regarding it. Listed ticket numbers etc.\n\nThey left the advertisement running.", "time": 1652286798, }, { "author_name": "Tevita Taufoou", "author_url": "https://www.google.com/maps/contrib/105937236918123663309/reviews", "language": "en", "profile_photo_url": "https://lh3.googleusercontent.com/a/AATXAJwZANdRSSg96QeZG--6BazG5uv_BJMIvpZGqwSz=s128-c0x00000000-cc-rp-mo", "rating": 1, "relative_time_description": "6 months ago", "text": "I need help. Google Australia is taking my money. Money I don't have any I am having trouble sorting this issue out", "time": 1637215605, }, { "author_name": "Jordy Baker", "author_url": "https://www.google.com/maps/contrib/102582237417399865640/reviews", "language": "en", "profile_photo_url": "https://lh3.googleusercontent.com/a/AATXAJwgg1tM4aVA4nJCMjlfJtHtFZuxF475Vb6tT74S=s128-c0x00000000-cc-rp-mo", "rating": 1, "relative_time_description": "4 months ago", "text": "I have literally never been here in my life, I am 17 and they are taking money I don't have for no reason.\n\nThis is not ok. I have rent to pay and my own expenses to deal with and now this.", "time": 1641389490, }, { "author_name": "Prem Rathod", "author_url": "https://www.google.com/maps/contrib/115981614018592114142/reviews", "language": "en", "profile_photo_url": "https://lh3.googleusercontent.com/a/AATXAJyEQpqs4YvPPzMPG2dnnRTFPC4jxJfn8YXnm2gz=s128-c0x00000000-cc-rp-mo", "rating": 1, "relative_time_description": "4 months ago", "text": "Terrible service. all reviews are fake and irrelevant. This is about reviewing google as business not the building/staff etc.", "time": 1640159655, }, { "author_name": "Husuni Hamza", "author_url": "https://www.google.com/maps/contrib/102167316656574288776/reviews", "language": "en", "profile_photo_url": "https://lh3.googleusercontent.com/a/AATXAJwRkyvoSlgd06ahkF9XI9D39o6Zc_Oycm5EKuRg=s128-c0x00000000-cc-rp-mo", "rating": 5, "relative_time_description": "7 months ago", "text": "Nice site. Please I want to work with you. Am Alhassan Haruna, from Ghana. Contact me +233553851616", "time": 1633197305, }, ], "url": "https://maps.google.com/?cid=10281119596374313554", "user_ratings_total": 939, "website": "http://google.com/", }, "status": "OK", } Usage in frontend example To display the reviews of a place you can do it like this (very basic markup!). I encourage every user to build their own markup of the reviews, fitting their design. <?php // 1. Connect to module $module = $modules->get('GooglePlaceDetails'); // 2. Save the details array to a variable $details = $module->getPlaceDetails(); // 3. Extract the data you want to iterate over $reviews = $details['result']['reviews']; // For debug purpose dump the array to inspect the data // TRACY DEBUGGER MODULE REQUIRED // dump($reviews); <? foreach ($reviews as $review) { ?> <div> <img src="<?=$review["profile_photo_url"]?>"/> <h4><?=$review["author_name"]?></h4> <? for ($i = 1; $i <= ($review['rating']); $i++) { ?> ★ <? } ?> <p><?=$review["text"]?></p> </div> <? } ?> ?> Here is a more advanced and nicer looking version (UIKit 3 Framework Markup is used) <div class="uk-container"> <div uk-slider> <div class="uk-position-relative"> <div class="uk-slider-container"> <ul class="uk-slider-items uk-child-width-1-2@s uk-child-width-1-3@m uk-grid uk-height-medium"> <? foreach ($reviews as $review) { ?> <li> <div class="uk-card uk-card-default uk-card-body"> <div> <img src="<?=$review["profile_photo_url"]?>" class="uk-responsive-width" style="height: 30px;" /> <span class="uk-text-middle"><?=$review["author_name"]?></span> </div> <div class="uk-margin"> <? for ($i = 1; $i <= ($review['rating']); $i++) { ?> ★ <? } ?> <? for ($i = 1; $i <= 5 - ($review['rating']); $i++) { ?> ☆ <? } ?> </div> <div uk-overflow-auto="selContainer: .uk-slider-items; selContent: .uk-card"> <p><?=$review["text"]?></p> </div> </div> </li> <? } ?> </ul> </div> <a class="uk-position-center-left-out uk-position-small" href="#" uk-slidenav-previous uk-slider-item="previous"></a> <a class="uk-position-center-right-out uk-position-small" href="#" uk-slidenav-next uk-slider-item="next"></a> </div> </div> </div> If you are already using UIKit 3 and just want to get a quick result I put the code example above in a function that can can be called like this: <? $module = $modules->get('GooglePlaceDetails'); echo $module->getUIKitMarkupExample(); ?> The template file which is used for the markup lies inside the module directory. Adjust it to your needs.1 point
-
Oh I forgot to mention you for giving me help on this one ? For all that that were not involved in the discussion here and to clear things up: The Google Maps API is in fact not for free. Depending how much API requests (and what kind of API requests) you send, you will have to pay for it. Google offers everyone a monthly discount of 200$. This discount should fit for most of the requests of a small website, so you never have to pay for anything as long as you are below this 200 $ Mark. There is also a nice calculator here: https://mapsplatform.google.com/intl/de/pricing/ If you just receive data over the place details API (which is the API the module uses) you have about 12.000 requests "for free". So I came up with a save function for the request. You can just fetch it once - save it - and don't have to use the API on each request form there on. But that would violate the Google Maps terms. Sadly I had to remove this save function from the module ?1 point
-
v. 1.2.0 is now released Twitter tags will be rendered by default Added support for image alternative text using custom fields in images Added support for inheriting image from parent pages (including home page) Added support for fallback page for image. If the image cannot be found from the current page (and possibly enabled inheritance fails), the module will try to find the image from the given page. You can use this to define default image for all pages. Fixed crash when passing an empty Pageimages object as an image Fixed crash when supplying an invalid image width or height Note that module now requires PW 3.0.142 or later, as custom fields for image and file fields were introduced in that version.1 point
-
I can confirm the same setup is very easy to count clicks + views...i need that for a project. Working good so far. Only difference is that i simple count clicks and views in a integer field without additional information - so it is "data economical" and i've no problem with saved IP's and so on... For manging the banner i use a PageTableExtended Field: I've a general settings about the amounth of ad slots on that page - and a flag option on every single page where a user can set ads to off for a single page. /** * Adsystem show Ads in several templates * * @var $limit (Int) set the limit of displayed ads ->look at anzeige_anzahl /settings/werbeanzeigen/ * @var $headline (string)set the headline of the ad list */ function renderAdsystem($headline = 'Anzeigenpartner') { //get all ad pages on basic setting - unpublished pages are not listed.... $limit = wire('pages')->get('1056')->anzeige_anzahl; //build add output $anzeigen = wire('pages')->find("template=part_ad, limit={$limit}, sort=random"); //render ads and collect them in $all_ads $all_ads = '<h4 class="subtitle">'.$headline.'<h4>'; foreach ($anzeigen as $anzeige) { $anzeige->of(false); $anzeige->anzeige_views += 1; $anzeige->save(array("quiet" => true, "uncacheAll" => false)); $anzeige->of(true); //get the right imagesize $anzeige_bild = $anzeige->anzeige_bild->size(260,120); //build ad link $all_ads .= '<a href="'.$anzeige->url.'" alt="'.$anzeige->title.'"><img class="anzeigen" src="'.$anzeige_bild->url.'" alt="'.$anzeige->title.'"></a>'; } //check if adds are off if ($limit == 0) { $out = ''; } else { $out = $all_ads; } return $out; } All is a page - so the adlink is a page for shure - and for pages we can count clicks == pageviews + redirect and views for every rendering that pageitem somewhere. Just as an addition to the great example from BitPoet! Thanks for that - so i'm tranquilised to find a way that a professional find, too - so it couldn't be that wrong Best regards mr-fan1 point
-
It shouldn't be hard to get the whole banner rotation thing up using PW's on-board functionalty. The only additional module necessary would be AdminCustomPages to view statistics. Here's how I'd do it: Create a bunch of templates for category and banner as well as categories and banners templates to group them together. The category template gets a view counter which we increment with every view of a banner through the category, and we'll use that counter to pick the next banner. The banner template gets a field for the category it belongs to, one for the URL to redirect to and, of course, the image field, optionally with size settings in the field configuration. Our "normal" templates get another page field in which we pick the banner category. We also add a "click" template to store each banner click with the remote IP. Thus we have the following templates with fields: banner_categories title field onlyWe create a banner categories page somewhere (it doesn't really matter where, can be under home). banner_category title (for the category selections) banner_cat_count (our counter) banners title field onlyLet's create one of these under home too. banner title (just for us) banner_image (image field, set to single image, size constraints in the field's config) banner_url (fieldtype URL, the address to redirect on click) banner_category (page field, single select, parent set to banner_categories page) click click_ip (text field)Our pages that should include banners get a new page field (single page, NullPage if empty) page_banner_category. Now we need two PHP templates: one to render the banner inside the page (let's name it "_banner_include.php") and one that does the counting and redirecting if a banner is clicked ("banner.php"). In banner.php, we simply add a click counter page under the banner page itself, then redirect to the configured URL: <?php $click = new Page(); $click->template = wire('templates')->get('click'); $click->name = 'click-' . time(); $click->parent = $page; $click->click_ip = $_SERVER["REMOTE_ADDR"]; $click->save(); $session->redirect($page->banner_url); In _banner_include.php, we increment the view counter for the category (we could also add a counter field to the banner template and increment it on the banner page), then use the counter value modulo the number of banners in the category to get the banner itself: <?php $banner = next_banner($page->page_banner_category); function next_banner($category) { global $pages; $banners = wire('pages')->find("template=banner, banner_category={$category}, include=hidden"); if( $banners->getTotal() == 0 ) return new NullPage(); $category->of(false); $category->banner_cat_count += 1; $category->save(); $category->of(true); return $banners->eq($category->banner_cat_count % $banners->getTotal()); } if( ! $banner instanceof NullPage ) { ?> <div class="banner"> <a href="<?= $banner->url ?>"><img src="<?= $banner->banner_image->url ?>"></a> </div> <?php } Now all we need in our regular pages' templates to render the banner: <?= $page->render("_banner_include.php"); ?> Getting an overview in the backend using AdminCustomPages should be quite easy too by just creating a custom page including a scipt that iterates over all the categories, reads the click counter, then iterates over each banner and counts the children (optionally limited by the timespan). A quick-and-dirty script (untested) to illustrate that: <?php foreach( wire('pages')->find("template=banner-category") as $cat ) { echo "<h1>{$cat->title}</h1>"; echo "<table><thead><tr><th>Banner</th><th>clicks</th></tr></thead>\n<tbody>\n"; foreach( wire('pages')->find("template=banner, banner_category={$cat}, sort=title") as $banner ) { $clicks = $banner->children->count(); echo "<tr><td>{$banner->title}</td><td>{$clicks}</td></tr>\n"; } echo "<tr><td colspan=2>{$cat->banner_cat_count} Views / {$clicks} clicks</td></tr>\n"; echo "</tbody>\n</table>\n"; } We could, of course, make things a little easier by grouping the banners under their category, and there's nothing major speaking against it. I used a different parent for the banners to have them all under the same URL, i.e. /banners/banner-name. All in all, it shouldn't take more than an hour to get things up and running this way.1 point