Jump to content

ryan

Administrators
  • Posts

    16,793
  • Joined

  • Last visited

  • Days Won

    1,540

Everything posted by ryan

  1. InputfieldImage is a type of InputfieldFile, so the two should work the same for the most part.
  2. There are name collision possibilities with date/time (two being added at same exact time), but it's reasonably reliable other than that. ProcessWire actually uses microtime when creating pages internally that aren't named (or don't yet have a name), like the pages it creates for repeaters. I could see using the same method for unnamed pages anywhere.
  3. Glad you got it! I'll have to remember that one too. No doubt these CKEditor 4.1+'s ACF system is a mixed blessing. On the whole, I'm glad to have it (especially with clients that like to copy/paste from Word, etc.) but I can see it being a real bear when trying to do something out of the norm. I've gone ahead and updated the CKEditor version so that it now supports options for: Enable ACF (Advanced Content Filter)? [Yes/No] (default=Yes) Enable HTML Purifier? [Yes/No] (default=Yes, if installed) When upgrading to this version, revisit all your textarea fields using CKEditor in Setup > Fields, and see the new options on the Input tab. Hit save, even if you don't change anything, just to commit the new settings.
  4. Teppo is right that all recent versions of PW will take care of creating a name for you, so long as you've at least set a title. It will also number-increment the name (i.e. "page-name-2") if one already exists. But if you want to go with a different way of setting the name, perhaps some other format, then it's always fine to set it yourself too.
  5. Another great site–nice job! I liked reading this. Great way to put it. It's exactly what I try to do with my clients too. Though I have had a couple of clients that want the site built in ProcessWire so that they can make edits, but then continue to sending text edits to me…
  6. Just testing out HTML purifier with a /test.php in my webroot: <?php include("./index.php"); $purifier = wire('modules')->get('MarkupHTMLPurifier'); $text = "<pre class='test' onclick='alert(1)'>Test</pre><script>alert(2)</script>"; $text = $purifier->purify($text); echo htmlentities($text); Result: <pre class="test">Test</pre> I have a feeling it's CKEditor that's stripping out your <pre> class attribute. Try adding "pre[class]" or "pre[*]" or "*[*]" to your extraAllowedContent rules in the latest CKEditor inputfield to see if that corrects it?
  7. It's a pretty recent addition. I think it's in 2.3 stable. But if not, it's the same thing as implode('/', $input->urlSegments);
  8. You are right, the best bet is to make sure your PHP versions are up to date. The current version of ProcessWire is the last version that will work with PHP versions prior to 5.3.8, so it won't be an issue for much longer.
  9. Thanks for posting this Matthew. A couple things I found that I wanted to mention, since this is a front-end form: // Set a temporary upload location where the submitted files are stored during form processing $upload_path = $config->paths->assets . "files/contact_files/"; You want your temporary upload path to be a non web accessible path. Also, if you've got multiple users uploading at the same time, it seems like there is potential for filename collisions, though not sure how big of a concern that is in this case. But you can at least make the directory non web accessible by preceding it with a period, i.e. ".contact_files" rather than "contact_files". $other_photos->setValidExtensions(array('jpg', 'jpeg', 'png', 'gif')); Keep in mind that WireUpload is only validating the extension. It is not telling you for sure that it's an image. It could very well be some kind of executable with a jpg extension. I don't necessarily know how such a thing could be exploited, other than that I'm not so comfortable with letting a front-end user upload files that end up untouched in a publicly accessible directory. What would probably be safer is to make a new copy of any uploaded images before adding them to the page, and validate the size is within allowed limits while you are at it. $error = ''; $imageInfo = getimagesize($pathname); if($imageInfo) list($width, $height) = $imageInfo; if(!$imageInfo) { $error = "Image is not valid"; } else if($width < 105 || $height < 105) { $error = "Image is too small"; } else if($width > 1600 || $height > 1200) { $error = "Image is too big"; } else { // create your own, slightly smaller copy $sizer = new ImageSizer($pathname); if(!$sizer->resize($width-10, $height-10)) $error = "Unable to resize image"; } if($error) { unlink($pathname); echo "<p>Error: $error</p>"; } else { // add image to page }
  10. I understand now, thanks So we just need to link to http://akismet.com/plans/ rather than http://en.wordpress.com/api-keys/ -- I will update.
  11. 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.
  12. 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.
  13. 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.
  14. 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.
  15. 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.
  16. 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.
  17. 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?
  18. 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>
  19. 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; }
  20. 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; }
  21. 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.
  22. 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.
  23. 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); }
  24. 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>";
  25. 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.
×
×
  • Create New...