-
Posts
17,304 -
Joined
-
Days Won
1,724
Everything posted by ryan
-
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?
-
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>
- 37 replies
-
- 12
-
-
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; }
-
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; }
-
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.
-
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.
-
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); }
-
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>";
-
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.
-
@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.
-
The Blog profile is a good example of tags in action.
-
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.
-
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?
-
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.
-
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?
-
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.
-
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?
-
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.
-
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.
-
I actually thought this Foundation 3 to 4 Migration Guide was really helpful in that regard. Even though I never technically knew Foundation 3 very well, it served as a good starting point for Foundation 4 in general.
-
I always liked Foundation 3, but it just seemed slower (on my computer) than other frameworks. Foundation 4 doesn't seem slow at all anymore. I was initially confused by the syntax with class names like "small-4 columns" and "large-8 columns", etc. But it's actually very simple: if you want a div to have 4 columns at small (mobile) and at large (desktop) then use "small-4 columns" ...Foundation is literally mobile-first, so the "small" settings inherit through to the large settings, unless you override them. But if you want it to only have those 4 columns on at large (desktop), and 1-column stacked on mobile, then use "large-4 columns" ... which would be like the behavior of Foundation 3. If you want to define different column amounts for small and large, like say 2 columns at small (mobile) and 4 columns at large (desktop) then specify both in the class, like "small-2 large-4 columns". I never got far enough in Foundation 3 to say for sure, but I don't think it had that distinction...I think it just stacked everything at small.
-
I just posted version 2 of this profile, which makes typographic tweaks to several styles. It also adds support for photo captions, seen on the typography tests page. Also, there is now a demo site of this profile. I'm okay with only supporting IE9 and newer for the desktop view. IE8 and lower can have the compatibility mobile view. It's not like IE8 and older are blocked. They just simply get a view that works consistently in older browsers (which is basically the mobile view). I think this is smart, and better than trying to make old browsers display like new browsers. Supporting old versions of IE always means extra bulk and shenanigans in the code. The less legacy-IE monkey business, the better, IMO. But if you are building a complex desktop grid for an audience using old IE versions (like inside a government office that has standardized on IE7 or something) then Foundation is probably not the right framework for that. Here's what this profile looks like in IE8 if you are interested. The rule is that you have to start and end an attribute with the same style quote. Otherwise, they are interchangeable. I prefer to use single quotes myself. But if I'm copying/pasting something, I'm not going to bother going and changing double quotes to single quotes either. But ultimately the biggest reason is what Teppo highlighted: what makes the most sense in the context where they are used.
-
ProcessWire Site Profile Using Zurb Foundation 4 This is a drop-in replacement for the default ProcessWire site profile. See the live demo at: http://processwire.com/foundation/ It is mobile-first and fully responsive, capturing all the benefits of Zurb Foundation 4. Ready to be expanded upon with built-in support for deeper levels of navigation nesting in the sidebar. Pagination ready with Foundation-specific pagination output, when/if you want it. Improved search engine, relative to the basic profile. Library of Foundation-specific markup generation functions included, primary for generation of navigation (in _nav.php). Uses ProcessWire 2.3+ prepend/append template file settings making it easy to work with. It is largely stock Foundation 4 in terms of look and feel, with a few tweaks. To Install Download this profile from GitHub or mods.pw/4u. Start with a copy of ProcessWire 2.3 or newer, and its default site profile. If starting with an uninstalled copy of ProcessWire Replace the /site-default/templates/ directory with the templates directory from this profile. Replace the /site-default/config.php file with the config.php file from this profile. Run the ProcessWire installer. If starting with an already-installed copy of ProcessWire Replace the /site/templates/ directory with the templates directory from this profile. Add the following two lines to your /site/config.php file: $config->prependTemplateFile = '_init.php'; $config->appendTemplateFile = '_main.php'; Desktop Screenshot Mobile Screenshot
- 71 replies
-
- 33
-
-
I think it can be set with CSS, but CKEditor apparently doesn't take the <textarea> rows attribute into account. We may be able to approximate it in the module initialization. I mainly use the inline mode with CKEditor, which always makes the height of the editor the same as the height of the content within it.
-
Thanks Soma, I've fixed that regex so that it ignores comments on the same line. I'm still seeing a few issues with Nico's module versions since they are using a different format from all the others, but not sure there is anything I can do about that yet.