function ubik_photo_meta_template($class = 'ubik-photo-meta', $mode = 'table')
{
    // Get photo metadata and exit early if there's nothing to work with
    $photo_meta = ubik_photo_meta();
    if (empty($photo_meta)) {
        return;
    }
    // Initialize
    $output = array();
    // Microdata handling
    $microdata_id = ubik_photo_meta_microdata_id();
    // Class is required for output
    if (empty($class)) {
        $class = 'ubik-photo-meta';
    }
    // Table output
    if ($mode == 'table') {
        // Loop through the array and format name and data
        foreach ($photo_meta as $key => $value) {
            if (!empty($microdata_id) && ubik_photo_meta_set('microdata', $value)) {
                $output[] = '<tr' . $value['microdata']['container'] . '><td class="' . esc_attr($class) . '-name"' . $value['microdata']['name'] . '>' . $value['name'] . '</td><td class="' . esc_attr($class) . '-data"' . $value['microdata']['value'] . '>' . $value['data'] . '</td></tr>';
            } else {
                $output[] = '<tr><td class="ubik-photo-meta-name">' . $value['name'] . '</td><td class="ubik-photo-meta-value">' . $value['data'] . '</td></tr>';
            }
        }
        // Return what we got
        if (!empty($output) && is_array($output)) {
            return '<table class="' . esc_attr($class) . '"' . $microdata_id . '>' . implode('', $output) . '</table>';
        }
    } elseif ($mode == 'list') {
        // Loop through the array and format name and data
        foreach ($photo_meta as $key => $value) {
            if (!empty($microdata_id) && ubik_photo_meta_set('microdata', $value)) {
                $output[] = '<li' . $value['microdata']['container'] . '><span' . $value['microdata']['name'] . '>' . $value['name'] . '</span>: <span' . $value['microdata']['value'] . '>' . $value['data'] . '</span></li>';
            } else {
                $output[] = '<li>' . $value['name'] . ': ' . $value['data'] . '</li>';
            }
        }
        // Return generated markup or null if nothing remains
        if (!empty($output) && is_array($output)) {
            return '<ul class="' . esc_attr($class) . '"' . $microdata_id . '><li>' . implode('', $output) . '</ul>';
        }
    }
    return;
}
function ubik_photo_meta_location_data($m = '')
{
    // Exit early if necessary
    if (!is_array($m) || empty($m)) {
        return;
    }
    // Initialize
    $d = array('latitude_raw' => '', 'latitude_string' => '', 'latitude_numeric' => '', 'latitude_ref' => '', 'latitude_sign' => 1, 'longitude_raw' => '', 'longitude_string' => '', 'longitude_numeric' => '', 'longitude_ref' => '', 'longitude_sign' => 1, 'string' => '', 'string_linked' => '', 'string_microdata' => '', 'string_microdata_linked' => '', 'numeric' => '', 'google_url' => '', 'google_img' => '', 'sublocation' => '', 'city' => '', 'province' => '', 'country' => '');
    // Extended IPTC metadata (warning: these properties are deprecated but still in use, for example by Adobe Lightroom)
    if (ubik_photo_meta_set('sublocation', $m)) {
        $d['sublocation'] = $m['sublocation'];
    }
    if (ubik_photo_meta_set('city', $m)) {
        $d['city'] = $m['city'];
    }
    if (ubik_photo_meta_set('province', $m)) {
        $d['province'] = $m['province'];
    }
    if (ubik_photo_meta_set('country', $m)) {
        $d['country'] = $m['country'];
    }
    // Populate the array with latitude and longitude data
    if (ubik_photo_meta_set('latitude', $m)) {
        $d['latitude_raw'] = $m['latitude'];
        $d['latitude_string'] = ubik_photo_meta_location_convert($m['latitude'], 'string');
        $d['latitude_numeric'] = ubik_photo_meta_location_convert($m['latitude']);
        if (ubik_photo_meta_set('latitude_ref', $m)) {
            $d['latitude_ref'] = $m['latitude_ref'];
            if ($d['latitude_ref'] == 'S') {
                $d['latitude_sign'] = -1;
            }
        }
    }
    if (ubik_photo_meta_set('longitude', $m)) {
        $d['longitude_raw'] = $m['longitude'];
        $d['longitude_string'] = ubik_photo_meta_location_convert($m['longitude'], 'string');
        $d['longitude_numeric'] = ubik_photo_meta_location_convert($m['longitude']);
        if (ubik_photo_meta_set('longitude_ref', $m)) {
            $d['longitude_ref'] = $m['longitude_ref'];
            if ($d['longitude_ref'] == 'W') {
                $d['longitude_sign'] = -1;
            }
        }
    }
    // Derived formatting
    if (!empty($d['latitude_string']) && !empty($d['longitude_string'])) {
        $d['string'] = $d['latitude_string'] . ' ' . $d['latitude_ref'] . ', ' . $d['longitude_string'] . ' ' . $d['longitude_ref'];
        $d['string_microdata'] = '<span itemscope itemprop="geo" itemtype="http://schema.org/GeoCoordinates"><span itemprop="latitude">' . $d['latitude_string'] . ' ' . $d['latitude_ref'] . '</span>, <span itemprop="longitude">' . $d['longitude_string'] . ' ' . $d['longitude_ref'] . '</span></span>';
    }
    if (!empty($d['latitude_numeric']) && !empty($d['longitude_numeric'])) {
        $d['numeric'] = $d['latitude_sign'] * number_format($d['latitude_numeric'], 6) . ',' . $d['longitude_sign'] * number_format($d['longitude_numeric'], 6);
    }
    if (!empty($d['numeric'])) {
        $d['google_url'] = esc_url('//maps.google.com/maps?q=' . $d['numeric'] . '&ll=' . $d['numeric'] . '&z=15&t=h');
        // Some help with Google Maps stuff: https://moz.com/ugc/everything-you-never-wanted-to-know-about-google-maps-parameters
        $d['google_img'] = esc_url('//maps.googleapis.com/maps/api/staticmap?zoom=12&size=500x500&maptype=roadmap&markers=color:blue%7Clabel:S%7C' . $d['numeric'] . '&sensor=false');
    }
    if (!empty($d['google_url'])) {
        $d['string_linked'] = '<a href="' . $d['google_url'] . '">' . $d['string'] . '</a>';
        $d['string_microdata_linked'] = '<a href="' . $d['google_url'] . '" itemprop="hasMap">' . $d['string_microdata'] . '</a>';
    }
    return apply_filters('ubik_photo_meta_location_data', $d, $m);
}
function ubik_photo_meta_read_image($meta, $file, $sourceImageType)
{
    // Exit early if we have no file to act upon
    if (empty($file)) {
        return $meta;
    }
    // Initialize
    $meta_extra = array();
    // Parse IPTC metadata; mapping codes courtesy of https://github.com/peterhudec/image-metadata-cruncher
    if (is_callable('iptcparse')) {
        getimagesize($file, $iptc);
        if (!empty($iptc['APP13'])) {
            $iptc = iptcparse($iptc['APP13']);
            if (!empty($iptc['2#090'])) {
                $meta_extra['city'] = $iptc['2#090'][0];
            }
            if (!empty($iptc['2#092'])) {
                $meta_extra['sublocation'] = $iptc['2#092'][0];
            }
            if (!empty($iptc['2#095'])) {
                $meta_extra['province'] = $iptc['2#095'][0];
            }
            if (!empty($iptc['2#101'])) {
                $meta_extra['country'] = $iptc['2#101'][0];
            }
            if (!empty($iptc['2#025']) && is_array($iptc['2#025'])) {
                $meta_extra['keywords'] = implode(', ', $iptc['2#025']);
            }
            // Store keywords as a comma-delimited string for easy manipulation and re-use
        }
    }
    // Parse EXIF metadata
    if (is_callable('exif_read_data') && in_array($sourceImageType, apply_filters('wp_read_image_metadata_types', array(IMAGETYPE_JPEG, IMAGETYPE_TIFF_II, IMAGETYPE_TIFF_MM)))) {
        $exif = @exif_read_data($file);
        // GPS data
        if (!empty($exif['GPSLatitude'])) {
            $meta_extra['latitude'] = $exif['GPSLatitude'];
        }
        if (!empty($exif['GPSLatitudeRef'])) {
            $meta_extra['latitude_ref'] = trim($exif['GPSLatitudeRef']);
        }
        if (!empty($exif['GPSLongitude'])) {
            $meta_extra['longitude'] = $exif['GPSLongitude'];
        }
        if (!empty($exif['GPSLongitudeRef'])) {
            $meta_extra['longitude_ref'] = trim($exif['GPSLongitudeRef']);
        }
        // Additional photographic metadata
        if (!empty($exif['ExposureBiasValue'])) {
            $meta_extra['exposure'] = trim($exif['ExposureBiasValue']);
        }
        if (!empty($exif['Flash'])) {
            $meta_extra['flash'] = trim($exif['Flash']);
        }
    }
    // Sanitize extra metadata keys; 'latitude', 'latitude_ref', 'longitude', 'longitude_ref' aren't necessarily strings so let's not run those through
    foreach (array('exposure', 'flash') as $key) {
        if (ubik_photo_meta_set($key, $meta_extra) && !seems_utf8($meta_extra[$key])) {
            $meta_extra[$key] = utf8_encode($meta_extra[$key]);
        }
    }
    // Add metadata to the existing metadata object
    foreach ($meta_extra as $field => $extra) {
        if (is_string($extra)) {
            $extra = wp_kses_post($extra);
        }
        if (is_array($extra)) {
            $extra = array_map('esc_attr', $extra);
        }
        $meta[$field] = $extra;
    }
    // Return and filter the results
    return apply_filters('ubik_photo_meta_read_image', $meta, $file, $sourceImageType);
}
function ubik_photo_meta_data($id, $fields = array())
{
    // Exit early if necessary
    if (empty($id)) {
        return;
    }
    // Fetch metadata
    if (!empty($id)) {
        $meta_raw = wp_get_attachment_metadata($id);
    }
    // Fill the photo metadata array with whatever we have available
    if (!empty($meta_raw) && ubik_photo_meta_set('image_meta', $meta_raw)) {
        // Setup
        $m = $meta_raw['image_meta'];
        // Raw source material
        $d = array();
        // Processed output
        // Populate the array with formatted metadata; there is also an orientation property in vanilla WordPress to consider
        if (ubik_photo_meta_set('aperture', $m) && in_array('aperture', $fields)) {
            $d['aperture'] = array('name' => __('Aperture', 'ubik'), 'data' => apply_filters('ubik_photo_meta_f_symbol', '<em>&#402;</em>') . '/' . $m['aperture'], 'microdata' => ubik_photo_meta_microdata('exif'));
        }
        if (ubik_photo_meta_set('camera', $m) && in_array('camera', $fields)) {
            $d['camera'] = array('name' => __('Camera', 'ubik'), 'data' => $m['camera'], 'microdata' => ubik_photo_meta_microdata('exif'));
        }
        if (ubik_photo_meta_set('caption', $m) && in_array('caption', $fields)) {
            $d['caption'] = array('name' => __('Caption', 'ubik'), 'data' => $m['caption']);
        }
        if (ubik_photo_meta_set('created_timestamp', $m) && in_array('created_timestamp', $fields)) {
            $d['created_timestamp'] = array('name' => __('Date taken', 'ubik'), 'data' => ubik_photo_meta_date($m['created_timestamp']), 'microdata' => ubik_photo_meta_microdata('created'));
        }
        if (ubik_photo_meta_set('credit', $m) && in_array('credit', $fields)) {
            $d['credit'] = array('name' => __('Credit', 'ubik'), 'data' => $m['credit'], 'microdata' => ubik_photo_meta_microdata('credit'));
        }
        if (ubik_photo_meta_set('copyright', $m) && in_array('copyright', $fields)) {
            $d['copyright'] = array('name' => __('Copyright', 'ubik'), 'data' => $m['copyright']);
        }
        if (ubik_photo_meta_set('focal_length', $m) && in_array('focal_length', $fields)) {
            $d['focal_length'] = array('name' => __('Focal length', 'ubik'), 'data' => ubik_photo_meta_focal_length($m['focal_length']), 'microdata' => ubik_photo_meta_microdata('exif'));
        }
        if (ubik_photo_meta_set('iso', $m) && in_array('iso', $fields)) {
            $d['iso'] = array('name' => __('ISO', 'ubik'), 'data' => $m['iso'], 'microdata' => ubik_photo_meta_microdata('exif'));
        }
        if (ubik_photo_meta_set('shutter_speed', $m) && in_array('shutter_speed', $fields)) {
            $d['shutter_speed'] = array('name' => __('Shutter speed', 'ubik'), 'data' => ubik_photo_meta_shutter_speed($m['shutter_speed']), 'microdata' => ubik_photo_meta_microdata('exif'));
        }
        if (ubik_photo_meta_set('title', $m) && in_array('title', $fields)) {
            $d['title'] = array('name' => __('Title', 'ubik'), 'data' => $m['title']);
        }
        // Extended EXIF metadata
        if (ubik_photo_meta_set('exposure', $m) && in_array('exposure', $fields)) {
            $d['exposure'] = array('name' => __('Exposure', 'ubik'), 'data' => ubik_photo_meta_exposure($m['exposure']), 'microdata' => ubik_photo_meta_microdata('exif'));
        }
        if (ubik_photo_meta_set('flash', $m) && in_array('flash', $fields)) {
            $d['flash'] = array('name' => __('Flash', 'ubik'), 'data' => ubik_photo_meta_binary($m['flash']), 'microdata' => ubik_photo_meta_microdata('exif'));
        }
        // Keywords; by default these are filtered to associate with tags in the database
        if (ubik_photo_meta_set('keywords', $m) && in_array('keywords', $fields)) {
            $keywords = ubik_photo_meta_keywords($m['keywords']);
            if (!empty($keywords)) {
                $d['keywords'] = array('name' => __('Keywords', 'ubik'), 'data' => $keywords, 'microdata' => ubik_photo_meta_microdata('keywords'));
            }
        }
        // Location data; this is a special case: 1) there are many kinds of location metadat, and 2) users may wish to display this in any number of ways
        if (in_array('location', $fields)) {
            $location = ubik_photo_meta_location($m);
            if (!empty($location) && is_array($location)) {
                $d['location'] = $location;
            }
        }
    }
    return apply_filters('ubik_photo_meta_data', $d);
}