Skip to content
82 changes: 80 additions & 2 deletions parsely.admin.inc
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,36 @@ function parsely_admin_settings($form, &$form_state) {
);

if (module_exists('taxonomy')) {
$form['parsely_section_vocabulary'] = array(
$form['parsely_sections_wrapper'] = array(
'#type' => 'fieldset',
'#title' => t('Section Settings'),
'#collapsible' => FALSE,
'#collapsed' => FALSE,
);
$form['parsely_sections_wrapper']['parsely_section_vocabulary'] = array(
'#type' => 'radios',
'#title' => t('Section Vocabulary'),
'#options' => _parsely_vocab_array_format(taxonomy_get_vocabularies()),
'#description' => t('Select the taxonomy to use for Parse.ly sections. The first term in this vocabulary is used to populate the Parse.ly section variable.'),
'#description' => t('Select the taxonomy vocabulary to use for Parse.ly sections. A single term from this vocabulary will be chosen for each tracked node, where applicable, using the criterion specified below.'),
'#default_value' => variable_get('parsely_section_vocabulary', ''),
);
$form['parsely_sections_wrapper']['parsely_section_term_criterion'] = array(
'#title' => t('Section Term Selection Criteria'),
'#type' => 'radios',
'#options' => array(
'first' => t('First term'),
'last' => t('Last term'),
'highest' => t('Highest-level term'),
'ancestor' => t('Highest-level ancestor'),
),
// Provide a long-winded explanation of how this impacts term selection.
// Note that there's currently no way to say "find the last/newest term,
// and then select its highest-level ancestor" or "use the term that
// appears first in the set of terms for a given term/entity reference
// field," etc.
'#description' => t('If a given node may be associated with multiple terms from the vocabulary you selected above, this setting can help determine which term to use. "First term" and "Last term" will choose a term based on the date/time the term was created (this may differ from the relative position of a term within a term reference field). "Highest term" will choose the term that lives closest to the root of the vocabulary (defaulting to the "first"/oldest of such terms if more than one exist). For hierarchical vocabularies, you can also opt to use the "Highest-level ancestor" of a given term, which means that if a given node is tagged with a lower-lever term (e.g. corresponding to a subsection of your site), we will report that term\'s parent (or grandparent, etc.) term as the section. Wherever a choice must be made between terms at the same depth, the first term will be chosen.'),
'#default_value' => variable_get('parsely_section_term_criterion', 'first'),
);

$form['parsely_tag_vocabularies'] = array(
'#type' => 'checkboxes',
Expand All @@ -37,6 +60,61 @@ function parsely_admin_settings($form, &$form_state) {
}
// TODO: Should display warning if taxonomy module isn't available

// Optional settings
$form['parsely_metadata'] = array(
'#type' => 'fieldset',
'#title' => t('Metadata Settings'),
'#description' => t('Customize the values reported to Parse.ly for each page. Read more about Parse.ly metadata !here.', array('!here' => l('here', 'http://www.parsely.com/docs/integration/metadata/ppage.html'))),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
// Thumbnail image.
$logo_url = ltrim(theme_get_setting('logo', variable_get('theme_default')), '/');
$logo_url = url($logo_url, array('absolute' => TRUE));
$form['parsely_metadata']['parsely_metadata_thumbnail_url'] = array(
'#type' => 'textfield',
'#title' => t('thumbnailUrl'),
'#description' => t('An image that will be used to illustrate a page/post in certain Parse.ly dashboard displays.'),
'#default_value' => variable_get('parsely_metadata_thumbnail_url', $logo_url),
'#maxlength' => 1024,
);
if (module_exists('token')) {
$form['parsely_metadata']['#description'] .= ' ' . t('You can use tokens from the list below to specify dynamic patterns for each metadata item.');
// Set up a token browser so admins can use relevant tokens to define
// metadata values. Update all textfields in this fieldset accordingly.
// Note: The ability to provide token_types param to theme_token_tree_link
// was introduced by the token module on 2014-06-19 in v. 7.x-1.5+4-dev.
// @see https://www.drupal.org/node/2289203.
$token_module_info = system_get_info('module', 'token');
$token_version = $token_module_info['version'];
if (version_compare($token_version, '7.x-1.5+4-dev', 'ge')) {
$theme_function = 'token_tree_link';
}
else {
$theme_function = 'token_tree';
}
$form['parsely_metadata']['tokens'] = array(
'#theme' => $theme_function,
'#token_types' => array('node'),
'#global_types' => TRUE,
'#click_insert' => TRUE,
);

foreach (element_children($form['parsely_metadata']) as $field_key) {
$field = $form['parsely_metadata'][$field_key];
if ($field['#type'] == 'textfield') {
// Note: because we're not requiring a minimum number of tokens for
// these fields, the only way they can fail validation is if they
// include a real token that belongs to a disallowed context.
$field['#element_validate'] = array(
'token_element_validate',
);
$field['#token_types'] = array('node');
$form['parsely_metadata'][$field_key] = $field;
}
}
}

// Optional settings
$form['parsely_optional_settings'] = array(
'#type' => 'fieldset',
Expand Down
152 changes: 113 additions & 39 deletions parsely.module
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,17 @@ function parsely_permission() {
* are authenticated here.
*/
function parsely_should_add_metas() {
// @todo: This needs to work on non-node pages.
$node = menu_get_object();
if ($node === NULL) {
return FALSE;
}

if (empty(variable_get('parsely_apikey', ''))) {
$parsely_api_key = variable_get('parsely_apikey', '');
if (empty($parsely_api_key)) {
return FALSE;
}


$path = drupal_strtolower(drupal_get_path_alias($_GET['q']));
if (drupal_match_path($path, implode("\r", parsely_paths_to_ignore()))) {
return FALSE;
Expand Down Expand Up @@ -169,7 +170,10 @@ function _parsely_get_canonical_url($node) {
}

function _parsely_get_authors($node) {
return array($node->name);
$authors = array($node->name);
drupal_alter('parsely_authors', $authors, $node);

return $authors;
}

function _parsely_get_pub_date_utc($node) {
Expand All @@ -188,55 +192,111 @@ function _parsely_get_pub_date_utc($node) {
}

/**
* Section is the first active term from the vocabulary we've been told to use
* for sections.
* Attempt to determine the "section" to which a given node belongs.
*/
function _parsely_get_section($node) {
$section_vocabulary = variable_get('parsely_section_vocabulary');
if (!module_exists('taxonomy') || $section_vocabulary === NULL)
return 'Uncategorized';

$active_terms_query = db_select('taxonomy_index', 't')
->fields('t')
->condition('nid', $node->nid, '=')
->execute();
$section_name = 'Uncategorized';
$section_term = NULL;
$section_vocabulary_id = variable_get('parsely_section_vocabulary');

if (module_exists('taxonomy') && !is_null($section_vocabulary_id)) {
// Find the most relevant term from the designated vocab, using the
// criterion specified in the module configuration. See
// parsely_admin_settings() for an explanation of the supported criteria.
$selection_criterion = variable_get('parsely_section_term_criterion', 'first');

$section_term_query = db_select('taxonomy_index', 'ti');
$section_term_query->join('taxonomy_term_data', 'ttd', 'ti.tid=ttd.tid');
$section_term_query->fields('ttd', array('tid'))
->condition('ti.nid', $node->nid)
->condition('ttd.vid', $section_vocabulary_id);

// "First term" or "Last term."
if ($selection_criterion == 'first' || $selection_criterion == 'last') {
$sort_direction = ($selection_criterion == 'first') ? 'ASC' : 'DESC';
$section_term_query->orderBy('ttd.tid', $sort_direction)
->range(0, 1);
$section_term_result = $section_term_query->execute();

if ($section_term_result->rowCount() > 0) {
$section_tid = $section_term_result->fetchField();
$section_term = taxonomy_term_load($section_tid);
}
}
// "Highest-level term."
elseif ($selection_criterion == 'highest') {
$all_terms_result = $section_term_query->orderBy('ttd.tid')
->execute();

if ($all_terms_result->rowCount() > 0) {
$tids = $all_terms_result->fetchCol();
$terms = taxonomy_term_load_multiple($tids);
// Examine each term and see how many parents it has. We could use
// taxonomy_get_tree() to get the depths of every term in the vocab,
// but that could be problematic for large vocabs.
foreach ($terms as $term) {
// Calculate depth by repeatedly calling taxonomy_get_parents(). This
// is preferable to calling taxonomy_get_parents_all(), because that
// would include all parents of any multi-parent terms, skewing our
// generation count. Note that $depth will be "1" for a top-level term
// here instead of the conventional "0."
$t = $term;
$depth = 0;
while (!is_null($t)) {
$depth++;
$parents = taxonomy_get_parents($t->tid);
$t = array_pop($parents);
}
if (!isset($highest_term) || $depth < $highest_term->depth) {
$term->depth = $depth;
$highest_term = $term;
}
}
$section_term = $highest_term;
}
}
// "Highest-level ancestor."
elseif ($selection_criterion == 'ancestor') {
$first_term_result = $section_term_query->orderBy('ti.created')
->range(0, 1)
->execute();

if ($first_term_result->rowCount() > 0) {
$first_term_tid = $first_term_result->fetchField();
$lineage = taxonomy_get_parents_all($first_term_tid);
$section_term = array_pop($lineage);
}
}
}

$row = $active_terms_query->fetchAssoc();
if (isset($row['tid'])) {
$term = taxonomy_term_load($row['tid']);
return $term->name;
} else {
return 'Uncategorized';
if (property_exists($section_term, 'name')) {
$section_name = $section_term->name;
}

// Allow any module to alter the section name.
drupal_alter('parsely_section', $section_name, $node, $section_term);

return $section_name;
}

function _parsely_get_tags($node) {
$supported_vocabularies = variable_get('parsely_tag_vocabularies');
if (!module_exists('taxonomy') || $supported_vocabularies === NULL)
if (!module_exists('taxonomy') || $supported_vocabularies === NULL) {
return array();

// Get a mapping of all the terms in this vocabulary so that we can look up
// the name of the active terms by term id
$_terms = array();
foreach ($supported_vocabularies as $vid) {
$_terms = array_merge($_terms, taxonomy_get_tree($vid));
}
$terms = array();
foreach ($_terms as $term) {
$terms[$term->tid] = $term->name;
}

// Check what vocabulary terms are on the page that is loading
$active_terms_query = db_select('taxonomy_index', 't')
->fields('t')
->condition('nid', $node->nid, '=')
// Fetch the names of all terms that (a) belong to one of the supported
// vocabularies and (b) are associated with the given node.
$active_terms_query = db_select('taxonomy_index', 'ti');
$active_terms_query->join('taxonomy_term_data', 'ttd', 'ti.tid=ttd.tid');
$active_terms_result = $active_terms_query->fields('ttd', array('name'))
->condition('ti.nid', $node->nid)
->condition('ttd.vid', $supported_vocabularies, 'IN')
->execute();

// Build up and return active_terms
$active_terms = array();
while ($row = $active_terms_query->fetchAssoc()) {
if (isset($row['tid']))
array_push($active_terms, $terms[$row['tid']]);
if ($active_terms_result->rowCount() > 0) {
$active_terms = $active_terms_result->fetchCol();
}

return $active_terms;
Expand All @@ -245,22 +305,36 @@ function _parsely_get_tags($node) {
function _parsely_get_node_metadata() {
$node = menu_get_object();

// @todo: Integrate with metatag module and/or support more tokens, etc.
$parsely_meta = array(
'@context' => 'http://schema.org',
'@type' => 'WebPage',
'articleId' => _parsely_get_content_id($node),
'headline' => $node->title,
'url' => _parsely_get_canonical_url($node),
'thumbnailUrl' => 'FIX ME',
'dateCreated' => _parsely_get_pub_date_utc($node),
'articleSection' => _parsely_get_section($node),
'creator' => _parsely_get_authors($node),
'keywords' => _parsely_get_tags($node),
);

if ($thumbnail_url = variable_get('parsely_metadata_thumbnail_url', FALSE)) {
$thumbnail_url = token_replace($thumbnail_url, array('node' => $node));
if (!empty($thumbnail_url)) {
$parsely_meta['thumbnailUrl'] = $thumbnail_url;
}
}

// @todo: Allow admins to customize the @type value.
if ($node->type === 'article') {
$parsely_meta['@type'] = 'NewsArticle';
}

// @todo: Also support the "metadata" key so users can pass in any additional custom data.

// Allow modules to alter the default metadata for this node.
// Should this be more generic (rather than node-specific)?
drupal_alter('parsely_node_metadata', $parsely_meta, $node);

return $parsely_meta;
}