-
Posts
695 -
Joined
-
Last visited
-
Days Won
9
psy last won the day on April 21
psy had the most liked content!
Contact Methods
-
Website URL
http://www.clipmagic.com.au
Profile Information
-
Gender
Not Telling
Recent Profile Visitors
9,647 profile views
psy's Achievements

Hero Member (6/6)
1k
Reputation
-
Is single-step paid registration/membership possible?
psy replied to GeorgeP's topic in General Support
Short answer is yes, for example you could use FormBuilder (Pro module) to collect user details, with a "Stripe Payment" action to collect payment. Then, use a hook on successful payment to create the user account and email the new user with the relevant info- 1 reply
-
- 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!
-
Thank you!
-
Thanks for reporting. My solution followed all the available advice. Times change... pun intended 😉
-
Another thing to check... The WebAuthn spec is very specific. If in the LoginPassKey module config field "Host name" by default is "mysite.com" and your .htaccess file changes it to "www.mysite.com" (or vice versa), the check will fail. The "Host name" field, aka "Relying Party" MUST match the URL served to the user.
-
Hi @Pavel Radvan One more thing to check before changing any code... Is your /lpk-api/ page viewable/accessible to guests on the frontend? It MUST be in order for the process to work. This page is a very simple API. If you're looking for something more sophisticated, I recommend using the AppAPI module instead. See the LoginPassKeyAppApi.php file in the examples folder for instructions.
-
@Pavel Radvan more... If that doesn't work, then it points to a Windows Hello config issue. I've made the module as broad as possible, to allow hybrid platform logins. There is no Windows OS for mobile, and while the doco indicates that WebAuthn SHOULD work on Windows Hello, to quote a Reddit article, "Webauthn is not nearly as confusing on other platforms.".
-
Thanks @Pavel Radvan I think I know what the problem may be. When you get a chance to test, please: Grab the latest version of the module from GitHub - all the files Open your browser dev tools to the console tab on the admin login page Go through the process again The console tab (and under the login form) should display a message about what happened. I suspect it's saying "Sorry, your browser does not support this feature" because of the way Windows Hello handles passkeys. If this is the case, in LoginPassKey.js, comment out the following: // check browser support if (!window.fetch || !navigator.credentials || !navigator.credentials.create) { $data = { fn: 'end', next: 'end', errno: 1 } return await connectPost(data,url).then( (res) => { if (res.end) return res } ) } Please try again and let me know. Appreciate your help 🙂
-
Hi @Pavel Radvan I'm using the module successfully on a number of sites. Today I launched a new site ported from my dev server. The PassKey was for admin only. After logging in with email, then deleting the old passkey (Access->PassKeys) and changing the Host Name in the LoginPassKey module config, logging out and logging again, I was invited to register a new passkey. It registered and saved to the db. I logged out and successfully logged in with the PassKey. Truly curious to know why it's not working for you. What devices are you using? In my case it's a MacBook Pro and the PassKey is saved in Apple KeyChain, ie the key is saved on my MacBook and because Apple KeyChain federates, I can log in with my Face ID on my iPhone.
-
That's great! The popup shows you've already passed some tests: Your device supports WebAuthn Your user role is permitted to use WebAuthn There is no matching registration in the database and you're being invited to register Couple of things to check: Where there any errors in the log? I forgot to include, delete the lpk-api template after deleting the page when manually uninstalling (will edit the post). Please check that the template for the lpk-api page is called lpk-api, and not for example lpk-api-1 Are you using SSL? This is requirement for WebAuthn
-
Hi Yes, please. If you get the error again, it's because the module didn't install correctly last time and so wasn't uninstalled properly. To fix: Delete or rename the folder in site/modules to .LoginPassKey and reload the page to get you back into the admin area Go to Modules and Refresh You should get a warning LoginPassKey and ProcessLoginPassKey exist in the database but the files could not be found For each, click the Edit link and check the box to remove the entries from the modules database table Places to look for leftover bits in the admin area: - /pages/LoginPassKey API - delete this page - /setup/templates - delete the lpk-api template - /admin/access/passkeys - delete this page - empty the trash to ensure they're gone for good You should now have a clean site to reinstall. PS: you can use this process to clean up any modules that failed to uninstall correctly.
-
Question... When using LoginPassKey on the frontend, eg with LoginRegisterPro, if the passkey isn't recognised, the user is prompted to use their password. I deliberately hid this message for admin logins in keeping with Ryan's choice to not notify what went wrong. Would it be helpful for passkey admin login attempts to be warned "Failed to verify your passkey. Please use your password to log in."? After installing and configuring the module, the admin must logout, then re-login using their password as the passkey isn't yet registered. On logging-in with their password, and if their device supports it, the admin is asked to register their passkey. Subsequent logins with passkey will work.
-
I'm looking into it now. By the way, the error '1170 BLOB/TEXT' is puzzling too. The 'credential_id' column is not indexed. The only key in the table is the user_id with a fixed length. Here are the two functions that seem to be the culprits: public function getTableName() : string { return wire('sanitizer')->pageName($this->className()); // could probably be changed to: return $this->className(['lowercase']); } protected function _createTable() { $sql = "DROP TABLE IF EXISTS `" . $this->getTableName() ."`;" . "CREATE TABLE `" . $this->getTableName() . "`(". "id INT(10) NOT NULL AUTO_INCREMENT, " . "user_id INT(10) UNSIGNED NOT NULL, " . "credential_id TEXT NOT NULL UNIQUE, " . "public_key TEXT NOT NULL UNIQUE, " . "created TIMESTAMP NOT NULL default CURRENT_TIMESTAMP, " . "PRIMARY KEY (id), " . "INDEX user_id_index (user_id) " . ") CHARSET=utf8"; $db = $this->getDatabase(); return $db->exec($sql); } One thing to try is to remove 'UNIQUE' from the 'credential_id' and 'public_key' columns. Maybe MYSQL has trouble ensuring BLOB/TEXT fields are unique? Uninstall and reinstall. Please let me know if you spot any obvious mistake. 🙂 Will get back to you asap. The 'UNIQUE' constraint on the 'credential_id' and 'public_key' columns is definitely an issue with later versions of MYSQL. Updated on GitHub.
-
Thanks again Pavel, appreciate you taking the time to help sort out the issue.
-
The table name should be 'loginpasskey', not 'pw.loginpasskey'. This may be the clue