Drupal Planet https://www.zengenuity.com/ en October's Most Interesting New Drupal Modules and Themes https://www.zengenuity.com/blog/2015-11/octobers-most-interesting-new-drupal-modules-and-themes <span class="field field--name-title field--type-string field--label-hidden">October's Most Interesting New Drupal Modules and Themes</span> <div class="paragraph html"> <div class="container"> <p>Some of the most interesting new modules and themes I saw posted to <a href="https://www.drupal.org">drupal.org</a> in October:</p> <h3><a href="https://www.drupal.org/node/2580209">Views Manager</a> [sandbox]</h3> <p>Gives you vertical tabs on the Views admin page, separating Views by their tags.</p> <p><a href="https://www.drupal.org/node/2580209"><img src="https://www.zengenuity.com/sites/default/files/migrated/views_manager.png" /></a></p> <h3><a href="https://www.drupal.org/project/big_pipe">BigPipe</a></h3> <p>Allows for speedier page loads for authenticated users. First you send the fast, cached parts of the page, then fill in the expensive parts when they are ready.</p> <h3><a href="https://www.drupal.org/project/amp">Accelerated Mobile Pages (<span class="caps">AMP</span>)</a></h3> <p>Make your Drupal site ready for <a href="https://www.ampproject.org/">Accelerated Mobile Pages</a>. Right now, it only adds the <span class="caps">AMP</span> javascript library, but the plan is to serve <span class="caps">AMP</span> pages directly from Drupal.</p> <h3><a href="https://www.drupal.org/node/2601614"><span class="caps">OG</span> Multiple Paths</a> [sandbox]</h3> <p>This module creates multiple paths for the same node using Organic Groups and Pathauto. It’s not best practice for <span class="caps">SEO</span>, but it’s nice in situations where content is posted to multiple groups, and you want the node path to reflect the group context the user is currently in.</p> <h3><a href="https://www.drupal.org/project/menu_link_field">Menu Link Field</a></h3> <p>Provides a field similar to the menu link form on node forms but for any entity type.</p> <h3><a href="https://www.drupal.org/project/human">Human Behavior</a></h3> <p>An anti-bot technology that looks for mouse movement and mobile device touch events to determine if a bot is filling out a form.</p> <h3><a href="https://www.drupal.org/node/2591875">Views Numeric Range Filter</a> [sandbox]</h3> <p>It gives you a numeric filter for Views that includes a +/- tolerance. So you can say 100 +/- 10%, and it will give you results between 90 and 110. Neat!</p> <h3><a href="https://www.drupal.org/node/2580615">Address Field Lookup</a> [sandbox]</h3> <p>Integrates the <a href="https://www.drupal.org/project/addressfield">Address Field</a> module with validation services to correct address formatting issues.</p> <h3><a href="https://www.drupal.org/project/fxx">Features Export Explode</a></h3> <p>Breaks your features into a bunch of different files, which makes it easier to see what changed when you review a git commit. For example, it breaks each view out into its own file.</p> <h3><a href="https://www.drupal.org/project/pistachio">Pistachio</a></h3> <p>An example theme for Drupal 8 that shows you how to use the new features of the D8 theme system. Heavily commented to explain your options.</p> <h3><a href="https://www.drupal.org/project/commerce_cart_link">Commerce Cart Link</a></h3> <p>Provides a new field formatter for Add to Cart fields attached to Commerce products and product displays that is a simple link, rather than a form. This way users can buy a product without invoking the full Form <span class="caps">API</span> on each product item.</p> <h3><a href="https://www.drupal.org/project/crud_log"><span class="caps">CRUD</span> log</a></h3> <p>Captures and logs all node <span class="caps">CRUD</span> operations, “create”, “read”, “update” and “delete”. This module logs the events in a custom table and exposes the data to Views. It allows you to select which operations will be captured, so you can turn off read events if that is too verbose.</p> <h3><a href="https://www.drupal.org/node/2595185">User Moderation</a> [sandbox]</h3> <p>A user moderation system that uses votes from other users to decide if a user is a spammer or not.</p> <h3><a href="https://www.drupal.org/project/responsive_menu_combined">Responsive Menu Combined</a></h3> <p>Allows you to combine multiple menus into the responsive hamburger menu with tabs in it.</p> <p><a href="https://www.drupal.org/project/responsive_menu_combined"><img src="https://www.zengenuity.com/sites/default/files/migrated/responsive_menu_combined_mockup.png" /></a></p> <h3><a href="https://www.drupal.org/node/2599032">Mailsuite</a> [sandbox]</h3> <p>Provides a unified administration interface for managing the templates of all the emails you send from your Drupal site.</p> <p><a href="https://www.drupal.org/node/2599032"><img src="https://www.zengenuity.com/sites/default/files/migrated/mailsuite.png" /></a></p> <h3><a href="https://www.drupal.org/project/twig_php">Twig <span class="caps">PHP</span> Filter</a></h3> <p>Ha! If you love doing everything wrong and putting <span class="caps">PHP</span> code in your template files, this is the Drupal 8 module for you. If you’re slightly less crazy and only want to query the <span class="caps">DB</span> from your twig files, then the new <a href="https://www.drupal.org/project/little_bobby_tables">Little Bobby Tables module</a> may be a “better” choice.</p> </div> </div> <span>Wayne Eaker</span>November 6, 2015 <div class="tags"> <div class="container"> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/drupal-planet" hreflang="en">Drupal Planet</a></span> </div> </div> Fri, 06 Nov 2015 18:05:44 +0000 Wayne Eaker 203 at https://www.zengenuity.com September's Most Interesting New Drupal Modules https://www.zengenuity.com/blog/2015-10/septembers-most-interesting-new-drupal-modules <span class="field field--name-title field--type-string field--label-hidden">September's Most Interesting New Drupal Modules</span> <div class="paragraph html"> <div class="container"> <p>Some of the most interesting new modules I saw posted to <a href="https://www.drupal.org">drupal.org</a> in September:</p> <h3><a href="https://www.drupal.org/project/views_advanced_routing">Views Advanced Routing</a></h3> <p>(for Drupal 8) Allows you to specify the routing configuration <span class="caps">YAML</span> for a Views page. Meaning, you can use custom access control callbacks, default parameters, etc. Sweet!</p> <p><a href="https://www.drupal.org/project/views_advanced_routing"><img src="https://www.zengenuity.com/sites/default/files/migrated/var_form.png" /></a></p> <h3><a href="https://www.drupal.org/project/commerce_responsive_ui">Commerce Responsive <span class="caps">UI</span></a></h3> <p>Provides replacement interfaces for the parts of Drupal Commerce that are table dependent and non-mobile responsive by default. These include Responsive Cart, Responsive Checkout, and Responsive User Facing Orders.</p> <h3><a href="https://www.drupal.org/project/contrib_tracker">Drupal 8 Contrib Porting Tracker</a></h3> <p>Not a module, but a centralized place for tracking the Drupal 8 porting status of contributed projects (modules, themes, distributions). The best place to find out that the <a href="https://www.drupal.org/project/bad_judgement">Bad Judgement module</a> is <a href="https://www.drupal.org/node/2575087">ready for D8</a>!</p> <p><a href="https://www.drupal.org/project/contrib_tracker"><img src="https://www.zengenuity.com/sites/default/files/migrated/contrib-kanban.png" /></a></p> <h3><a href="https://www.drupal.org/project/advanced_crop">Advanced Image Crop</a></h3> <p>This image field cropper lets the user do a different crop in each of the image styles configured by the admin. You better have some saavy users to comprehend this, but if you do, it looks awesome.</p> <p><a href="https://www.drupal.org/project/advanced_crop"><img src="https://www.zengenuity.com/sites/default/files/migrated/advanced-image-crop.png" /></a></p> <h3><a href="https://www.drupal.org/node/2564565">Webform Replay</a> [sandbox]</h3> <p>Extends the Webform module by adding an option to “replay” selected webform values in situations where multiple webform submissions per user are allowed, and some of that information is likely to be repeated on each submission. By enabling webform replay for these fields, the user only needs to complete them for the initial webform submission, and on subsequent entries these fields will be pre-populated with the values from the previous submission.</p> <h3><a href="https://www.drupal.org/project/fff">Forbidden File Format</a></h3> <p>Flips the file field extension checking around so that you can allow all types of files <em>except</em> the extensions specified. So you could deny .js, .exe, .bat, and .com, but allow other types.</p> <p><a href="https://www.drupal.org/project/fff"><img src="https://www.zengenuity.com/sites/default/files/migrated/sel6_0.png" /></a></p> <h3><a href="https://www.drupal.org/node/2569871">Tableau <span class="caps">WDC</span></a> [sandbox]</h3> <p>Tableau 9.1 includes a new Web Data Connector feature, which lets you build connections to data accessible over <span class="caps">HTTP</span> with <span class="caps">JSON</span> data and <span class="caps">REST</span> APIs. This module attempts to bridge the gap between Drupal and Tableau by adding a new views plugin (tableau_wdc) which renders content as a <span class="caps">JSON</span> with some extra meta information needed by Tableau. Once you have created your endpoints, you can add the tableau-wdc block to any page and it will automatically render a button for each data source together with all the necessary scripts to parse and prepare the data for import.</p> <h3><a href="https://www.drupal.org/project/ndf">Nuke Drupal Frontend</a></h3> <p>Allows you to completely disable frontend <span class="caps">HTML</span> access to a Drupal site, for when you’re building a headless site, and you’re not using the Drupal-provided frontend.</p> <h3><a href="https://www.drupal.org/node/2570705">Doubtfire</a> [sandbox]</h3> <p>An alternative to the <a href="https://www.drupal.org/project/masquerade">Masquerade module</a>, with some useful <span class="caps">UI</span> additions.</p> <h3><a href="https://www.drupal.org/node/2575773">Gmail Connector</a> [sandbox]</h3> <p>Lets users view their Gmail inbox and messages in Drupal using the Gmail RESTful <span class="caps">API</span>.</p> </div> </div> <span>Wayne Eaker</span>October 1, 2015 <div class="tags"> <div class="container"> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/drupal-planet" hreflang="en">Drupal Planet</a></span> </div> </div> Thu, 01 Oct 2015 18:00:04 +0000 Wayne Eaker 202 at https://www.zengenuity.com DrupalCamp Michigan 2015: January 31 at Henry Ford College https://www.zengenuity.com/blog/2014-12/drupalcamp-michigan-2015-january-31-henry-ford-college <span class="field field--name-title field--type-string field--label-hidden">DrupalCamp Michigan 2015: January 31 at Henry Ford College</span> <div class="paragraph html"> <div class="container"> <p>Attention Michigan Drupalers! Mark your calendars for <a href="https://2015camp.michigandrupal.com/">DrupalCamp Michigan</a>, Saturday January 31 at Henry Ford College in Dearborn. For only $15, you get a full day of Drupal presentations, discussions and lunch!</p> <p>Online registration/tickets will be available soon, and we’re currently accepting session proposals. If you’ve got an interesting presentation to share, please post it to the website here: <a href="https://2015camp.michigandrupal.com">https://2015camp.michigandrupal.com</a>. Session selections will be made in early January.</p> <p>Interested is sponsoring the camp and getting your name out to the local Drupal community? Sponsorship information is available here: <a href="https://2015camp.michigandrupal.com/sponsors">https://2015camp.michigandrupal.com/sponsors</a>.</p> </div> </div> <span>Wayne Eaker</span>December 17, 2014 <div class="tags"> <div class="container"> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/drupal-planet" hreflang="en">Drupal Planet</a></span> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/drupalcamp-michigan" hreflang="en">DrupalCamp Michigan</a></span> </div> </div> Wed, 17 Dec 2014 17:49:05 +0000 Wayne Eaker 201 at https://www.zengenuity.com Decoupling Your Backend Code from Drupal (and Improving Your Life) with Wrappers Delight https://www.zengenuity.com/blog/2014-12/decoupling-your-backend-code-drupal-and-improving-your-life-wrappers-delight <span class="field field--name-title field--type-string field--label-hidden">Decoupling Your Backend Code from Drupal (and Improving Your Life) with Wrappers Delight</span> <div class="paragraph html"> <div class="container"> <p>If you've ever written a lot of custom code for a Drupal site, then you know it can be a tedious and error-prone experience. Your IDE doesn't know how Drupal's data structures are organized, and it doesn't have a way to extract information about configured fields to do any autocomplete or check data types. This leads to some frustrations:</p> <ul> <li>You spend a lot of time typing out by hand all the keys in every array of doom you come across. It's tedious, verbose, and tiring.</li> <li>Your code can contains errors your IDE won't alert you to. Simple typos can go unnoticed since the IDE has no idea how the objects and arrays are structured.</li> <li>Your code is tightly coupled to specific field names, configured in the database. You must remember these, because your IDE can't autocomplete them.</li> <li>Your code is tightly coupled to specific field types. (If you start off with a text field and then decide to switch to an email field, for example, you will find the value is now stored in a different key of the data array. You need to update all your custom code related to that field.)</li> <li>It can be easy to create cross-site-scripting vulnerabilities in your code. You need to keep in mind all the field data that needs to be sanitized for output. It only takes one forgotten spot to open your site to attacks.</li></ul> <p><a href="https://www.drupal.org/project/wrappers_delight">Wrappers Delight</a> (<a href="https://www.drupal.org/project/wrappers_delight">https://www.drupal.org/project/wrappers_delight</a>) is a development tool I've created to help address these issues, and make my life easier. Here's what it does:</p> <ul> <li>Provides wrapper classes for common entity types, with getters and setters for the entities' base properties. (These classes are wrappers/decorators around EntityMetadataWrapper.)</li> <li>Adds a Drush command that generates wrapper classes for the specific entity bundles on your site, taking care of the boilerplate getter and setter code for all the fields you have configured on the bundles.</li> <li>Returns sanitized values by default for the generated getters for text fields. (raw values can be returned with an optional parameter)</li> <li>Allows the wrapper classes to be customized, so that you can decouple your custom code from specific Drupal field implementation.</li></ul> <p>With Wrappers Delight, your custom code can be written to interface with wrapper classes you control instead of with Drupal objects directly. So, in the example of changing a text type field to an email type field, only the corresponding wrapper class needs to be updated. All your other code could work as it was written.</p> <h2>But wait, there's more!</h2> <p>Wrappers Delight also provides bundle-specific wrapper classes for EntityFieldQuery, which allow you to build queries (with field-level autocomplete) in your IDE, again decoupled from specific internal Drupal field names and formats. Whatever your decoupled CRUD needs may be, Wrappers Delight has you covered!</p> <h2>Getting Started with Wrappers Delight</h2> <p>To generate wrapper classes for all the content types on your site:</p> <ol> <li>Install and enable the Wrapper Delight module.</li> <li>Install Drush, if you don't already have it.</li> <li>At the command line, in your Drupal directory, run <code>drush wrap node</code>.</li> <li>This will generate a new module called "wrappers_custom" that contains wrapper classes for all your content types.</li> <li>Enable the wrappers_custom module, and you can start writing code with these wrapper classes.</li> <li>This process works for other entity types, as well: Users, Commerce Products, OG Memberships, Messages, etc. Just follow the Drush command pattern: <code>drush wrap ENTITY_TYPE</code>. For contributed entity types, you may need to enable a submodule like Wrappers Delight: Commerce to get all the base entity properties.</li></ol> <h2>Using the Wrapper Classes</h2> <p>The wrapper classes generated by Wrappers Delight have getters and setters for the fields you define on each bundle, and they inherit getters and settings for the entity's base properties. The class names follow the pattern <code>BundlenameEntitytypeWrapper</code>. So, to use the wrapper class for the standard article node type, you would do something like this:</p> <pre><span>$article </span><span>= new </span><span>ArticleNodeWrapper</span><span>(</span><span>$node</span><span>);<br /></span><span>$body_value </span><span>= </span><span>$article</span><span>-></span><span>getBody</span><span>();<br /></span><span>$image </span><span>= </span><span>$article</span><span>-></span><span>getImage</span><span>();</span></pre> <p>Wrapper classes also support passing an ID to the constructor instead of an entity object:</p> <pre><span>$article </span><span>= new </span><span>ArticleNodeWrapper</span><span>(</span><span>$nid</span><span>);</span></pre> <p>In addition to getters that return standard data arrays, Wrappers Delight creates custom utility getters for certain field types. For example, for image fields, these will all work out of the box:</p> <pre><span>$article </span><span>= new </span><span>ArticleNodeWrapper</span><span>(</span><span>$node</span><span>);<br /></span><span>$image_array </span><span>= </span><span>$article</span><span>-></span><span>getImage</span><span>();<br /></span><span>$image_url </span><span>= </span><span>$article</span><span>-></span><span>getImageUrl</span><span>();<br /></span><span>$image_style_url </span><span>= </span><span>$article</span><span>-></span><span>getImageUrl</span><span>(</span><span>'medium'</span><span>);<br /></span><span>$absolute_url </span><span>= </span><span>$article</span><span>-></span><span>getImageUrl</span><span>(</span><span>'medium'</span><span>, </span><span>TRUE</span><span>);<br /></span><span><br /></span><span>// Get a full <img> tag (it's calling theme_image_style<br />// under the hood)<br /></span><span>$image_html </span><span>= </span><span>$article</span><span>-></span><span>getImageHtml</span><span>(</span><span>'medium'</span><span>);</span></pre> <h2>Creating New Entities and Using the Setter Methods</h2> <p>If you want to create a new entity, wrapper classes include a static <code>create()</code> method, which can be used like this:</p> <pre><span>$values </span><span>= array</span><span>(<br /></span><span> </span><span>'title' </span>=> <span>'My Article'</span><span>,<br /></span><span> </span><span>'status' </span>=> <span>1</span><span>,<br /></span><span> </span><span>'promote' </span>=> <span>1</span><span>,<br /></span><span>);<br /></span><span>$article </span><span>= </span><span>ArticleNodeWrapper</span><span>::</span><span>create</span><span>(</span><span>$values</span><span>);<br /></span><span>$article</span><span>-></span><span>save</span><span>();</span></pre> <p>You can also chain the setters together like this:</p> <pre><span>$article </span><span>= </span><span>ArticleNodeWrapper</span><span>::</span><span>create</span><span>();<br /></span><span>$article</span><span>-></span><span>setTitle</span><span>(</span><span>'My Article'</span><span>)<br /></span><span> </span><span>-></span><span>setPublished</span><span>(</span><span>TRUE</span><span>)<br /></span><span> </span><span>-></span><span>setPromoted</span><span>(</span><span>TRUE</span><span>)<br /></span><span> </span><span>-></span><span>save</span><span>();</span></pre> <h2>Customizing Wrapper Classes</h2> <p>Once you generate a wrapper class for an entity bundle, you are encouraged to customize it to your specific needs. Add your own methods, edit the getters and setters to have more parameters or different return types. The Drush command can be run multiple times as new fields are added to your bundles, and your customizations to the existing methods will not be overwritten. Take note that Wrappers Delight never deletes any methods, so if you delete a field, you should clean up the corresponding methods (or rewrite them to get the data from other fields) manually.</p> <h2>Drush Command Options</h2> <p>The Drush command supports the following options:</p> <ul> <li>--bundles: specify the bundles to export (defaults to all bundles for a given entity type)</li> <li>--module: specify the module name to create (defaults to wrappers_custom)</li> <li>--destination: specify the destination directory of the module (defaults to sites/all/modules/contrib or sites/all/modules) </li></ul> <h2>Packaging Wrapper Classes with Feature Modules or Other Bundle-Supplying Modules</h2> <p>With the options listed above, you can export individual wrapper classes to existing modules by running a command like the following:</p> <p><code>drush wrap node --bundles=blog --module=blog_feature</code></p> <p>That will put the one single wrapper class for blog in the blog_feature module. Wrappers Delight will be smart enough to find this class automatically on subsequent runs <strong>if you have enabled the blog_feature module</strong>. This means that once you do some individual exports, you could later run something like this:</p> <p><code>drush wrap node</code></p> <p>and existing classes will be updated in place and any new classes would end up in the wrappers_custom module.</p> <h2>Did You Say Something About Queries?</h2> <p>Yes! Wrappers Delight includes a submodule called Wrapper Delight Query that provides bundle-specific wrapper classes around EntityFieldQuery. Once you generate the query wrapper classes (by running <code>drush wrap ENTITY_TYPE</code>), you can use the find() method of the new classes to execute queries:</p> <pre><span>$results </span><span>= </span><span>ArticleNodeWrapperQuery</span><span>::</span><span>find</span><span>()<br /></span><span> </span><span>-></span><span>byAuthor</span><span>(</span><span>$uid</span><span>)<br /></span><span> </span><span>-></span><span>bySomeCustomField</span><span>(</span><span>$value1</span><span>)<br /></span><span> </span><span>-></span><span>byAnotherCustomField</span><span>(</span><span>$value2</span><span>)<br /></span><span> </span><span>-></span><span>orderByCreatedTime</span><span>(</span><span>'DESC'</span><span>)<br /></span><span> </span><span>-></span><span>range</span><span>(</span><span>0</span><span>, </span><span>10</span><span>)<br /></span><span> </span><span>-></span><span>execute</span><span>();</span></pre> <p>The results array will contain objects of the corresponding wrapper type, which in this example is ArticleNodeWrapper. That means you can immediately access all the field methods, with autocomplete, in your IDE:</p> <pre><span>foreach </span><span>(</span><span>$results </span><span>as </span><span>$article</span><span>) {<br /></span><span> </span><span>$output </span><span>.= </span><span>$article</span><span>-></span><span>getTitle</span><span>();<br /></span><span> </span><span>$output </span><span>.= </span><span>$article</span><span>-></span><span>getImageHtml</span><span>(</span><span>'medium'</span><span>);<br /></span><span>}</span></pre> <p>You can also run queries across all bundles of a given entity type by using the base wrapper query class:</p> <pre><span>$results </span><span>= </span><span>WdNodeWrapperQuery</span><span>::</span><span>find</span><span>()<br /></span><span> </span><span>-></span><span>byAuthor</span><span>(</span><span>$uid</span><span>)<br /></span><span> </span><span>-></span><span>byTitle</span><span>(</span><span>'%Awesome%'</span><span>, </span><span>'LIKE'</span><span>)<br /></span><span> </span><span>-></span><span>execute</span><span>();</span></pre> <p>Note that results from a query like this will be of type WdNodeWrapper, so you'll need to check the actual bundle type and re-wrap the object with the corresponding bundle wrapper in order to use the bundle-level field getters and setters.</p> <h2>Wrapping Up</h2> <p>So, that's Wrappers Delight. I hope you'll give it a try and see if it makes your Drupal coding experience more pleasant. Personally, I've used on four new projects since creating it this summer, and it's been amazing. I'm kicking myself for not doing this earlier. My code is easier to read, WAY easier to type, and more adaptable to changes in the underlying architecture of the project.</p> <p>If you want to help me expand the project, here are some things I could use help with:</p> <ul> <li>Additional base entity classes for common core and contrib entities like comments, taxonomy terms, and Workflow states.</li> <li>Additional custom getter/setter templates for certain field types where utility functions would be useful, such as Date fields.</li> <li>Feedback from different use cases. Try it out and let me know what could make it work better for your projects.</li> </ul> <p>Post in <a href="https://www.drupal.org/project/issues/wrappers_delight">the issue queue</a> (<a href="https://www.drupal.org/project/issues/wrappers_delight">https://www.drupal.org/project/issues/wrappers_delight</a>) if you have questions or want to lend a hand.</p> </div> </div> <span>Wayne Eaker</span>December 3, 2014 <div class="tags"> <div class="container"> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/drupal" hreflang="en">Drupal</a></span> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/drupal-planet" hreflang="en">Drupal Planet</a></span> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/module-development" hreflang="en">Module Development</a></span> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/wrappers-delight" hreflang="en">Wrappers Delight</a></span> </div> </div> Wed, 03 Dec 2014 19:48:07 +0000 Wayne Eaker 200 at https://www.zengenuity.com DrupalCamp Michigan 2013: October 12 in Ann Arbor https://www.zengenuity.com/blog/2013-10/drupalcamp-michigan-2013-october-12-ann-arbor <span class="field field--name-title field--type-string field--label-hidden">DrupalCamp Michigan 2013: October 12 in Ann Arbor</span> <div class="paragraph html"> <div class="container"> <p>Michigan Drupalers! Let’s get together for DrupalCamp Michigan, Saturday October 12 in Ann Arbor. <a href="https://2013.drupalcampmi.org/program">A great program has just been announced</a>, including sessions on security, integrating Drupal with external systems, mobile app development, and Drupal 8. Registration is $15, which includes lunch and t-shirt. It’s a small venue, and there are limited tickets, so <a href="https://2013.drupalcampmi.org/register">register soon</a> to make sure you can attend.</p> <p>Drupalers from neighboring states, you’re invited too! Yes, even Buckeyes. (we’ll try to be nice)</p> <p><strong>The full program is up at:</strong> <a href="https://2013.drupalcampmi.org/program">https://2013.drupalcampmi.org/program</a><br /> <strong>Tickets are available here:</strong> <a href="https://2013.drupalcampmi.org/register">https://2013.drupalcampmi.org/register</a></p> <h2>The Scheduled Sessions</h2> <p><strong><a href="https://2013.drupalcampmi.org/sessions/keynote-power-drupal-higher-education">Keynote: The Power of Drupal in Higher Education</a></strong><br /> <a href="https://www.acquia.com/about-us/team/chris-hartigan">Chris Hartigan, General Manager for Higher Education at Acquia</a></p> <p><strong><a href="https://2013.drupalcampmi.org/sessions/social-your-site-right-way">Social in Your Site, The Right Way</a></strong><br /> <a href="https://drupal.org/user/1563308">Kevin Champion</a></p> <p><strong><a href="https://2013.drupalcampmi.org/sessions/secure-your-site">Secure Your Site</a></strong><br /> <a href="https://drupal.org/user/25701">Matt Farina</a></p> <p><strong><a href="https://2013.drupalcampmi.org/sessions/editors-swim-show-what-i-mean-versus-wysiwyg">Editors: <span class="caps">SWIM</span> (show what I mean) versus <span class="caps">WYSIWYG</span></a></strong><br /> <a href="https://groups.drupal.org/user/36164">Kieran Mathieson</a></p> <p><strong><a href="https://2013.drupalcampmi.org/sessions/drupalgap-mobile-application-development-kit-drupal">DrupalGap - A Mobile Application Development Kit for Drupal</a></strong><br /> <a href="https://drupal.org/user/150680">Tyler Frankenstein</a></p> <p><strong><a href="https://2013.drupalcampmi.org/sessions/building-curriculum-management-and-publication-system-using-erp-data-and-web-services">Building a Curriculum Management and Publication System using <span class="caps">ERP</span> Data and Web Services</a></strong><br /> <a href="https://drupal.org/user/40138">Micah Webner</a></p> <p><strong><a href="https://2013.drupalcampmi.org/sessions/drupal-and-lms-lti">Drupal and the <span class="caps">LMS</span> with <span class="caps">LTI</span></a></strong><br /> <a href="https://drupal.org/user/157079">Matthew Radcliffe</a></p> <p><strong><a href="https://2013.drupalcampmi.org/sessions/drupal-8-what-you-need-know">Drupal 8: What You Need to Know</a></strong><br /> <a href="https://drupal.org/user/326925">Wayne Eaker</a></p> <p>We will also have an after-party for networking, but we’re still working out the logistics of that.</p> </div> </div> <span>Wayne Eaker</span>October 2, 2013 <div class="tags"> <div class="container"> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/drupal-planet" hreflang="en">Drupal Planet</a></span> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/drupalcamp-michigan" hreflang="en">DrupalCamp Michigan</a></span> </div> </div> Wed, 02 Oct 2013 14:12:30 +0000 Wayne Eaker 199 at https://www.zengenuity.com Creating an iPhone App for a Drupal Website https://www.zengenuity.com/blog/2011-05/creating-iphone-app-drupal-website <span class="field field--name-title field--type-string field--label-hidden">Creating an iPhone App for a Drupal Website</span> <div class="paragraph html"> <div class="container"> <p><img src="https://www.zengenuity.com/sites/default/files/migrated/LA2M-iphone-app-1_0_0.png" alt="iPhone App for Drupal" title="iPhone App for Drupal" width="150" height="299" />Recently, Zengenuity released the <a href="https://zengenuity.com/portfolio/iphone-app-drupal-website">LA2M to Go iPhone app</a>, a companion to the <a href="https://zengenuity.com/portfolio/non-profit-marketing-group-website">Lunch Ann Arbor Marketing website</a>. The LA2M website is one we built on Drupal 6, and it uses custom content types, <a href="https://drupal.org/project/views">Views</a> and <a href="https://drupal.org/project/imagecache">Imagecache</a> for displaying upcoming and archived event information for the group. In creating our app, I wanted to build a system to synchronize the mobile device with the website, since event information changes on at least a weekly basis. It took a lot of research and testing various techniques (many of which of which didn’t end up working), but finally I worked out how to have the app to pull all its information automatically from the <a href="https://drupal.org">Drupal</a> site. I hope that by publishing this blog post, I can speed up this process for you, and save you some frustration. There isn’t room here to go into every detail, but I’ll discuss the major frameworks I used, and I’ll point out some important issues that wasted a lot of my time.</p> <h2>The Titanium Mobile Framework</h2> <p><img src="https://www.zengenuity.com/sites/default/files/migrated/titanium.png" alt="Titanium SDK" title="Titanium SDK" width="295" height="200" />LA2M to Go is built with the <a href="https://www.appcelerator.com/products/titanium-mobile-application-development/">Titanium Mobile Framework</a> by <a href="https://www.appcelerator.com/">Appcelerator</a>. The Titanium SDK is free, and it allows you to write native iPhone apps in web developer friendly Javascript, instead of Objective-C. It also allows you to compile the same source code as a native Android app, though this is something I haven’t tried yet. To get started with Titanium, <a href="https://www.appcelerator.com/products/download/">download the SDK here</a>. This will provide you with the Titanium Developer application you need to compile your app. You will also need to have developer tools for the target mobile platform you plan to work with. So, if you are writing an iPhone app, you need to have an <a href="https://developer.apple.com/programs/register/">Apple iOS developer account</a>, and download XCode and the iOS SDK from Apple. Android developers will need to get the <a href="https://developer.android.com/sdk/index.html">Android SDK from Google</a>.</p> <p>When coding for Titanium, you have to use your own text editor or IDE, and then load your project into the Titanium Developer application. Titanium-based apps are compiled in a two-step process. First, your code is rewritten by Titanium Developer as native iPhone or Android source code. Then, command line tools provided by the iPhone and Android SDKs are used to compile the native code and install it into the simulator or directly onto your mobile device. </p> <p><strong>One word of warning: This process absolutely sucks.</strong> It’s the worst compilation and debugging system I’ve ever experienced. When working with Titanium, be prepared to write a lot of output log statements, because that’s how you’ll be doing much of your debugging. Also, the Titanium platform is seriously lacking in documentation, and the docs that do exist are difficult to use and search. There are very few example code snippets in the <a href="https://developer.appcelerator.com/apidoc/mobile/latest">API docs</a>, which means you need to search the <a href="https://developer.appcelerator.com/questions/created">developer forums</a>. In the forums, code is often wrong or out-of-date, as there’s no way to know which version of the Titanium API it’s designed for. (The API has changed significantly from its initial release.) Appcelerator recently purchased <a href="https://aptana.com/">Aptana</a>, the makers of the excellent Aptana Studio IDE. So, I’m praying they ditch the Titanium Developer application for a more fully-featured IDE, with debugging tools and built-in documentation. This would help developers immensely. (<strong>Update (5/17/11):</strong> They have now done exactly this: <a href="https://developer.appcelerator.com/blog/2011/05/major-titanium-updates.html">https://developer.appcelerator.com/blog/2011/05/major-titanium-updates.html</a>)</p> <p>That said, I’ve written code in Objective-C with XCode before, and while XCode has both excellent debugging features and documentation, the speed at which you I create robust apps with Titanium, in a language that is more familiar to me as a web developer, means I’ll coming to back to it for my next app, despite its failings. </p> <h2>The DrupalCon Chicago App</h2> <p><img src="https://www.zengenuity.com/sites/default/files/migrated/drupalcon-chicago.jpeg" alt="DrupalCon Chicago App" title="DrupalCon Chicago App" width="175" height="175" />If you were at <a href="https://chicago2011.drupal.org/">DrupalCon Chicago</a>, you probably used the <a href="https://itunes.apple.com/us/app/drupalcon/id421074805?mt=8">conference app</a> that was available for iPhone and Android. That app was created in Titanium, by the folks at <a href="https://www.palantir.net/">Palantir</a>. Fortunately for all of us developing Drupal-connected apps, they have made <a href="https://github.com/palantirnet/drupalcon_mobile">the app’s source code available</a>, and <a href="https://www.palantir.net/blog/mobilizing-drupalcon-chicago">their blog post about it is very informative</a>.</p> <p>There isn’t a lot of technical documentation about how expand or change the app, but after spending some time with it, I was able to figure out the key details:</p> <h3>Schema Definitions</h3> <p>The app is designed to import Drupal nodes and store them in an SQLite database on the device. This way the app can be used offline. In the code, there is a schema definition designed for the DrupalCon event node type in the <span><strong>Resources/drupalcon/entity.js</strong></span> file. (You will also find a schema for Drupal.org users in there, too.) If you are building a new app from this source code, you need to modify their schema to match whatever fields your content types have. They have chosen to call their event schema “node”, but you can rename this to whatever you want, and add additional schemas for more types. If you are adding new schema types (or changing the name of them), you need to make entries for them in the <strong><span>Resources/drupal/entity.js</span></strong> file. There’s a section at the top that starts with </p> <p><strong><span>main: {<span> <br /></span>     ...</span></strong><br /><strong><span>     types {</span></strong><br /><strong><span>      <em>[a list of types and primary keys is here]</em></span></strong></p> <p>Add your new schema names to this list, following the pattern of the others in that section.</p> <p>One thing I found useful when modifying the schema were the two commented out lines in app.js that clear the DB on every app launch. </p> <p><strong><span>//Drupal.entity.db('main', 'node').initializeSchema();</span></strong><br /><strong><span>//Drupal.entity.db('main', 'user').initializeSchema();</span></strong></p> <p>These obviously need to be commented out in production, but are useful when changing the schema. (Adjust the parameters to fit your schema names.) Another helpful tool you will want to have is some sort of SQLite database browser. I ended up using the <a href="https://addons.mozilla.org/en-US/firefox/addon/sqlite-manager/">SQLite Manager Firefox extension</a>, though, I didn’t especially enjoy it. It works, but it’s kind of clunky. If you are building and iPhone app and working in the simulator, the SQLite database is difficult to find. Here’s where it’s at:</p> <p><span><strong>/Users/[your_username]/Library/Application Support/iPhone Simulator/[sdk_version]/Applications/[application_guid]/Library/Application Support/database/main.sql</strong></span></p> <p>Yeah...it’s really buried down there. One of those folders is hidden too, so you will not find that database file with Finder. You just have to know where it is.</p> <h2>Renderers</h2> <p>If you look in <strong><span>Resources/drupalcon/drupalcon.js</span></strong>, you will see a series of sections that start like this:</p> <p><strong><span>DrupalCon.renderers.session = function(session) {</span></strong></p> <p>These sections define “renderers” for different entities that you have in the SQLite database. They don’t do anything on their own. You have to call it yourself after you query the DB. But, when you look around in the app code, you’ll see that having a function that renders an SQLite database row as a TableViewRow is very handy. It’s sort of like defining a theme template for a particular node type. If you have added fields and types to the database schema, you will need to update the renderers to display your new fields.</p> <h2>Windows</h2> <p>In <strong><span>Resources/windows</span></strong> are a bunch of files that define the various screens of the app. I don’t have space in this post to go through them all, but if you look at each one, you’ll get an idea of what you need to do to make similar windows that match your project. Most of them are TableViews, which is often what you want. There’s also a MapDetailWindow that shows a static image, and other windows to render HTML content. Be sure to include any new window files you create in your <strong><span>Resources/app.js</span></strong> file. (There’s a section in it with all the includes in a list.) Also, you need to add your new windows to the app’s tab group. That can be done by following the pattern in <strong><span>Resources/main.js</span></strong>. </p> <h2>Connecting the App to Drupal</h2> <p>When I started this project, I expected to use the <a href="https://drupal.org/project/services">Services module </a>to access the Drupal data over JSON. My plan was to create a view for each content type that I needed to download and expose those views with the Views Service module. However, when trying this approach, I came across a big limitation of Views Service. This module only exposes the Views data that is directly returned by the query. It completely skips the normal rendering step of Views. This means if you want to include the URL for a specific Imagecache-processed version of an image, it can’t be done with Views Service. You also can’t rewrite the output of fields to combine two fields into one. Pretty much everything that is awesome about Views you can’t do with Views Service. </p> <p>Views Datasource to the rescue! The day after I hit this limitation, Palantir released their code for their DrupalCon app. In their post, you will see they reference the <a href="https://drupal.org/project/views_datasource">Views Datasource module</a> as an alternative to Services. Views Datasource allows you to expose Views as JSON, and it DOES send the output through the normal Views rendering engine. So, this means you can rewrite fields, get Imagecache urls, change field labels, and probably re-theme fields if you need to, though I didn’t try this last one. With this module, I was able to get exactly the JSON output I needed to download data into the app. I’m not going to go into a full detailed explanation, because actually, if you know how to use Views, it’s pretty straightforward. It works exactly like you expect it to.</p> <h2>Putting Everything Together</h2> <p><img src="https://www.zengenuity.com/sites/default/files/migrated/la2m-to-go-icon.jpeg" alt="LA2M to Go" title="LA2M to Go" width="175" height="175" />Once I had the JSON-formatted view on the Drupal site and the schemas defined in my application, everything else was just a matter of getting of the user experience and workflow that I wanted. (Ever app is different, so you’ll have to study the Titanium API and figure out what works for your project.) After only a couple weeks of development, I was able to go from conception to launching <a href="https://itunes.apple.com/us/app/la2m-to-go/id433060438?mt=8">LA2M to Go in the App Store</a>. While it was frustrating at times deciphering both the Titanium debug output and the DrupalCon app’s design, I’m really happy with the outcome. I’ll definitely use both of these technologies in future for Drupal-connected iPhone apps, and hopefully, this post will make it a little less frustrating for you to do the same.</p> <h2>Useful Resources</h2> <p><a href="https://developer.appcelerator.com/get_started">Getting Started with Titanium Mobile Platform</a></p> <p><a href="https://developer.appcelerator.com/blog/2010/05/how-to-create-a-tweetie-like-pull-to-refresh-table.html">How to Create a Pull-to-Refresh Table in Titanium</a> - Everyone loves this UI control.</p> <p><a href="https://github.com/workhabitinc/drupal-ios-sdk">Drupal-iOS SDK</a> - A separate project that connects an iPhone app to a Drupal site using only native code in XCode. Have not tried this one out. </p> </div> </div> <span>Wayne Eaker</span>May 10, 2011 <div class="tags"> <div class="container"> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/android" hreflang="en">Android</a></span> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/drupal" hreflang="en">Drupal</a></span> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/drupal-planet" hreflang="en">Drupal Planet</a></span> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/drupalcon-chicago" hreflang="en">DrupalCon Chicago</a></span> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/iphone" hreflang="en">iPhone</a></span> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/mobile" hreflang="en">Mobile</a></span> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/services-module" hreflang="en">Services module</a></span> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/titanium-mobile-framework" hreflang="en">Titanium mobile framework</a></span> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/views-module" hreflang="en">Views module</a></span> </div> </div> Tue, 10 May 2011 12:00:00 +0000 Wayne Eaker 143 at https://www.zengenuity.com How to Import Files into Drupal CCK Fields https://www.zengenuity.com/blog/2010-11/how-import-files-drupal-cck-fields <span class="field field--name-title field--type-string field--label-hidden">How to Import Files into Drupal CCK Fields</span> <div class="paragraph html"> <div class="container"> <p>At last night's <a href="https://groups.drupal.org/ann-arbor">Ann Arbor Drupal Meetup</a>, a question was raised about how to write an import script to create <a href="https://drupal.org">Drupal</a> nodes that include a <a href="https://drupal.org/project/filefield">Filefield CCK</a> field. Filefields and <a href="https://drupal.org/project/imagefield">Imagefields</a> present a particular problem for import because the file must be stored into the Drupal files table before you can save them to the node. In the discussion that followed, it seemed that several of us at the meeting had developed our own ways to do this in the past for various reasons. So, I thought I would post a couple solutions for others who need to do this.</p> <h2>Option 1: Read the Manual</h2> <p>There is <a href="https://drupal.org/node/330421">a manual page that explains how to programmatically import files to Filefield</a>. However, like many Drupal manual pages, if you read through the comments, there are about 20 different versions of the code, and the discussion is often difficult for novice users to follow. The code on this page does work, but there is something simpler that we ended up with at the meeting.</p> <h2>Option 2: Use a Helper Function</h2> <p>The Filefield module has a helper function called <a href="https://api.lullabot.com/field_file_save_file">field_file_save_file()</a> that does a lot of the work for you. To use the function, you just need to give the original file path and a destination path if you want the file copied into a new location. Here's a simple example of a script that creates a story node and imports an image file into the "field_image" CCK field. (The file importing lines are highlighted.)</p> <pre><pre class="[8,9]'"> <?php $node = new StdClass(); $node->type = 'story'; $node->body = 'Testing.'; $node->title = 'Testing'; $file = field_file_save_file('import-images/buzz2.jpg', array(), 'sites/default/files/images'); $node->field_image = array($file); $node->uid = 1; $node->status = 1; node_save($node); </pre></pre> <p>This script can't be run on the command line without some other include lines to bootstrap the Drupal system. However, if you use <a href="https://drupal.org/project/drush">Drush</a> (and you should be using Drush), you don't need to do anything extra. Just run "<span><strong><span>drush scr import.php</span></strong></span>". That's it. Adding those two lines to your import script is all need to do to import files to Filefield CCK fields. Easy to do now that you know.</p> <p>If you have any questions or problems using the method above, leave a comment below, and I'll see if I can help you out.</p> </div> </div> <span>Wayne Eaker</span>November 30, 2010 <div class="tags"> <div class="container"> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/ann-arbor" hreflang="en">Ann Arbor</a></span> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/drupal" hreflang="en">Drupal</a></span> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/drupal-planet" hreflang="en">Drupal Planet</a></span> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/filefield-module" hreflang="en">Filefield module</a></span> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/imagefield-module" hreflang="en">Imagefield module</a></span> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/import" hreflang="en">Import</a></span> </div> </div> Tue, 30 Nov 2010 15:28:12 +0000 Wayne Eaker 120 at https://www.zengenuity.com Widespread SEO Problems with Drupal’s Taxonomy Module https://www.zengenuity.com/blog/2010-11/widespread-seo-problems-drupals-taxonomy-module <span class="field field--name-title field--type-string field--label-hidden">Widespread SEO Problems with Drupal’s Taxonomy Module</span> <div class="paragraph html"> <div class="container"> <p>This week I’ve been working on fixing some <a href="https://172.28.128.3/blog/a/201011/seo-problem-drupals-site-map-module-how-fix-it">duplicate content problems on our site coming from the Site Map module</a>. In doing so, it’s become obvious that the design of <a href="https://drupal.org">Drupal’s</a> taxonomy module makes it very easy to accidentally end up with Google indexing multiple copies of your term pages. This SEO problem isn’t just happening to Drupal rookies. Even top Drupal firms are screwing this up. Take a look at some examples from prominent Drupal company websites:</p> <p><strong>[Update:</strong> Most of these have now been fixed, so the results now are lower than I state. But they originally were correct.<strong>]</strong></p> <p><strong><a href="https://www.google.com/search?q=site%3Ahttp%3A%2F%2Fwww.lullabot.com%2Ftaxonomy%2Fterm%2F4">Lullabot’s Podcast Taxonomy Page</a></strong><br />There are 84 Google-indexed pages for only 10 pager pages for this term. PLUS, there are <a href="https://www.google.com/search?q=site:http%3A%2F%2Facquia.com%2Fcategory%2Ftags%2Facquia-drupal-planet">an additional 20 indexed pages at the aliased URL for this page</a>.</p> <p><strong><a href="https://www.google.com/search?q=site%3Ahttp%3A%2F%2Fdevelopmentseed.org%2Ftaxonomy%2Fterm%2F42">Development Seed’s “Drupal” Taxonomy Page</a></strong><br />60 indexed pages for 31 pager pages, <a href="https://www.google.com/search?q=site:http%3A%2F%2Fdevelopmentseed.org%2Ftags%2Fdrupal">plus 201 indexed pages for the aliased address</a>.</p> <p><strong><a href="https://www.google.com/search?q=site:https://www.leveltendesign.com/taxonomy/term/147">LevelTen’s SEO Taxonomy Page</a></strong><br />19 indexed pages for 10 pager pages, <a href="https://www.google.com/search?q=site:https://www.leveltendesign.com/category/tags/seo">plus 10 indexed pages for the alias address</a>.</p> <p><strong><a href="https://www.google.com/search?q=site%3Ahttp%3A%2F%2Facquia.com%2Ftaxonomy%2Fterm%2F422">Acquia's Drupal Planet Taxonomy Page</a></strong><br />55 indexed pages for 26 pager pages, <a href="https://www.google.com/search?q=site:http%3A%2F%2Facquia.com%2Fcategory%2Ftags%2Facquia-drupal-planet">plus 37 indexed pages indexed for the alias address</a>. </p> <p>It’s clear that Google is routinely picking up duplicates of taxonomy pages for Drupal sites. Why? Examining the results above, I see a few different reasons:</p> <ol><li>Some of these sites are not using the <a href="https://drupal.org/project/globalredirect">Global Redirect module</a>, which means that the internal taxonomy page path “taxonomy/term/##” returns the exact same page as the aliased address. It doesn’t redirect. It just shows the same page. That in itself wouldn’t be a problem if nobody ever linked to the internal path. (because then Google would never pick it up) But, it’s clear that linking mistakes have been made at some point, or some module has exposed the internal links to Google by using them on a page. Now they are in the Google index and they won’t come out on their own.</li><li>The term depth argument for the taxonomy page (and taxonomy_term view) allows identical pages to be indexed at both at “taxonomy/term/##” and “taxonomy/term/0”, and potentially also at “taxonomy/term/##/all”. (The “/all” link may result in different content if you have hierarchical tags. But, in most cases, it’s the same.) The Global Redirect module can take care of the “/0” for you. Handling the “/all” will take some more effort. (see the solution below) </li><li>The taxonomy pages actually allow anything to be put in the depth argument position.  This leads to a problem where if you have an accidental relative link in a node on the page, you will create an entirely new set of indexed pages. For example, on page two of the Development Seed results above are URLs that look like this:<br /><br /> <strong><span>https://developmentseed.org/taxonomy/term/42/www.mapbox.com?page=32</span></strong><br /><br />At some point in the past, there was probably a link that didn’t have an https:// at the beginning, so it was treated like a relative link. Google followed the link, and a whole new series of identical indexed pages was created by the following the pager links on these pages. </li></ol> <h2>Fixing This Problem</h2> <p>There’s a few steps you can take on your Drupal site if you want to prevent these duplicate term pages from getting indexed or if you want to tell Google to remove already indexed pages from their results.</p> <ol><li>If you haven’t, <a href="https://172.28.128.3/blog/a/201011/fixing-duplicate-content-seo-problems-drupal">fix your .htaccess to redirect to a single domain as described in this previous post</a>.</li><li>Install the <a href="https://drupal.org/project/globalredirect">Global Redirect module</a>. This will redirect the taxonomy page's internal path URL with and without the “/0” to your user-friendly alias URL. </li><li>To fix the issue with arbitrary text being appended to the URL, you can add a Rewrite rule to your .htaccess to redirect URLs with additional arguments to the main URL. The one I have put on this site is:<br /><br /><span><strong><span>RewriteRule ^taxonomy/term/([0-9]+)/(.*)$  /taxonomy/term/$1 [NC,L,QSA,R=301] </span></strong></span><br /><br />This rule won’t work for sites that need to use the “/all” address, but I’m sure it can be rewritten to support that. I’m not a RewriteRule expert, though, so if someone has an alternative, please post it in the comments and I’ll update the post.</li><li>If you have hierarchical tags, and you want to make the default taxonomy page function like the “/all” page, you can enable the taxonomy_term view, remove the Term Depth argument, and then set your own depth on the Term ID argument. </li></ol> <h2>Views with Pagers - Should You Index All the Pages?</h2> <p>The last thing you might consider doing is telling Google not to index pager subpages beyond the first one. This is a preference issue. Personally, I think it’s better to have Google focus its results on a single page for each term. In my opinion, it’s better for SEO and better for users who click on result links to go to the first page for a term rather than somewhere in the middle. But, I can understand why some people might want all the pages indexed to make sure nothing is missed.</p> <p>If you do want to remove your pager pages from the Google index, you need to add a NOINDEX,FOLLOW meta tag to all the pager pages, except for the first one. There are two ways you can do this:</p> <ol><li>Change the setting for this in the <a href="https://drupal.org/project/nodewords">Nodewords module</a> - <strong>DO NOT DO THIS</strong>! While there is a configuration option designed for this purpose in Nodewords, it’s got a major bug (<a href="https://drupal.org/node/835172">https://drupal.org/node/835172</a>) in the stable release. Do not mess with this option, or you’ll likely end up getting NOINDEX on the exact pages you want Google to index. The thing is coded backwards or something. This issue's supposedly fixed in the -dev version if you want to try that.</li><li><a href="https://www.seo-expert-blog.com/blog/avoiding-duplicate-title-tags-on-pager-pages-in-drupal">Follow the instructions on this page to modify your theme page template to add the required meta tag to pager pages.</a> This isn’t the best long-term option, especially if you’re using a standard template, since you’ll lose this change in an upgrade. But, until Nodewords is fixed, it’s the best way to go. </li></ol> <h2>Conclusion</h2> <p>I’ve implemented all of the techniques listed above on this site, and now we have a single indexable page for each taxonomy term. All the other variations <a href="https://www.google.com/support/webmasters/bin/answer.py?hl=en&answer=93633">301 redirect</a> to the user-friendly aliased URL. Hopefully, this will allow us to concentrate as much page rank as possible on those pages for those terms. I recommend that everyone who uses the Taxonomy module on their Drupal website and is concerned about SEO take a look at how your site is being indexed. These fixes are pretty easy. But, it's clear (and surprising) that hardly anyone in the Drupal community has noticed this issue. I'm adding these items to my pre-launch checklist for all future websites. </p> </div> </div> <span>Wayne Eaker</span>November 17, 2010 <div class="tags"> <div class="container"> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/drupal" hreflang="en">Drupal</a></span> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/drupal-planet" hreflang="en">Drupal Planet</a></span> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/search-engine-optimization" hreflang="en">Search Engine Optimization</a></span> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/taxonomy-module" hreflang="en">Taxonomy module</a></span> </div> </div> Wed, 17 Nov 2010 16:41:22 +0000 Wayne Eaker 117 at https://www.zengenuity.com An SEO Problem with Drupal's Site Map Module & How to Fix It https://www.zengenuity.com/blog/2010-11/seo-problem-drupals-site-map-module-how-fix-it <span class="field field--name-title field--type-string field--label-hidden">An SEO Problem with Drupal's Site Map Module & How to Fix It</span> <div class="paragraph html"> <div class="container"> <p>Having recently rebuilt the Zengenuity website, it just was this week that I finally got around to setting it up on our SEOmoz account. After the initial site crawl, I was surprised to discover this:</p> <p><img src="https://www.zengenuity.com/sites/default/files/migrated/errors.png" alt="Duplicate Page Title Erros" width="163" height="81" /></p> <p>Duplicate content in <a href="https://drupal.org">Drupal</a> is often a problem. The URL alias system causes content to show up at multiple URLs by default. <a href="https://zengenuity.com/blog/a/201011/fixing-duplicate-content-seo-problems-drupal">I’ve posted about this problem (and it’s solutions) before</a>. But, I thought we doing everything right, so I was surprised to see so many duplicate content errors.</p> <h2>The Problem: The Site Map Module’s Default Settings</h2> <p>After chasing down the issue, I found that the <a href="https://drupal.org/project/site_map" title="Site Map module">Site Map module</a> was the culprit. Site Map creates a simple, human-readable list of your website content using your menus items and taxonomy terms. It’s a good way to ensure that both real users and search engines know about all the content on your site. However, the default settings for the taxonomy part of the module can cause a duplicate content to show up. Here’s how:</p> <p>Drupal has a built in page for each taxonomy term on your site. The URL for this page looks something like this: </p> <p><span><strong><span>https://zengenuity.com/taxonomy/term/8</span></strong></span></p> <p>If you have installed and configured the <a href="https://drupal.org/project/pathauto" title="Pathauto Module">Pathauto module</a>, this URL will probably look more user friendly. Ours looks like this: </p> <p><span><strong>https://zengenuity.com/blog/tags/panels-module</strong></span></p> <p>Theses aliases work great for normal taxonomy page links, like the ones that appear at the bottom of our blog posts. However, the aliases don't work with Site Map. Instead of using the friendly URLs by default, it appends “/all” to the original URLs. So, all the taxonomy links look like this:</p> <p><span><strong>https://zengenuity.com/taxonomy/term/8/all</strong></span></p> <p>The configuration option that controls this is here:<br /><img src="https://www.zengenuity.com/sites/default/files/migrated/site_map_seo_1.png" alt="Site Map Taxonomy Settings" width="550" height="186" /> </p> <p>The reason "all" is the default is that websites with hierarchal taxonomy structures will likely want to display all content tagged with a term or any of its children. That’s what the “/all” option does. However, most sites, including this one, do not use hierarchical tags. So, this option has no effect other than to create duplicate content. When Google and other search engines index the site map page and see these "/all" links, they will index them separately from your friendly taxonomy links, and you may end up getting penalized for the content duplication.</p> <h2>The Solution</h2> <p>You should change the Site Map taxonomy setting setting to “-1” instead of “all”. <br /><img src="https://www.zengenuity.com/sites/default/files/migrated/site_map_seo_2.png" alt="Site Map Taxonomy Settings" width="550" height="186" /> </p> <p>Once this is done, all the links in your Site Map page will change to their Pathauto-generated values. For sites with a flat taxonomy, there is no downside to doing this. For sites that do have hierarchical taxonomies, you can replace the standard Drupal taxonomy pages with the taxonomy_term view that comes with Views. Once you do that, you can remove the depth argument and manage the depth you want to display with the term argument.</p> <h2>Conclusion</h2> <p>Over 30,000 sites currently use the Site Map module. I’m betting that many of them have never noticed this issue before, since as site builders we rarely actually look at the site map. Luckily, this problem is pretty easy to fix, once you know it’s there. It’s just one more thing to add to your pre-launch checklist. </p> </div> </div> <span>Wayne Eaker</span>November 16, 2010 <div class="tags"> <div class="container"> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/drupal" hreflang="en">Drupal</a></span> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/drupal-planet" hreflang="en">Drupal Planet</a></span> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/search-engine-optimization" hreflang="en">Search Engine Optimization</a></span> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/seo" hreflang="en">SEO</a></span> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/site-map-module" hreflang="en">Site Map module</a></span> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/taxonomy-module" hreflang="en">Taxonomy module</a></span> </div> </div> Tue, 16 Nov 2010 15:05:26 +0000 Wayne Eaker 116 at https://www.zengenuity.com Fixing Duplicate Content SEO Problems in Drupal https://www.zengenuity.com/blog/2010-11/fixing-duplicate-content-seo-problems-drupal <span class="field field--name-title field--type-string field--label-hidden">Fixing Duplicate Content SEO Problems in Drupal</span> <div class="paragraph html"> <div class="container"> <p>Having duplicate content is a major problem for <a href="https://172.28.128.3/services/seo-search-engine-optimization" title="Michigan Search Engine Optimization">search engine optimization</a> (<a href="https://172.28.128.3/services/seo-search-engine-optimization" title="Michigan SEO">SEO</a>). To put it simply, by having multiple copies of the same content online, you’re competing against yourself for search result rankings. And <a href="https://www.google.com/support/webmasters/bin/answer.py?answer=66359">Google has also been explicit that duplicate content on your site may result in lower search ranking</a>. Avoiding this penalty is important for maximizing your site's search rankings, however, Drupal's default configuration can often lead you to unknowingly create duplicate content. Read on to find out how to fix this issue.</p> <p></p> <h2>The Problem: Drupal's URL Path System Causes Duplicate Content</h2> <p>In general, it’s pretty easy to avoid showing content more than one place, but <a href="https://drupal.org">Drupal</a> users need to pay special attention to the way URL paths are managed. Let’s take a made-up example: a page called "My Puppies" on the site https://www.example.com. We'll assume that the page’s internal Drupal path is “node/10”, and we’ve given it a friendly path alias of “my-puppies”. With the default configuration of Drupal, this page will now visible at ALL of the following URLs:</p> <table><tbody><tr><td>https://www.example.com/node/10<br />https://www.example.com/node/10/<br />https://www.example.com/my-puppies<br />https://www.example.com/my-puppies/<br />https://www.example.com/My-Puppies<br />https://www.example.com/My-Puppies/</td><td>https://example.com/node/10<br />https://example.com/node/10/<br />https://example.com/my-puppies<br />https://example.com/my-puppies/<br />https://example.com/My-Puppies<br />https://example.com/My-Puppies/</td></tr></tbody></table> <p>That’s <span>TWELVE</span> different pages with exactly the same content, all from only one node. And it could be even more, since any variation in capitalization ("/mY-pUppIES", "/My-puppiES", etc.) will also load this page. Luckily, this problem is relatively easy to fix by doing the following two things.</p> <h2>Step One: Edit Drupal's .htaccess file to redirect all users to a single domain.</h2> <p>The .htaccess file that comes with Drupal is located at the top level of the website folder tree. (with index.php and update.php) In this file, there is a disabled section of options that looks like this:</p> <pre><pre class=" php;'"> # If your site can be accessed both with and without the 'www.' prefix, you # can use one of the following settings to redirect users to your preferred # URL, either WITH or WITHOUT the 'www.' prefix. Choose ONLY one option: # # To redirect all users to access the site WITH the 'www.' prefix, # (https://example.com/... will be redirected to https://www.example.com/...) # adapt and uncomment the following: # RewriteCond %{HTTP_HOST} ^example\.com$ [NC] # RewriteRule ^(.*)$ https://www.example.com/$1 [L,R=301] # # To redirect all users to access the site WITHOUT the 'www.' prefix, # (https://www.example.com/... will be redirected to https://example.com/...) # uncomment and adapt the following: # RewriteCond %{HTTP_HOST} ^www\.example\.com$ [NC] # RewriteRule ^(.*)$ https://example.com/$1 [L,R=301] </pre></pre> <p>Pick one of the domain options by deleting the # in front of the two associated lines, and don't forget to actually put your own domain name in there. So, on this website we use:</p> <pre><pre class=" php;'"> # RewriteCond %{HTTP_HOST} ^www\.zengenuity\.com$ [NC] # RewriteRule ^(.*)$ https://zengenuity.com/$1 [L,R=301] </pre></pre> <p>You have now eliminated half of the duplicate URLs in the list above.</p> <h2>Step Two: Install and Enable the Global Redirect Module</h2> <p>The <a href="https://drupal.org/project/globalredirect">Global Redirect module</a> does three things for you. First, it will automatically remove any slash at the end of a URL. Second, it will redirect any URL that has different capitalization to the URL address that you actually selected. So, https://www.example.com/My-Puppies will automatically redirect to https://www.example.com/my-puppies. Finally, Global Redirect will ensure that anyone accessing the original internal Drupal path (https://www.example.com/node/10) gets redirected to the path alias that you created.</p> <h2>Conclusion</h2> <p>After editing .htaccess and enabling Global Redirect, you will be left with a single URL for this content:</p> <p>https://www.example.com/my-puppies</p> <p>Every other variation of the URL will be redirected to this address with search engine friendly <a href="https://www.google.com/support/webmasters/bin/answer.py?hl=en&answer=93633">301 redirects</a>. By following these guidelines, you will eliminate the duplicate content that is generated by the Drupal system itself and take a big step towards maximizing the search engine ranking of your site content. </p> </div> </div> <span>Wayne Eaker</span>November 15, 2010 <div class="tags"> <div class="container"> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/drupal" hreflang="en">Drupal</a></span> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/drupal-planet" hreflang="en">Drupal Planet</a></span> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/duplicate-content" hreflang="en">Duplicate Content</a></span> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/global-redirect-module" hreflang="en">Global Redirect module</a></span> <span class="tag"><a href="https://www.zengenuity.com/blog/tags/search-engine-optimization" hreflang="en">Search Engine Optimization</a></span> </div> </div> Mon, 15 Nov 2010 15:50:30 +0000 Wayne Eaker 115 at https://www.zengenuity.com