4 * Implement an API that other modules can use to implement meta tags.
8 * The minimum API version supported.
10 define('NODEWORDS_MINIMUM_API_VERSION', '1.12');
13 * The current API version implemented.
15 define('NODEWORDS_API_VERSION', '1.14');
18 * The type of objects to which the meta tags are associated.
20 define('NODEWORDS_TYPE_BLOG', 13);
21 define('NODEWORDS_TYPE_DEFAULT', 1);
22 define('NODEWORDS_TYPE_ERRORPAGE', 2);
23 define('NODEWORDS_TYPE_FORUM', 12);
24 define('NODEWORDS_TYPE_FRONTPAGE', 3);
25 define('NODEWORDS_TYPE_NONE', 0);
26 define('NODEWORDS_TYPE_NODE', 5);
27 define('NODEWORDS_TYPE_OFFLINE', 11);
28 define('NODEWORDS_TYPE_PAGE', 10);
29 define('NODEWORDS_TYPE_PAGER', 4);
30 define('NODEWORDS_TYPE_TERM', 6);
31 define('NODEWORDS_TYPE_TRACKER', 7);
32 define('NODEWORDS_TYPE_USER', 8);
33 define('NODEWORDS_TYPE_VOCABULARY', 9);
36 * The types of meta tags the module is able to handle.
38 define('NODEWORDS_META', 0);
39 define('NODEWORDS_HTTP_EQUIV', 1);
40 define('NODEWORDS_LINK_REL', 2);
41 define('NODEWORDS_LINK_REV', 3);
42 // This requires theme customizations, see README.txt for further details.
43 define('NODEWORDS_META_PROPERTY', 4);
45 define('NODEWORDS_GENERATION_NEVER', 0);
46 define('NODEWORDS_GENERATION_WHEN_EMPTY', 1);
47 define('NODEWORDS_GENERATION_ALWAYS', 2);
49 define('NODEWORDS_GENERATION_BODY', 1);
50 define('NODEWORDS_GENERATION_TEASER', 2);
51 define('NODEWORDS_GENERATION_TEASER_BODY', 3);
54 * Implements hook_content_extra_fields().
55 * Allow the meta tags fields to be sorted in the node edit forms.
57 function nodewords_content_extra_fields() {
60 $extras['nodewords'] = array(
61 'label' => t('Meta tags'),
62 'description' => t('Meta tags fieldset.'),
70 * Implements hook_form_alter().
72 function nodewords_form_alter(&$form, &$form_state, $form_id) {
74 isset($form['type']) &&
75 isset($form['#node']) &&
76 $form_id == $form['type']['#value'] .
'_node_form' &&
77 variable_get('nodewords_edit_metatags_' .
$form['type']['#value'], TRUE
)
81 $node = $form['#node'];
83 $form['nodewords'] = nodewords_form(
85 !empty($node->nodewords
) ?
$node->nodewords
: array(),
87 'tag_options' => array('node_type' => $form['type']['#value']),
94 * Implements hook_form_FORM_ID_alter().
96 function nodewords_form_node_type_form_alter(&$form, &$form_state) {
97 if (isset($form['#node_type'])) {
99 $form['nodewords'] = array(
100 '#type' => 'fieldset',
101 '#title' => t('Meta tags settings'),
102 '#collapsible' => TRUE
,
103 '#collapsed' => TRUE
,
104 '#group' => 'additional_settings',
107 $form['nodewords']['nodewords_edit_metatags'] = array(
108 '#type' => 'checkbox',
109 '#title' => t('Allow editing of meta tags'),
110 '#description' => t('If selected, the node edit form will allow the users with the right permissions to edit the meta tags associated with nodes of this content type.'),
111 '#default_value' => variable_get('nodewords_edit_metatags_' .
$form['#node_type']->type
, TRUE
),
114 $form['nodewords']['metatags_generation'] = array(
115 '#type' => 'fieldset',
116 '#title' => t('Node meta tags generation options'),
117 '#description' => t('These options change how a meta tag content is generated from the node content. These settings apply to specific meta tags.'),
118 '#collapsible' => TRUE
,
122 NODEWORDS_GENERATION_NEVER
=> t('Do not generate meta tags content'),
123 NODEWORDS_GENERATION_WHEN_EMPTY
=> t('Generate meta tag content when the meta tag content is empty (default)'),
124 NODEWORDS_GENERATION_ALWAYS
=> t('Always generate the meta tag content'),
127 $form['nodewords']['metatags_generation']['nodewords_metatags_generation_method'] = array(
129 '#options' => $options,
130 '#default_value' => variable_get(
131 'nodewords_metatags_generation_method_' .
$form['#node_type']->type
,
132 NODEWORDS_GENERATION_WHEN_EMPTY
137 NODEWORDS_GENERATION_BODY
=> t('Generate meta tags content from the node body'),
138 NODEWORDS_GENERATION_TEASER
=> t('Generate meta tags content from the node teaser (default)'),
139 NODEWORDS_GENERATION_TEASER_BODY
=> t('Generate meta tags content from the node teaser, or the node body when the node teaser is empty'),
142 $form['nodewords']['metatags_generation']['nodewords_metatags_generation_source'] = array(
144 '#title' => t('Generation source'),
145 '#options' => $options,
146 '#default_value' => variable_get(
147 'nodewords_metatags_generation_source_' .
$form['#node_type']->type
,
148 NODEWORDS_GENERATION_TEASER
152 $form['nodewords']['metatags_generation']['nodewords_use_alt_attribute'] = array(
153 '#type' => 'checkbox',
154 '#title' => t('Replace the tag IMG content with the attribute ALT'),
155 '#default_value' => variable_get(
156 'nodewords_use_alt_attribute_' .
$form['#node_type']->type
,
162 'imagebrowser' => 'imagebrowser.module',
163 'img_assist' => 'img_assist.module',
166 $form['nodewords']['metatags_generation']['nodewords_filter_modules_output'] = array(
167 '#type' => 'checkboxes',
168 '#title' => t('Filter the text added by third-party modules in the node teaser'),
169 '#options' => $options,
170 '#default_value' => variable_get(
171 'nodewords_filter_modules_output_' .
$form['#node_type']->type
,
177 $form['nodewords']['metatags_generation']['nodewords_filter_regexp'] = array(
178 '#type' => 'textfield',
179 '#title' => t('Custom regular expression'),
180 '#description' => t('A regular expression used to filter the text added in the node teaser from a third-party module. The regular expression uses the <a href="http://www.php.net/manual/en/pcre.pattern.php">Perl compatible</a> syntax. Slashes are assumed as delimiters and all expressions are case-sensitive.'),
181 '#element_validate' => array('nodewords_filter_regex_validate'),
182 '#default_value' => variable_get(
183 'nodewords_filter_regexp_' .
$form['#node_type']->type
, ''
185 '#field_prefix' => '/',
186 '#field_suffix' => '/',
190 foreach (nodewords_get_possible_tags() as
$name => $info) {
191 $function = $info['callback'] .
'_settings_form';
194 if (function_exists($function)) {
195 $function($form, 'node_type_form', $options);
202 * Implements hook_form_FORM_ID_alter().
204 function nodewords_form_taxonomy_form_term_alter(&$form, &$form_state) {
205 $bool = (isset($form['tid']['#value']) &&
206 !isset($form_state['confirm_delete']) &&
207 !isset($form_state['confirm_parents'])
211 $id = $form['tid']['#value'];
213 if (!empty($form_state['values']['nodewords'])) {
214 $tags = $form_state['values']['nodewords'];
216 elseif (isset($id) && is_numeric($id)) {
217 $tags = nodewords_load_tags(NODEWORDS_TYPE_TERM
, $id);
223 $form['nodewords'] = nodewords_form(
228 // Ensure the submit & delete buttons are at the bottom. Hopefully.
229 $form['submit']['#weight'] = 1000;
230 $form['delete']['#weight'] = 1001;
235 * Implements hook_form_FORM_ID_alter().
237 function nodewords_form_taxonomy_form_vocabulary_alter(&$form, &$form_state) {
238 if (isset($form['vid']['#value'])) {
239 $id = $form['vid']['#value'];
241 if (!empty($form_state['values']['nodewords'])) {
242 $tags = $form_state['values']['nodewords'];
244 elseif (isset($id) && is_numeric($id)) {
245 $tags = nodewords_load_tags(NODEWORDS_TYPE_VOCABULARY
, $id);
251 $form['nodewords'] = nodewords_form(
252 NODEWORDS_TYPE_VOCABULARY
,
256 // Ensure the submit & delete buttons are at the bottom. Hopefully.
257 $form['submit']['#weight'] = 1000;
258 $form['delete']['#weight'] = 1001;
263 * Implemenation of hook_help().
265 function nodewords_help($path, $arg) {
267 case
'admin/content/nodewords/meta-tags':
268 $output = '<p>' .
t('On this page you can enter the default values for the meta tags of your site.') .
'</p>';
271 case
'admin/content/nodewords/meta-tags/errorpage_403':
272 $output = '<p>' .
t('On this page you can enter the meta tags for the <q>access denied</q> error page of your site.') .
'</p>';
275 case
'admin/content/nodewords/meta-tags/errorpage_404':
276 $output = '<p>' .
t('On this page you can enter the meta tags for the <q>page not found</q> error page of your site.') .
'</p>';
279 case
'admin/content/nodewords/meta-tags/frontpage':
280 $output = '<p>' .
t('On this page you can enter the meta tags for the front page of your site.') .
'</p>';
283 case
'admin/content/nodewords/meta-tags/custom':
284 $output = '<p>' .
t('On this page you can enter the meta tags for other pages of your site. The meta tags set in these page are used before the ones set for nodes or user profiles, and they can ovverride those meta tags.') .
'</p>';
296 * Implements hook_menu().
298 function nodewords_menu() {
299 $admin_access = array('administer meta tags');
302 $items['admin/content/nodewords'] = array(
303 'title' => 'Meta tags',
304 'page callback' => 'drupal_get_form',
305 'page arguments' => array('nodewords_settings_form'),
306 'description' => 'Configure HTML meta tags for all the content.',
307 'access arguments' => $admin_access,
308 'type' => MENU_NORMAL_ITEM
,
309 'file' => 'nodewords.admin.inc',
312 $items['admin/content/nodewords/settings'] = array(
313 'title' => 'Settings',
314 'access arguments' => $admin_access,
315 'type' => MENU_DEFAULT_LOCAL_TASK
,
317 'file' => 'nodewords.admin.inc',
320 $items['admin/content/nodewords/meta-tags'] = array(
321 'title' => 'Default and specific meta tags',
322 'page callback' => 'drupal_get_form',
323 'page arguments' => array('nodewords_tags_form'),
324 'access arguments' => $admin_access,
325 'type' => MENU_LOCAL_TASK
,
326 'file' => 'nodewords.admin.inc',
330 $items['admin/content/nodewords/meta-tags/default'] = array(
331 'title' => 'Default values',
332 'access arguments' => $admin_access,
333 'type' => MENU_DEFAULT_LOCAL_TASK
,
334 'file' => 'nodewords.admin.inc',
338 $items['admin/content/nodewords/meta-tags/frontpage'] = array(
339 'title' => 'Front page',
340 'page callback' => 'drupal_get_form',
341 'page arguments' => array('nodewords_tags_form', (string) NODEWORDS_TYPE_FRONTPAGE
),
342 'access arguments' => $admin_access,
343 'type' => MENU_LOCAL_TASK
,
344 'file' => 'nodewords.admin.inc',
348 $items['admin/content/nodewords/meta-tags/offline'] = array(
349 'title' => 'Site off-line page',
350 'page callback' => 'drupal_get_form',
351 'page arguments' => array('nodewords_tags_form', (string) NODEWORDS_TYPE_OFFLINE
),
352 'access arguments' => $admin_access,
353 'type' => MENU_LOCAL_TASK
,
354 'file' => 'nodewords.admin.inc',
358 $items['admin/content/nodewords/meta-tags/errorpage_403'] = array(
359 'title' => 'Error 403 page',
360 'page callback' => 'drupal_get_form',
361 'page arguments' => array('nodewords_tags_form', (string) NODEWORDS_TYPE_ERRORPAGE
, '403'),
362 'access arguments' => $admin_access,
363 'type' => MENU_LOCAL_TASK
,
364 'file' => 'nodewords.admin.inc',
368 $items['admin/content/nodewords/meta-tags/errorpage_404'] = array(
369 'title' => 'Error 404 page',
370 'page callback' => 'drupal_get_form',
371 'page arguments' => array('nodewords_tags_form', (string) NODEWORDS_TYPE_ERRORPAGE
, '404'),
372 'access arguments' => $admin_access,
373 'type' => MENU_LOCAL_TASK
,
374 'file' => 'nodewords.admin.inc',
378 // Only show the forum settings page if the core Blog module is enabled.
379 if (module_exists('blog')) {
380 $items['admin/content/nodewords/meta-tags/blog'] = array(
381 'title' => 'Blog main page',
382 'page callback' => 'drupal_get_form',
383 'page arguments' => array('nodewords_tags_form', (string) NODEWORDS_TYPE_BLOG
),
384 'access arguments' => $admin_access,
385 'type' => MENU_LOCAL_TASK
,
386 'file' => 'nodewords.admin.inc',
391 // Only show the forum settings page if the core Forum module is enabled.
392 if (module_exists('forum')) {
393 $items['admin/content/nodewords/meta-tags/forum'] = array(
394 'title' => 'Forum main page',
395 'page callback' => 'drupal_get_form',
396 'page arguments' => array('nodewords_tags_form', (string) NODEWORDS_TYPE_FORUM
),
397 'access arguments' => $admin_access,
398 'type' => MENU_LOCAL_TASK
,
399 'file' => 'nodewords.admin.inc',
404 $items['admin/content/nodewords/meta-tags/custom'] = array(
405 'title' => 'Custom pages',
406 'page callback' => 'drupal_get_form',
407 'page arguments' => array('nodewords_custom_pages_overview'),
408 'access arguments' => $admin_access,
409 'type' => MENU_LOCAL_TASK
,
411 'file' => 'nodewords.admin.inc',
414 $items['admin/content/nodewords/meta-tags/custom/add'] = array(
415 'title' => 'Add custom pages meta tags',
416 'page callback' => 'drupal_get_form',
417 'page arguments' => array('nodewords_custom_pages_edit'),
418 'access arguments' => $admin_access,
419 'type' => MENU_CALLBACK
,
420 'file' => 'nodewords.admin.inc',
423 $items['admin/content/nodewords/meta-tags/custom/%nodewords_page/delete'] = array(
424 'title' => 'Delete custom pages meta tags',
425 'page callback' => 'drupal_get_form',
426 'page arguments' => array('nodewords_custom_pages_confirm_delete', 5),
427 'access arguments' => $admin_access,
428 'parent' => 'admin/content/nodewords/meta-tags/custom',
429 'type' => MENU_CALLBACK
,
430 'file' => 'nodewords.admin.inc',
433 $items['admin/content/nodewords/meta-tags/custom/%nodewords_page/edit'] = array(
434 'title' => 'Edit custom pages meta tags',
435 'page callback' => 'drupal_get_form',
436 'page arguments' => array('nodewords_custom_pages_edit', 5),
437 'access arguments' => $admin_access,
438 'parent' => 'admin/content/nodewords/meta-tags/custom',
439 'type' => MENU_CALLBACK
,
440 'file' => 'nodewords.admin.inc',
447 * Implements hook_node_operations().
449 function nodewords_node_operations() {
451 'delete_metatags' => array(
452 'label' => t('Delete meta tags'),
453 'callback' => 'nodewords_mass_update',
454 'callback arguments' => array('type' => NODEWORDS_TYPE_NODE
, 'operation' => 'delete'),
462 * Implements hook_node_type().
464 function nodewords_node_type($op, $info) {
465 if ($op == 'delete') {
467 'nodewords_metatags_generation_method_',
468 'nodewords_metatags_generation_source_',
469 'nodewords_edit_metatags_',
470 'nodewords_filter_modules_output_',
471 'nodewords_filter_regexp_',
472 'nodewords_use_alt_attr_',
473 'nodewords_use_teaser_',
476 foreach ($variables as
$variable) {
477 variable_del($variable .
$info->type
);
483 * Implements hook_nodeapi().
485 function nodewords_nodeapi(&$node, $op, $teaser = NULL
, $page = NULL
) {
488 nodewords_delete_tags(NODEWORDS_TYPE_NODE
, $node->nid
);
493 if (isset($node->nodewords
)) {
494 nodewords_save_tags(NODEWORDS_TYPE_NODE
, $node->nid
, $node->nodewords
, TRUE
);
500 'nodewords' => nodewords_load_tags(NODEWORDS_TYPE_NODE
, $node->nid
),
503 case
'prepare translation':
504 if (isset($node->translation_source
->nodewords
)) {
505 $node->nodewords
= $node->translation_source
->nodewords
;
510 $output_tags = array();
511 $tag_options = array(
512 'type' => NODEWORDS_TYPE_NODE
,
514 'output' => 'update index',
517 if (isset($node->nodewords
)) {
519 foreach (nodewords_get_possible_tags() as
$name => $info) {
521 !empty($info['templates']['search index']) &&
522 function_exists($function = $info['callback'] .
'_prepare')
528 isset($node->nodewords
[$name]) ?
$node->nodewords
[$name] : array(),
534 drupal_alter('nodewords_tags', $output_tags, $tag_options);
535 $output = _nodewords_output_tags($output_tags, 'update index');
536 drupal_alter('nodewords_tags_output', $output, $tag_options);
546 * Implements hook_perm().
548 function nodewords_perm() {
549 return array('administer meta tags');
553 * Implements hook_preprocess_page().
555 function nodewords_preprocess_page(&$variables) {
556 // Do not do anything when running in install or update mode.
557 if (defined('MAINTENANCE_MODE')) {
561 $options = _nodewords_detect_type_and_id();
562 $output_tags = array();
565 'default' => nodewords_load_tags(),
569 if ($options['type'] == NODEWORDS_TYPE_PAGER
) {
570 foreach (nodewords_get_possible_tags() as
$name => $info) {
572 !empty($info['context']['allowed']) &&
573 in_array(NODEWORDS_TYPE_PAGER
, $info['context']['allowed']) &&
574 function_exists($function = $info['callback'] .
'_prepare')
578 $function($output_tags, array(), $options);
583 // User profiles meta tags are not enabled.
584 if ($options['type'] == NODEWORDS_TYPE_USER
&& !variable_get('nodewords_enable_user_metatags', TRUE
)) {
587 // If the visitor doesn't have access to this node, don't load anything.
588 elseif ($options['type'] == NODEWORDS_TYPE_NODE
&& !node_access('view', node_load($options['id']))) {
591 // Otherwise, load the tags for this page.
593 $tags = nodewords_load_tags($options['type'], $options['id']);
597 foreach (nodewords_get_possible_tags() as
$name => $info) {
598 if (function_exists($function = $info['callback'] .
'_prepare')) {
599 $function($output_tags, isset($tags[$name]) ?
$tags[$name] : array(), $options);
604 drupal_alter('nodewords_tags', $output_tags, $options);
606 // Title tag has its own page preprocess variable and must be handled
608 if (isset($output_tags['page_title'])) {
609 if (!empty($output_tags['page_title'])) {
610 drupal_set_title($output_tags['page_title']);
611 $variables['head_title'] = strip_tags(drupal_get_title());
613 unset($output_tags['page_title']);
616 $output = _nodewords_output_tags($output_tags);
617 drupal_alter('nodewords_tags_output', $output, $options);
619 // Output the tags to the header.
620 drupal_set_html_head($output);
621 $variables['head'] = drupal_get_html_head();
625 * Implements hook_preprocess_maintenance_page().
627 function nodewords_preprocess_maintenance_page(&$variables) {
628 nodewords_preprocess_page($variables);
632 * Implements hook_taxonomy().
634 function nodewords_taxonomy($op, $type, $array = NULL
) {
636 if ($type == 'term') {
637 $type = NODEWORDS_TYPE_TERM
;
640 elseif ($type == 'vocabulary') {
641 $type = NODEWORDS_TYPE_VOCABULARY
;
650 nodewords_delete_tags($type, $id);
655 if (isset($array['nodewords'])) {
656 nodewords_save_tags($type, $id, $array['nodewords'], TRUE
);
664 * Implements hook_theme().
666 function nodewords_theme() {
668 'nodewords_custom_pages_overview' => array(
669 'arguments' => array('form' => array()),
670 'file' => 'nodewords.admin.inc',
676 * Implements hook_user().
678 function nodewords_user($op, &$edit, &$account, $category = NULL
) {
683 if (arg(0) == 'user' && !empty($account->uid
) && variable_get('nodewords_enable_user_metatags', TRUE
)) {
684 $account->nodewords
= nodewords_load_tags(NODEWORDS_TYPE_USER
, $account->uid
);
689 nodewords_delete_tags(NODEWORDS_TYPE_USER
, $account->uid
);
694 if (isset($edit['nodewords'])) {
695 nodewords_save_tags(NODEWORDS_TYPE_USER
, $account->uid
, $edit['nodewords'], TRUE
);
698 $edit['nodewords'] = NULL
;
704 user_access('administer meta tags') ||
705 $user->uid
== $account->uid
706 ) && variable_get('nodewords_enable_user_metatags', TRUE
) &&
707 $category == 'account'
711 $tags = nodewords_load_tags(NODEWORDS_TYPE_USER
, $account->uid
);
712 $form['nodewords'] = nodewords_form(
724 * Implements hook_user_operations().
726 function nodewords_user_operations() {
728 'delete_metatags' => array(
729 'label' => t('Delete meta tags'),
730 'callback' => 'nodewords_mass_update',
731 'callback arguments' => array('type' => NODEWORDS_TYPE_USER
, 'operation' => 'delete'),
739 * Delete tags from table.
741 function nodewords_delete_tags($type, $id) {
742 db_query("DELETE FROM {nodewords} WHERE type = %d AND id = %d", $type, $id);
744 if ($type == NODEWORDS_TYPE_PAGE
) {
745 db_query("DELETE FROM {nodewords_custom} WHERE pid = %d", $id);
750 * Return the form used to set the meta tags values.
753 * The object to which the meta tags are associated (node, user, taxonomy
756 * The meta tags array as returned by nodewords_load_tags().
759 * An array as requested by the form API.
761 function nodewords_form($type, $tags, $options = array()) {
762 $default_options = array(
764 'fieldset:title' => t('Meta tags'),
765 'fieldset:weight' => 20,
767 $default_tag_options = array(
768 'default' => nodewords_load_tags(),
771 $edit_tags = variable_get('nodewords_edit', array());
773 $options += $default_options;
775 if (isset($options['tag_options']) && is_array($options['tag_options'])) {
776 $tag_options = $options['tag_options'] + $default_tag_options;
779 $tag_options = $default_tag_options;
782 $tags_info = nodewords_get_possible_tags();
784 foreach ($tags_info as
$name => $info) {
787 user_access('administer meta tags')
789 !empty($edit_tags[$name])
795 !empty($info['context']['allowed']) &&
796 is_array($info['context']['allowed']) &&
797 !in_array($type, $info['context']['allowed'])
800 !empty($info['context']['denied']) &&
801 is_array($info['context']['denied']) &&
802 in_array($type, $info['context']['denied'])
812 user_access('administer meta tags')
815 !empty($info['permission']) &&
816 user_access($info['permission'])
821 if (function_exists($function = $info['callback'] .
'_form')) {
822 $function($form, isset($tags[$name]) ?
$tags[$name] : array(), $tag_options);
828 if (!empty($form) && $options['fieldset']) {
829 $form['#type'] = 'fieldset';
830 $form['#title'] = $options['fieldset:title'];
831 $form['#tree'] = TRUE
;
832 $form['#collapsible'] = TRUE
;
833 $form['#collapsed'] = TRUE
;
834 $form['#weight'] = $options['fieldset:weight'];
835 $form['#group'] = 'additional_settings';
842 * Query all the modules implementing meta tags and return the list of meta tags.
845 * If TRUE, the file containing the code implementing the meta tags will be loaded.
848 * An array containing the list of meta tags definitions.
850 function nodewords_get_possible_tags($load = FALSE
) {
851 static
$tags_info = array();
854 $tags_info = array();
857 if (empty($tags_info)) {
858 // Allow third-party modules to alter the meta tags list, or to add new
860 foreach (module_implements('nodewords_tags_info') as
$module) {
861 if (module_hook($module, 'nodewords_api')) {
862 $info = module_invoke($module, 'nodewords_api');
866 if (is_string($info)) {
869 elseif (is_array($info) && isset($info['version'])) {
870 $version = $info['version'];
872 if ($load && !empty($info['file'])) {
873 $include_file = $info['file'];
875 if (isset($info['path'])) {
876 $include_file = $info['path'] .
'/' .
$include_file;
879 include_once
$include_file;
885 version_compare($version, NODEWORDS_MINIMUM_API_VERSION
, '<') ||
886 version_compare($version, NODEWORDS_API_VERSION
, '>')
893 $result = module_invoke($module, 'nodewords_tags_info');
895 if (isset($result) && is_array($result)) {
896 $tags_info = array_merge($tags_info, $result);
906 * Return the term object matching a term ID. This is a modified version of
907 * taxonomy_get_term() which uses db_rewrite_sql().
912 * The user ID; if not passed, the function will use the global user ID.
915 * A term object, or FALSE. Results are statically cached.
917 function nodewords_get_term($tid, $uid = NULL
) {
919 static
$terms = array();
925 if (!isset($terms[$uid][$tid])) {
926 $terms[$uid][$tid] = db_fetch_object(
928 db_rewrite_sql('SELECT * FROM {term_data} t WHERE t.tid = %d', 't', 'tid'),
934 return !empty($terms[$uid][$tid]) ?
$terms[$uid][$tid] : FALSE
;
938 * Load tags from table.
940 function nodewords_load_tags($type = NODEWORDS_TYPE_DEFAULT
, $id = 0) {
941 static
$queries = array();
943 // If the metatags haven't been loaded before, load them.
944 if (!isset($queries[$type][$id])) {
945 $result = db_query("SELECT * FROM {nodewords} WHERE type = %d AND id = %d", $type, $id);
947 $tags_info = nodewords_get_possible_tags();
949 while ($row = db_fetch_object($result)) {
950 if (isset($tags_info[$row->name
])) {
951 $tags[$row->name
] = unserialize($row->content
);
955 // If no metatags are found for this term, try loading the vocabulary's.
956 if (empty($tags) && $type == NODEWORDS_TYPE_TERM
) {
957 $tags = nodewords_load_tags(NODEWORDS_TYPE_VOCABULARY
, db_result(db_query('SELECT vid FROM {term_data} WHERE tid = %d', $id)));
960 // Cache the metatags for later.
961 $queries[$type][$id] = $tags;
964 return $queries[$type][$id];
969 * Delete the nodes meta tags.
974 * The type of the object associated with the IDs (NODEWORDS_TYPE_NODE, NODEWORDS_TYPE_USER,
975 * NODEWORDS_TYPE_PAGER, NODEWORDS_TYPE_PAGE, ...).
977 * The operation to execute (currently implemented: delete).
979 function nodewords_mass_update($ids, $type, $operation = 'delete') {
980 if ($operation == 'delete') {
981 if (($count = count($ids))) {
983 db_query("DELETE FROM {nodewords} WHERE id IN (" .
db_placeholders($ids, 'int') .
") AND type = %d",
984 array_merge($ids, array($type))
987 if ($type == NODEWORDS_TYPE_PAGE
) {
988 db_query("DELETE FROM {nodewords_custom} WHERE pid IN (" .
db_placeholders($ids, 'int') .
")", $ids);
991 drupal_set_message(t('The update has been performed.'));
995 'operations' => array(
996 array('_nodewords_mass_delete_batch_process', array($ids, $type))
998 'finished' => '_nodewords_mass_update_batch_finished',
999 'title' => t('Processing'),
1000 'progress_message' => '',
1001 'error_message' => t('The update has encountered an error.'),
1002 'file' => drupal_get_path('module', 'nodewords') .
'/nodewords.admin.inc',
1011 * Create the content of a meta tag from a node teaser.
1014 * The node object the meta tag refers to.
1016 * The meta tag content.
1018 * An array of options; currently, the only option used is the maximum allowed
1022 * The string used as meta tag content.
1024 function nodewords_metatag_from_node_content($node, $content, $options = array()) {
1025 // The method used to generate the summary string.
1026 $method = variable_get('nodewords_metatags_generation_method_' .
$node->type
, NODEWORDS_GENERATION_WHEN_EMPTY
);
1028 // If not generating an automatic description, return immediately.
1029 if ($method == NODEWORDS_GENERATION_NEVER
1030 || ($method == NODEWORDS_GENERATION_WHEN_EMPTY
&& !empty($content))) {
1034 // Proceed as normal.
1036 $source = variable_get('nodewords_metatags_generation_source_' .
$node->type
, NODEWORDS_GENERATION_TEASER
);
1038 // If generating an automatic description, determine the source.
1039 if (!empty($node->teaser
) && ($source == NODEWORDS_GENERATION_TEASER
|| $source == NODEWORDS_GENERATION_TEASER_BODY
)) {
1040 $result = $node->teaser
;
1042 elseif (!empty($node->body
) && ($source == NODEWORDS_GENERATION_BODY
|| ($source == NODEWORDS_GENERATION_TEASER_BODY
&& empty($node->teaser
)))) {
1043 $result = $node->body
;
1046 // Clean up the text by running it through applicable filters.
1047 if (!empty($result)) {
1048 // Check for the presence of the PHP evaluator filter in the current format.
1049 // If the text contains PHP code, do not split it up to prevent parse errors.
1050 $filters = filter_list_format($node->format
);
1051 if (isset($filters['php/0']) && strpos($result, '<?') !== FALSE
) {
1055 // Continue as normal.
1057 // Run all of the normal text filters on the summary text.
1058 $result = check_markup($result, $node->format
, FALSE
);
1060 // Ensure there's a setting for controlling the maximum summary length.
1061 if (!isset($options['size'])) {
1062 $options['size'] = variable_get('nodewords_max_size', 350);
1065 // Optionally replace the tag IMG with its ALT attribute.
1066 if (variable_get('nodewords_use_alt_attribute', FALSE
)) {
1067 $result = preg_replace("/<img\s[^>]*alt=[\"']([^\"']*)[\"'][^>]*>/i", ' ($1) ', $result);
1070 // Strip off all the HTML tags.
1071 $result = strip_tags($result);
1073 // Remove the strings added from third-party modules.
1074 $modules = array_filter(variable_get('nodewords_filter_modules_output_' .
$node->type
, array()));
1076 'imagebrowser' => '/\[ibimage[^\]]*\]/i',
1077 'img_assist' => '/\[img_assist[^\]]*\]/i',
1079 foreach ($regexps as
$module => $regexp) {
1080 if (isset($modules[$module])) {
1081 $result = preg_replace($regexp, '', $result);
1085 // Remove the text matching the type-specific regular expression.
1086 if ($regexp = trim(variable_get('nodewords_filter_regexp_' .
$node->type
, ''))) {
1087 $result = preg_replace('/' .
$regexp .
'/', '', $result);
1090 // Remove line breaks.
1091 $result = preg_replace('/(\r\n?|\n)/', ' ', $result);
1093 // Remove any leading & trailing whitespace.
1094 $result = trim($result);
1096 // Remove excess inline whitespace.
1097 $result = preg_replace('/\s\s+/', ' ', $result);
1099 // Trim the filtered text to the maximum length allowed.
1100 $result = node_teaser($result, $node->format
, $options['size']);
1108 * This function is used from the menu system when a menu callback path contains
1109 * %nodewords_page_load.
1111 function nodewords_page_load($pid) {
1112 return _nodewords_get_custom_pages_data($pid);
1116 * Update or insert tags in the table.
1118 function nodewords_save_tags($type, $id, $tags, $log_message = FALSE
) {
1122 $tags_info = nodewords_get_possible_tags();
1124 NODEWORDS_TYPE_BLOG
=> t('blog main page'),
1125 NODEWORDS_TYPE_DEFAULT
=> t('default'),
1126 NODEWORDS_TYPE_ERRORPAGE
=> t('HTTP error page'),
1127 NODEWORDS_TYPE_FORUM
=> t('forum main page'),
1128 NODEWORDS_TYPE_FRONTPAGE
=> t('front page'),
1129 NODEWORDS_TYPE_NODE
=> t('node'),
1130 NODEWORDS_TYPE_OFFLINE
=> t('site offline page'),
1131 NODEWORDS_TYPE_PAGE
=> t('custom page'),
1132 NODEWORDS_TYPE_PAGER
=> t('list page'),
1133 NODEWORDS_TYPE_TERM
=> t('taxonomy term'),
1134 NODEWORDS_TYPE_USER
=> t('user profile'),
1135 NODEWORDS_TYPE_VOCABULARY
=> t('vocabulary'),
1138 foreach ($tags as
$name => $content) {
1139 if (isset($tags_info[$name])) {
1140 $content = serialize($content);
1141 $result = db_fetch_object(
1143 "SELECT * FROM {nodewords} WHERE type = %d AND id = %d AND name = '%s'",
1144 $type, $id, $name, 0, 1
1148 if ($result === FALSE
) {
1149 $row = new
stdClass();
1158 $row->content
= $content;
1160 drupal_write_record('nodewords', $row, $result !== FALSE ?
'mtid' : array());
1163 watchdog('nodewords', 'User %name changed the meta tags for type %type (ID %id).', array('%name' => $user->name
, '%type' => isset($types_str[$type]) ?
$types_str[$type] : t('unknown'), '%id' => $id));
1169 // Reload the tags so that future API calls get the correct data.
1170 nodewords_load_tags($type, $id);
1174 * Remove the duplicates from a list of items separated from the separator,
1175 * preserving the order in which they appear.
1177 * The string containing the list of items concatenated using $separator.
1179 * The string used to split the string into an array. A space will be appended
1180 * to the string before it is used to create the string from the array of
1181 * unique items found in the string passed as argument.
1183 * The maximum number of items accepted in the returned array; the default
1184 * value is -1, which means no limit.
1187 * A string containing only unique items present in the string of concatenated
1190 function nodewords_unique_values($text, $separator = ',', $max_items = -1) {
1191 $lc_values = array();
1192 $unique_values = array();
1198 foreach (array_filter(array_map('trim', explode($separator, $text))) as
$item) {
1199 $lowercase = drupal_strtolower($item);
1201 if (!in_array($lowercase, $lc_values)) {
1202 $lc_values[] = $lowercase;
1203 $unique_values[] = $item;
1207 if ($max_items > 0) {
1208 $unique_values = array_slice($unique_values, 0, $max_items);
1211 return implode("$separator", $unique_values);
1215 * Return the absolute URL of a path.
1217 * Return the absolute URL of a path built using the base URL saved in the
1218 * Drupal variable nodewords_base_url.
1221 * The path for which the absolute must be built.
1223 * An array of options as used by url().
1225 function nodewords_url($path, $options = array()) {
1226 // Remove the trailing slash, it will be added in the url() function.
1227 $base_url = rtrim(variable_get('nodewords_base_url', ''), '/');
1237 // Identify if the URL alias should be used.
1238 if (variable_get('nodewords_use_path_alias', TRUE
)) {
1239 // Note: the url() function's 'alias' argument is to specify that the
1240 // path is *already* an alias, it is not for requesting an alias be loaded.
1241 $options['alias'] = FALSE
;
1244 $options['base_url'] = empty($base_url) ? NULL
: $base_url;
1246 return url($path, $options);
1250 * Implementation of hook_migrate_api().
1252 function nodewords_migrate_api() {
1255 'integration modules' => array(
1256 'nodewords' => array('status' => FALSE
),
1263 * Internal functions
1268 * Try to guess the $type and $id by looking at $_GET['q'].
1270 function _nodewords_detect_type_and_id() {
1272 $default = array('type' => NODEWORDS_TYPE_PAGE
, 'id' => 0);
1274 // Do not do anything when running in install or update mode.
1275 if (defined('MAINTENANCE_MODE')) {
1276 return array('type' => NODEWORDS_TYPE_NONE
);
1279 if (variable_get('site_offline', 0) && !user_access('administer site configuration')) {
1280 // User will see the site-offline page.
1281 return array('type' => NODEWORDS_TYPE_OFFLINE
, 'id' => 0);
1284 $headers = drupal_get_headers();
1286 if (preg_match('@HTTP/1\.[01]\x20+403@', $headers)) {
1287 return array('type' => NODEWORDS_TYPE_ERRORPAGE
, 'id' => 403);
1290 if (preg_match('@HTTP/1\.[01]\x20+404@', $headers)) {
1291 return array('type' => NODEWORDS_TYPE_ERRORPAGE
, 'id' => 404);
1295 !variable_get('nodewords_list_repeat', FALSE
) &&
1296 isset($_REQUEST['page']) &&
1297 intval($_REQUEST['page']) > 0
1301 return array('type' => NODEWORDS_TYPE_PAGER
, 'id' => 0);
1304 if (drupal_is_front_page() && variable_get('nodewords_use_frontpage_tags', TRUE
)) {
1305 return array('type' => NODEWORDS_TYPE_FRONTPAGE
, 'id' => 0);
1308 if (module_exists('blog') && $_GET['q'] == 'blog') {
1309 return array('type' => NODEWORDS_TYPE_BLOG
, 'id' => 0);
1312 if (module_exists('forum') && $_GET['q'] == 'forum') {
1313 return array('type' => NODEWORDS_TYPE_FORUM
, 'id' => 0);
1316 // Check all of the custom page paths.
1317 foreach (_nodewords_get_pages_paths() as
$pid => $path) {
1318 // The path is a system path.
1319 if (drupal_match_path($_GET['q'], $path)) {
1320 return array('type' => NODEWORDS_TYPE_PAGE
, 'id' => $pid);
1323 // The path is a URL alias.
1324 $alias = drupal_get_path_alias($_GET['q']);
1325 if ($alias != $_GET['q'] && drupal_match_path($alias, $path)) {
1326 return array('type' => NODEWORDS_TYPE_PAGE
, 'id' => $pid);
1330 if (!isset($arg[0])) {
1334 _nodewords_load_all_includes();
1337 'type' => NODEWORDS_TYPE_NONE
,
1341 foreach (module_implements('nodewords_type_id') as
$module) {
1342 $function = $module .
'_nodewords_type_id';
1343 $function($result, $arg);
1345 if ($result['type'] != NODEWORDS_TYPE_NONE
) {
1354 * Load the page meta tags data from the cache.
1357 * The ID of the page to load; by default the function loads all the custom
1360 function _nodewords_get_custom_pages_data($id = NULL
) {
1363 if (!isset($pages)) {
1365 $result = db_query("SELECT * FROM {nodewords_custom} ORDER BY weight ASC");
1367 while ($page = db_fetch_object($result)) {
1368 $page->tags
= nodewords_load_tags(NODEWORDS_TYPE_PAGE
, $page->pid
);
1369 $pages[$page->pid
] = $page;
1373 return isset($id) ?
(isset($pages[$id]) ?
$pages[$id] : FALSE
) : $pages;
1377 * Answer a listing of page paths, stored in a static cache.
1379 function _nodewords_get_pages_paths() {
1382 if (!isset($paths)) {
1384 $result = db_query("SELECT pid, path FROM {nodewords_custom} ORDER BY weight ASC");
1386 while ($page = db_fetch_object($result)) {
1387 $paths[$page->pid
] = $page->path
;
1395 * Load the files in the directory "includes" basing on the enabled modules.
1397 function _nodewords_load_all_includes() {
1398 $dir = drupal_get_path('module', 'nodewords') .
'/includes';
1400 // Update this array when new files are added.
1410 foreach ($includes as
$include) {
1411 if (module_exists($include) && !module_hook($include, 'nodewords_type_id')) {
1412 module_load_include('inc', 'nodewords', 'includes/' .
$include);
1418 * Render the meta tag values as HTML.
1423 * @param $output_type
1424 * The type of output, 'head' (default), or 'update index'.
1427 * A string containing the HTML output for the META tag.
1429 function _nodewords_output_tags($tags, $output_type = 'head') {
1431 $tags_info = nodewords_get_possible_tags();
1434 foreach ($tags as
$name => $content) {
1435 if ($content === '') {
1439 // Check if it's a property meta tag (which may contain an XML namespace).
1441 $output_type == 'head' &&
1442 isset($tags_info[$name]['templates']['head'][$name]) &&
1443 $tags_info[$name]['templates']['head'][$name] == NODEWORDS_META_PROPERTY
1445 $parts = $is_property ?
array($name, $name) : explode(':', $name);
1447 // Ensure that the previous assigned output template was cleared.
1450 if (!isset($parts[1])) {
1451 $parts[1] = $parts[0];
1454 if ($output_type == 'update index') {
1456 isset($tags_info[$parts[0]]['templates']['search index'][$parts[1]])
1460 // The '%content' element will be added later.
1462 '%attributes' => empty($tags_info[$parts[0]]['attributes'][$parts[1]]) ?
'' : drupal_attributes($tags_info[$parts[0]]['attributes'][$parts[1]]),
1464 $template = $tags_info[$parts[0]]['templates']['search index'][$parts[1]];
1465 $weight = isset($tags_info[$parts[0]]['weight'][$parts[1]]) ?
$tags_info[$parts[0]]['weight'][$parts[1]] : 0;
1470 isset($tags_info[$parts[0]]['templates']['head'][$parts[1]]) &&
1471 ($meta_name = check_plain(decode_entities(strip_tags($parts[1]))))
1475 // The '%content' element will be added later.
1477 '%name' => $meta_name,
1478 '%attributes' => empty($tags_info[$parts[0]]['attributes'][$parts[1]]) ?
'' : drupal_attributes($tags_info[$parts[0]]['attributes'][$parts[1]]),
1480 $template = $tags_info[$parts[0]]['templates']['head'][$parts[1]];
1481 $weight = isset($tags_info[$parts[0]]['weight'][$parts[1]]) ?
$tags_info[$parts[0]]['weight'][$parts[1]] : 0;
1483 if (!is_string($template)) {
1484 switch ($template) {
1485 case NODEWORDS_META
:
1486 $template = '<meta name="%name" content="%content"%attributes />';
1489 case NODEWORDS_HTTP_EQUIV
:
1490 $template = '<meta http-equiv="%name" content="%content"%attributes />';
1493 case NODEWORDS_LINK_REL
:
1494 $template = '<link rel="%name" href="%content"%attributes />';
1497 case NODEWORDS_LINK_REV
:
1498 $template = '<link rev="%name" href="%content"%attributes />';
1501 // This requires theme customizations in order for the output to
1502 // remain valid XHTML, see the OpenGraph section of the README.txt
1503 // for further details.
1504 case NODEWORDS_META_PROPERTY
:
1505 $template = '<meta property="%name" content="%content"%attributes />';
1516 if (!empty($template)) {
1517 // Allow the meta tags to have multiple values.
1518 if (!empty($tags_info[$name]['multiple'])) {
1519 $content = explode("\n", $content);
1522 // Simplify the output handling.
1523 if (!is_array($content)) {
1524 $content = array($content);
1527 // Now add the content element(s) to the replacement array.
1528 foreach ($content as
$content_item) {
1529 $replace['%content'] = str_replace(''', "'", trim(check_plain(decode_entities(strip_tags($content_item)))));
1530 $output[] = strtr($template, $replace);
1531 $weights[] = $weight;
1536 if (count($output)) {
1537 array_multisort($weights, $output);
1539 return implode("\n", $output);
1547 * Provide a complete path for the current URL, optionally using a value
1548 * manually assigned via a form.
1551 * An array of settings for this current field, optionally including the
1552 * manually assigned path.
1554 * An array of options for this current page.
1557 * Fully qualified absolute URL for the current page.
1559 function _nodewords_prepare_path($content, $options) {
1560 // Need to compare the value against system base path.
1561 $base_path = base_path();
1563 // Remove the base path from the front of the URL.
1564 if (!empty($content['value']) && strpos($content['value'], $base_path) === 0) {
1565 $content['value'] = drupal_substr($content['value'], drupal_strlen($base_path));
1568 // Start with a system path.
1569 if (empty($content['value'])) {
1572 switch ($options['type']) {
1573 case NODEWORDS_TYPE_FRONTPAGE
:
1577 case NODEWORDS_TYPE_NODE
:
1578 $path = 'node/' .
$options['id'];
1581 case NODEWORDS_TYPE_PAGE
:
1585 case NODEWORDS_TYPE_TERM
:
1586 // Accommodate other modules that change the path via hook_term_path().
1587 $term = taxonomy_get_term($options['id']);
1588 $path = module_invoke('taxonomy', 'term_path', $term);
1591 case NODEWORDS_TYPE_USER
:
1592 $path = 'user/' .
$options['id'];
1596 if (!empty($path)) {
1597 $content['value'] = $path;
1601 if (!empty($content['value'])) {
1602 return check_url(nodewords_url($content['value'], $options));
1610 * Extract or generate the description for the current object.
1613 * The array of tags currently generated.
1615 * An array of settings for this current field, optionally including the
1616 * manually assigned path.
1618 * An array of options for this current page.
1620 * The name of the tag being generated.
1623 * The final string to be used for the description tag.
1625 function _nodewords_prepare_description(&$tags, $content, $options, $tag_name) {
1627 if (!isset($content['value'])) {
1628 $content['value'] = '';
1631 // If this is a node, try to load it then see if the description can or needs
1632 // to be automatically generated.
1633 if ($options['type'] == NODEWORDS_TYPE_NODE
&& $node = node_load($options['id'])) {
1634 $autogenerate = variable_get('nodewords_metatags_generation_method_' .
$node->type
, NODEWORDS_GENERATION_WHEN_EMPTY
);
1636 if ($autogenerate == NODEWORDS_GENERATION_ALWAYS
1637 || (empty($content['value']) && $autogenerate == NODEWORDS_GENERATION_WHEN_EMPTY
)) {
1638 $content['value'] = nodewords_metatag_from_node_content($node, $content['value']);
1642 // Load the default if no value is present.
1643 if (empty($content['value']) && !empty($options['default'][$tag_name]['value'])) {
1644 $content['value'] = $options['default'][$tag_name]['value'];
1647 $tags[$tag_name] = $content['value'];