Leaderboard
Popular Content
Showing content with the highest reputation on 09/26/2024 in all areas
-
Excuse me if I am wrong, but is this not what Module::upgrade($fromVersion, $toVersion) was intended for? Or do you specifically not want to bump a version number?2 points
-
Hi, I'm trying to get some basic knowledge of Processwire module development. I've read the beginner guides and I've done a lot of research. My current use case is to extend a custom admin template for pages with an update button in the "settings" tab of this template. The update button should then update the page's data through an external API on this page using its metadata that this template type has when you edit the page in the admin. Here's the shortened PHP code (yes, I've build own classes for the hooks) of what I trying to achieve that comes along with a few problems: <?php namespace ProcessWire; class TestUpdate extends Process implements ConfigurableModule { public const XHR_PAGE_NAME = 'test-update'; public const JSON_TPL_NAME = 'json'; public static function getModuleInfo() { return [ 'title' => 'My Admin Extender', 'summary' => 'An admin module to easily bring test data up-to-date', 'version' => '0.0.1', 'permission' => 'test-update', 'permissions' => [ 'test-update' => 'View the button in the template', 'test-update-save' => 'Permission to perform the actual update from API', ], 'icon' => 'table', 'author' => 'alpham8', 'requires' => 'UpdatePlugin>=0.0.1, ProcessWire>=3.0.133', 'page' => [ 'name' => self::XHR_PAGE_NAME, 'title' => 'update-product', 'template' => 'admin' ] ]; } public function ready() { /** @var Page $page */ $page = $this->wire('page'); // add helper notices about ProCache to page and template editors if (static::isTemplate('admin', $page)) { $this->addHookAfter('ProcessPageEdit::buildFormSettings', $this, 'hookPageEdit'); } } public function hookPageEdit(HookEvent $event) { /** @var WirePageEditor $process */ $process = $event->object; $page = $process->getPage(); if (static::isTemplate('product', $page)) { // TODO: maybe add $this->user->isSuperuser() check $form = $event->return; /** @var InputfieldMarkup $field */ $field = $this->modules->get("InputfieldMarkup"); $info = HaroImportProductPages::getModuleInfo(); $field->label = $info['title']; $field->icon = $info['icon']; /** @var InputfieldButton $btn */ $btn = $this->modules->get('InputfieldButton'); $btn->attr('value', 'update product data'); if ($page->hasField('param1') && $page->hasField('param2')) { $btn->attr( 'href', '#' ); $btn->addClass('update-link'); $btn->attr('data-param1', $page->get('param1')); $btn->attr('data-url', 'https://' . $_SERVER['HTTP_HOST'] . '/processwire/test-update/save/'); } $btn->icon = 'refresh'; $btn->attr('id+name', 'UpdateProductPagesBtn'); $btn->label = 'Update product data'; $btn->collapsed = Inputfield::collapsedNever; $field->collapsed = Inputfield::collapsedNever; $field->attr('value', $btn->render()); $form->add($field); } } /** * Does user have permission? * * @param string $name * @return bool * @throws WireException */ protected function hasPermission(string $name): bool { $user = $this->wire()->user; if ($user->isSuperuser()) { return true; } return $user->hasPermission($name); } public function execute(): array|string { return 'test1'; } public function executeSave(): array|string { $input = $this->wire()->input; $testparam = $input->post('testparam'); $result = false; /** @var UpdatePlugin $module */ $module = $this->wire->modules->get('UpdatePlugin'); // possibly product number check: if ($testparam !== '' && is_numeric($testparam) && \mb_strlen($testparam, 'UTF-8') >= 4) { $result = $module->writeDataToPage($testparam); } return json_encode([ 'testparam' => $testparam, 'success' => $result === 1, ]); } } My problems or questions are: 1st The ready method doesn't get called in an admin module, instead, I need to place it in another module like this: <?php namespace ProcessWire; class UpdatePlugin extends Wire implements Module { public static function getModuleInfo() { return [ 'title' => 'UpdatePlugin', 'version' => '0.0.1', 'summary' => 'Summary for UpdatePlugin ...', 'author' => 'alpham8', 'icon' => 'table', 'autoload' => true, 'requires' => 'ProcessWire>=3.0.123', ]; } public function init() { $scriptUrl = $this->urls->$this . 'assets/product-update.js'; $this->config->scripts->add($scriptUrl); } public function ready() { /** @var Page $page */ $page = $this->wire('page'); // add helper notices about ProCache to page and template editors if (static::isTemplate('admin', $page)) { $this->addHookAfter('ProcessPageEdit::buildFormSettings', $this, 'hookPageEdit'); } } // [see above] ... } 2nd Isn't there a way to just build an ajax request that returns plain JSON without that page / template skeleton? As far as I understood it, a page is mandatory in Processwire for this. That would be fine, if we can hide the page in the admin's navbar by using css. I thought that might be possible with creating an own template that just outputs JSON, right? But how to do a template without a parent one? 3rd How can I bring or access variables from modules in pages? Just by calling it without the knowledge that they are exist on the concrete (PHP) page? 4th Isn't there a way to get the absolute URL out of $config->urls ? I'd like to avoid that insecure $_SERVER['HTTP_HOST'] call. We're currently on ProcessWire 3.0.210. An update is planned after that modules. Any help to that above question would be great. Thanks in advice, alpham81 point
-
@Jan Romero and @da², thanks for your help! I just tried it again with the selector from Jan and it gives the correct results. I didn't correctly understand the query from the original post for my situation but this helped a lot. Thanks!1 point
-
The difference between the two snippets you posted is that $pages->find() will make a database query and $page->event_locations->find() filters the repeater items already in memory (a PageArray). Confusingly, there are some features that are only available in one or the other. My guess is probably the “today” condition doesn’t work with in-memory selectors, because it doesn’t know you’re comparing dates at that point. So under the hood it’ll be something like 1726956531 >= 'today' (or maybe '09/22/2024 00:08' >= 'today' due to output formatting, I’m not sure). As @da² suggested, try this: $pages->find('template=repeater_event_locations, event_date>=today, include=all, event_locations.owner.id=' . $page->id . ', sort=event_date, sort=location.location_city') That should get all repeater items from the database which belong to $page. If you want to use $page->event_locations instead, I would suggest just foreaching them and skipping the ones you don’t want. You’re going to foreach them anyway, so you might as well do filtering and output in a single loop. But the other option should be more performant, because you’re never loading the ones you don’t need.1 point
-
Hello, I've had a look at Instagram API with Instagram Login and initial results are pretty promising. I actually found it easier to set up the app and generate an access token, and the actual API is easier to work with too. It'll take a bit of work to create a new module for this API, but I should have something ready in plenty of time. Will update here once I have something. Cheers, Chris1 point
-
Oops. Copypasta can be a bit of a blunt instrument. Now fixed. I appreciate you taking the time to let me know!1 point
-
@DrewPH Glad to hear you are enjoying to work with ProcessWire. 👍 Sorry for nitpicking, but there is a left-over image from WordPress on your newest article. 😅 https://www.eltikon.online/catenary/1 point
-
Today while working on RockCalendar I had the need to change the database schema of my daterange fieldtime to add support for recurring events. I didn't know how to do it, so I had to do some research and that took quite some time. Here's the solution that I found in FieldtypeComments.module. When developing the fieldtype you can add a database schema like this and PW will take care of creating the table and columns for you: public function getDatabaseSchema(Field $field) { $schema = parent::getDatabaseSchema($field); $schema['data'] = 'timestamp NOT NULL'; // the from timestamp $schema['foo'] = 'timestamp NOT NULL'; return $schema; } This is quite easy, but I wanted to add another column and tried this: public function getDatabaseSchema(Field $field) { $schema = parent::getDatabaseSchema($field); $schema['data'] = 'timestamp NOT NULL'; // the from timestamp $schema['foo'] = 'timestamp NOT NULL'; $schema['bar'] = 'timestamp NOT NULL'; return $schema; } No luck. You will get an error that column "bar" does not exist. Ok, so we have to modify the table somehow... But we also have to make sure that this is only done once. How to we do that? The solution is to save the schema version to the field and use that to compare versions and conditionally update the schema: public function getDatabaseSchema(Field $field) { $schema = parent::getDatabaseSchema($field); $schema['data'] = 'timestamp NOT NULL'; // the from timestamp $schema['foo'] = 'timestamp NOT NULL'; $schema['bar'] = 'timestamp NOT NULL'; $schemaVersion = (int) $field->get('schemaVersion'); $updateSchema = true; $table = $field->getTable(); $database = wire()->database; if ($schemaVersion < 1 && $updateSchema) { try { if (!$database->columnExists($table, 'bar')) { $database->query("ALTER TABLE `$table` ADD bar " . $schema['bar']); } $field->set('schemaVersion', 1); $field->save(); } catch (\Throwable $th) { $this->error($th->getMessage()); $updateSchema = false; } } return $schema; } And maybe at a later point you want to add another column "baz": public function getDatabaseSchema(Field $field) { $schema = parent::getDatabaseSchema($field); $schema['data'] = 'timestamp NOT NULL'; // the from timestamp $schema['foo'] = 'timestamp NOT NULL'; $schema['bar'] = 'timestamp NOT NULL'; $schema['baz'] = 'timestamp NOT NULL'; $schemaVersion = (int) $field->get('schemaVersion'); $updateSchema = true; $table = $field->getTable(); $database = wire()->database; if ($schemaVersion < 1 && $updateSchema) { try { if (!$database->columnExists($table, 'bar')) { $database->query("ALTER TABLE `$table` ADD bar " . $schema['bar']); } $field->set('schemaVersion', 1); $field->save(); } catch (\Throwable $th) { $this->error($th->getMessage()); $updateSchema = false; } } if ($schemaVersion < 2 && $updateSchema) { try { if (!$database->columnExists($table, 'baz')) { $database->query("ALTER TABLE `$table` ADD baz " . $schema['baz']); } $field->set('schemaVersion', 2); $field->save(); } catch (\Throwable $th) { $this->error($th->getMessage()); $updateSchema = false; } } return $schema; } 😎1 point
-
That's a very good question 😅 I think that should work as well. One benefit I see with the approach above is that we can define the $schema[] array in one place and then reuse this for the ALTER TABLE statement. Ok you could extract that array into a dedicated method as well, then you'd have basically the same with a Module::upgrade() approach. Another thing is that I find it easier to read like this: if($schemaVersion < 2) ... compared to this: if(version_compare($oldVersion, $newVersion) < 1) ... I never know which operator to use here 🙂 And module versions can be confusing, as they can be integers like 104 or version strings like 1.0.4 And last but not least this approach is what I found in FieldtypeComments which is built by Ryan, which should be argument enough 😄 But thanks for the question! If you want to try another route please let us know what you find out 🙂1 point
-
I’m assuming you do something like this: <a href="<?=$manufacturer->url()?>#siding">Get Wood</a> Go to the template settings of the $manufacturer page’s template and in the URLs tab, set the slash option to “No”: This will make the url() method generate links without the trailing slash and it will stop redirecting to the slashed version. That said, I’m pretty sure the links should work either way.1 point
-
1 point
-
Can’t you autoload your Process module? https://processwire.com/docs/modules/development/#automatically-loading-modules If you set 'autoload' => 'template=admin' in getModuleInfo() the ready() method should be called on every request within the PW admin. You can then determine if your hooks are needed as you’re already doing. I'm not sure what you mean by this, but you can use $modules->get('TestUpdate') to get an instance of your module? Yes, you can use $config->urls->admin, for example, but since you're creating a Process module, you should be able to get the entire URL using $this->getProcessPage(). That should be way to go since users can theoretically move or rename your Process page (as well as the Admin itself). If you're inside a request to the Process page, it will get you the current Page. Otherwise it will get the first page using the Process.1 point
-
Thank you @bernhard for helping optimizing the code!👍 For checking the template field I already used a guard clause, but for the template check it makes sense and is more readable to use it, too. I would like to keep the german special characters like äöüß instead of exchanging (->pageNameTranslate) them with ae, oe, ue, ss. So wire('sanitizer')->pageNameUTF8($title); should be the way to go. Right?1 point
-
Congrats and welcome to a new world of superpowers 😉 I don't have details but I'm always using pageNameTranslate: Not sure why that would work differently on new or existing pages... Instead of if(...) { ... } you can use early exit strategy which is usually cleaner in hooks. This is also called "guard clause": So you could write it like this: /* NEWS pages only: prepend date in page-name */ $wire->addHookAfter('Pages::saveReady', function($event) { // get current Page object — in this case this is the first argument for the $event object $page = $event->arguments(0); // Only for pages with news template if($page->template != 'news') return; // Test if field day exists, otherwise exit if (!$page->template->hasField('day')) { $event->message("Field 'day' is missing!"); return; } $title = $page->get('title'); // Sanitize title and substitute special characters $optimizedTitle = wire('sanitizer')->pageNameUTF8($title); //$optimizedTitle = wire('sanitizer')->pageName($title, Sanitizer::okUTF8); // or translate option $date = wireDate('Y-m-d', $page->day); // Set output formatting state off, for page manipulation $page->of(false); $page->name = $date.'-'.$optimizedTitle; //$page->save('name'); $event->message("New saved name is $page->name"); //$event->message("Path of new saved page is $page->path"); }); The difference is minimal here but using if(...) { ... } is usually harder to read, because you could have an else { } somewhere further down the hook, so when trying to understand your hook you have more workload for your brain. When using if($template != 'whatever') return; you instantly know the hook only applies to 'whatever' templates.1 point
-
It depends on what you want to achieve but basically with lazycron you specify a time interval target and whenever someone loads a page the lazycron checks if the interval has passed and then triggers its function. This means that depending on where you put the hook, it may check more often or not: if you put the code in a template, then the lazycron is checked only when a guest is visiting a page with this template. However if you put the hook in init.php (or ready.php) then the lazycron will be checked for every pageview, even the admin ones.1 point
-
After strolling aroung the chaotic interface of the Facebook Developers platform, I could find the place to create an Access Token. Seems you need to add the app "Instagram API with Instagram Login" and not anymore the "Instagram Basic Display". Once you have the access token and authorize it: $http = new WireHttp(); $http->setData([ // Choose the fields you want; you will need the user_id at least 'fields' => 'user_id,username,followers_count,follows_count,media_count,profile_picture_url', 'access_token' => $igAccessToken, ]); $response = $http->get('https://graph.instagram.com/v20.0/me'); if ($response !== false) { // The answer comes as JSON, I cast it to an array so I could store it in ProcessWire's cache $profile = (array)json_decode($response); } else { // Your error procedure here } The response will be like: [ 'user_id' => '17941778910156051', 'username' => 'yourusername' 'followers_count' => 1560 'follows_count' => 294 'media_count' => 231 'profile_picture_url' => '...' 'id' => '3317109405575570' ] Pay attention to the "user_id": it is a scoped, temporary ID (valid for one hour, I guess) that you'll need to insert on the URL to finally get the posts on a subsequent request, like this: $newHttp = new WireHttp(); $newHttp->setData([ 'fields' => 'id,caption,media_url,media_type,timestamp,permalink,thumbnail_url', // Again, choose what you need 'limit' => '14', // Optional 'access_token' => $igAccessToken, // The same as before ]); $newResponse = $newHttp->get('https://graph.instagram.com/v20.0/'.$profile['user_id'].'/media'); if ($newResponse !== false) { // Once again this is my preference, you can use it as an object $posts = (array)json_decode($newResponse); } else { // Your error procedure here } And by doing this I am not needing your module anymore. Hope this is useful, and hope they don't change things too much soon.1 point
-
Well it's been trundling along for the last year on the British Record Shop Archive using the above method. We've had a couple of thousand comments ... and I honestly don't remember having a single bit of spam (famous last words there) so I'd say it's been pretty successful. I'm planning to redo the comments forms on the BRSA to allow users to submit images, so I'm probably going to end up with a custom form rather than the above, but it's worked so far.1 point
-
Change Default Language to be None-English | Walk Trough When you start a new (single) language site and the default language shouldn't be English, you can change it this way: Go to the modules core section: Select the Language ones by the filter function: We have four language related modules here, but for a single language site in none english, we only need the base module, named "Languages Support". So go on and install it. After that, you can leave it, ... ... and switch to the newly created Language section under SETUP: Select the default language Enter your new language name or its Shortcut and save the page. I will use DE for a single language site in german here as example: Now I go to the ProcessWire online modules directory, down to the subsection for language packs and select and download my desired (german) one: After downloading a lang pack as ZIP, I go back into my SETUP > LANGUAGES > default language page in admin, select the downloaded lang pack ZIP and install it: After the ZIP is uploaded, the files are extracted and installed, most of my screen is already in the new default language. To get all fully switched, we save and leave that page, ... ... and completely logout from the admin. Now, of course, we directly login back, ... ... and see, that now also the cached parts of the admin have switched to the new default language. ? That was it for a single language site in none english. If you want to have a multi language site, just add more languages to the SETUP > LANGUAGES section. When using a multi language site, I think you also want to use multi language input fields, and maybe different page names for your language page pendents. If so, you need to go into MODULES > CORE > filter LANGUAGE and install what you need or want to use of it, (if not already done). Thanks for reading and happy coding, ?1 point
-
I assume that you know how to show field label on front (there is few options), one option is: echo $page->fields->summary->label; // for field with name "summary" But today I had a problem how to show field label depending on the current page template (example: in template "member", "summary" field label is override to "Personal info"). In that case, previous example will not give you desired result. To get field label in that case, this is what works: $page->getField('summary')->label; // result would be "Personal info" Maybe this is helpful for someone. Regards.1 point