-
Posts
17,307 -
Joined
-
Days Won
1,725
Everything posted by ryan
-
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.
-
In this case you are modifying content at runtime to change it before it's output. You could setup something in your header include (if you using one) to take care of all your fields at once. Or you could create a Textformatter module to do it for you. This one would be particularly simple: /site/modules/TextformatterResponsiveImages.module <?php class TextformatterResponsiveImages extends Textformatter { public static function getModuleInfo() { return array( 'title' => "Responsive Images", 'version' => 100, 'summary' => "Remove the width tags from images so they can be responsive." ); } public function format(&$str) { $str = preg_replace('/ (width|height)=.\d+./', '', $str); } } Place in /site/modules/, install it, and then select it for any of your fields where you want it to apply.
-
Sorry about that Digitex, I should have thought to update that stuff before, so thank you for letting me know. I have added an Upgrading section to the README file, and have also updated this upgrade thread: http://processwire.com/talk/index.php/topic,58.0.html Did you come across any other places I might have missed? Thanks, Ryan
-
How does Comments module processInput works?
ryan replied to apeisa's topic in Module/Plugin Development
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. -
How does Comments module processInput works?
ryan replied to apeisa's topic in Module/Plugin Development
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. -
How does Comments module processInput works?
ryan replied to apeisa's topic in Module/Plugin Development
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. -
How does Comments module processInput works?
ryan replied to apeisa's topic in Module/Plugin Development
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.