/** * Wraps a set of values for a model into JSON suitable for submission to the Indicia data services, * and also grabs the images and links them to the model. * * @param array $values Array of form data (e.g. $_POST). * @param string $modelName Name of the model to wrap data for. If this is sample, occurrence or location * then custom attributes will also be wrapped. Furthermore, any attribute called $modelName:image can * contain an image upload (as long as a suitable entity is available to store the image in). */ public static function wrap_with_images($values, $modelName, $fieldPrefix = null) { // Now search for an input controls with the name image_upload. // For example, this occurs on the taxon_image edit page of the Warehouse. // We do this first so the uploaded image path can be put into the submission if (array_key_exists('image_upload', $_FILES) && $_FILES['image_upload']['name']) { $file = $_FILES['image_upload']; // Get the original file's extension $parts = explode(".", $file['name']); $fext = array_pop($parts); // Generate a file id to store the image as $destination = time() . rand(0, 1000) . "." . $fext; $uploadpath = dirname($_SERVER['SCRIPT_FILENAME']) . '/' . (isset(parent::$indicia_upload_path) ? parent::$indicia_upload_path : 'upload/'); if (move_uploaded_file($file['tmp_name'], $uploadpath . $destination)) { // record the new file name, also note it in the $_POST data so it can be tracked after a validation failure $_FILES['image_upload']['name'] = $destination; $values['path'] = $destination; // This is the final file destination, so create the image files. Image::create_image_files($uploadpath, $destination); } } // Get the parent model into JSON $modelWrapped = self::wrap($values, $modelName, $fieldPrefix); // Build sub-models for the media files. Don't post to the warehouse until after validation success. This // also moves any simple uploaded files to the interim image upload folder. $media = data_entry_helper::extract_media_data($values, $modelName . '_medium', true, true); foreach ($media as $item) { $wrapped = self::wrap($item, $modelName . '_medium'); $modelWrapped['subModels'][] = array('fkId' => $modelName . '_id', 'model' => $wrapped); } return $modelWrapped; }
public static function build_three_level_sample_with_occ_submission($values, $website_id, $password, $gpxDataAttrId, $diveStartTimeAttrId, $exifDateTimeAttrId) { $standardGridValues = array(); //Create two different $values arrays. //The $standardGridValues array contains all the values you would expect from a normal species grid entry form. This contains the species grid we don't have images for. //The $values array contains the values from the form and only includes the other three level sample species grid foreach ($values as $key => $value) { if (strpos($key, 'sc:') === false || strpos($key, 'sc:') !== false && strpos($key, 'first-level-smp-occ-grid') !== false) { $standardGridValues[$key] = $values[$key]; } if (strpos($key, 'first-level-smp-occ-grid') !== false) { unset($values[$key]); } } //Identify any occurrences which don't have images and will therefore be attached to the top level sample. $standardOccurrences = data_entry_helper::wrap_species_checklist($standardGridValues); $modelName = 'sample'; //Wrap up the main parent sample $modelWrapped = data_entry_helper::wrap($values, $modelName, null); //2nd level samples are prefixed with the word new_sample_sub_sample:<num>: or existing_sample_sub_sample:<id>: in the name. //This function returns an array of possible sub-samples (habitats) which have been extracted from the values on the page by looking //for this text on the front of the keys in the values array. $possibleSubSampleModels = self::extract_sub_sample_data($values, $website_id, $password, $gpxDataAttrId); //Cycle through the sub-samples and then set them up as sub-samples of the main sample if (!empty($possibleSubSampleModels)) { foreach ($possibleSubSampleModels as $subSample) { $modelWrapped['subModels'][] = array('fkId' => 'sample_id', 'model' => $subSample); } } // Build sub-models for the sample media files. Also extract the image exif data $media = data_entry_helper::extract_media_data($values, $modelName . '_medium', true, true); if (function_exists('exif_read_data')) { $uploadpath = './sites/all/modules/iform/client_helpers/upload/'; foreach ($media as $idx => $mediaItem) { if (file_exists($uploadpath . $mediaItem['path'])) { $exif = exif_read_data($uploadpath . $mediaItem['path'], 0, true); $media[$idx]['exif'] = json_encode($exif); $strToTime = strtotime($exif['EXIF']['DateTimeOriginal']); $time = explode(' ', $exif['EXIF']['DateTimeOriginal']); //On the first tab (when we don't have a date field) then collect the date from the exif from the earliest photo //and also set a default on the time field in the same way //Cycle round the media items and only set the date/time if it is the smallest one so far. if ((empty($smallestStrToTime) || $smallestStrToTime > $strToTime) && !empty($exif['EXIF']['DateTimeOriginal']) && empty($modelWrapped['fields']['sample:date']['value'])) { $smallestStrToTime = $strToTime; $gpsFromFirstExif = $exif['GPS']; $modelWrapped['fields']['date']['value'] = date('d/m/Y', $smallestStrToTime); $values['sample:date'] = date('d/m/Y', $smallestStrToTime); $modelWrapped['fields']['smpAttr:' . $diveStartTimeAttrId]['value'] = $time[1]; $values['smpAttr:' . $diveStartTimeAttrid] = $time[1]; } //Save the dates and times from the photos into an attribute for easy access by javascript, so contruct a string to save first. //Note I didn't use json as that dates include colons. So the format is date,time;date,time;date,time;date,time;date,time; if (!empty($mediaDates)) { $mediaDates = $mediaDates . ';' . date('d/m/Y', $strToTime) . ',' . $time[1]; } else { $mediaDates = date('d/m/Y', $strToTime) . ',' . $time[1]; } } } //When the images are first loaded, we don't have a spatial reference to create the main sample with, so try to read one from the earliest picture exif data if (!empty($gpsFromFirstExif['GPSLatitude']) && !empty($gpsFromFirstExif['GPSLongitude']) && !empty($gpsFromFirstExif['GPSLatitudeRef']) && !empty($gpsFromFirstExif['GPSLongitudeRef'])) { //Read from exif, concert from degrees, minutes, seconds to decimal degrees $gpsLat0 = explode('/', $gpsFromFirstExif['GPSLatitude'][0]); $gpsLat0 = doubleval($gpsLat0[0]) / doubleval($gpsLat0[1]); $gpsLat1 = explode('/', $gpsFromFirstExif['GPSLatitude'][1]); $gpsLat1 = doubleval($gpsLat1[0]) / doubleval($gpsLat1[1]) / 60; $gpsLat2 = explode('/', $gpsFromFirstExif['GPSLatitude'][2]); $gpsLat2 = doubleval($gpsLat2[0]) / doubleval($gpsLat2[1]) / 3600; $gpsLon0 = explode('/', $gpsFromFirstExif['GPSLongitude'][0]); $gpsLon0 = doubleval($gpsLon0[0]) / doubleval($gpsLon0[1]); $gpsLon1 = explode('/', $gpsFromFirstExif['GPSLongitude'][1]); $gpsLon1 = doubleval($gpsLon1[0]) / doubleval($gpsLon1[1]) / 60; $gpsLon2 = explode('/', $gpsFromFirstExif['GPSLongitude'][2]); $gpsLon2 = doubleval($gpsLon2[0]) / doubleval($gpsLon2[1]) / 3600; $lat = (string) floatval($gpsLat0 + $gpsLat1 + $gpsLat2); $lon = (string) floatval($gpsLon0 + $gpsLon1 + $gpsLon2); //Convert back into format that is acceptable to the seasearch on screen spatial reference extension which is again in degrees, minutes $latArray = explode('.', $lat); $lat = $latArray[0] . ':' . round(floatval('0.' . $latArray[1]) * 60, 4); $lonArray = explode('.', $lon); $lon = $lonArray[0] . ':' . round(floatval('0.' . $lonArray[1]) * 60, 4); $gpsFromFirstExif = $lat . $gpsFromFirstExif['GPSLatitudeRef'] . ' ' . $lon . $gpsFromFirstExif['GPSLongitudeRef']; $modelWrapped['fields']['entered_sref']['value'] = $gpsFromFirstExif; $values['sample:entered_sref'] = $gpsFromFirstExif; $modelWrapped['fields']['entered_sref_system']['value'] = '4277'; $values['sample:entered_sref_system'] = '4277'; } } if (!empty($mediaDates)) { //Need to find the attribute that starts with smpAttr:<exifDateTimeAttrId> as in edit mode it will also have the sample_attribute_value on the end so in that //case we need to overwrite existing value instead of creating new one. foreach ($values as $theKey => $theValue) { if (substr($theKey, 0, strlen('smpAttr:' . $exifDateTimeAttrId)) === 'smpAttr:' . $exifDateTimeAttrId) { $modelWrapped['fields'][$theKey]['value'] = $mediaDates; $values[$theKey] = $mediaDates; } } } foreach ($media as $item) { //Only add media to the main sample if it isn't already contained in any sub-sample if (empty($values['sample_medium:' . $item['id'] . ':sample_id']) || $values['sample_medium:' . $item['id'] . ':sample_id'] == $modelWrapped['fields']['id']['value']) { $wrapped = data_entry_helper::wrap($item, $modelName . '_medium'); $modelWrapped['subModels'][] = array('fkId' => $modelName . '_id', 'model' => $wrapped); } } // Put any extra occurrences (without images) // the user has identified onto the end of the main sample model. if (array_key_exists('subModels', $modelWrapped)) { $modelWrapped['subModels'] = array_merge($modelWrapped['subModels'], $standardOccurrences); } else { $modelWrapped['subModels'] = $standardOccurrences; } //Create the third level samples with occurrences $modelWrapped = self::create_third_level_sample_model($modelWrapped, $values, $website_id, $password, $gpxDataAttrId); //The user is can rearrange which third level sample points to which second level sample. When the user does this we just need to attach the //change to the parent_id to the end of the submission model $thirdLevelSampleShiftModel = array(); foreach ($values as $key => $value) { //TODO I think this name is misleading, as it isn't the sample_id for the occurrence, it is the sample_id for the occurrence sample's parent sample. //Maybe change name at some point. if (strpos($key, 'occurrence_medium') !== false && strpos($key, ':sample_id') !== false) { $splitKey = explode(':', $key); $thirdLevelSampleShiftModel['id'] = 'sample'; $thirdLevelSampleShiftModel['fields']['id']['value'] = $splitKey[3]; $thirdLevelSampleShiftModel['fields']['parent_id']['value'] = $value; if (!empty($modelWrapped['submission_list']['entries'])) { $modelWrapped['submission_list']['entries'][] = $thirdLevelSampleShiftModel; } else { $multiSubmission['id'] = 'sample'; $multiSubmission['submission_list']['entries'][0] = $modelWrapped; $multiSubmission['submission_list']['entries'][1] = $thirdLevelSampleShiftModel; $modelWrapped = $multiSubmission; } } } return $modelWrapped; }
/** * Wraps a set of values for a model into JSON suitable for submission to the Indicia data services, * and also grabs the images and links them to the model. * * @param array $values Array of form data (e.g. $_POST). * @param string $modelName Name of the model to wrap data for. If this is sample, occurrence or location * then custom attributes will also be wrapped. Furthermore, any attribute called $modelName:image can * contain an image upload (as long as a suitable entity is available to store the image in). */ public static function wrap_with_images($values, $modelName, $fieldPrefix = null) { // Now search for an input control values which imply that an image file will need to be // either moved to the warehouse, or if already on the warehouse, processed to create // thumbnails. foreach ($_FILES as $fieldname => &$file) { if ($file['name']) { // Get the original file's extension $parts = explode(".", $file['name']); $fext = array_pop($parts); // Generate a file id to store the image as $filename = time() . rand(0, 1000) . "." . strtolower($fext); if ($fieldname === 'image_upload') { // image_upload is a special case only used on the warehouse, so can move the file directly to its final place // @todo: Should this special case exist? $uploadpath = dirname($_SERVER['SCRIPT_FILENAME']) . '/' . (isset(parent::$indicia_upload_path) ? parent::$indicia_upload_path : 'upload/'); if (move_uploaded_file($file['tmp_name'], $uploadpath . $filename)) { // record the new file name, also note it in the $_POST data so it can be tracked after a validation failure $file['name'] = $filename; $values['path'] = $filename; // This is the final file destination, so create the image files. Image::create_image_files($uploadpath, $filename); } } elseif (preg_match('/^(' . $modelName . ':)?[a-z_]+_path$/', $fieldname)) { // image fields can be of form {model}:{qualifier}_path (e.g. group:logo_path) if they are // directly embedded in the entity, rather than in a child media entity. These files need // to be moved to interim upload folder and will be sent to the warehouse after a successful // save. $values[$fieldname] = $filename; $interim_image_folder = isset(parent::$interim_image_folder) ? parent::$interim_image_folder : 'upload/'; $uploadpath = $uploadpath = helper_base::relative_client_helper_path() . $interim_image_folder; $tempFile = isset($file['tmp_name']) ? $file['tmp_name'] : ''; if (!move_uploaded_file($tempFile, $uploadpath . $filename)) { throw new exception('Failed to move uploaded file from temporary location'); } $file['name'] = $filename; } } } // Get the parent model into JSON $modelWrapped = self::wrap($values, $modelName, $fieldPrefix); // Build sub-models for the media files. Don't post to the warehouse until after validation success. This // also moves any simple uploaded files to the interim image upload folder. $media = data_entry_helper::extract_media_data($values, $modelName . '_medium', true, true); foreach ($media as $item) { $wrapped = self::wrap($item, $modelName . '_medium'); $modelWrapped['subModels'][] = array('fkId' => $modelName . '_id', 'model' => $wrapped); } return $modelWrapped; }