Jump to content
apeisa

How does Comments module processInput works?

Recommended Posts

I am building new module little bit similar to comments, and I am wondering how does it work. The part that is confusing for me is that how does it know when to process input? Only thing that we need in templates is <?php echo $page->comments->render(); and yet somehow PW knows that it should process post before generating the output.

Share this post


Link to post
Share on other sites

What you are looking for is in this file:

/wire/modules/Fieldtype/FieldtypeComments/CommentForm.php

Though the process is the same regardless of what module or file it comes from. The render() method sends the output for the form, but it also has a line checking to see if the form has been submitted. Something like this:

<?php
if($input->post->submit_comment) {
    // comment was submitted
    $this->processInput(); 
}
// then generate output

Note that this won't work if your page is cached. So you'd either have to disable cache on comments page, or set it to recognize one of the submitted variables (like page_id) as a var to disable comments, or delegate another page dedicated to saving comments.

Share this post


Link to post
Share on other sites

Thanks Ryan - I don't know how I missed that. This helps a lot!

Share this post


Link to post
Share on other sites

Ryan, when we build the adminbar module you wrote in the init() comments this:

* The init method of a module is called right after ProcessWire is bootstrapped, when all

* API vars are ready. Whereas the __construct() is called DURING bootstrap, so the init()

* method is a better place to attach hooks to API vars.

But it seems that I cannot access $this->page in module's init() method? Also wire('page') returns null. This is just a confirmation - I know how I can go ahead, but wanted to ask this anyway.

Share this post


Link to post
Share on other sites

If its an autoload module Page actually isn't ready at init() because otherwise hooks couldn't catch page/field load events if it was. So at init you may want to hook Page::render or there are lots of other options too. Tell me more about your need here and I should be able to suggest a good one to hook.

Share this post


Link to post
Share on other sites

I used this one:

$this->addHookBefore('Page::render', $this, "processInput");

How much there is overhead using autoload modules? I am using that hook to process form inputs and only want to do those on certain templates - so most of the time processInput just returns false. I want to use autoload here, since it is very important to do valid redirect here after post (so no "re-posts" are possible).

EDIT: fixed addHookAfter to addHookBefore

Share this post


Link to post
Share on other sites

I wouldn't worry about overhead there. The only overhead comes from what happens in the hook, so if you are just doing a simple check before deciding whether to execute something in your hook you should be good. If you are processing forms, you could always scan that there is a post request, or some post var present before adding the hook. When I'm back at the computer tmrw I'll see if there is a better hook to use, but I think what you've got sounds good so far.

Share this post


Link to post
Share on other sites

Ok. I actually get something released soon (this will be open source module), just need to polish a little bit and add install() and uninstall functions. Then I should have pretty stable and well working version ready.

Share this post


Link to post
Share on other sites

Sounds great, can't wait to see what you are working on with this. I also did some PW work for a few hours this weekend, continued progress on the multi language stuff.

Share this post


Link to post
Share on other sites

I'm back at the computer and looking at hooks. I think that Page::render is still a good way to go. The only thing you need to consider here is that it's possible for it to be called more than once during a request. But that only happens if the API is being used to render the output of other pages too. I do that sometimes... for instance my 'ad' template contains the logic to select an ad and renders all the markup needed to display an ad, so main template calls $ad->render() to output the ad in the sidebar.

So if you want a hook that's guaranteed to only be called once without you having to check anything, then you can hook into one of these:

ProcessPageView::execute – $page will be available in an 'after' hook. This hook also returns the same rendered output that Page::render does (in $event->return) except that it only does it for the actual page that was requested.

ProcessPageView::finished – called when the request is finished and output has been sent. This is a good place to put time-expensive operations, as it doesn't hold up the request.

Also, I just added committed another… Your question got me thinking that we don't yet have the perfect hook for this. Ideally, we need the opposite of a finished() hook–something that can only be called once and is the first thing called when $this->page is ready to be used. Using a before(Page::render) hook is roughly the same, except that is can potentially be called more than once in a request. So I've just added this hook in the latest commit:

ProcessPageView::ready – called when the $page API var is ready and before the page's output is rendered. You'd use this in the same place that you would use a before(Page::render) hook, except that this one is guaranteed to only be called for the $page that initiated the request.

Share this post


Link to post
Share on other sites

It seems that the last one is just perfect. Will try this soon.

Share this post


Link to post
Share on other sites

Ryan: this new hook works perfectly!

Other thing: only thing keeping me from releasing this little "beauty" is that to get to coding quickly I build fields (4) and templates (3) by hand. I started to code those through API, but soon realized that it might be much faster to do some kind of export/import here...

Fields are pretty easy, since each of them have their own tables and no required relations to other tables, right?

Templates aren't too hard either. I have few relations there (template ID:s are different on different sites):

{"childTemplates":[45],"parentTemplates":[43],"allowPageNum":1,"slashUrls":1}

But those parts I could easily add after the import. Is there any downsides on my thinking? Should I do these through API or just DB import?

EDIT: Ok, I think I create templates through API, since there is fieldgroups_id relation also. Field export/import seems to be very simple.

EDIT2: Started doing fields import and at some point noticed that each fields_table is almost identical, after that I noticed that there is actually fields table also, which actually keeps all the settings etc. So it seems that this might be easiest to just build fields & templates through API. It will be great stuff when we got the templates export / import stuff ready - it will make building & sharing modules also faster.

Share this post


Link to post
Share on other sites

While importing may work fine here, I think it's best to use the API for this stuff because it provides the opportunity for all the operations to be hooked or tracked. Plus, if anything changes in DB structure for a field, then you won't have to worry about that if you are using the API to create them. I'm actually doing the exact same thing today as I build the installer for the Languages Support. It has to create templates, fields and pages and is rather lengthly. I actually have moved the installer to it's own file since it's something that's only needed once. Not sure if it helps to look at, but here it is:

<?php

/**
* Installer and uninstaller for LanguageSupport module
*
* Split off into a seprate class/file because it's only needed once and 
* didn't want to keep all this code in the main module that's loaded every request.
*
* ProcessWire 2.x 
* Copyright (C) 2011 by Ryan Cramer 
* Licensed under GNU/GPL v2, see LICENSE.TXT
* 
* http://www.processwire.com
* http://www.ryancramer.com
*
*/

class LanguageSupportInstall extends Wire {

        /**
         * Install the module and related modules
         *
         */
        public function ___install() {

                $configData = array();

                if($this->templates->get(self::languageTemplateName))
                        throw new WireException("There is already a template installed called 'language'");

                if($this->fields->get(self::languageFieldName))
                        throw new WireException("There is already a field installed called 'language'");

                $adminPage = $this->pages->get($this->config->adminRootPageID);
                $setupPage = $adminPage->child("name=setup"); 
                if(!$setupPage->id) throw new WireException("Unable to locate {$adminPage->path}setup/"); 

                // create the languages parent page
                $languagesPage = new Page(); 
                $languagesPage->parent = $setupPage; 
                $languagesPage->template = $this->templates->get('admin'); 
                $languagesPage->process = $this->modules->get('ProcessLanguage'); // installs ProcessLanguage module
                $languagesPage->name = 'languages';
                $languagesPage->title = 'Languages';
                $languagesPage->save();
                $configData['languagesPageID'] = $languagesPage->id; 

                // create the 'language_files' field used by the 'language' fieldgroup
                $field = new Field();   
                $field->type = $this->modules->get("FieldtypeFile"); 
                $field->name = 'language_files';
                $field->label = 'Language Translation Files';   
                $field->extensions = 'json';
                $field->maxFiles = 0; 
                $field->inputfieldClass = 'InputfieldFile';     
                $field->unzip = 1;      
                $field->descriptionRows = 1; 
                $field->save();

                // create the fieldgroup to be used by the language template
                $fieldgroup = new Fieldgroup(); 
                $fieldgroup->name = self::languageTemplateName;
                $fieldgroup->add($this->fields->get('title')); 
                $fieldgroup->add($field); // language_files
                $fieldgroup->save();

                // create the template used by Language pages
                $template = new Template();     
                $template->name = self::languageTemplateName;
                $template->fieldgroup = $fieldgroup; 
                $template->parentTemplates = array($adminPage->template->id); 
                $template->slashUrls = 1; 
                $template->pageClass = 'Language';
                $template->pageLabelField = 'name';
                $template->noGlobal = 1; 
                $template->noMove = 1; 
                $template->noChangeTemplate = 1; 
                $template->nameContentTab = 1; 
                $template->save();

                // create the default language page
                $en = new Language();
                $en->template = $template; 
                $en->parent = $languagesPage; 
                $en->name = 'en';
                $en->title = 'English'; 
                $en->save();
                $configData['defaultLanguagePageID'] = $en->id; 

                // save the module config data
                $this->modules->saveModuleConfigData('LanguageSupport', $configData); 
                
                // install related modules
                $translator = new LanguageTranslator($en); 
                $translator->install();
                $this->modules->get('ProcessLanguageTranslator'); 

                // install 'language' field that will be added to the user fieldgroup
                $field = new Field(); 
                $field->type = $this->modules->get("FieldtypePage"); 
                $field->name = self::languageFieldName; 
                $field->label = 'Language';
                $field->derefAsPage = 1;
                $field->parent_id = $languagesPage->id;
                $field->labelFieldName = 'name';
                $field->inputfield = 'InputfieldSelect';
                $field->required = 1;
                $field->flags = Field::flagPermanent; // once working, make it Field::flagSystem; 
                $field->save();

                // make the 'language' field part of the profile fields the user may edit
                $profileConfig = $this->modules->getModuleConfigData('ProcessProfile');
                $profileConfig['profileFields'][] = 'language';
                $this->modules->saveModuleConfigData('ProcessProfile', $profileConfig);

                // add to 'user' fieldgroup
                $userFieldgroup = $this->templates->get('user')->fieldgroup;
                $userFieldgroup->add($field);
                $userFieldgroup->save();

                // update all users to have the default value set for this field
                foreach($this->users as $user) {
                        $user->set('language', $en);
                        $user->save();
                }
        }

        public function ___uninstall() {
                // undo everything up there, later... 
        }
}

Share this post


Link to post
Share on other sites
Plus, if anything changes in DB structure for a field, then you won't have to worry about that if you are using the API to create them.

Yep, that is true. It's not that big job, probably just 15 mins, but every time there is "boring" stuff ahead, I start to look alternatives :)

Thanks for the code, will definitely use it as a reference!

Share this post


Link to post
Share on other sites

Is there simple and 100% sure way to get /access/users/ page id? Need to set that as a parent_id for one of my fields (Page).

Share this post


Link to post
Share on other sites

There are a few ways you could do this, but the 100% sure way would be:

$usersPage = $this->pages->get($this->config->usersPageID); 

Another way to go:

$usersPage = $this->user->parent;

Share this post


Link to post
Share on other sites
wire('pages')->get('/processwire/access/users/')->id;

This won't work if someone has renamed their admin page. Though I don't think many people actually do rename their admin page, but just saying... :)

Share this post


Link to post
Share on other sites

I know, that is why I asked for help for this :) I updated that already on my local copy, but need re-install my module to make sure it works (that I didn't make a typo etc).

EDIT: And it works, I update the module here on forums, will put it to GitHub soon.

Share this post


Link to post
Share on other sites

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 account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...