Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation on 07/13/2013 in all areas

  1. 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 points
  2. 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>
    3 points
  3. Hmm, I must confess that the PageAutocomplete was in a Repeater that was in a tab . But I'm happy to report that in the dev branch the PageAutocomplete does work in a Repeater which is in a tab. I just realized that I like more to see all the already defined tags, so I'm keeping the AsmSelect. When time and brain allows, I'll try to see if I can cobble together a Page input field like the tags field from MODx 2.x. Also I'm happy to report that my slideshow is coming up nicely. Thank you all! Oh, and I absolutely love the way ProcessWire makes whatever I want it to do. Ryan, you should embed The Three Laws of Robotics in ProcessWire before it's too late!
    2 points
  4. This might help: http://www.lynda.com/Foundation-tutorials/Up-Running-Foundation/122442-2.html
    2 points
  5. Since you guys asked for it, I'll take a stab at a case study on the development process. Most of the development was done in about a week and a half. I started with the basic profile, but it ended up being something somewhat similar to the Blog profile in terms of how it's structured. Below I'll cover some details on the biggest parts of the project, which included data conversion, the template structure, the front-end development and anything else I can think of. Data Conversion from WordPress to ProcessWire One of the larger parts of the project was converting all of the data over from WordPress to ProcessWire. I wrote a conversion script so that we could re-import as many times as needed since new stories get added to cmscritic.com almost daily. In order to get the data out of WordPress, I queried the WordPress database directly (my local copy of it anyway) to extract what we needed from the tables wp_posts for the blog posts and pages, and then wp_terms, wp_term_relationships, and wp_term_taxonomy for the topics and tags. WordPress stores its TinyMCE text in a state that is something in between text and HTML, with the most obvious thing being that there are no <p> tags present in the wp_posts database. Rather than trying to figure out the full methodology behind that, I just included WP's wp-formatting.php file and ran the wpautop() function on the body text before inserting into ProcessWire. I know a lot of people have bad things to say about WordPress's architecture, but I must admit that the fact that I can just include a single file from WordPress's core without worrying about any other dependencies was a nice situation, at least in this case. In order to keep track of the WordPress pages imported into ProcessWire through repeat imports, I kept a "wpid" field in ProcessWire. That just held the WordPress post ID from the wp_posts table. That way, when importing, I could very easily tell if we needed to create a new page or modify an existing one. Another factor that had to be considered during import was that the site used a lot of "Hana code", which looked like [hana-code-insert name="something" /]. I solved this by making our own version of the Hanna code module, which was posted earlier this week. Here's an abbreviated look at how to import posts from WordPress to ProcessWire: $wpdb = new PDO("mysql:dbname=wp_cmscritic;host=localhost", "root", "root", 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']; // assign the bodycopy after adding <p> tags // the wpautop() function is from WordPress /wp-includes/wp-formatting.php $post->body = wpautop($row['post_content']); $post->save(); echo "Saved post: $post->path\n"; } What I've left out here is the importing of images, topics, tags, and setting the correct authors for each post. If anyone is interested, I'll be happy to go more in depth on that, but didn't want to overwhelm this message with code. Template File Structure This site makes use of the $config->prependTemplateFile to automatically include the file _init.php before rendering a template file, and $config->appendTemplateFile to automatically include the file _main.php after. So the /site/config.php has this: $config->prependTemplateFile = '_init.php'; $config->appendTemplateFile = '_main.php'; You may recognize this as being the same setup from the Skyscrapers profile. The _init.php includes files containing functions we want to be available to all of our templates, and set default values for the regions we populate: /site/templates/_init.php /** * Include function and hook definition files * */ require_once("./includes/render.php"); require_once("./includes/hooks.php"); /** * Initialize variables populated by templates that get output in _main.php * */ $browserTitle = $page->get('browser_title|title'); $body = "<h1>" . $page->get('headline|title') . "</h1>" . $page->body; $side = ''; $renderMain = true; // whether to include the _main.php file The includes/render.php file that is included above includes several functions for generating markup of navigation and post summaries, or any other shared markup generation functions. Examples are renderPost(), renderNav(), renderTags(). This is similar to the blog.inc file from the Blog profile except that I'm letting these functions generate and return their own markup rather than splitting them into separate view files. I personally find this easier to maintain even if it's not as MVC. The includes/hooks.php sets up any hooks I want to be present for all of my templates. I could have also done this with an autoload module, but found this to just be a little simpler since my hooks were only needed on the front-end. The main hook of interest is one that makes all posts look like they live off the root "/" level rather than "/posts/" (where they actually live). This was in order to keep consistency with the URLs as they were in WordPress, so that the new site would have all the same URL as the old site, without the need for 301 redirects. /site/templates/includes/hooks.php /** * This hook modifies the default behavior of the Page::path function (and thereby Page::url) * * The primary purpose is to redefine blog posts to be accessed at a URL off the root level * rather than under /posts/ (where they actually live). * */ wire()->addHookBefore('Page::path', function($event) { $page = $event->object; if($page->template == 'post') { // ensure that pages with template 'post' live off the root rather than '/posts/' $event->replace = true; $event->return = "/$page->name/"; } }); Our /site/templates/_main.php contains the entire markup for the overall template used site wide, from <html> to </html>. It outputs those variables we defined in _init.php in the right places. For example, $body gets output in the <div id='bodycopy'>, $side gets output in the right <aside>, and $browserTitle gets output in the <title> tag. /site/templates/_main.php <?php if($renderMain): ?> <html> <head> <title><?=$browserTitle?></title> </head> <body> <div id='masthead'> // ... </div> <div id='content'> <div id='bodycopy'><?=$body?></div> <aside id='sidebar'><?=$side?></aside> </div> <footer> // ... </footer> </body> </html> <?php endif; ?> We use the rest of the site's template files to simply populate those $body, $side and $browserTitle variables with the contents of the page. As an example, this is an abbreviated version of the /site/templates/post.php template: /site/templates/post.php // functions from /site/templates/includes/render.php $meta = renderMeta($page); $tags = renderTags($page); $authorBox = renderAuthor($page->createdUser); $comments = renderComments($page); $body = " <article class='post post-full'> <header> <h1>$page->title</h1> $meta </header> $page->body $tags $authorBox $comments </article> "; if(count($page->related)) { $side = "<h4>Related Stories</h4>" . renderNav($page->related); } What might also be of interest is the homepage template, as it handles the other part of routing of post URLs since they are living off the root rather than in /posts/. That means the homepage is what is triggering the render of each post: /site/templates/home.php if(strlen($input->urlSegment2)) { // we only accept 1 URL segment here, so 404 if there are any more throw new Wire404Exception(); } else if(strlen($input->urlSegment1)) { // render the blog post named in urlSegment1 $name = $sanitizer->pageName($input->urlSegment1); $post = $pages->get("/posts/")->child("name=$name"); if($post->id) echo $post->render(); else throw new Wire404Exception(); // tell _main.php not to include itself after this $renderMain = false; } else { // regular homepage output $limit = 7; // number of posts to render per page $posts = $pages->find("parent=/posts/, limit=$limit, sort=-date"); $body = renderPosts($posts); } The rest of the site's template files were handled in the same way. Though most were a little simpler than this. Several were simply blank, since the default values populated in _init.php were all that some needed. Front-end development using Foundation 4 The front-end was developed with the Foundation 4 CSS framework. I started with the Foundation blog template and then tweaked the markup and css till I had something that I thought was workable. Then Mike and I sent the _main.php template file back and forth a few times, tweaking and changing it further. There was no formal design process here. It was kind of a photoshop tennis (but in markup and CSS) where we collaborated on it equally, but all under Mike's direction. After a day or two of collaboration, I think we both felt like we had something that was very good for the reader, even if it didn't originate from a design in Photoshop or some other tool like that. I think it helps a lot that Foundation provides a great starting point and lends itself well to fine tuning it the way you want it. I also felt that the mobile-first methodology worked particularly well here. Comments System using Disqus We converted the comments system over to Disqus while the site was still running WordPress. This was done for a few reasons: Disqus comments provide one of the best experiences for the user, in my opinion. They also are platform agnostic, in that we could convert the whole site from WP to PW and not have to change a thing about the comments… no data conversion or importing necessary. Lastly, ProcessWire's built-in comments system is not quite as powerful as WordPress's yet, so I wanted cmscritic.com to get an upgrade in that area rather than anything else, and Disqus is definitely an upgrade from WP's comments. In order to ensure that Disqus could recognize the relations of comment threads to posts, we again made use of that $page->wpid variable that keeps the original WordPress ID, and also relates to the ID used by the Disqus comments. This is only for posts that originated in WordPress, as new posts use a ProcessWire-specific ID.
    1 point
  6. Just finished another one: http://www.hirschenhotels.com Their styleguide is a bit weird for a "romantic" hotel. Futura as font and these green/red colors are not ideas of mine ;-) On the PW side pretty regular stuff, mostly core functions.
    1 point
  7. Another good tutorial: http://webdesign.tutsplus.com/sessions/foundation-for-beginners/
    1 point
  8. Sure, just go to "Profile" and set the new password.
    1 point
  9. Hey all, I thought this might be useful for anyone who uses preprocessing tools (like the excellent CodeKit: http://incident57.com/codekit). I came across Prepros the other day: http://alphapixels.com/prepros/ I always used CodeKit up until recently. However, it's only available for mac and recently I have had to dip my toes into the waters of the dark side again with a PC laptop I needed something that would work cross-platform (Prepros works across PC, Mac and Linux). From what I have seen so far Prepros is growing quickly, free, supports nearly every type compiled CSS, has real time refreshes and works across platforms.
    1 point
  10. Dear Mr.MetaData It's not that simple? Well I guess you already know that the type of calculation you try to do is "impossible". After all I don't really care. I'm sure there's some formula which would consider different aspects, but none of them will be speaking something different than what already is seen. Those calculations are complicated and lead to results you'd have to put some bias/weight into it and it will never be "correct" considering you can't put all factors into account. To make it short, not worth the effort. What I would recommend is doing different list, you can sort by count of likes, post, ratio.. and everybody can read what he likes out of it.
    1 point
  11. And the time that people hesitated when giving the like. Do we have that info?
    1 point
  12. I was on mobile, so the answer was a bit dry. Thanks for completing it Pete. And welcome to the forum Ollie
    1 point
  13. 1 point
  14. I haven't used it myself but there's also http://modules.processwire.com/modules/process-date-archiver/ if that something you were looking for. Personally i find the notion of an archive in the web world a bit confusing, because most sites are already archives in itself.
    1 point
  15. I've played Go a little too, but it's been some years since I last played against a human player, even online. Computer has been enough of a challenge to me so far as I never quite reached single digit ratings being at 11-12k at my best. It would be great to visit IGS sometimes and play a game or two if you're willing to give a few handicap stones for me Soma. If only I had the time as it's the busiest time of year for me right now - week one out of four of my summer holiday.
    1 point
  16. I used to be a long time user of Google Reader. Then sometime last year, I simply stopped using it. I stopped visiting the sites I relied on for so long for web dev and design news, tips/tricks, inspiration, etc. Now that Reader is dead, I decided to get back into RSS, so I downloaded/installed Tiny Tiny RSS. My question for you friends: what web design/development related blogs/feeds to you recommend? Of course I have some of the most popular ones: Noupe, CSS Tricks, Smashing Magazine. What else do you recommend? [Friend recommended Feedly, which I am really liking at the moment ]
    1 point
  17. Hey Matthew, great to know you also play Go! I haven't played for some time but would be great to have a few games. I'm on IGS http://pandanet-igs.com/communities/pandanet as iSoma (6-7k). Yeah it's hard to find players but chances are that there's a Go club near, at least here in Europe. But there's also the chance to play online and it has an active community around.
    1 point
  18. Greetings, Only saw this post today, after reading an (unrelated) discussion -> http://processwire.com/talk/topic/4044-forum-likes-and-posts-and-the-top-list/ Go is great! I have been trying to master it for years. Thanks, Matthew
    1 point
  19. Soma, I play Go, and have been working at it for years! The difficulty is in finding other people with the patience to play. I agree with the idea of doing more than just a straight "like count." There should be some reflection of the "like proportions." Another matter to consider is what I would call "like density." Does the user get likes from many different users, or does he/she have a smaller number of people who "like" him/her? Thanks, Matthew
    1 point
  20. r = $likes / $posts and call it a day or make some sudoku () Or very much better play some Go ! (can't believe nobody here plays)
    1 point
  21. I must confess I'm pretty confused by that math, can you explain the intention? Is it some kind of known and tested formula? Edit: the only truly fair formula is one where Ryan stays on the top
    1 point
  22. Greetings, When I started this discussion, my goal was to pull together various renditions of code that were being discussed in forum discussions and create a single full example people can use to make front-end page submissions, and to also do this with photos. This is a vital part of building the kinds of sites that are really exciting with ProcessWire. I have now used the techniques detailed in this discussion for several projects, including one where I am building a completely custom front-end interface with page submission and page editing (more on that soon). In my first post in this discussion, I have the "unlink($pathname)" included in the routine. This removes the temporary image. Horst makes reference to the same code posted by Ryan. HANDLING MULTIPLE FILE-UPLOAD FIELDS IN THE SAME FORM Since starting this discussion, I have also added multiple file uploads in the same form. The steps are pretty easy. Below is my original code from post #1, with additional code for a second file-upload field (indicated with "[NEW CODE]"): <?php// First, confirm that a submission has been made if($input->post->contactname) { // Set a temporary upload location where the submitted files are stored during form processing $upload_path = $config->paths->assets . "files/contact_files/"; // New wire upload $contact_photo = new WireUpload('contact_photo'); // Reference field name in HTML form that uploads photos $contact_photo->setMaxFiles(5); $contact_photo->setOverwrite(false); $contact_photo->setDestinationPath($upload_path); $contact_photo->setValidExtensions(array('jpg', 'jpeg', 'png', 'gif')); // Second wire upload (other_photos) [NEW CODE] $other_photos = new WireUpload('other_photos'); // Reference field name in HTML form that uploads photos $other_photos->setMaxFiles(10); // Allow 10 other photos $other_photos->setOverwrite(false); // Use the temporary location set above $other_photos->setDestinationPath($upload_path); $other_photos->setValidExtensions(array('jpg', 'jpeg', 'png', 'gif')); // execute upload and check for errors $files = $contact_photo->execute(); $other_files = $other_photos->execute(); // [NEW CODE] // Run a count($files) test to make sure there are actually files; if so, proceed; if not, generate getErrors() if(!count($files)) { $contact_photo->error("Sorry, but you need to add a photo!"); return false; } // Set up submissions in the ProcessWire page tree $np = new Page(); // create new page object $np->template = $templates->get("contact_submission"); // Set template for pages created from form submissions $np->parent = $pages->get("/customer-service/contact-us/contact-submission-listing/"); // Set parent for pages created from form submissions // Send form submissions through ProcessWire sanitization and apply each to a template field for the new page $np->of(false); $np->title = $sanitizer->text($input->post->contactname); $np->name = $np->title; $np->contactname = $sanitizer->text($input->post->contactname); $np->email = $sanitizer->email($input->post->email); $np->comments = $sanitizer->textarea($input->post->comments); $np->save(); // Run photo upload for "contact_photo" foreach($files as $filename) { $pathname = $upload_path . $filename; $np->contact_photo->add($pathname); $np->message("Added file: $filename"); unlink($pathname); } // Run photo upload for "other_photos" [NEW CODE] foreach($other_files as $other_file) { $pathname = $upload_path . $other_file; $np->other_photos->add($pathname); $np->message("Added file: $other_file"); unlink($pathname); } // Save page again $np->save(); ?> <p>Thank you for your contact information.</p> <?php return true; } else { ?> <p> Sorry, your photo upload was not successful...</P> <?php} ?> And here is the updated submission form that would connect with the above code: <form action="/customer-service/contact/contact-success/" method="post" enctype="multipart/form-data"> <p><label for="contactname">Name:</label></p> <p><input type="text" name="contactname"></p> <p><label for="email">E-Mail:</label></p> <p><input type="email" name="email"></p> <p><label for="comments">Comments:</label></p> <p><textarea name="comments" cols="25" rows="6"></textarea></p> <p>Click the "Select Files" button below to upload your photo.</p> <input type="file" name="contact_photo" /> <input type="file" name="other_photos[]" multiple /> <button type="submit">Submit</button> </form> Points to keep in mind: 1. Add a WireUpload action for each photo-upload field, setting the rules for each one. 2. Add a separate "execute" action for each photo-upload field. 3. Add a separate foreach loop for each photo field you need. 4. If the file-upload field is for multiple files, make sure that the setMaxFiles() action matches what you set in the admin panel for that field. 5. In your form, if the file-upload field is for multiple files, specify the name with square brackets, and add the "multiple" designation. Thanks, Matthew
    1 point
  23. I know the topic isn't really RSS readers, but I just have to express my love for Feedly as well. I was disappointed in losing Google reader at first, but now I'm glad it's gone since it led me to discover Feedly I had always wanted to be able to categorize my feeds by topic. The mobile app is excellent as well.
    1 point
  24. Greetings, I have been following this discussion -- an excellent example of a case study with highlights of how to accomplish several key goals in ProcessWire. This last post highlights a couple of interesting points for me: 1. Emphasizes the advantage of having everything exist as a page in ProcessWire (in this case, tags). 2. How easy it is to use the API to implement functions that take care of major actions in ProcessWire. 3. How to migrate an existing CMS to ProcessWire. Might be dangerous if more people in the WordPress community knew about it! Regarding 3: I come from the Joomla world. It seems that WordPress databases are more logical than Joomla databases. I think the migrate script for Joomla would be much more involved because simple "page" data is very fragmented in that CMS. But the same principles would apply. As always, a very illuminating discussion! Thanks, Matthew
    1 point
  25. 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; }
    1 point
  26. Don't forget us.. we run on ProcessWire too! http://www.cmscritic.com/feed
    1 point
  27. 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; }
    1 point
  28. Our clients have reported identical problems a few times. After clearing browser history things have returned to normal. This is probably unrelated, just wanted to point out that the issue may not be site-related at all..
    1 point
  29. The Blog profile is a good example of tags in action.
    1 point
  30. class FieldtypeFieldsetOpen extends Fieldtype { /** * Appended to the name of a 'Close' version of a FieldsetOpen * */ const fieldsetCloseIdentifier = '_END'; It's just a constant string to suffix the fieldsetclose name so the admin knows what fieldset to close.
    1 point
  31. I think the problem you have is that you're using ->get() instead of ->find(). Try this: $slides = wire("pages")->find("parent=/slideshow/, template=slidefeat, limit=3, sort=-date")->images; Edit: A little more info since it seems like you're just getting started. Using ->get() will always return only 1 result (if any matches are found). Using ->find() will return all the pages that match your selector. Edit 2: Actually, I just really looked at your code a little more rather than just glancing at it. You can't really return the images from multiple pages like that. You're going to have to loop through the matching pages and pull the images from each page independently. For example: $slide_pages = wire("pages")->find("parent=/slideshow/, template=slidefeat, limit=3, sort=-date"); foreach ($slide_pages as $slide_page){ $slides = $slide_page->images; // do something with the slides } Without seeing more of your code or a little more information about the page template/structure, that's my best guess at what you're trying to do.
    1 point
  32. Aah, so you want to use them for templates in the admin? How about: $opener = new Field(); $opener->type = new FieldtypeFieldsetOpen(); $opener->name = "myfieldset"; $opener->label = "Open fieldset"; $opener->save(); $closer = new Field(); $closer->type = new FieldtypeFieldsetClose(); $closer->name = "myfieldset" . FieldtypeFieldsetOpen::fieldsetCloseIdentifier; $closer->label = "Close an open fieldset"; $closer->save(); $tpl = $templates->get("custom"); $tpl->fieldgroup->add($opener); $tpl->fieldgroup->add($fields->get("body")); $tpl->fieldgroup->add($fields->get("counter")); $tpl->fieldgroup->add($closer); $tpl->fieldgroup->save();
    1 point
  33. Hi guys. I just happened to need a method of allowing image uploads and what do you know? There's a whole thread dedicated to it. Matthew, nice work. Thank you. I have only 2 things to add: 1. The code as is makes the assumption that each user will only ever upload one image or that there are no people with the same name. Since the title is derived from the contactname an error is thrown if a second upload is made with the same name. I added: $n = 0; $np->title = $sanitizer->text($input->post->contactname) . "-" . (++$n); It ensures each upload will get a unique ID. 2. I noticed the images uploaded to the .temp directory don't get deleted. With extensive use this will lead to lots of wasted server space as the .temp folder gets more and more bloated with duplicate images. Is there a way to solve this? You made my job very easy anyway thanks again.
    1 point
  34. ModulesManager doesn't have tabs. Well I guess you already know you use getModuleConfigInputfields(array $data){ ... } to add configuration inputfields (not fields!) And you need to add the ConfigurableModule to the implements list (implements Module, ConfigurableModule). Now you can create a new InputfieldWrapper and add Inputfields to it. static public function getModuleConfigInputfields(array $data) { // if you have a static $defaults array for defaults you can merge them with saved ones in $data $data = array_merge(self::$defaults, $data); $fields = new InputfieldWrapper(); ... return $fields; } From there it's pretty easy. You can use all inputfield modules for inputs. It's much like creating a front-end form using API. Know my famous thread? Those inputfields are not bound to the fieldtype, like when used in the admin on pages. So the InputfieldSelect is a simple select module you can fill in options as you like. They're also used in FormBuilder. Ryan has even adapted some a little to support pure interactive inputs better. When used on a page (fields) they're actually a mix of the fieldtype and the inputfield and they depend on each other. Fieldtypes are more used to sanitize and save to db, wakup or sleep values etc, while inputfields kinda serve as an plain interface seen by the user, thus having a render() method. Some special Inputfields are more bound to functionality of other modules like "process" modules, but only remember InputfieldPageListSelect. From there it's just generaly PW API coding. So this would look like this to add a multiple ASM select static public function getModuleConfigInputfields(array $data) { $data = array_merge(self::$defaults, $data); $fieldwrapper = new InputfieldWrapper(); $modules = wire("modules"); $field = $modules->get('InputfieldAsmSelect'); $field->attr('name', 'mymodules'); $field->attr('value', $data['mymodules']); $field->label = 'Select modules.'; foreach($modules as $m) { // loop all installed modules if(strpos("$m->name","Process") === 0) $field->addOption("$m", "$m->className"); } $fieldwrapper->append($field); return $fieldwrapper; }
    1 point
  35. The more simpler and direct way for a custom select would be: $sel = $modules->get("InputfieldSelect"); $sel->attr("name", "cms"); $sel->addOptions(array( 'pw' => 'ProcessWire', 'joomla' => 'Joomla!', 'wp' => 'Wordpress' )); // setting the value will select the option, here with coming either from post or the default $sel->attr("value", $input->post->cms ? $input->post->cms : 'pw'); echo $sel->render();
    1 point
  36. Hi kyle, The below code should do it (not tested, just wrote it in the browser) Markup: $html = "<h3>Condimentum Purus</h3><p>Cras Sollicitudin Vulputate Nullam</p>"; $markup = $modules->get("InputfieldMarkup"); ​$markup->set("label", "Label for this"); $markup->set("value", $html); Default selected: $needed = "ProcessWire"; $array = array( array( "cms" => "Joomla", "value" => "If you don't mind code" ), array( "cms" => "modx", "value" => "Good for trouble shooters." ), array( "cms" => "ProcessWire", "value" => "Great, we selected this." ), ); $select = $modules->get('InputfieldSelect'); foreach($array as $option){ $attr = $needed == $option['cms'] ? array('selected' => 'selected') : null; $select->addOption($option['cms'], $option['value'], $attr); }
    1 point
  37. Just a little note to let you all know I just love PW even more after using the few lines above to create my import module for some plain old html website. It is so easy to use the PW api... Thanks guys! Perhaps you can extend it a bit more with some exception catching to make a more 'real world' example. But don't know if that is the scope of this post (or posts like this). I could post my import example, it is the same as above but just a little more.
    1 point
  38. I would try to work as much with what PW gives you and adapt the CSS, I think there's even an responsive theme in Formbuilder... but could be wrong. If you really need to adapt the HTML markup and CSS classes used by the InputfieldWrapper you can. As in this thread already mentioned: from the InputfieldWrapper.php core class /** * Markup used during the render() method - customize with InputfieldWrapper::setMarkup($array) * */ static protected $defaultMarkup = array( 'list' => "\n<ul {attrs}>\n{out}\n</ul>\n", 'item' => "\n\t<li {attrs}>\n{out}\n\t</li>", 'item_label' => "\n\t\t<label class='ui-widget-header' for='{for}'>{out}</label>", 'item_content' => "\n\t\t<div class='ui-widget-content'>\n{out}\n\t\t</div>", 'item_error' => "\n<p><span class='ui-state-error'>{out}</span></p>", 'item_description' => "\n<p class='description'>{out}</p>", 'item_head' => "\n<h2>{out}</h2>", 'item_notes' => "\n<p class='notes'>{out}</p>", ); Using the form inputfield object you could simply $form->setMarkup(array('list' => "<div {attrs}>{out}</div>")); The same exists for the classes used. But at the end I don't know if messing with it helps. Rendering form fields: As I said earlier, it's always possible to render a certain field explicit. echo $form->get("name")->render(); You could even use the form building method in this thread starting post and render fields where you like. There'll be still markup being outputed depending on the inputfieldtype, but maybe allows for more flexibility. $showform = true; $form = $modules->get("InputfieldForm"); $field = $modules->get("InputfieldEmail"); $field->attr("name", "email"); $field->label = "Email"; $form->append($field); if($input->post->submit) $form->processInput($input->post); if($form->getErrors()) { $showform = true; } else { $showform = false; // form valid, do something } if($showform) { echo "<form action='#' method='post'>"; echo "<div class='row'>" . $form->get('email')->render() . "<div>"; ... } Anything is possible, just use what you need. Think it's almost same as you would work with fields or pages.
    1 point
  39. Hi all, I want to present a preview of my new Portfolio. Actually I'm unsure with some points and the images aren't the final ones and other pieces aren't finished too. But the look is ready and maybe it would be good to get some feedback from the critics here ;-) The site actually runs on PW 2.3 stable, the used modules are: ImagesManager ImageMinSize Thumbnails FieldtypeTextUnique Minify I have disabled direct access via .htaccess to portfolio-images that resides in the files folder. Thumbnails and images related to infos and news can be accessed directly, all others have to use a proxy page I have build as PW-template. With the ImagesManager I initially have created my six categories (albums) and manage all uploads. Via a hook into ImagesManager on upload I'm able to rename the original images and create 3 variations (600px, 900px, 1200px). Also I stripp all Exif and IPTC data from them but populate them with the minimal needed IPTC data. That way I never have custom related infos in the images, neither in filename nor in metadata. I use serverside mobiledetection and also JS to detect viewport dimensions. This should be used to serve images that fit best to devices and viewport dimensions. But it isn't ready now. The layout / design is done from HTML5 boilerplate. There are no frameworks or thatlike used / needed. But there is heavy use of Javascript (jQuery-Libs: Swipe, Stapel, Flexslider), but the site also runs completly in noscript mode. Also the single-image-view in NoScriptMode lets you loop through the gallery and displays the content scrolled down to the image if needed. (That's one of the advantages of old veterans that have build html-pages during browser war 1995/98 ) All critic, suggestions etc is welcome: http://pw4.nogajski.de/
    1 point
  40. I'm all for more validation options, and generally add them as needs come up. But I want to point out that Fieldtypes define a database schema. That's something that differentiates the need for different fieldtypes, is the fact that most represent different storage needs for data. Behind the scenes, they represent a database table. You don't need different fieldtypes purely for validation reasons. Though FieldtypeEmail and FieldtypeURL (and I'm sure others) probably have very similar storage needs... and maybe they could really be the same Fieldtype if we really wanted it that way, but I'd rather have people choosing an "email" or "URL" field rather than a "email/URL" or generic "text" field. This is more consistent with the way HTML5 is going too, as types like "email" and "date" are being used over some validation attribute on text fields. Overall, I think it's easier on the user if they are choosing reasonably well defined types when creating fields, rather than abstracted "I can be anything" types. As for a "unique" validation, that's different from the others. This would best be handled at the Fieldtype level since it's a type of database index that enforces it. I've been planning to add this to the base Fieldtype class, so that it is available for all Fieldtypes. You may have seen the FieldtypeTextUnique, which was more about proof of concept and solving a need quickly, rather than a suggestion of what path should be taken overall. But we'll definitely be adding support for unique indexes in a manner that spans all Fieldtypes rather than just individually. Fieldtypes are involved in the getting and setting of page variables at the API level, making sure that the format is correct for the storage it represents. This is sanitization, just making sure that what gets set to it is valid in general for the type and suitable for storage. But specific interactive validations are the job of Inputfield modules. If there are any major validations we're missing on any of them, let me know and we can get it on the to-do list. I have been working to expand the validation options behind the InputfieldText module, as it now supports regular expressions, which will let you be as specific as you want with validation. New validations have also been added to several other Inputfields over the last year. Configurable error messages at the field-by-field level are something we don't have, but I would also like to see them. This is something I think we can add in the near future. They are currently configurable at the fieldtype-by-fieldtype level, but only if you have the multi-language support modules installed.
    1 point
  41. Thank you for pointing this out, I have wasted hours trying to figure out the differences between the dev and production environments that was preventing admin login. For some reason git was not pushing up the /site/assets/sessions/ directory. Simply making this directory fixed my problem.
    1 point
  42. Two possible ways to do this: 1) Use the Site Profile Exporter Just install it on the local installation, run it and then install Processwire with the new created setup on the new host. Don't forget to copy your templates. 2) Manual by exporting the database and import it again. Copy your local files to the Webserver. Don't forget, that some folders need write permissions for Processwire to work(/site/assets /site/config.php, ...). You should have a phpmyadmin installed on your local machine. Use it, to export the database. Then import the database on your server(again, you could use phpmyAdmin). Check, if the database connection is possible with the login details saved in the /site/config.php. You might also have to delete the session and cache files in the /site/assets directory. By reinstalling a fresh PW, you would have to create the fields and template and page stuff again. You can copy the templates but the content will be missing .
    1 point
  43. I just moved a site from my local MAMP to the production server and could not login. I tried everything suggest here in the forum, but the "forged" message remained. Because I did not want to upload all sessions I excluded the "/site/assets/sessions/" folder when uploading the site by ftp. As soon as I created the "/site/assets/sessions/" folder by hand on the server everything worked again. Conclusion: Remember that you need these folders: /site/assets/cache/ /site/assets/logs/ /site/assets/sessions/
    1 point
  44. looks really interesting... 8)
    1 point
×
×
  • Create New...