Jump to content

ryan

Administrators
  • Posts

    17,132
  • Joined

  • Days Won

    1,656

Everything posted by ryan

  1. 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");
  2. 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.
  3. 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... } }
  4. ryan

    PHP Simple HTML DOM

    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.
  5. ggtr1138–thanks for the feedback and welcome to the forums!
  6. 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.
  7. 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.
  8. 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.
  9. 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
  10. 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.
  11. 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.
  12. 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.
  13. 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.
  14. I think that both WordPress and Joomla are only configured with one rich text (HTML) field per entry, by default. ProcessWire is different in this respect in that it's built around all custom fields and you may have as many or few of them as you want. PW's default profile is using the rich text editor for both the body and sidebar fields, so when both were populated with HTML, that's what was triggering it.
  15. I agree, it is worth asking. But I also think that a hosting environment with such tight mod_security rules is a CMS-unfriendly environment (for any CMS). It looks to me like in this particular case, Baki could probably get around this one by just limiting each template to having only a single TinyMCE field, and using Markdown for any other fields that need any kind of formatting (given that HTML won't be allowed except in POST field). But personally I would consider looking at alternate hosting providers... there are so many, and it's not really worth jumping through a lot of hoops to get around overly aggressive mod_security settings. I also worry about hosting environments that have this kind of setup because I think these strict rules are sometimes in lieu of real managed security.
  16. Good ideas Diogo! This really may be a feasible way to accomplish this type of field without major changes underneath, while also enabling any existing field to be repeatable without consideration from the fieldtype/inputfield. Or to word it differently, a simpler approach that is more powerful than the alternative. I am enthusiastic about this approach… it's something we should continue thinking and talking about.
  17. I haven't actually tried to import users yet. But it should be possible. Go ahead and add the 'title' field to your user template and populate it with something (email address?) to see if that makes the import happy. I'll plain to make the import a little smarter in this regard on the next update.
  18. PW still needs to support the PHP 5.2 branch, so we're not using namespaces yet, and I don't think that CI is either. As a result, I'm betting there's at least a chance that PW and CI would have a namespace conflict. include('./pw/index.php'); It's possible that CI may chdir somewhere else, so just as a test, you may want to put in an absolute path to PW. Given that the wire() function apparently isn't even defined, it makes me think that PW hasn't even had a chance to load yet.
  19. I agree–I think that the thread linked to by diogo is exactly it. Please let us know what you find.
  20. Thanks I received your email and logged in and was able to reproduce the issue you mentioned. I think I also have a better understanding of what is going on. Though it is rather bizarre. Try editing your homepage. You should be able to save it now. Then try going down to the 'sidebar' field, and type "<p>" tags around the the last line. Then hit save. Notice you now get a 404. It appears we are running up against a strange filter (mod_security or something with suhosin)? It is behaving with this rule set: There may be more factors to the rule, but that's it as best as I can tell. Note that you can reproduce this issue on any other page by applying that rule to any other page. I turned your sidebar field into a regular textarea just to make it easier to test. Go and type "<p>test</p>" in the sidebar field on any page, hit enter twice, and type it again, then save. You'll trigger the filter and get the 404. To test that the server really was wiping out the GET/POST vars, I tried changing your 404 page to use the Admin template, which shows debugging info. Then I saved the homepage. See the attached screenshot which shows the POST vars in the 404 page–there are none. Yet my HTTP header log shows all the vars that were POSTed and no redirects. Meaning, it appears something at the server is wiping out the vars before ProcessWire even loads, which is why it's giving a 404. Pretty bizarre. I think we're hitting up against a bad mod_security rule or some other server-side "security" filter that is getting triggered by the presence of some HTML combinations in a POST request. Another indication is that the server handles most requests pretty quickly, but as soon as a POST request has any kind of HTML in it, the POST request becomes unusually slow… as if the server is loading some other software to analyze the request before letting it go through. I believe your webhost may have made a mistake in whatever security measures they've put in place to cause this.
  21. I usually use something recursive as a simple solution if having to deal with unknown levels. I think in this case it might also help to see the full scope of results you are trying to achieve, whether visually or in markup form. But I also think the solution you posted most recently seems reasonable and good, if that achieves what you are trying to do. I thought it was interesting how you are limiting the nav to certain templates. I usually use the hidden status on a page to direct whether it should appear in that type of navigation or not. But what you are doing gives a different type of control by delegating it to the output generation, and the template, rather than the page. Nothing wrong with that, I just hadn't tried that myself yet. Oops I meant for that to be wire('page'), though passing it in the function as an argument like you did is just as good.
  22. I'll focus on your simple example because I was able to understand that one better. The most obvious way to simplify this would be to avoid repeating the same code by bundling it into a function. <?php function renderPageList($children) { $out = "<ul>"; foreach($children as $child) { $class = $child === $page ? 'class="active"' : ''; $out .= "\n<li><a href='{$child->url}' $class>{$child->title}</a></li>"; } return $out . "</ul>"; } if(count($page->parents) == 3 && $page->numChildren > 0) echo renderPageList($page->children); if(count($page->parents) > 3) echo renderPageList($page->parent->children);
  23. Thanks I received your phpinfo and have reviewed it. It looks good to me, though there are two things there that I've not tested on my own before. That is eaccelerator and suhosin. A search for these on google reveals that there are apparently a few rare conflicts with other CMSs, so it's possible there's something there. Though I think both eaccelerator and suhosin are relatively common in commercial hosting environments so if that's the issue, it's more likely to be a particular setting in the extension rather than than the extension itself. However, I think there's only a 25% chance that either of these are the issue. If you are seeing a ProcessWire 404 page (as opposed to an Apache one), then it's PW that's still handling the request. Since there's not a clear answer yet, would it be possible for you to create an account for me in your ProcessWire installation? I would like to login and reproduce it myself while watching the request headers to see if there is anything unusual I can spot. Even better would be if I could get ssh or ftp access to make and test debug edits to track down exactly what's happening–that would be a sure way to tell. But let me know what works for you. Thanks, Ryan
  24. This need has come up a couple times and I agree about the value of automatically keeping a history of such things for redirects. I will eventually make a module to do this if someone else doesn't beat me to it.
  25. We'll definitely be adding more validation rules for existing fieldtypes and this is something I've been doing incrementally and regularly. The integer fieldtype used to have min/max range options in PW1, and they'll be coming back in PW2. You may already see that some fieldtypes and inputfields carry more validation rules than others. I also think it would be good for me to get a few starting-point examples of fieldtypes/inputfields going to show you guys how simple it is to make your own too. For instance, if I need a field that has specific requirements for input, I take the route of copying one of the existing ones into /site/modules/, renaming it and modifying it to do what I need. It's easy, but not described anywhere so I'll work on a tutorial here soon.
×
×
  • Create New...