Jump to content

ryan

Administrators
  • Posts

    16,715
  • Joined

  • Last visited

  • Days Won

    1,516

Everything posted by ryan

  1. Sounds like your webhost is preventing you from using htaccess Options. The most important one from a security aspect is the "-Indexes" option. You really don't want to allow indexes, as that means anyone can browse a directory on your server just by typing in the URL. Hopefully they've got this disabled already, but I would be a little uncomfortable with the webhost not letting me specify these options.
  2. What attributes are getting stripped that you need? (other than data attributes). HTML Purifier seems to allow class attributes by default (not to mention style attributes too), so it wouldn't be the one stripping those. My understanding is that the default filters in HTMLPurifier are primarily aimed at cleaning the HTML to make it safe (i.e. removing any attributes that could introduce javascript/XSS). So things like class/style attributes don't seem to be a concern to HTML Purifier, but data-* attributes probably would. But the content filters in HTMLPurifier and CKEditor seem to have different purposes. I've been experimenting here with adding the 'extraAllowedContent' option (rather than 'allowedContent') to the CKEditor module, as it seems like this is the one designed for modifying the default behavior? (not positive on that). Though I've found the allowedContent rules string to be a little unpredictable. For instance, an extraAllowedContent string of "p[class]" should mean "Allow <p> elements with optional class attribute." But that doesn't seem to work. I can't get CKEditor to allow class attributes that way unless I manually specify the class name, like "p(test)" to allow a <p class="test"></p> Just pushed this update to CKEditor, so you should have the extraAllowedContent filter available. Let me know what else we need.
  3. Online polls and ratings are always a tricky thing just because if someone is motivated, they can manipulate it. And depending on how creative they are, there's not much you can do about it. Even PollDaddy isn't immune to this. What you did last time, by requiring a social network login, is a definitely improvement over security by IP address. But still not foolproof, and you do end up with a lot of votes coming from people that know nothing about the product, other than someone asked them to vote. But at least a social network login increases the cost to any individual trying to manipulate results. But if it were me, I would use your own custom form (or Form Builder) and place some burden of proof on the voter, like an example of a site where they have used the CMS. For example, these form fields: • How many sites have you used with this CMS? [ ] 0 [ ] 1 [ ] 2-3 [ ] More than 3 • List one example of a site you have used this CMS on? (must be currently running the CMS) [Enter the URL] • For the site you listed above, please provide one URL where we can confirm the CMS. This may be any one of the following: 1. Any URL that refers to the CMS (like a login, info or credits URL–we will keep confidential). 2. Any URL that contains a link to CMSCritic.com. 3. Any URL that references you as an employee, author, editor, designer or developer of the site. [Enter the URL] • How do you use the CMS? [ ] As a web developer [ ] As a web designer [ ] As an editor [ ] As a user [ ] I don't use it • What is your favorite feature of this CMS? [Text field] • Enter your email address (we will send you a email to confirm your vote) (confirm) I think questions like the above would really help to keep the control away from those looking to manipulate results. It would also keep the votes focused on people that actually have experience with the product. I would also include IP filtering and email verification, but not rely upon it. If there's any question of feasibility, this is all stuff that Form Builder (or other form services) can do without too much effort.
  4. There are a lot of big security considerations with forms that accept files, and what happens to those files afterwards. The scope of it is a bit beyond what I think most would want to implement and monitor for a front-end form. As a result, I recommend using something existing that can do it safely, like ProcessWire Form Builder, or an external service like WuFoo.
  5. I haven't actually tried an autocomplete in a repeater yet, but theoretically it should work. However, you may need to switch to the dev branch, since that contains an update that enabled autocomplete fields to work in tabs (which I'm hoping means they also work in repeaters). Let me know if you find it still doesn't work.
  6. Not specific to multi-language page names, but since it's in the dev branch and related, I figured I'd mention it here… Multi-language page fields inherit the default language value when they are blank. While probably a good default, sometimes this behavior is not desirable. So on the latest dev commit, you can now configure that behavior on a field-by-field basis (for any multi-language field). The screenshot below probably explains it best.
  7. One idea came up, and was just curious about the feasibility of it: What if the "active" tab was based on the current user's language? So if I had my language set as Spanish, I'd see the Spanish tabs active (rather than default language tab) when editing a page?
  8. For authors, there were only about 6 of them at import time, so I created the authors as users in PW manually. I also added the "wpid" field to the "user" template, and populated the value of that manually. That was easy to find in WordPress just by editing the author and noting the ID in the URL. The WordPress wp_posts table has a field in it called post_author, which is the ID of the author. So assuming we've got a user in ProcessWire with a "wpid" that matches up to that, it's easy for us to assign the right PW user to each post. You'll see how this takes place in the code below. Wrapping it up Here is the same "import" code as in the first post, but I added all the code accounting for authors, topics, tags, and images back into it. This all just goes in a ProcessWire template file, and viewing the page triggers the import. Because it's aware of stuff that is already imported, it can be run multiple times without causing duplication. <!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <title>Import Posts</title> </head> <body> <table border='1' width='100%'> <thead> <tr> <th>New?</th> <th>ID</th> <th>Author</th> <th>Date</th> <th>Name</th> <th>Title</th> <th>Images</th> <th>Topics</th> <th>Changes</th> </tr> </thead> <tbody> <?php // get access to WordPress wpautop() function include("/path/to/wordpress/wp-includes/formatting.php"); $wpdb = new PDO("mysql:dbname=wp_cmscritic;host=localhost", "user", "pass", array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'UTF8'")); $posts = wire('pages')->get('/posts/'); $sql = " SELECT * FROM wp_posts WHERE post_type='post' AND post_status='publish' ORDER BY post_date "; $query = $wpdb->prepare($sql); $query->execute(); while($row = $query->fetch(PDO::FETCH_ASSOC)) { $post = $posts->child("wpid=$row[ID]"); // do we already have this post? if(!$post->id) { // create a new post $post = new Page(); $post->template = 'post'; $post->parent = $posts; echo "Creating new post...\n"; } $post->of(false); $post->name = wire('sanitizer')->pageName($row['post_name']); $post->title = $row['post_title']; $post->date = $row['post_date']; $post->summary = $row['post_excerpt']; $post->wpid = $row['ID']; // find the post author $author = wire('users')->get("wpid=$row[post_author]"); // if we don't have this post author, assign one (Mike) if(!$author->id) $author = wire('users')->get("mike"); // set the post author back to the page $post->createdUser = $author; // assign the bodycopy after adding <p> tags // the wpautop() function is from WordPress /wp-includes/wp-formatting.php $post->body = wpautop($row['post_content']); // give detailed report about this post echo "<tr>" . "<td>" . ($post->id ? "No" : "Yes") . "</td>" . "<td>$row[ID]</td>" . "<td>$row[post_author]</td>" . "<td>$row[post_date]</td>" . "<td>$row[post_name]</td>" . "<td>$row[post_title]</td>" . "<td>" . importImages($post) . "</td>" . "<td>" . importTopicsAndTags($wpdb, $post) . "</td>" . "<td>" . implode('<br>', $post->getChanges()) . "</td>" . "</tr>"; $post->save(); } function importTopicsAndTags(PDO $wpdb, Page $page) { // see implementation in previous post } function importImages(Page $page) { // see implementation in previous post } ?> </tbody> </table> </body> </html>
  9. Topics and tags: The first step was to create the parent pages and templates for these. For topics, there were only a few of them, so I created all the category pages ahead of time. On the other hand, with tags, there are 2000+ of those, so those are imported separately. Here are the manual steps that I performed in the PW admin before importing topics and tags: Created template "topics" and page /topics/ that uses this template. Created template "topic" and 6 topic pages that use it, like /topics/cms-reviews/ for example. Created Page reference field "topics" with asmSelect input, set to use parent /topics/ and template "topic". Created template "tags" and page /tag/ that uses this template. Note that I used /tag/ as the URL rather than /tags/ for consistency with the old WordPress URLs. Otherwise I would prefer /tags/ as the URL for consistency with the template name. Created template "tag". Created Page reference field "tags" with PageAutocomplete input, set to use parent /tag/ and template "tag". I also set this one to allow creating of new pages from the field, so the admin can add new tags on the fly. Added the new "topics" and "tags" fields to the "post" template. With all the right templates, fields and pages setup, we're ready to import. WordPress stores the topics, tags and the relationships of them to posts in various tables, which you'll see referenced in the SQL query below. It took some experimenting with queries in PhpMyAdmin before I figured it out. But once I got the query down, I put it in a function called importTopicsAndTags(). This function needs a connection to the WordPress database, which is passed into the function as $wpdb. For more details on $wpdb, see the first post in this thread. /** * Import WordPress topics and tags to ProcessWire * * This function assumes you will do your own $page->save(); later. * * @param PDO $wpdb Connection to WordPress database * @param Page $page The ProcessWire "post" page you want to add topics and tags to. * This page must have a populated "wpid" field. * @return string Report of what was done. * */ function importTopicsAndTags(PDO $wpdb, Page $page) { $out = ''; $sql = <<< _SQL SELECT wp_term_relationships.term_taxonomy_id, wp_term_taxonomy.taxonomy, wp_term_taxonomy.description, wp_terms.name, wp_terms.slug FROM wp_term_relationships LEFT JOIN wp_term_taxonomy ON wp_term_taxonomy.term_taxonomy_id=wp_term_relationships.term_taxonomy_id LEFT JOIN wp_terms ON wp_terms.term_id=wp_term_taxonomy.term_id WHERE wp_term_relationships.object_id=$page->wpid ORDER BY wp_term_relationships.term_order _SQL; $query = $wpdb->prepare($sql); $query->execute(); while($row = $query->fetch(PDO::FETCH_ASSOC)) { if($row['taxonomy'] == 'category') { // this is a topic: find the existing topic in PW $topic = wire('pages')->get("/topics/$row[slug]/"); if($topic->id) { // if $page doesn't already have this topic, add it if(!$page->topics->has($topic)) $page->topics->add($topic); // report what we did $out .= "<div>Topic: $topic->title</div>"; } } else if($row['taxonomy'] == 'post_tag') { // this is a tag: see if we already have it in PW $tag = wire('pages')->get("/tag/$row[slug]/"); if(!$tag->id) { // we don't already have this tag, so create it $tag = new Page(); $tag->template = 'tag'; $tag->parent = '/tag/'; $tag->name = $row['slug']; $tag->title = $row['name']; $tag->save(); } // if $page doesn't already have this tag, add it if(!$page->tags->has($tag)) { $page->tags->add($tag); $out .= "<div>Tag: $tag->title</div>"; } } } return $out; }
  10. I'll cover these each separately. First I'll start with the images, and will come back to the others a little later when I've got more time. WordPress really only uses images for placement in body copy, so I extracted the links to them right out of there and imported them that way. I did this after the pages had already been imported. In order to keep track of which images had already been imported (so that I could feasibly run the importer multiple times without getting duplicate images), I turned on ProcessWire image "tags" option, and stored the original filename in there. Here's the function I used, which I've used many different variations of over the years with different sites. You basically just give it a $page you've already imported (but is still linking to the old site's images) and it converts the images linked in the body copy from the old site to the new. function importImages(Page $page) { if(!$page->id) return 'You need to save this page first'; $out = ''; $body = $page->body; // find all images reference in the 'body' field $regex = '{ src="(http://www.cmscritic.com/wp-content/uploads/[^"]+)"}'; if(!preg_match_all($regex, $body, $matches)) return $out; foreach($matches[0] as $key => $fullMatch) { $url = $matches[1][$key]; // image URL $tag = basename($url); // image filename $tag = wire('sanitizer')->name($tag); // sanitized filename $image = $page->images->getTag($tag); // do we already have it? if(!$image) { // we don't already have this image, import it try { $page->images->add($url); } catch(Exception $e) { $out .= "<div>ERROR importing: $url</div>"; continue; } $numAdded++; $image = $page->images->last(); // get image that was just added $status = "NEW"; } else { $status = "Existing"; } $image->tags = $tag; // replace old image URL with new image URL $body = str_replace($url, $image->url, $body); // report what we did $out .= "<div>$status: $image->basename</div>"; } // assign the updated $body back to the page $page->body = $body; // return a printable report of what was done return $out; }
  11. Unless you had previously modified this, I doubt that's it. The default setting of 86400 is 1 day, and that's not going to cause you to get logged out every 30 seconds or 3 minutes, etc. I am wondering more about the gc_divisor and gc_probability settings that WillyC posted about, as that has more potential to answer the apparent randomness and short active sessions that you've described.
  12. This always seems to be an issue when translating multi-level drop down navigation to mobile. The other issue with dropdown navigation is that even on the desktop, people may miss the index pages, which might be important. The renderTopNav() function in /site/templates/_nav.php has a "repeat" option, that makes it repeat an index page as it's own first child. This solves both issues. Before I had it default to true, but set it to false later on for some unknown reason. I've switched it to be the default again.
  13. Thanks jtborger, that makes sense to me. I'm updating it to this: if(empty($options['baseUrl'])) { $baseUrl = $this->page->url; $urlSegmentStr = $this->input->urlSegmentStr; if(strlen($urlSegmentStr)) $baseUrl = rtrim($baseUrl, '/') . "/$urlSegmentStr/"; $pager->setBaseUrl($baseUrl); }
  14. You can tell what the unexpected page is by outputting the contents of children() rather than just outputting the count. foreach($pages->get(1)->children) as $n => $child) echo "<p>$n. $child->url</p>";
  15. PW also uses $_SESSION, so there's no harm in using it. It's not going to break anything if you use both $_SESSION and $session. But one thing to note is that PW keeps $session variables in a namespace within $_SESSION. So while you can use $_SESSION or $session, you can't access the same variables from them, i.e. $_SESSION['first_name'] will not be accessible via $session->first_name, or the other way around.
  16. @adamspruijt: How are you making that grid of images like that seen in the "select image" screenshot? I did try to check myself, but am not seeing the grid here locally.
  17. The Blog profile is a good example of tags in action.
  18. The timezone you use has to be one predefined in PHP. See here for an official list: http://www.php.net/manual/en/timezones.php I'm thinking you want "Europe/Berlin" or "Europe/Warsaw" ? I'm still not clear about where you are seeing this in ProcessWire? Dates/times are stored as either MySQL DATETIME fields or unix timestamps (integers), none of these are actually stored as formatted dates/times. So if you saw that format in PhpMyAdmin, then that would be because that's how PhpMyAdmin is formatting it, and I'm not sure that's within our ability to control. But output in ProcessWire is within our ability to control, I just need to understand specifically where you are talking about. If it's the front-end of your site, then you can change the output format by editing the field (Setup > Fields > your date field), clicking on "details" and selecting the format that you prefer.
  19. Does Akismet no longer require an API key? I did a quick search on Google, and browsed the Akismet site, but can't find anything to that effect?
  20. You have a couple of options here. First would be to make it a "global" field, which should make it appear on the first screen when adding a page. But not all fields support that, like files, images and repeaters for instance. The other option would be to copy /wire/modules/Process/ProcessPageAdd/ into /site/modules/Process/ProcessPageAddCustom/ and rename the file name/class name consistent with the directory name (ProcessPageAddCustom). Modify the module to do exactly what you want. In the admin, edit the page Admin > Pages > Add Page. Change the "process" field to be your custom version. Anything is possible, but this might be too abstract/vague of a request to provide a tangible answer. But it does potentially sound like you want a hook, or a custom Fieldtype or Inputfield to perform some automated actions.
  21. Not sure I understand the question. For the most part, ProcessWire's API works the same no matter where you use it from (whether a template or a module, etc). If you'd like, post a code sample of what's not working?
  22. You might also want to check out the new Foundation site profile, which is a variation on the Basic Site Profile included with ProcessWire, but a good one to look at next to see techniques that can scale.
  23. Looks good to me too! Alessio, do you want me to go ahead and point http://it.processwire.com to it? Or do you want me to setup the account here?
  24. That syntax is valid–I think when I replied before that the morning coffee hadn't quite kicked in yet. But, if you need to access the first element, I think it's better to use first() rather than get(0) just because there's always a possibility that the item at index 0 was removed somewhere else, or that the PageArray was at some point sorted so that index 0 was no longer the first item. If you don't have any other manipulations going on with your PageArray, then accessing get(0) is probably just fine though.
  25. Most likely repeaters aren't what you want. You can create any structure with pages so that opens up your options, even if it makes input less convenient. But for specific needs like this, you can us the API to automate any input challenges. You could also make a spreadsheet that contains your data and use the ImportPagesCSV module to import them (or your own import script). Another option is to create your own SeatingChart Fieldtype an Inputfield. Fieldtypes are designed to represent potentially very complex data, and are the most efficient way to create a reusable complex data type. But creating a Fieldtype does require getting into database schema and more PHP than usual site development.
×
×
  • Create New...