-
Posts
17,102 -
Joined
-
Days Won
1,643
Everything posted by ryan
-
Yes, sorry I missed that step– The cache field needs to be assigned to the template where it's used, just like any other field.
-
I wish my wife would pick up knitting– If she is home I'm in trouble if I get near the computer. In fairness, she's been quite supportive in me cutting my client work 50% to focus on PW. But I do miss the evening hacking.
-
It's used by the 'process' field on the admin template. That lets you select what module should be executed on the page. I figured it might come in handy for something else in the future, so left it as a more abstract module selection field. But to date, I've only ever used it for that 'process' field.
-
Progress on ProcessWire 2.2 and overview of multi-language support
ryan replied to ryan's topic in Multi-Language Support
My es-ES is probably very flawed. I'm just using Google Translate. But my wife knows Spanish so figured it would be a good one to test out on her once I had it all working. -
Looks like I implemented the ConfigurableModule interface for this module, but didn't provide an implementation. I have a little note to myself in there that says: "Add custom config options (coming soon, just a placeholder for now)". But I honestly don't know what I had intended to add there.
-
To use a cache field: 1. Go to Setup > Fields > Add New Field 2. Name it "keywords" (or something else if you prefer) 3. In the field type selection, select "cache". If you don't see it there, go to Modules > Fieldtype > Cache > Install, and then go back to step 1. 4. Save the field. In the details tab, it will ask you to select what fields you want to be part of the cache. Select them and click Save. It will build the cache right there. Now you can use your 'keywords' field for searches. Another benefit: If you make it 'autojoin' (if that's appropriate for your use), or you reference it before other fields in the cache (in your API use) then it will use the cached version. Meaning same data loaded but with fewer queries (faster and less overhead).
-
I just used wire('page') as an example, but of course it applies to any API variable. Also, speaking of wire('page'), I just committed an update that makes the ready() function part of the module interface. Now you can add a ready() function to any autoload module, and that function will be called when the API is fully ready (with the $page variable). I figured this would be more convenient than hooking ProcessPageView::ready, but either will work. I've always liked that markItUp editor. I even had an Inputfield for it in PW1. Will have to bring it back in PW2 at some point.
-
I've been looking through the code and it looks very nicely put together. A few comments on things I wasn't sure about: <?php if (!$post->discussions_message) wire('session')->redirect("./?discussions_title={$post->discussions_title}"); if (!$post->discussions_title) wire('session')->redirect("./?discussions_message={$post->discussions_message}"); There might be issues sending all that copy through a redirect header, especially straight out of the $post input. I'd recommend instead saving it to a $session variable and then just redirecting to something like "./?missing_something=1", and then pull your discussions_title or discussions_message from the $session var instead. With any API vars, these are all equivalent: <?php $page = Wire::getFuel('page'); // older style for static use $page = $this->fuel('page'); // older style for internal API $page = wire('page'); // newer style for external API, internal or static use $page = $this->page; // internal API internal API = classes descended from Wire external API = other classes or other applications or command line API It doesn't matter which you use, but just wanted to mention they are all the same because I saw you 3 different versions. The $this->page is the 'prettiest', but relies on the class or one of it's parent class(es) checking the fuel and matching it. This is something that WireData (the class you are using) does, so you can use that format if you prefer it. That's the version I usually use, though technically it has a little more overhead than the rest. If you want one that you can use anywhere regardless of context, I would just use the wire('page') function. For your render() methods, I recommend adding an output formatter to your discussion_message. It's likely that title will already have an output formatter applied to it (htmlentities by default), though maybe good to double check. Here's how you would add a textformatter during your install (second to last line): <?php $field_message = new Field(); $field_message->type = $this->modules->get("FieldtypeTextarea"); $field_message->name = 'discussions_message'; $field_message->label = 'Message'; $field_message->textformatters = array('TextformatterEntities'); // or TextformatterMarkdownExtra $field_message->save(); Double check that it works (written in browser). Another option might be to dispense with textformatters completely and send any output through htmlentities() or the filter_var() that you are using in the inputs.
-
You were right. I'd pasted in the wrong thing. It works perfectly now. Sorry about that. Great examples, I'm very impressed with this–great module you've put together! Thanks, Ryan
-
ProcessWire won't do a partial match for multiple words with a single operator, i.e "Mike Hunt" matching "Michael Hunter". However, this is easy to solve: $pages->find("keywords*=Mike, keywords*=Hunt"); This is how you might handle it from a submitted search query: <?php $selector = ''; $words = explode(' ', $this->input->get->phrase); foreach($words as $word) { $word = $this->sanitizer->selectorValue($word); if($word) $selector .= "keywords*=$word, "; } $pages->find($selector); This is correct. If the term "notfall" is present in the search but not in any of the pages, it's not going to match any pages. The %= performs the same phrase match as *= except that it uses a non-indexed MySQL 'LIKE' search, which is slower but can match stopwords and words shorter than what MySQL will fulltext index. Like with *=, if you wanted it to match parts of multiple words then you'd have to split it like in the example above. Either of these will work: $pages->find("keywords*=zeit, keywords*=bern"); $pages->find("keywords%=zeit, keywords%=bern");
-
Site copied to development server -> FieldType does not exist....
ryan replied to formmailer's topic in General Support
I think it's most likely the modules cache. Delete these files and see if that fixes it? rm /site/assets/cache/Modules.*.cache -
Works for me too. Hit reload in your browser twice, just in case an older JS file might still be cached. Then try again. If it's still not working, let us know the info Soma mentioned (browser and PW version).
-
Progress on ProcessWire 2.2 and overview of multi-language support
ryan replied to ryan's topic in Multi-Language Support
That is definitely planned. Sounds good, I will take a look at that. What we have now may be close. The way it's currently setup is that you have a files field that you can drag in a ZIP of a full translation, or individual translation files. Then you can choose to edit the translations in the file if you want to. Or, you can create a new one just by clicking the 'new' link at the bottom. If you create a new one, then it'll appear in your files list and you can download and share it with others too. See the attached screenshots. I may add a URL input to the upload area of the files fieldtype so that one could just paste in the URL directly to the file (at processwire.com) and add it that way too. Yes, I think this is the way many are already setting up multi language sites with PW. Thanks Pete! And I'll take care of en-US This is great, I can't wait to get this finished and get these translations going. Thanks again to all of you that are offering to make translations. -
Multiple word searches should work fine over multiple fields using this syntax: $pages->find("title|body|sidebar*=Something"); Whether to use *= or ~= or %= just depends on what you need. If you want to match an exact phrase, then you'd use *= or %=. If you want to simply match all the words regardless of where they appear in a field, then you'd use ~=. More description here in the 'Selector Operators' section: http://processwire.com/api/selectors/ If you start needing to search lots of fields, then you may want to bundle them up into a 'cache' field. The advantage of a cache field is that there are fewer things for PW and MySQL to join together, so it's more efficient and may be faster. For example, you could create a cache field called 'keywords' that compiles the data from: title, body, sidebar, summary, description or whatever other fields you wanted it to. Then you could just search the cache field, like this: $pages->find("keywords*=Something"); Edit: bundling them into a cache field also gives you the possibility of a ~= being able to match one word in one field and another word in another field.
-
Very cool! Nice work Antti! I'm just playing around with it here and wondering about the discussion-reply template. Is this one that we should create the file for too, and is there anything specific that needs to go in that? Currently when I click on a reply, I get a 404. The other question I had is that after submitting a topic, I get another blank form and wasn't sure if that was intended or not. If I type something in it and submit, then it shows up as a reply to the original one posted. Thanks, Ryan
-
How does Comments module processInput works?
ryan replied to apeisa's topic in Module/Plugin Development
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... -
Thanks Soma. Hopefully it works I just typed in the browser and didn't test it locally. Let me know if it doesn't work, as I don't know if I've ever actually used this particular type of search in my sites. But I think it should work.
-
How does Comments module processInput works?
ryan replied to apeisa's topic in Module/Plugin Development
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; -
Here's what draws the form for the skyscrapers search: <form id='skyscraper_search' method='get' action='<?php echo $config->urls->root?>search/'> <h3>Skyscraper Search</h3> <p> <label for='search_keywords'>Keywords</label> <input type='text' name='keywords' id='search_keywords' value='<?php if($input->whitelist->keywords) echo htmlentities($input->whitelist->keywords, ENT_QUOTES); ?>' /> </p> <p> <label for='search_city'>City</label> <select id='search_city' name='city'> <option value=''>Any</option> <?php // generate the city options, checking the whitelist to see if any are already selected foreach($pages->get("/cities/")->children() as $city) { $selected = $city->name == $input->whitelist->city ? " selected='selected' " : ''; echo "<option$selected value='{$city->name}'>{$city->title}</option>"; } ?> </select> </p> <p> <label for='search_height'>Height</label> <select id='search_height' name='height'> <option value=''>Any</option> <?php // generate a range of heights, checking our whitelist to see if any are already selected foreach(array('0-250', '250-500', '500-750', '750-1000', '1000+') as $range) { $selected = $range == $input->whitelist->height ? " selected='selected'" : ''; echo "<option$selected value='$range'>$range ft.</option>"; } ?> </select> </p> <p> <label for='search_floors'>Floors</label> <select id='search_floors' name='floors'> <option value=''>Any</option> <?php // generate our range of floors, checking to see if any are already selected foreach(array('1-20', '20-40', '40-60', '60-80', '80+') as $range) { $selected = $range == $input->whitelist->floors ? " selected='selected'" : ''; echo "<option$selected value='$range'>$range floors</option>"; } ?> </select> </p> <p> <label for='search_year'>Year</label> <select id='search_year' name='year'> <option value=''>Any</option> <?php // generate a range of years by decade, checking to see if any are selected for($year = 1850; $year <= 2010; $year += 10){ $endYear = $year+9; $range = "$year-$endYear"; $selected = $input->whitelist->year == $range ? " selected='selected'" : ''; echo "<option$selected value='$range'>{$year}s</option>"; } ?> </select> </p> <p><input type='submit' id='search_submit' name='submit' value='Search' /></p> </form> And here's the code that processes it and performs the search: <?php /** * This template looks for search terms as GET vars and formulates a selector to find matching skyscrapers * */ // most of the code in this template file is here to build this selector string // it will contain the search query that gets sent to $skyscraperList $selector = ''; // we use this to store the info that generates the summary of what was searched for // the summary will appear above the search results $summary = array( "city" => "", "height" => "", "floors" => "", "year" => "", "keywords" => "", ); // if a city is specified, then we limit the results to having that city as their parent if($input->get->city) { $city = $pages->get("/cities/" . $sanitizer->pageName($input->get->city)); if($city) { $selector .= "parent=$city, "; $summary["city"] = $city->title; $input->whitelist('city', $city->name); } } // we are allowing these GET vars in the format of 999, 999-9999, or 999+ // so we're using this loop to parse them into a selector foreach(array('height', 'floors', 'year') as $key) { if(!$value = $input->get->$key) continue; // see if the value is given as a range (i.e. two numbers separated by a dash) if(strpos($value, '-') !== false) { list($min, $max) = explode('-', $value); $min = (int) $min; $max = (int) $max; $selector .= "$key>=$min, $key<=$max, "; $summary[$key] = (substr($max, 0, 3) == '999') ? "$min and above" : "$min to $max"; $input->whitelist($key, "$min-$max"); // see if the value ends with a +, which we used to indicate 'greater than or equal to' } else if(substr($value, -1) == '+') { $value = (int) $value; $selector .= "$key>=$value, "; $summary[$key] = "$value and above"; $input->whitelist($key, "$value+"); // plain value that doesn't need further parsing } else { $value = (int) $value; $selector .= "$key=$value, "; $summary[$key] = $value; $input->whitelist($key, $value); } } // if there are keywords, look in the title and body fields for the words. Note in our selector // we are using the "~=" operator, rather than the "*=" operator, which means that we want to // match all the words, but they don't have to be in a phrase right next to each other. if($input->get->keywords) { $value = $sanitizer->selectorValue($input->get->keywords); $selector .= "title|body~=$value, "; $summary["keywords"] = htmlentities($value); $input->whitelist('keywords', $value); } // display a summary of what was searched for above the search results $content = "<ul id='search_summary'>"; $browserTitle = "Skyscrapers - "; foreach($summary as $key => $value) { if(!$value) continue; $key = ucfirst($key); $content .= "\n\t<li><strong>$key:</strong> $value</li>"; $browserTitle .= "$key: $value, "; } $content .= "\n</ul>"; $skyscrapers = $pages->find($selector); $content .= $skyscrapers->render(); // substitute your own output code $browserTitle = rtrim($browserTitle, ", "); $headline = "Skyscraper Search"; include("./main.php");
-
You are right about how to do it. This is all it should take: <?php $contacts = $pages->find("body*='search term', template=emergency-contact"); $parents = $pages->find("emergency-contacts=$contacts"); emergency-contact = your template name (optional) emergency-contacts = your page reference field name $parents will have all the pages that had one of the emergency contacts selected with the given search term.
-
How does Comments module processInput works?
ryan replied to apeisa's topic in Module/Plugin Development
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... } } -
Great link! I sure could have used this several times in the past. I've always just used regexps for scraping, and this looks so much simpler.
-
ggtr1138–thanks for the feedback and welcome to the forums!
-
It seems like we've got good ideas and momentum here and I'm very enthusiastic about getting this setup. I need to focus on getting the multi language features finalized first, but would like to start working on this afterwards while it's still fresh on everyone's mind.
-
I agree! Thanks everyone for all the great feedback! When other people read these reviews, they are very likely to give ProcessWire a try too, so it really makes a difference. Thanks again for taking the time to write these reviews.