Here's what I ended up doing (not using views). I'm using Panels on this site, and my default view for this content-type is controlled via Panelizer. Since I couldn't find a way to do this with Views, I created a custom panels pane (code below).
Basically, this pane accepts one setting: the number of related events to display. The render callback counts the editor-defined related nodes, and grabs more (if needed) based on shared terms.
<?php
$plugin = array(
'single' => TRUE,
'title' => t('Suggested Events'),
'description' => t('Combines curated suggested events with those related by category.'),
'category' => t('Custom Panes'),
'edit form' => 'MYMODULE_suggested_events_edit_form',
'render callback' => 'MYMODULE_suggested_events_render',
'admin info' => 'MYMODULE_suggested_events_info',
'defaults' => array(
'events_to_show' => 4
),
'all contexts' => TRUE,
);
/**
* Edit Form.
*/
function MYMODULE_suggested_events_edit_form($form, &$form_state) {
$conf = $form_state['conf'];
$form['events_to_show'] = array(
'#title' => t('Number of events to show'),
'#description' => t('Used to control the number of suggested events shown. Defaults to 4.'),
'#type' => 'select',
'#options' => drupal_map_assoc(range(3, 6)),
'#default_value' => $conf['events_to_show'],
'#required' => TRUE,
);
return $form;
}
/**
* Submit callback for settings form.
*/
function MYMODULE_suggested_events_edit_form_submit($form, &$form_state) {
foreach (array_keys($form_state['plugin']['defaults']) as $key) {
if (isset($form_state['values'][$key])) {
$form_state['conf'][$key] = $form_state['values'][$key];
}
}
}
/**
* Run-time rendering of the content
*/
function MYMODULE_suggested_events_render($subtype, $conf, $args, $contexts) {
if (isset($contexts['panelizer'])) {
$nid = $contexts['panelizer']->data->nid;
} else {
return false;
}
// Grab the Category Term IDs from referenced node.
$node = node_load($nid);
$categoryField = field_get_items('node', $node, 'field_category');
$categoryTIDs = array_column($categoryField, 'tid');
// suggestedEvents are the events which are selected by the editor.
$suggestedEventsField = field_get_items('node', $node, 'field_event_suggested');
if (empty($suggestedEventsField)) {
$suggestedEvents = array();
} else {
$suggestedEventsNIDs = array_column($suggestedEventsField, 'target_id');
$suggestedEvents = entity_load('node', $suggestedEventsNIDs ); // @returns array of entities
}
// determine how many events we need to find
// based upon the total to display and how many are site-editor-defined
$relatedEvents = array();
$relatedEventsCount = 0;
if (count($suggestedEvents) < $conf['events_to_show']) {
$relatedEventsCount = $conf['events_to_show'] - count($suggestedEvents);
}
if ($relatedEventsCount > 0) {
// Query for other events that share the referenced categories.
$query = new EntityFieldQuery();
$query->entityCondition('entity_type', 'node')
->entityCondition('bundle', 'event')
->propertyCondition('status', NODE_PUBLISHED)
->propertyCondition('nid', $nid, '<>')
->fieldCondition('field_category', 'tid', $categoryTIDs, 'IN')
->fieldCondition('field_datetime', 'value', date('Y-m-d').'00:00:00', '>=', 0)
->range(0, $relatedEventsCount)
// Run the query as user 1.
->addMetaData('account', user_load(1));
$result = $query->execute();
if (isset($result['node'])) {
$relatedEventsNIDs = array_keys($result['node']);
$relatedEvents = entity_load('node', $relatedEventsNIDs);
}
}
$events = array_merge($suggestedEvents, $relatedEvents);
$block = new stdClass();
$block->title = 'You may also enjoy...';
$block->content = '';
if (!empty($events)) {
$block->content .= '<div class="block__content">';
$eventsRenderArray = entity_view('node', $events, 'showcase_card');
$block->content .= render( $eventsRenderArray );
$block->content .= '</div>';
return $block;
} else {
return false;
}
}
/**
* Admin info.
*/
function MYMODULE_suggested_events_info($subtype, $conf, $contexts) {
if (!empty($conf)) {
$block = new stdClass;
$block->title = $conf['override_title'] ? $conf['override_title_text'] : '';
$block->content = t('Show @events_to_show suggested events.', array(
'@events_to_show' => $conf['events_to_show']
));
return $block;
}
}