Stefanowitsch Posted January 3, 2023 Posted January 3, 2023 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. 6 2
bernhard Posted January 3, 2023 Posted January 3, 2023 Congrats for your first module! ? That license thing really sucks... in theory - wouldn't then also using ProCache violate their terms?? How many API requests per month are free, do you know that or have an estimate? 200$ every month... but what does that mean in terms of pageviews or api requests. Also that means that the pageload will drastically increase, no? Unless you use ProCache (which would be against their terms) or you use a JS-based solution that loads reviews after page load. 1
Stefanowitsch Posted January 3, 2023 Author Posted January 3, 2023 55 minutes ago, bernhard said: Congrats for your first module! ? That license thing really sucks... in theory - wouldn't then also using ProCache violate their terms?? How many API requests per month are free, do you know that or have an estimate? 200$ every month... but what does that mean in terms of pageviews or api requests. Also that means that the pageload will drastically increase, no? Unless you use ProCache (which would be against their terms) or you use a JS-based solution that loads reviews after page load. 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 2
psy Posted July 3 Posted July 3 Found this GooglePlaceDetails module today. Have no idea why it took so long! Installed and tested. Unfortunately when testing in the module config, I got an error saying I was using a deprecated legacy API - so Google! It's now Google Places (New). A bit of fiddling got it to work: Hide contents <?php namespace ProcessWire; /** * Gooogle Place Details Module * * This module can be used to display Google place details like reviews and other information in the frontend of a website. * * @author Stefan Thumann, 27.12.2022 * @license https://processwire.com/about/license/mit/ * * ProcessWire 2.x & 3.x, Copyright 2020 by Ryan Cramer * https://processwire.com * https://processwire.com/about/license/mit/ * **/ class GooglePlaceDetails extends WireData implements Module, ConfigurableModule { public static function getModuleInfo() { return [ 'title' => 'Google Place Details', 'summary' => 'Display Google place details like reviews and other information.', 'author' => 'Stefan Thumann', 'version' => '1.0.0', 'icon' => 'google' ]; } static protected $defaults = array( 'apiKey' => '', 'placeId' => '', 'dataFields' => 'name,reviews', 'reviewSort' => 'most_relevant', 'previewDetails' => '', 'detailsData' => '' ); public function __construct() { // populate defaults, which will get replaced with actual // configured values before the init/ready methods are called $this->setArray(self::$defaults); } public function getModuleConfigInputfields(array $data) { $inputfields = new InputfieldWrapper(); $data = array_merge(self::$defaults, $data); $f = wire('modules')->get('InputfieldMarkup'); $f->attr('name', 'infoData'); $f->label = 'Before you begin'; $f->icon = 'info'; $f->attr('value', 'Before you start using the Google Maps API you need an API key and a project with a billing account.'); $f->notes = 'Use Googles quick start widget here: [https://developers.google.com/maps/third-party-platforms/quick-start-widget-users](https://developers.google.com/maps/third-party-platforms/quick-start-widget-users)'; $inputfields->add($f); // API Key Inputfield $f = wire('modules')->get('InputfieldText'); $f->attr('name', 'apiKey'); $f->label = 'API Key'; $f->attr('value', $data['apiKey']); $f->columnWidth = 50; $f->notes = 'The API key is a unique identifier that authenticates requests associated with your project for usage and billing purposes: [https://developers.google.com/maps/documentation/javascript/get-api-key](https://developers.google.com/maps/documentation/javascript/get-api-key)'; $f->required = true; $inputfields->add($f); // Place ID Inputfield $f = wire('modules')->get('InputfieldText'); $f->attr('name', 'placeId'); $f->label = 'Place ID'; $f->attr('value', $data['placeId']); $f->columnWidth = 50; $f->notes = 'Use the place ID finder to search for a place and get its ID: [https://developers.google.com/maps/documentation/places/web-service/place-id](https://developers.google.com/maps/documentation/places/web-service/place-id)'; $f->required = true; $inputfields->add($f); // Fields Parameter Inputfield $f = wire('modules')->get('InputfieldText'); $f->attr('name', 'dataFields'); $f->label = 'Fields to include in request'; $f->attr('value', $data['dataFields']); $f->description = 'Specify a comma-separated list of place data types to return. Leave empty to load all default fields.'; $f->notes = 'For an overview of the available fields see: [https://developers.google.com/maps/documentation/places/web-service/details](https://developers.google.com/maps/documentation/places/web-service/details)'; $inputfields->add($f); // Sorting Options $f = wire('modules')->get('InputfieldSelect'); $f->attr('name', 'reviewSort'); $f->label = 'Review Sorting'; $f->icon = 'filter'; $f->options = array( 'most_relevant' => 'Most relevant', 'newest' => 'newest' ); $f->attr('value', $data['reviewSort']); $f->notes = 'Info: The amount of reviews is limited to 5 by the API.'; $inputfields->add($f); // Fetch Place Details Checkbox $f = wire('modules')->get('InputfieldCheckbox'); $f->attr('name', 'previewDetails'); $f->label = 'Preview Place Details'; $f->description = 'If checked the place details can be previewed for debugging/development purpose after submit. This preview data will not be saved and is lost after leaving this page.'; $f->attr('value', 1); $f->icon = 'heartbeat'; $inputfields->add($f); if($this->session->previewDetails) { $this->fetchData('liveData'); } // Markup Field for Preview Data Array $f = wire('modules')->get('InputfieldMarkup'); $f->attr('name', 'previewData'); $f->label = 'Preview Data'; $f->icon = 'filter'; $f->notes = 'Note that is is not allowed to store Google Maps content outside their services. [https://cloud.google.com/maps-platform/terms](https://cloud.google.com/maps-platform/terms) '; if($this->session->previewDetails) { $f->attr('value', $this->previewData()); } $inputfields->add($f); if(wire('input')->post->previewDetails) { // if checkbox was checked, set session data $session = wire('session'); $session->set('previewDetails', 1); } return $inputfields; } /** * This function previews the revieved data in a markup field on the module page */ private function previewData() { // remove session data $session = wire('session'); $session->remove('previewDetails'); $detailsData = $this->detailsData; if (!empty($detailsData)) { // When there is data, show it // hier config $outputTemplate = "<pre style=\"overflow:scroll !important; margin:15px auto; padding:10px; background-color:#ffffdd; color:#555; border:1px solid #AAA; font-family:'Hack', 'Source Code Pro', 'Lucida Console', 'Courier', monospace; font-size:12px; line-height:15px;\">".print_r(json_decode($detailsData,true), true)."</pre>"; } else { $outputTemplate = "<pre style=\"overflow:scroll !important; margin:15px auto; padding:10px; background-color:#ffffdd; color:#555; border:1px solid #AAA; font-family:'Hack', 'Source Code Pro', 'Lucida Console', 'Courier', monospace; font-size:12px; line-height:15px;\">No data received yet.</pre>"; } return $outputTemplate; } /** * This function recieves the place data over the google api via a http request and saves it for later use * @return array|string */ private function fetchData() { // Get Values $apiKey = $this->apiKey; $placeId = $this->placeId; $dataFields = $this->dataFields; $reviewSort = $this->reviewSort; // $apiUrl = "https://maps.googleapis.com/maps/api/place/details/json?fields=$dataFields&reviews_sort=$reviewSort&reviews_no_translations=true&place_id=$placeId&key=$apiKey"; $apiUrl = "https://places.googleapis.com/v1/places/$placeId?fields=$dataFields&key=$apiKey"; $http = new WireHttp(); $responseJson = $http->get($apiUrl); if($responseJson !== false) { // Description: // The Response is in JSON Format. // To return just the result data, we have to decode the JSON into a php array, select the result object and return it // Turn JSON into php array $responseArray = json_decode($responseJson, true); // For preview purpose get only the result data and encode it back to JSON $this->detailsData = $responseJson; // return response array to frontend return $responseArray; } else { echo "HTTP request failed: " . $http->getError(); } } /** * This function recieves the place data over the google api via a http request and returns it in real time * @return array */ public function getPlaceDetails() { return ($this->fetchData()); } public function getUIKitMarkupExample() { $responseArray = $this->fetchData(); // return only the result array to the frontend and include it in a sample markup $result = $this->wire('files')->render('../modules/GooglePlaceDetails/reviews-markup',['details' => $responseArray]); return $result; } } Please note that the review results are automatically sorted by "relevant" and a maximum of 5 reviews on the 'free' (low usage) plan. The available data has also changed. See https://developers.google.com/maps/documentation/places/web-service/data-fields for the available field info. The examples in the help text will no longer work. It's simply a matter of mapping the new fields to your template. Would love to see an official upgrade of this module and hope the above helps. PS: Hardest part was navigating Google to get the API key!
Stefanowitsch Posted July 3 Author Posted July 3 3 hours ago, psy said: Found this GooglePlaceDetails module today. Have no idea why it took so long! Installed and tested. Unfortunately when testing in the module config, I got an error saying I was using a deprecated legacy API - so Google! It's now Google Places (New). A bit of fiddling got it to work: Reveal hidden contents Hide contents <?php namespace ProcessWire; /** * Gooogle Place Details Module * * This module can be used to display Google place details like reviews and other information in the frontend of a website. * * @author Stefan Thumann, 27.12.2022 * @license https://processwire.com/about/license/mit/ * * ProcessWire 2.x & 3.x, Copyright 2020 by Ryan Cramer * https://processwire.com * https://processwire.com/about/license/mit/ * **/ class GooglePlaceDetails extends WireData implements Module, ConfigurableModule { public static function getModuleInfo() { return [ 'title' => 'Google Place Details', 'summary' => 'Display Google place details like reviews and other information.', 'author' => 'Stefan Thumann', 'version' => '1.0.0', 'icon' => 'google' ]; } static protected $defaults = array( 'apiKey' => '', 'placeId' => '', 'dataFields' => 'name,reviews', 'reviewSort' => 'most_relevant', 'previewDetails' => '', 'detailsData' => '' ); public function __construct() { // populate defaults, which will get replaced with actual // configured values before the init/ready methods are called $this->setArray(self::$defaults); } public function getModuleConfigInputfields(array $data) { $inputfields = new InputfieldWrapper(); $data = array_merge(self::$defaults, $data); $f = wire('modules')->get('InputfieldMarkup'); $f->attr('name', 'infoData'); $f->label = 'Before you begin'; $f->icon = 'info'; $f->attr('value', 'Before you start using the Google Maps API you need an API key and a project with a billing account.'); $f->notes = 'Use Googles quick start widget here: [https://developers.google.com/maps/third-party-platforms/quick-start-widget-users](https://developers.google.com/maps/third-party-platforms/quick-start-widget-users)'; $inputfields->add($f); // API Key Inputfield $f = wire('modules')->get('InputfieldText'); $f->attr('name', 'apiKey'); $f->label = 'API Key'; $f->attr('value', $data['apiKey']); $f->columnWidth = 50; $f->notes = 'The API key is a unique identifier that authenticates requests associated with your project for usage and billing purposes: [https://developers.google.com/maps/documentation/javascript/get-api-key](https://developers.google.com/maps/documentation/javascript/get-api-key)'; $f->required = true; $inputfields->add($f); // Place ID Inputfield $f = wire('modules')->get('InputfieldText'); $f->attr('name', 'placeId'); $f->label = 'Place ID'; $f->attr('value', $data['placeId']); $f->columnWidth = 50; $f->notes = 'Use the place ID finder to search for a place and get its ID: [https://developers.google.com/maps/documentation/places/web-service/place-id](https://developers.google.com/maps/documentation/places/web-service/place-id)'; $f->required = true; $inputfields->add($f); // Fields Parameter Inputfield $f = wire('modules')->get('InputfieldText'); $f->attr('name', 'dataFields'); $f->label = 'Fields to include in request'; $f->attr('value', $data['dataFields']); $f->description = 'Specify a comma-separated list of place data types to return. Leave empty to load all default fields.'; $f->notes = 'For an overview of the available fields see: [https://developers.google.com/maps/documentation/places/web-service/details](https://developers.google.com/maps/documentation/places/web-service/details)'; $inputfields->add($f); // Sorting Options $f = wire('modules')->get('InputfieldSelect'); $f->attr('name', 'reviewSort'); $f->label = 'Review Sorting'; $f->icon = 'filter'; $f->options = array( 'most_relevant' => 'Most relevant', 'newest' => 'newest' ); $f->attr('value', $data['reviewSort']); $f->notes = 'Info: The amount of reviews is limited to 5 by the API.'; $inputfields->add($f); // Fetch Place Details Checkbox $f = wire('modules')->get('InputfieldCheckbox'); $f->attr('name', 'previewDetails'); $f->label = 'Preview Place Details'; $f->description = 'If checked the place details can be previewed for debugging/development purpose after submit. This preview data will not be saved and is lost after leaving this page.'; $f->attr('value', 1); $f->icon = 'heartbeat'; $inputfields->add($f); if($this->session->previewDetails) { $this->fetchData('liveData'); } // Markup Field for Preview Data Array $f = wire('modules')->get('InputfieldMarkup'); $f->attr('name', 'previewData'); $f->label = 'Preview Data'; $f->icon = 'filter'; $f->notes = 'Note that is is not allowed to store Google Maps content outside their services. [https://cloud.google.com/maps-platform/terms](https://cloud.google.com/maps-platform/terms) '; if($this->session->previewDetails) { $f->attr('value', $this->previewData()); } $inputfields->add($f); if(wire('input')->post->previewDetails) { // if checkbox was checked, set session data $session = wire('session'); $session->set('previewDetails', 1); } return $inputfields; } /** * This function previews the revieved data in a markup field on the module page */ private function previewData() { // remove session data $session = wire('session'); $session->remove('previewDetails'); $detailsData = $this->detailsData; if (!empty($detailsData)) { // When there is data, show it // hier config $outputTemplate = "<pre style=\"overflow:scroll !important; margin:15px auto; padding:10px; background-color:#ffffdd; color:#555; border:1px solid #AAA; font-family:'Hack', 'Source Code Pro', 'Lucida Console', 'Courier', monospace; font-size:12px; line-height:15px;\">".print_r(json_decode($detailsData,true), true)."</pre>"; } else { $outputTemplate = "<pre style=\"overflow:scroll !important; margin:15px auto; padding:10px; background-color:#ffffdd; color:#555; border:1px solid #AAA; font-family:'Hack', 'Source Code Pro', 'Lucida Console', 'Courier', monospace; font-size:12px; line-height:15px;\">No data received yet.</pre>"; } return $outputTemplate; } /** * This function recieves the place data over the google api via a http request and saves it for later use * @return array|string */ private function fetchData() { // Get Values $apiKey = $this->apiKey; $placeId = $this->placeId; $dataFields = $this->dataFields; $reviewSort = $this->reviewSort; // $apiUrl = "https://maps.googleapis.com/maps/api/place/details/json?fields=$dataFields&reviews_sort=$reviewSort&reviews_no_translations=true&place_id=$placeId&key=$apiKey"; $apiUrl = "https://places.googleapis.com/v1/places/$placeId?fields=$dataFields&key=$apiKey"; $http = new WireHttp(); $responseJson = $http->get($apiUrl); if($responseJson !== false) { // Description: // The Response is in JSON Format. // To return just the result data, we have to decode the JSON into a php array, select the result object and return it // Turn JSON into php array $responseArray = json_decode($responseJson, true); // For preview purpose get only the result data and encode it back to JSON $this->detailsData = $responseJson; // return response array to frontend return $responseArray; } else { echo "HTTP request failed: " . $http->getError(); } } /** * This function recieves the place data over the google api via a http request and returns it in real time * @return array */ public function getPlaceDetails() { return ($this->fetchData()); } public function getUIKitMarkupExample() { $responseArray = $this->fetchData(); // return only the result array to the frontend and include it in a sample markup $result = $this->wire('files')->render('../modules/GooglePlaceDetails/reviews-markup',['details' => $responseArray]); return $result; } } Please note that the review results are automatically sorted by "relevant" and a maximum of 5 reviews on the 'free' (low usage) plan. The available data has also changed. See https://developers.google.com/maps/documentation/places/web-service/data-fields for the available field info. The examples in the help text will no longer work. It's simply a matter of mapping the new fields to your template. Would love to see an official upgrade of this module and hope the above helps. PS: Hardest part was navigating Google to get the API key! Thank your for letting me know! I updated the module to Version 1.0.1. 1
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now