View source
<?php
define('OPENGRAPH_META_TABLE', 'opengraph_meta');
define('OPENGRAPH_META_PERM_ADMIN', 'administer Open Graph meta tags');
define('OPENGRAPH_META_PERM_EDIT', 'edit Open Graph meta tags');
define('OPENGRAPH_META_VAR_CONTENT_TYPES_ENABLED', 'opengraph_meta_types_enabled');
define('OPENGRAPH_META_VAR_CONTENT_TYPE_', 'opengraph_meta_type_');
define('OPENGRAPH_META_VAR_CONTENT_TYPE_CCK_', 'opengraph_meta_cck_');
define('OPENGRAPH_META_VAR_SITE_NAME', 'opengraph_meta_site_name');
define('OPENGRAPH_META_VAR_FALLBACK_IMG', 'opengraph_meta_fallback_img');
define('OPENGRAPH_META_VAR_OPTIONAL_TAGS', 'opengraph_meta_optional_tags');
define('OPENGRAPH_META_DRUPAL_VERSION', '7.x' == DRUPAL_CORE_COMPATIBILITY ? 7 : 6);
class OpenGraphMeta {
const TITLE = 'title';
const DESCRIPTION = 'description';
const IMAGE = 'image';
const SITE_NAME = 'site_name';
const TYPE = 'type';
const URL = 'url';
const LATITUDE = 'latitude';
const LONGITUDE = 'longitude';
const STREET_ADDRESS = 'street-address';
const LOCALITY = 'locality';
const REGION = 'region';
const POST_CODE = 'postal-code';
const COUNTRY_NAME = 'country-name';
const EMAIL = 'email';
const PHONE_NUMBER = 'phone_number';
const FAX_NUMBER = 'fax_number';
const __OPTIONAL_DB_FIELD = 'optional';
private static $instance = NULL;
private $settings_obj = NULL;
private $render_obj = NULL;
private $data_obj = NULL;
private $tags_already_output = array();
private function __construct() {
$this->settings_obj = new OGMDrupalSettings();
$this->render_obj = new OGMDrupalRender();
$this->data_obj = new OGMDrupalData();
}
public static function instance() {
if (empty(self::$instance)) {
self::$instance = new OpenGraphMeta();
}
return self::$instance;
}
public function get_og_optional_tag_defaults($node = NULL) {
static $defaults = array();
if (empty($defaults)) {
$defaults = array(
self::LATITUDE => '',
self::LONGITUDE => '',
self::STREET_ADDRESS => '',
self::LOCALITY => '',
self::REGION => '',
self::POST_CODE => '',
self::COUNTRY_NAME => '',
self::EMAIL => '',
self::PHONE_NUMBER => '',
self::FAX_NUMBER => '',
);
}
if (!empty($node)) {
$optionals = variable_get(OPENGRAPH_META_VAR_OPTIONAL_TAGS, array());
foreach ($defaults as $f => &$i) {
$i = !empty($optionals[$f]) ? $optionals[$f] : $i;
}
}
return $defaults;
}
private function get_og_tag_defaults($node = NULL, $full_view = FALSE) {
static $defaults = array();
if (empty($defaults)) {
$defaults = array(
self::TITLE => '',
self::DESCRIPTION => '',
self::IMAGE => '',
self::TYPE => '',
self::URL => '',
);
}
$ret = array_merge($defaults, $this
->get_og_optional_tag_defaults($node));
$ret[self::SITE_NAME] = $this->settings_obj
->get(OPENGRAPH_META_VAR_SITE_NAME, $this->settings_obj
->get('site_name', 'Drupal'));
if (!empty($node)) {
$ret[self::TITLE] = $full_view ? drupal_get_title() : $node->title;
$body = OpenGraphMetaDrupalLayer::get_node_body($node);
$ret[self::DESCRIPTION] = !empty($body) ? drupal_substr(strip_tags($body), 0, 200) : $node->title;
$ret[self::TYPE] = $this->settings_obj
->get(OPENGRAPH_META_VAR_CONTENT_TYPE_ . $node->type, '');
$ret[self::IMAGE] = $this->settings_obj
->get(OPENGRAPH_META_VAR_FALLBACK_IMG, '');
$images = $this
->harvest_images_from_node($node);
if (!empty($images)) {
$i = array_shift($images);
$ret[self::IMAGE] = $i['url'];
}
$ret[self::URL] = url(drupal_get_path_alias('node/' . $node->nid), array(
'absolute' => TRUE,
));
}
return $ret;
}
public function get_all_og_types_for_select_field() {
static $ret = array();
if (empty($ret)) {
$ogtypes = array(
t('Activities') => array(
'activity',
'sport',
),
t('Businesses') => array(
'bar',
'company',
'cafe',
'hotel',
'restaurant',
),
t('Groups') => array(
'cause',
'sports_league',
'sports_team',
),
t('Organizations') => array(
'band',
'government',
'non_profit',
'school',
'university',
),
t('People') => array(
'actor',
'athlete',
'author',
'director',
'musician',
'politician',
'public_figure',
),
t('Places') => array(
'city',
'country',
'landmark',
'state_province',
),
t('Products and Entertainment') => array(
'album',
'book',
'drink',
'food',
'game',
'movie',
'product',
'song',
'tv_show',
),
t('Websites') => array(
'article',
'blog',
'website',
),
);
$ret = array(
'' => '',
);
foreach ($ogtypes as $cat => $t) {
$ret[$cat] = array_combine($t, $t);
}
}
return $ret;
}
public function tags_are_enabled_for_content_type($type) {
$content_types = $this->settings_obj
->get(OPENGRAPH_META_VAR_CONTENT_TYPES_ENABLED, array());
$content_types = array_filter($content_types);
return empty($content_types) || !empty($content_types[$type]);
}
public function delete_node_data($nid) {
$this->data_obj
->delete_tags($nid);
}
public function save_node_data($nid, $data) {
$ret = $this->data_obj
->load_tags($nid);
if (FALSE === $ret) {
$row = new stdClass();
$row->nid = $nid;
}
else {
$row = $ret;
}
$collapsed_data = new stdClass();
$collapsed_data->keys = array();
$collapsed_data->values = array();
array_walk_recursive($data, create_function('$val, $key, $obj', 'array_push($obj->keys, $key); array_push($obj->values, $val);'), $collapsed_data);
$collapsed_data = array_combine($collapsed_data->keys, $collapsed_data->values);
foreach ($this
->get_og_tag_defaults() as $field => $_d) {
$row->{$field} = isset($collapsed_data[$field]) ? $collapsed_data[$field] : '';
}
$this->data_obj
->update_tags($row, FALSE !== $ret ? 'nid' : array());
}
public function load_node_data($node) {
$fields = $this
->get_og_tag_defaults();
$row = $this->data_obj
->load_tags($node->nid);
if (FALSE !== $row) {
foreach ($fields as $field => &$val) {
if (isset($row->{$field})) {
$val = $row->{$field};
}
}
}
return $fields;
}
public function render_data(&$node, $opengraph_data) {
$allfields = $this
->get_og_tag_defaults($node, TRUE);
foreach ($allfields as $field => $_d) {
if (array_key_exists($field, $this->tags_already_output)) {
$this
->warn(t("Already output og:%s", array(
'%s' => $field,
)));
continue;
}
$v = !empty($opengraph_data[$field]) ? $opengraph_data[$field] : $_d;
if (!empty($v)) {
switch ($field) {
case self::IMAGE:
$v = url(ltrim($v, '/'), array(
'absolute' => TRUE,
));
break;
case self::TITLE:
case self::DESCRIPTION:
case self::STREET_ADDRESS:
case self::LOCALITY:
case self::REGION:
case self::COUNTRY_NAME:
$v = htmlspecialchars(htmlspecialchars_decode($v));
break;
case self::LATITUDE:
case self::LONGITUDE:
$v = floatval($v);
break;
}
$v = OpenGraphMetaDrupalLayer::invoke_filter('og_tag_render_alter', $v, array(
$node,
$field,
));
$this->render_obj
->add_meta('og:' . $field, $v);
$this->tags_already_output[$field] = true;
}
}
}
public function harvest_images_from_node($node) {
$ret = array();
OpenGraphMetaDrupalLayer::extract_image_fields((array) $node, $ret);
$body = OpenGraphMetaDrupalLayer::get_node_body($node);
if (!empty($body)) {
libxml_use_internal_errors(TRUE);
$doc = new DOMDocument();
$doc
->loadHTML($body);
$list = $doc
->getElementsByTagName('img');
for ($i = 0; $list->length > $i; ++$i) {
$item = $list
->item($i);
if ($item
->hasAttribute('src')) {
$url = $item
->getAttribute('src');
if (!empty($url)) {
$ret[] = array(
'title' => $url,
'alt' => $url,
'url' => $url,
);
}
}
}
libxml_use_internal_errors(FALSE);
}
return $ret;
}
public function warn($msg) {
watchdog('opengraph_meta', $msg, array(), WATCHDOG_WARNING);
}
public function __set_objects($data_obj, $settings_obj, $render_obj) {
$this->data_obj = $data_obj;
$this->settings_obj = $settings_obj;
$this->render_obj = $render_obj;
}
public function __get_objects() {
return array(
$this->data_obj,
$this->settings_obj,
$this->render_obj,
);
}
public function __reset_tags_already_output() {
$this->tags_already_output = array();
}
}
interface OGMSettings {
public function get($name, $default);
public function set($name, $value);
}
class OGMDrupalSettings implements OGMSettings {
public function get($name, $default) {
return variable_get($name, $default);
}
public function set($name, $value) {
variable_set($name, $value);
}
}
interface OGMRender {
public function add_meta($name, $content);
}
class OGMDrupalRender implements OGMRender {
public function add_meta($name, $content) {
OpenGraphMetaDrupalLayer::render_meta_tag($name, $content);
}
}
interface OGMData {
public function load_tags($nid);
public function delete_tags($nid);
public function update_tags($field_data_including_nid, $primary_key = array());
}
class OGMDrupalData implements OGMData {
public function load_tags($nid) {
$ret = OpenGraphMetaDrupalLayer::load_tags($nid);
if (!empty($ret)) {
$optional_db_field_name = OpenGraphMeta::__OPTIONAL_DB_FIELD;
$optionals = !empty($ret->{$optional_db_field_name}) ? unserialize($ret->{$optional_db_field_name}) : array();
foreach (OpenGraphMeta::instance()
->get_og_optional_tag_defaults() as $tag => $dv) {
$ret->{$tag} = !empty($optionals[$tag]) ? $optionals[$tag] : $dv;
}
}
return $ret;
}
public function delete_tags($nid) {
OpenGraphMetaDrupalLayer::delete_tags($nid);
}
public function update_tags($field_data_including_nid, $primary_key = array()) {
$optionals = array();
foreach (OpenGraphMeta::instance()
->get_og_optional_tag_defaults() as $tag => $dv) {
if (!empty($field_data_including_nid->{$tag})) {
$optionals[$tag] = $field_data_including_nid->{$tag};
}
}
$optional_db_field_name = OpenGraphMeta::__OPTIONAL_DB_FIELD;
$field_data_including_nid->{$optional_db_field_name} = $optionals;
drupal_write_record(OPENGRAPH_META_TABLE, $field_data_including_nid, $primary_key);
}
}
class OpenGraphMetaDrupalLayer {
private function __construct() {
}
public static function get_node_types() {
switch (OPENGRAPH_META_DRUPAL_VERSION) {
case 6:
return node_get_types();
break;
case 7:
case 8:
return node_type_get_types();
break;
default:
self::err('No API for fetching node types found.');
return array();
}
}
public static function render_meta_tag($name, $content) {
switch (OPENGRAPH_META_DRUPAL_VERSION) {
case 6:
drupal_set_html_head("<meta property=\"{$name}\" content=\"{$content}\" />");
break;
case 7:
case 8:
$element = array(
'#tag' => 'meta',
'#attributes' => array(
'property' => $name,
'content' => $content,
),
);
drupal_add_html_head($element, "opengraph_meta_{$name}");
break;
default:
self::err('No API for rendering META tags.');
}
}
public static function update_default_ogtype_for_node_type($type_id, $form_values) {
switch (OPENGRAPH_META_DRUPAL_VERSION) {
case 6:
$cck_content_type_id = OPENGRAPH_META_VAR_CONTENT_TYPE_CCK_ . $type_id;
if (array_key_exists($cck_content_type_id, $form_values['description_cck'])) {
variable_set($cck_content_type_id, $form_values['description_cck'][$cck_content_type_id]);
}
default:
$content_type_id = OPENGRAPH_META_VAR_CONTENT_TYPE_ . $type_id;
if (array_key_exists($content_type_id, $form_values['types'])) {
variable_set($content_type_id, $form_values['types'][$content_type_id]);
}
}
}
public static function get_node_body($node) {
$body = '';
switch (OPENGRAPH_META_DRUPAL_VERSION) {
case 6:
$description_cck_field = variable_get(OPENGRAPH_META_VAR_CONTENT_TYPE_CCK_ . $node->type, '');
if (!empty($description_cck_field) && !empty($node->{$description_cck_field})) {
$v = $node->{$description_cck_field};
if (is_array($v[0]) && !empty($v[0]['value'])) {
$body = $v[0]['value'];
}
}
if (empty($body) && !empty($node->body)) {
$body = $node->body;
}
break;
default:
$lang = field_language('node', $node, 'body');
if (!empty($node) && !empty($node->body) && !empty($node->body[$lang]) && !empty($node->body[$lang]['0']) && !empty($node->body[$lang]['0']['value'])) {
$body = $node->body[$lang]['0']['value'];
}
break;
}
return $body;
}
public static function extract_image_fields($fields, array &$resultarray) {
$_uri_field = 'uri';
if (6 == OPENGRAPH_META_DRUPAL_VERSION) {
$_uri_field = 'filepath';
}
if (is_array($fields)) {
if (!empty($fields['filemime']) && FALSE !== stripos($fields['filemime'], 'image') && !empty($fields[$_uri_field])) {
$url = $fields[$_uri_field];
if (7 <= OPENGRAPH_META_DRUPAL_VERSION) {
$url = image_style_url('thumbnail', $fields[$_uri_field]);
}
array_push($resultarray, array(
'title' => !empty($fields['title']) ? $fields['title'] : $url,
'alt' => !empty($fields['alt']) ? $fields['alt'] : $url,
'url' => $url,
));
}
else {
foreach ($fields as $cv) {
self::extract_image_fields($cv, $resultarray);
}
}
}
}
public static function theme_selector_image($image) {
$attributes = array(
'class' => 'opengraph-thumb',
);
$abs_path = url(ltrim($image['url'], '/'), array(
'absolute' => TRUE,
));
switch (OPENGRAPH_META_DRUPAL_VERSION) {
case 6:
return theme('image', $abs_path, $image['alt'], $image['title'], array_merge($attributes, array(
'width' => '32px',
'height' => '32px',
)), FALSE);
break;
case 7:
case 8:
return theme('image', array(
'path' => $abs_path,
'alt' => $image['alt'],
'height' => '60px',
'attributes' => array_merge($attributes, array(
'title' => $image['title'],
)),
));
break;
default:
self::err('No API for theming images');
return '';
}
}
public static function delete_tags($nid) {
switch (OPENGRAPH_META_DRUPAL_VERSION) {
case 6:
db_query("DELETE FROM {" . OPENGRAPH_META_TABLE . "} WHERE nid = %d", $nid);
break;
default:
db_query("DELETE FROM {" . OPENGRAPH_META_TABLE . "} WHERE nid = :nid", array(
':nid' => $nid,
));
}
}
public static function load_tags($nid) {
switch (OPENGRAPH_META_DRUPAL_VERSION) {
case 6:
return db_fetch_object(db_query("SELECT * FROM {" . OPENGRAPH_META_TABLE . "} WHERE nid = %d", $nid));
break;
default:
$result = db_query("SELECT * FROM {" . OPENGRAPH_META_TABLE . "} WHERE nid = :nid", array(
':nid' => $nid,
));
if (0 >= $result
->rowCount()) {
return FALSE;
}
return $result
->fetchObject();
}
}
public static function invoke_filter($name, $value, $args = array()) {
array_push($args, $value);
$valueIndex = count($args) - 1;
foreach (module_implements($name) as $module) {
$args[$valueIndex] = call_user_func_array($module . '_' . $name, $args);
}
return $args[$valueIndex];
}
private static function err($msg) {
watchdog('opengraph_meta', '%class: %msg', array(
'%class' => __CLASS__,
'%msg' => $msg,
), WATCHDOG_ERROR);
}
}