Hacking Managing News, part 1: Adding a Starred channel

For my work with AdHack, I've started using Managing News as a Drupal-based solution for brand monitoring. As with most Development Seed products, it's a well-designed piece of software that is a joy to work with and enhance.

One of my first tasks was to allow users to mark articles with a star, and to find those starred items in their own channel. In MN, channels are lists of articles that are tagged with a specific set of tags. The channel is defined by its tags, and the MN views logic dynamically creates the list by matching a channel's tags with existing articles.

Of course, the starring logic is different: an article's star has nothing to do with tags. But from a usability perspective, we felt that users would intuitively search for their starred items in the channels, since this is the section where content is manually organized. That's why we decided to have a "Starred" channel that would list all starred articles. What follows is a description of how I implemented this feature.

Configuring the starring infrastructure

  • Fortunately, MN already uses a marking mechanism, namely the Mark module, based on the flexible Voting API. It is used in MN to mark items as trashed, so I created a new mark called "star" and associated it with the Data table "feeds_data_syndication" (at admin/settings/mark).

  • I then added the "Syndication: star" field to all the views where I want the Star icon to appear:

    • List of feed articles (mn_news)
    • List of channel articles (mn_channels_news)

When I edited these views, I noticed there are many displays, so I made sure to add the new field everywhere the "Syndication: trash" appears. I also removed the label from the field, because the theme will take care of displaying the Star icon where necessary.

Displaying the starring UI

This is where things started to get interesting. I wanted to modify the articles display to display a star above the trash icon, like so: The Star icon

  • Modify profiles/managingnews/themes/jake/templates/mn-feeditem.tpl.php to display the star among the right-hand side icons:
- $links = jake_views_render_field($fields['simpleshare_link']) . jake_views_render_field($fields['mark_trash']);
+ $links = jake_views_render_field($fields['simpleshare_link']) . jake_views_render_field($fields['mark_star']) . jake_views_render_field($fields['mark_trash']);
  • Modify profiles/managingnews/themes/jake/js/theme.js to prevent star-clicking from hiding the article (which is the behaviour for the trash):
- $('.mark-link > a').bind('mark.drupalMark', function() { $(this).parents('li.views-row').hide(600);});
+ $('.views-field-mark-trash a').bind('mark.drupalMark', function() { $(this).parents('li.views-row').hide(600);});
  • Add necessary CSS to display star and un-star icons. Because I'm no designer, I just used existing icons in the MN sprites. I placed this file in a new module in sites/all/modules/mymodule but you could place it in your theme too:
/**
* @file mymodule.css
*/

div.feeditem-links div.views-field-mark-star div.mark-link.unmarked a {
  background:transparent url(../../../../profiles/managingnews/themes/jake/images/sprite.png) no-repeat scroll -370px 0px;
}

div.feeditem-links div.views-field-mark-star div.mark-link.marked a {
  background:transparent url(../../../../profiles/managingnews/themes/jake/images/sprite.png) no-repeat scroll -370px -30px;
}

div.feeditem-links div.views-field-mark-star div.mark-link a:hover {
  background-color:#444;
  width:160px;
  text-indent:0px;
  overflow:hidden;
  padding-left:30px;
}
  • Add necessary JavaScript to modify the icon when an article is starred. I placed this file in the same new module in sites/all/modules/mymodule:
// @file mymodule.js

Drupal.behaviors.mymodule = function(context) {
  $('.views-field-mark-star a').bind('mark.drupalMark', function() {
    if ($(this).parents('.mark-link').hasClass('marked')) {
      $(this).css('background-position', '-370px -30px');
    }
    else {
      $(this).css('background-position', '-370px 0px');
    }
  });
}
  • Finally, I ensured these .css and .js files would appear on all pages in mymodule.module:
<?php
// @file mymodule.module

function mymodule_init() {
 
drupal_add_js(drupal_get_path('module', 'mymodule') . '/mymodule.js');
 
drupal_add_css(drupal_get_path('module', 'mymodule') . '/mymodule.css');
}

?>

Displaying the Starred channel

At this point, I had a functional starring mechanism. What was missing was the hard-coded Starred channel that would display all starred items. It did involve some hacking...

  • Create a new channel entitled "Starred". The title will be important later on. Provide any tag (e.g. star) although it will not be used.

  • Clone the view mn_channel_news into a new view called starred_channel_news that will display the starred items. Perform the following modifications:

    • Change the page path to channel/starred
    • Change the RSS path to channel/starred/rss.xml
    • Remove the argument "Node: Nid"
    • Remove the relationship "Data taxonomy: Nodes with term."
    • Add a new relationship "Syndication: Votes" with Value type = "Mark" and Vote tag = "star". Call this relationship "Stars".
    • Add a new filter "Votes: Value" on relationship "Stars" with operator "Is not empty (NOT NULL)".
    • Make sure these changes apply to all displays.
  • Modify context mn-section-channels to apply to the new view as well:

    • In Conditions > Views, add the new view starred_channel_news.
  • Clone context mn-section-channels-notrash into a new context for the Starred channel. Perform the following modifications:

    • Change Conditions > Views to apply to our new view starred_channel_news instead of mn_channels_news.
  • Modify profiles/managingnews/modules/features/mn_channels/mn_channels.module to invoke our new view when opening the starred channel:

<?php
function mn_channels_node_page_view($node, $cid = NULL) {
$starred = strcasecmp($node->title, 'starred') == 0; // Starred channel is a special case
 
if (isset($_GET['display']) && $_GET['display'] != 'default') {
-  
drupal_goto('channel/'. $node->nid, 'display='. urlencode($_GET['display']));
+  
drupal_goto('channel/'. ($starred ? 'starred' : $node->nid), 'display='. urlencode($_GET['display']));
  }
 
$terms = array();
  foreach (
$node->taxonomy as $term) {
    if (
$plugin = context_get_plugin('condition', 'node')) {
     
$plugin->execute($node, 'view');
    }
-  
$view = views_get_view('mn_channels_news');
+  
$view = views_get_view($starred ? 'starred_channel_news' : 'mn_channels_news');
   
$output = $view->execute_display('page_1', array($node->nid));
    if (
$view->total_rows != 0) {
      return
$output;
...
?>
  • Modify profiles/managingnews/modules/features/mn_channels/views/mn_channels_views_handler_field_item_count.inc to display the correct item count for Starred channel:
<?php
-        if ($this->view->base_table == 'node') {
-         
$this->items[$tid] = db_result(db_query("SELECT COUNT(DISTINCT dt.id) AS count FROM {node} n LEFT JOIN {term_node} tn ON tn.nid = n.nid LEFT JOIN {data_taxonomy} dt ON dt.tid = tn.tid INNER JOIN {feeds_data_syndication} fds ON fds.id = dt.id LEFT JOIN {votingapi_vote} vapi ON fds.id = vapi.content_id  AND vapi.content_type = 'feeds_data_syndication' WHERE n.nid = %d AND vapi.value IS NULL", $tid));
-        } else {
-         
$this->items[$tid] = db_result(db_query("SELECT COUNT(dt.id) AS count FROM {data_taxonomy} dt INNER JOIN {feeds_data_syndication} fds ON fds.id = dt.id LEFT JOIN {votingapi_vote} vapi ON fds.id = vapi.content_id  AND vapi.content_type = 'feeds_data_syndication' WHERE tid = %d AND vapi.value IS NULL", $tid));
-        }
+        if (
$this->view->base_table == 'node' && strcasecmp($row->node_title, 'starred') == 0) { // Starred channel behaves by counting starred items
+          $this->items[$tid] = db_result(db_query("
+            SELECT COUNT(*)
+            FROM {votingapi_vote} v1
+            WHERE v1.value_type='mark'
+            AND v1.tag='star'
+            AND NOT EXISTS (
+              SELECT v2.vote_id
+              FROM {votingapi_vote} v2
+              WHERE v1.content_id = v2.content_id
+              AND v2.value_type='mark'
+              AND v2.tag='trash'
+            )
+          "
));
+        }
+        else {
+          if (
$this->view->base_table == 'node') {
+           
$this->items[$tid] = db_result(db_query("
+              SELECT COUNT(DISTINCT dt.id) AS count
+              FROM {node} n
+              LEFT JOIN {term_node} tn ON tn.nid = n.nid
+              LEFT JOIN {data_taxonomy} dt ON dt.tid = tn.tid
+              INNER JOIN {feeds_data_syndication} fds ON fds.id = dt.id
+              WHERE n.nid = %d
+              AND NOT EXISTS (
+                SELECT vapi.vote_id
+                FROM {votingapi_vote} vapi
+                WHERE fds.id = vapi.content_id
+                AND vapi.content_type = 'feeds_data_syndication'
+                AND vapi.value_type= 'mark'
+                AND vapi.tag = 'trash'
+              )
+           "
, $tid));
+          } else {
+           
$this->items[$tid] = db_result(db_query("
+              SELECT COUNT(dt.id) AS count
+              FROM {data_taxonomy} dt
+              INNER JOIN {feeds_data_syndication} fds ON fds.id = dt.id
+              WHERE tid = %d
+              AND NOT EXISTS (
+                SELECT vapi.vote_id
+                FROM {votingapi_vote} vapi
+                WHERE fds.id = vapi.content_id
+                AND vapi.content_type = 'feeds_data_syndication'
+                AND vapi.value_type= 'mark'
+                AND vapi.tag = 'trash'
+              )
+            "
, $tid));
+          }
+        }
?>

That's it! I didn't particularly enjoy hacking into MN's modules, so please let me know if you see a better way!

AttachmentSize
star.png66.44 KB

Comments

Nice tutorial. Rather than

Nice tutorial. Rather than hack mn_channels_views_handler_field_item_count, you could duplicate it, include the duplicate in your custom module, and use a views default alter hook to switch the default views in MN to use your custom handler. This would get you closer to the point of wrapping this hack into a downloadable feature that can simply be dropped in and turned on for any MN site.

Will

Updated some code

I updated the code for profiles/managingnews/modules/features/mn_channels/views/mn_channels_views_handler_field_item_count.inc. Adding the star mark had caused the original channel count to fail. It happens!

great hack

hello,

I follow your hack but i have an error:

Fatal error: Call to a member function execute_display() on a non-object in htdocs\managingnews\profiles\managingnews\modules\features\mn_channels\mn_channels.module on line 72

any idea?

i found, i have

i found, i have starred_channelS_news instead of starred_channel_news

;)

Id love to get hold of the

Id love to get hold of the update

The updated code is in the

The updated code is in the article body.