exif_custom.module in EXIF Custom 7
Primary hook implementations for EXIF Custom.
File
exif_custom.moduleView source
<?php
/**
* @file
* Primary hook implementations for EXIF Custom.
*/
/**
* Implements hook_menu().
*/
function exif_custom_menu() {
$items['admin/config/media/exif_custom'] = array(
'type' => MENU_NORMAL_ITEM,
'title' => 'Custom EXIF Mappings',
'description' => 'Customise mappings of EXIF data',
'page callback' => 'exif_custom_mappings',
'access callback' => 'exif_custom_main_page_access',
'file' => 'exif_custom.pages.inc',
);
$items['admin/config/media/exif_custom/maps'] = array(
'type' => MENU_DEFAULT_LOCAL_TASK,
'title' => 'Custom EXIF Mappings',
'description' => 'Customise mappings of EXIF data',
'page callback' => 'exif_custom_mappings',
'access callback' => 'exif_custom_main_page_access',
'file' => 'exif_custom.pages.inc',
);
$items['admin/config/media/exif_custom/add'] = array(
'type' => MENU_LOCAL_TASK,
'title' => 'New EXIF Mapping',
'description' => 'Customise mappings of EXIF data',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'exif_custom_new_map_form',
),
'access arguments' => array(
'administer exif_custom',
),
'file' => 'exif_custom.add.inc',
);
$items['admin/config/media/exif_custom/map/%'] = array(
'type' => MENU_NORMAL_ITEM,
// @todo Title callback?
'title' => 'Edit mapping',
'description' => 'Customise mappings of EXIF data',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'exif_custom_map_edit_form',
),
'access arguments' => array(
'administer exif_custom',
),
'file' => 'exif_custom.edit.inc',
);
$items['admin/config/media/exif_custom/settings'] = array(
'type' => MENU_LOCAL_TASK,
'title' => 'Settings',
'description' => 'Customise general settings for EXIF import',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'exif_custom_settings_form',
),
'access arguments' => array(
'administer exif_custom',
),
'file' => 'exif_custom.settings.inc',
);
$items['admin/config/media/exif_custom/user'] = array(
'type' => MENU_LOCAL_TASK,
'title' => 'User Settings',
'description' => 'Customise general settings for EXIF import',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'exif_custom_settings_form_user',
),
'access arguments' => array(
'have default image metadata profile',
),
'file' => 'exif_custom.user.inc',
);
return $items;
}
/**
* Access callback for the main admin page.
*/
function exif_custom_main_page_access() {
if (user_access('view image metadata')) {
return TRUE;
}
elseif (user_access('administer exif_custom')) {
return TRUE;
}
return FALSE;
}
/**
* Implements hook_permission().
*/
function exif_custom_permission() {
return array(
'have default image metadata profile' => array(
'title' => t('Have default image metadata profile'),
'description' => t('Allow users to override teh site default image metadata profile.'),
),
'view image metadata' => array(
'title' => t('View image metadata'),
'description' => t('See what image metdata import profiles have been created.'),
),
'administer exif_custom' => array(
'title' => 'Administer image metadata',
'description' => t('Administer image metadata import profiles'),
),
);
}
/**
* Load a user's custom settings ID.
*/
function exif_custom_get_user_default() {
global $user;
$mid = db_query('SELECT mid FROM {exif_custom_users} WHERE uid = :uid', array(
':uid' => $user->uid,
))
->fetchField();
if (!empty($mid)) {
return $mid;
}
else {
return '';
}
}
/**
* Get a list of custom maps.
*
* @return object|bool
* Either the list of mappings, or FALSE if none available.
*/
function _exif_custom_get_maps() {
$results = db_query("SELECT mid, name FROM {exif_custom_maps};");
if ($results
->rowCount() > 0) {
return $results;
}
else {
return FALSE;
}
}
/**
* Implements hook_entity_presave().
*/
function exif_custom_entity_presave($entity, $entity_type) {
if ($entity_type == 'file') {
exif_custom_process_entity($entity);
}
}
/**
* Process a given entity to see if it has EXIF data to be saved.
*
* @param object $file
* The file being processed.
*/
function exif_custom_process_entity(&$file) {
if (!isset($file->type) || !isset($file->uri)) {
return;
}
if ($file->type != 'image') {
return;
}
if (arg(3) == 'exif_custom') {
return;
}
// Media Browser saves in two steps: first a temporary file is saved after
// which the user is presented with a form an can edit fields on the file.
// After potentially updating the file fields the user presses save and the
// file is made permanent and saved. As exif_custom_process_entity is invoked
// in both situations we need to detect the last save and avoid mapping as it
// would overwrite any changes the user made.
if (arg(0) == 'media' && arg(1) == 'browser' && $file->status == FILE_STATUS_PERMANENT) {
return;
}
// Skip mapping if the file has a path that is excluded.
$patterns = variable_get('exif_custom_exclude_paths', NULL);
if (!empty($patterns) && drupal_match_path($file->uri, $patterns)) {
return;
}
$data = exif_custom_get_exif_fields($file->uri, TRUE);
if (empty($data)) {
return;
}
$mappings = exif_custom_get_mapping();
if (empty($mappings)) {
return;
}
$lang = entity_language('file', $file);
if (!$lang) {
$lang = LANGUAGE_NONE;
}
foreach ($mappings as $field => $values) {
// Ignore fields that aren't present.
if (!isset($data[$values->exif_field])) {
continue;
}
// File name is special case.
if ($field == 'filename') {
$file->filename = $data[$values->exif_field];
continue;
}
// Optionally process fields which have already been imported. This avoids
// re-processing files which have already been saved.
if (!variable_get('exif_custom_overwrite_existing') && !empty($file->{$field})) {
continue;
}
$array = array();
$field_info = field_info_field($field);
$instance_info = field_info_instance('file', $field, 'image');
switch ($field_info['type']) {
case 'taxonomy_term_reference':
$vids = array();
foreach ($field_info['settings']['allowed_values'] as $allowed_value) {
$vocabulary = taxonomy_vocabulary_machine_name_load($allowed_value['vocabulary']);
if ($allowed_value['vocabulary'] === 0) {
break;
}
$vids[$vocabulary->vid] = $vocabulary;
}
$element = array();
$element['#value'] = isset($data[$values->exif_field]) ? $data[$values->exif_field] : FALSE;
$form_state = array();
$array = array();
$array[$lang] = _exif_custom_taxonomy_autocomplete_validate($element, $vids);
break;
case 'country':
$countries = array_flip(countries_get_countries('name'));
if (isset($countries[$data[$values->exif_field]])) {
$array[$lang][0]['iso2'] = $countries[$data[$values->exif_field]];
}
break;
case 'creative_commons':
module_load_include('module', 'creative_commons');
$licence_by_id = array(
'CC_NONE' => 1,
'CC_BY' => 2,
'CC_BY_SA' => 3,
'CC_BY_ND' => 4,
'CC_BY_NC' => 5,
'CC_BY_NC_SA' => 6,
'CC_BY_NC_ND' => 7,
'CC_0' => 8,
'CC_PD' => 9,
);
$licence_by_id = array_merge($licence_by_id, array_flip(creative_commons_get_licence_types()));
if (isset($licence_by_id[$data[$values->exif_field]])) {
$array[$lang]['0']['licence'] = $licence_by_id[$data[$values->exif_field]];
}
break;
case 'date':
$array[$lang]['0']['value'] = strtotime($data[$values->exif_field]);
break;
default:
$array[$lang]['0']['value'] = $data[$values->exif_field];
}
// Set the values.
$file->{$field} = $array;
}
}
/**
* Load a mapping for the current user, or the default if not set.
*
* @return array
* The appropriate mapping for this user.
*/
function exif_custom_get_mapping() {
// First try and get users default.
$mid = exif_custom_get_user_default();
// Use site default or item 0.
if (empty($mid)) {
$mid = variable_get('exif_custom_default', 0);
}
$return = db_query("SELECT * FROM {exif_custom_mapped_fields}\n WHERE mid = :mid AND img_field != 'none'", array(
':mid' => $mid,
))
->fetchAllAssoc('img_field');
return $return;
}
/**
*
*
* @param string $uri
* A file URL.
* @param bool $concatenate_arrays
* Whether or not to concatenate values or overwrite them.
*
* @return array
* The fields extracted.
*/
function exif_custom_get_exif_fields($uri, $concatenate_arrays = TRUE) {
// Generate the absolute URL for the image just once.
if (variable_get('exif_custom_request_method', 'legacy') == 'legacy') {
$image_url = drupal_realpath($uri);
}
else {
$image_url = file_create_url($uri);
}
$fields = array();
// EXIF. Because there can be corrupt / incompatible data in the image, and
// it doesn't support stream options, throw away any error messages.
$exif = @exif_read_data($image_url, NULL, TRUE);
if (isset($exif) && is_array($exif)) {
foreach ($exif as $name => $section) {
foreach ($section as $key => $value) {
if ($concatenate_arrays && is_array($value)) {
$value = implode(', ', $value);
}
$fields['EXIF:' . $name . ':' . $key] = exif_custom_check_plain($value);
}
}
}
// XMP.
$fields = array_merge($fields, exif_custom_get_xmp($image_url));
// IPTC. This function doesn't support stream options so ignore any errors.
$size = @getimagesize($image_url, $info);
if (isset($info) && is_array($info)) {
foreach ($info as $block) {
$iptc = iptcparse($block);
if (!empty($iptc)) {
// IPTC:2#254 can contain name=value pairs.
if (isset($iptc['2#254']) && is_array($iptc['2#254'])) {
$i = 0;
foreach ($iptc['2#254'] as $iptc_field) {
$subfields = explode('=', $iptc_field);
$iptc['2#254.' . $subfields[0]] = $subfields[1];
}
unset($iptc['2#254']);
}
foreach ($iptc as $key => $value) {
if ($concatenate_arrays && is_array($value)) {
$value = implode(', ', $value);
}
$fields['IPTC:' . $key] = exif_custom_check_plain($value);
}
}
}
}
return $fields;
}
/**
*
*/
function _exif_custom_taxonomy_autocomplete_validate(&$element, $vocabularies) {
// Autocomplete widgets do not send their tids in the form, so we must detect
// them here and process them independently.
$items = array();
if ($tags = $element['#value']) {
// Translate term names into actual terms.
if (is_array($tags)) {
// If #value is an array, it was probably a bag in the XMP, thus no need
// to run through drupal_explode_tags().
$typed_terms = $tags;
}
else {
$typed_terms = drupal_explode_tags($tags);
}
foreach ($typed_terms as $typed_term) {
// See if the term exists in the chosen vocabulary and return the tid;
// otherwise, create a new 'autocreate' term for insert/update.
if ($possibilities = taxonomy_term_load_multiple(array(), array(
'name' => trim($typed_term),
'vid' => array_keys($vocabularies),
))) {
$term = array_pop($possibilities);
}
else {
$vocabulary = reset($vocabularies);
$term = array(
'tid' => 'autocreate',
'vid' => $vocabulary->vid,
'name' => $typed_term,
'vocabulary_machine_name' => $vocabulary->machine_name,
);
}
$items[] = (array) $term;
}
}
foreach ($items as $delta => $item) {
if ($item['tid'] == 'autocreate') {
$term = (object) $item;
unset($term->tid);
taxonomy_term_save($term);
$items[$delta]['tid'] = $term->tid;
}
}
return $items;
}
/**
*
*/
function exif_custom_check_plain($text) {
if (is_null($text)) {
$text = "";
}
if (!mb_detect_encoding($text, 'UTF-8', TRUE)) {
$text = html_entity_decode($text);
$text = mb_convert_encoding($text, 'UTF-8', 'ISO-8859-1');
$text = str_replace('>', '>', $text);
$text = str_replace('<', '<', $text);
$text = str_replace('<br />', '\\n', $text);
}
return $text;
// Removed as it stops italics in descriptions.
// return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
}
/**
*
*/
function exif_custom_get_xmp($image) {
$content = file_get_contents($image);
$xmp_data_start = strpos($content, '<x:xmpmeta');
$xmp_data_end = strpos($content, '</x:xmpmeta>');
if ($xmp_data_start === FALSE || $xmp_data_end === FALSE) {
return array();
}
$xmp_length = $xmp_data_end - $xmp_data_start;
$xmp_data = substr($content, $xmp_data_start, $xmp_length + 12);
unset($content);
$xmp = simplexml_load_string($xmp_data);
if ($xmp === FALSE) {
return array();
}
// $namespaces = $xmp->getDocNamespaces(true);
// $fields = array();
// foreach ($namespaces as $namespace) {
// $fields[] = exif_custom_xml_recursion($xmp->children($namespace));
$field_data = array();
exif_custom_xml_recursion($xmp, $field_data, 'XMP');
return $field_data;
}
/**
*
*
* @param object $obj
* @param array $fields
* @param string name
*
* @return array
*/
function exif_custom_xml_recursion($obj, array &$fields, $name) {
$namespace = $obj
->getDocNamespaces(TRUE);
$namespace[NULL] = NULL;
$children = array();
$attributes = array();
$text = trim((string) $obj);
if (strlen($text) === 0) {
$text = NULL;
}
if (strtolower((string) $obj
->getName()) == "bag") {
// @todo Add support for bags of objects other than just text?
$childValues = array();
$objChildren = $obj
->children("rdf", TRUE);
foreach ($objChildren as $child) {
$childValues[] = trim((string) $child);
}
if (count($childValues) > 0) {
$fields[$name] = $childValues;
}
}
else {
$name = $name . ':' . strtolower((string) $obj
->getName());
// Get info for all namespaces.
if (is_object($obj)) {
foreach ($namespace as $ns => $nsUrl) {
// Attributes.
$objAttributes = $obj
->attributes($ns, TRUE);
foreach ($objAttributes as $attributeName => $attributeValue) {
$attribName = strtolower(trim((string) $attributeName));
$attribVal = trim((string) $attributeValue);
if (!empty($ns)) {
$attribName = $ns . ':' . $attribName;
}
$attributes[$attribName] = $attribVal;
}
// Children.
$objChildren = $obj
->children($ns, TRUE);
foreach ($objChildren as $childName => $child) {
$childName = strtolower((string) $childName);
if (!empty($ns)) {
$childName = $ns . ':' . $childName;
}
$children[$childName][] = exif_custom_xml_recursion($child, $fields, $name);
}
}
}
if (!is_null($text)) {
$fields[$name] = $text;
}
}
return array(
'name' => $name,
'text' => html_entity_decode($text),
'attributes' => $attributes,
'children' => $children,
);
}
/**
* Get the mapping ID from a Mapping name.
*
* @param string $name
* The name of the mapping.
*
* @return int
* The ID, or if not found -1.
*/
function exif_custom_get_mid($name) {
$mid = db_select('exif_custom_maps', 'm')
->fields('m', array(
'mid',
))
->condition('m.name', $name)
->execute()
->fetchField();
if (!empty($mid)) {
return $mid;
}
return -1;
}
Functions
Name | Description |
---|---|
exif_custom_check_plain | |
exif_custom_entity_presave | Implements hook_entity_presave(). |
exif_custom_get_exif_fields | |
exif_custom_get_mapping | Load a mapping for the current user, or the default if not set. |
exif_custom_get_mid | Get the mapping ID from a Mapping name. |
exif_custom_get_user_default | Load a user's custom settings ID. |
exif_custom_get_xmp | |
exif_custom_main_page_access | Access callback for the main admin page. |
exif_custom_menu | Implements hook_menu(). |
exif_custom_permission | Implements hook_permission(). |
exif_custom_process_entity | Process a given entity to see if it has EXIF data to be saved. |
exif_custom_xml_recursion | |
_exif_custom_get_maps | Get a list of custom maps. |
_exif_custom_taxonomy_autocomplete_validate |