/**
  * 
  * @param type $args The args for the page including the options selected on the edit tab.
  * @param type $node The node
  * @return type The parent get_form method html
  */
 public static function get_form($args, $node)
 {
     $pageMode = self::getMode($args, $node);
     global $user;
     //Always override any role a user has with a larger role if they have that role.
     $roleType = 'guest';
     if (in_array('volunteer', $user->roles)) {
         $roleType = 'volunteer';
     }
     if (in_array('staff', $user->roles)) {
         $roleType = 'staff';
     }
     if (in_array('data manager', $user->roles)) {
         $roleType = 'data manager';
     }
     //Get the data such as phone number associated with the user
     $user_fields = user_load($user->uid);
     $userPhoneNum = null;
     $userEmail = null;
     $userName = null;
     if (!empty($user_fields->field_main_phone_number['und']['0']['value'])) {
         $userPhoneNum = $user_fields->field_main_phone_number['und']['0']['value'];
     }
     if (!empty($user->mail)) {
         $userEmail = $user->mail;
     } else {
         //we need to pass javascript an empty string rather than null variable
         $userEmail = '';
     }
     if (!empty($user->name)) {
         $userName = "******";
     } else {
         //we need to pass javascript an empty string rather than null variable
         $userName = "******";
     }
     $readAuth = data_entry_helper::get_read_auth($args['website_id'], $args['password']);
     //Need to collect the user's name from the people table
     $defaultUserData = data_entry_helper::get_report_data(array('dataSource' => 'library/users/get_people_details_for_website_or_user', 'readAuth' => $readAuth, 'extraParams' => array('user_id' => hostsite_get_user_field('indicia_user_id'), 'website_id' => $args['website_id'])));
     //Pass the javascript the sample id if in edit mode.
     if (!empty($_GET['sample_id'])) {
         data_entry_helper::$javascript .= "indiciaData.sample_id = " . $_GET['sample_id'] . ";\n";
     }
     //Allow the user to match up the attributes on the edit tab so we don't hard code attribute ids
     //Pass these into javascript
     data_entry_helper::$javascript .= "\r\n    indiciaData.person_name = '" . $defaultUserData[0]['fullname_surname_first'] . "';     \r\n    indiciaData.observer_name_attr_id = " . $args['observer_name'] . ";\r\n    indiciaData.observer_email_attr_id = " . $args['observer_email'] . ";\r\n    indiciaData.observer_phone_number_attr_id = " . $args['observer_phone_number'] . ";\r\n    \r\n    indiciaData.start_time_attr_id = " . $args['start_time'] . ";\r\n    indiciaData.end_time_attr_id = " . $args['end_time'] . ";  \r\n      \r\n    indiciaData.sea_state_attr_id = " . $args['sea_state'] . ";  \r\n    indiciaData.visibility_attr_id = " . $args['visibility'] . ";  \r\n    indiciaData.cetaceans_seen_attr_id = " . $args['cetaceans_seen'] . ";\r\n    indiciaData.non_cetacean_marine_animals_seen_attr_id = " . $args['non_cetacean_marine_animals_seen'] . ";\r\n    indiciaData.feeding_birds_seen_attr_id = " . $args['feeding_birds_seen'] . ";\r\n    indiciaData.number_of_people_spoken_to_during_watch_attr_id = " . $args['number_of_people_spoken_to_during_watch'] . ";\r\n\r\n    indiciaData.wdcs_newsletter_attr_id = " . $args['wdcs_newsletter'] . ";\r\n    indiciaData.bearing_to_sighting_attr_id = " . $args['bearing_to_sighting'] . ";\r\n    indiciaData.reticules_attr_id = " . $args['reticules'] . ";\r\n    indiciaData.reticules_from_attr_id = " . $args['reticules_from'] . ";\r\n    indiciaData.distance_estimate_attr_id = " . $args['distance_estimate'] . "; \r\n    indiciaData.adults_attr_id = " . $args['adults'] . ";\r\n    indiciaData.calves_attr_id = " . $args['calves'] . "; \r\n    indiciaData.activity_attr_id = " . $args['activity'] . ";\r\n    indiciaData.behaviour_attr_id = " . $args['behaviour'] . ";\r\n    \r\n    indiciaData.user_phone_number = \"{$userPhoneNum}\"; \r\n    indiciaData.user_email = \"{$userEmail}\";   \r\n    indiciaData.username = \"{$userName}\";\r\n    indiciaData.roleType = \"{$roleType}\"; \r\n    indiciaData.adhocMode = \"" . $args['adhocMode'] . "\";\r\n    indiciaData.tenMinMessage = \"" . lang::get('SW efforts should always be 10 mins. If you saw a sightings that started after the 10 mins then please use the ad-hoc form') . "\";\r\n    indiciaData.emailPhoneMessage = \"" . lang::get('Please supply a phone number or email address') . "\";  \r\n    indiciaData.addSpeciesMessage = \"" . lang::get('Please add a species') . "\";";
     //We need to put root folder into indiciadata so the addrowtogrid.js code knows the details of the server
     //we are running without hard coding it
     //The user needs to specify which page to go to when the user clicks on the notes icon on the species grid
     //The indiciaData.notesIcon is used by the addrowtogrid.js code so that the code for notes on the species grid is not run by old code.
     data_entry_helper::$javascript .= "\r\n    indiciaData.rootFolder = '" . data_entry_helper::getRootFolder() . "';    \r\n    ";
     $r = '';
     //We need a div so we can can put the form into read only mode when required.
     $r = '<div id = "disableDiv">';
     $r .= parent::get_form($args, $node);
     $r .= '</div>';
     //Get a list of sightings, this is used to determine if there are any that are verified or if there are no sightings at all.
     //If we find this is the case then we set the form into read only mode.
     if (!empty($_GET['sample_id'])) {
         $readAuth = data_entry_helper::get_read_auth($args['website_id'], $args['password']);
         $sightingsData = data_entry_helper::get_report_data(array('dataSource' => 'reports_for_prebuilt_forms/Shorewatch/get_sightings_for_sample', 'readAuth' => $readAuth, 'extraParams' => array('sample_id' => $_GET['sample_id'])));
     }
     $verifiedDataDetected = false;
     //Check for sample id as we don't do this in add mode.
     if (!empty($_GET['sample_id']) && empty($sightingsData)) {
         $r = '<div><i>This page is locked as it does not having any sightings.</i></div>' . $r;
         data_entry_helper::$javascript .= "\$('[id*=_lock]').remove();\n";
         data_entry_helper::$javascript .= "\$('#disableDiv').find('input, textarea, text, button, select').attr('disabled','disabled');\n";
         data_entry_helper::$javascript .= "\$('.species-grid, .page-notice, .indicia-button').hide();\n";
         //If the page is locked then we don't run the logic on the Save/Next Step button
         //as this logic enables the button when we don't want it enabled.
         data_entry_helper::$javascript .= "indiciaData.dontRunCetaceanSaveButtonLogic=true;";
     }
     if (!empty($sightingsData)) {
         //If any verified sightings are found then put page into read-only mode.
         foreach ($sightingsData as $sightingData) {
             if ($verifiedDataDetected === false) {
                 if ($sightingData['verification_status'] === 'D' || $sightingData['verification_status'] === 'V' || $sightingData['verification_status'] === 'R') {
                     $r = '<div><i>This page is locked as it has at least 1 verified sighting.</i></div>' . $r;
                     data_entry_helper::$javascript .= "\$('[id*=_lock]').remove();\n \$('.remove-row').remove();\n";
                     data_entry_helper::$javascript .= "\$('.scImageLink,.scClonableRow').hide();\n";
                     data_entry_helper::$javascript .= "\$('.edit-taxon-name,.remove-row').hide();\n";
                     data_entry_helper::$javascript .= "\$('#disableDiv').find('input, textarea, text, button, select').attr('disabled','disabled');\n";
                     //If the page is locked then we don't run the logic on the Save/Next Step button
                     //as this logic enables the button when we don't want it enabled.
                     data_entry_helper::$javascript .= "indiciaData.dontRunCetaceanSaveButtonLogic=true;";
                     $verifiedDataDetected = true;
                 }
             }
         }
     }
     return $r;
 }
 /**
  * Retrieve the HTML required for an details of the same species button.
  * @param array $auth Read authorisation array
  * @param array $args Form options
  * @param string $tabalias The alias of the tab this appears on
  * @param array $options Options configured for this control. 
  * @return string HTML for the button.
  */
 protected static function buttons_species_details($auth, $args, $tabalias, $options)
 {
     if (!empty($args['species_details_url'])) {
         $url = $args['species_details_url'];
         if (strcasecmp(substr($url, 0, 12), '{rootfolder}') !== 0 && strcasecmp(substr($url, 0, 4), 'http') !== 0) {
             $url = '{rootFolder}' . $url;
         }
         $pathParam = function_exists('variable_get') && variable_get('clean_url', 0) == '0' ? 'q' : '';
         $rootFolder = data_entry_helper::getRootFolder() . (empty($pathParam) ? '' : "?{$pathParam}=");
         $url = str_replace('{rootFolder}', $rootFolder, $url);
         $url .= strpos($url, '?') === false ? '?' : '&';
         $url .= 'taxon_meaning_id=' . self::$record['taxon_meaning_id'];
         $taxon = empty(self::$record['preferred_taxon']) ? self::$record['taxon'] : self::$record['preferred_taxon'];
         return '<a class="button" href="' . $url . '">' . lang::get('{1} details page', $taxon) . '</a>';
     }
     return '';
 }
 /**
  * Performs the sending of invitation emails.
  * @param array $args Form configuration arguments
  * @param array $auth Authorisation tokens
  * @todo Integrate with notifications for logged in users.
  */
 private static function sendInvites($args, $auth)
 {
     $emails = helper_base::explode_lines($_POST['invitee_emails']);
     // first task is to populate the groups_invitations table
     $base = uniqid();
     $success = true;
     $failedRecipients = array();
     foreach ($emails as $idx => $email) {
         $values = array('group_invitation:group_id' => $_GET['group_id'], 'group_invitation:email' => $email, 'group_invitation:token' => $base . $idx, 'website_id' => $args['website_id']);
         $s = submission_builder::build_submission($values, array('model' => 'group_invitation'));
         $r = data_entry_helper::forward_post_to('group_invitation', $s, $auth['write_tokens']);
         $pathParam = function_exists('variable_get') && variable_get('clean_url', 0) == '0' ? 'q' : '';
         $rootFolder = data_entry_helper::getRootFolder() . (empty($pathParam) ? '' : "?{$pathParam}=");
         $protocol = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off' || $_SERVER['SERVER_PORT'] == 443 ? "https://" : "http://";
         $acceptUrl = $protocol . $_SERVER['HTTP_HOST'] . $rootFolder . $args['accept_invite_path'] . (empty($pathParam) ? '?' : '&') . 'token=' . $base . $idx;
         $body = $_POST['invite_message'] . "<br/><br/>" . '<a href="' . $acceptUrl . '">' . lang::get('Accept this invitation') . '</a>';
         $message = array('id' => 'iform_group_invite', 'to' => implode(',', $emails), 'subject' => 'Invitation to join a recording group', 'body' => $body, 'headers' => array('MIME-Version' => '1.0', 'Content-type' => 'text/html; charset=iso-8859-1'));
         $mimeheaders = array();
         foreach ($message['headers'] as $name => $value) {
             $mimeheaders[] = $name . ': ' . mime_header_encode($value);
         }
         $thismailsuccess = mail($message['to'], mime_header_encode($message['subject']), str_replace("\r", '', $message['body']), join("\n", $mimeheaders));
         if (!$thismailsuccess) {
             $failedRecipients[$message['to']] = $acceptUrl;
         }
         $success = $success && $thismailsuccess;
     }
     if ($success) {
         drupal_set_message(lang::get('Invitation emails sent'));
     } else {
         drupal_set_message(lang::get('The emails could not be sent due to a server configuration issue. Please contact the site admin. ' . 'The list below gives the emails and the links you need to send to each invitee which they need to click on in order to join the group.'), 'warning');
         $list = array();
         foreach ($failedRecipients as $email => $link) {
             $list[] = lang::get("Send link {1} to {2}.", $link, $email);
         }
         drupal_set_message(implode('<br/>', $list), 'warning');
     }
     drupal_goto($args['redirect_on_success']);
 }
 /**
  * Helper function to generate a species checklist from a given taxon list.
  *
  * Please not that although this is based on the data_entry_helper function, it has only been tested with the following
  * options for seasearch - @id,@useThirdLevelSamples,@lookupListId,@gridIdAttributeId,@speciesControlToUseSubSamples,@subSamplePerRow,@resizeWidth,@resizeHeight
  * If you intend to use any other options, they will require further testing or development.
  *
  */
 private static function species_checklist($options)
 {
     global $indicia_templates;
     data_entry_helper::add_resource('addrowtogrid');
     $options = data_entry_helper::get_species_checklist_options($options);
     $classlist = array('ui-widget', 'ui-widget-content', 'species-grid');
     if (!empty($options['class'])) {
         $classlist[] = $options['class'];
     }
     if ($options['subSamplePerRow']) {
         // we'll track 1 sample per grid row.
         $smpIdx = 0;
     }
     if ($options['columns'] > 1 && count($options['mediaTypes']) > 1) {
         throw new Exception('The species_checklist control does not support having more than one occurrence per row (columns option > 0) ' . 'at the same time has having the mediaTypes option in use.');
     }
     data_entry_helper::add_resource('json');
     data_entry_helper::add_resource('autocomplete');
     $filterArray = data_entry_helper::get_species_names_filter($options);
     $filterNameTypes = array('all', 'currentLanguage', 'preferred', 'excludeSynonyms');
     //make a copy of the options so that we can maipulate it
     $overrideOptions = $options;
     //We are going to cycle through each of the name filter types
     //and save the parameters required for each type in an array so
     //that the Javascript can quickly access the required parameters
     foreach ($filterNameTypes as $filterType) {
         $overrideOptions['speciesNameFilterMode'] = $filterType;
         $nameFilter[$filterType] = data_entry_helper::get_species_names_filter($overrideOptions);
         $nameFilter[$filterType] = json_encode($nameFilter[$filterType]);
     }
     if (count($filterArray)) {
         $filterParam = json_encode($filterArray);
         data_entry_helper::$javascript .= "indiciaData['taxonExtraParams-" . $options['id'] . "'] = {$filterParam};\n";
         // Apply a filter to extraParams that can be used when loading the initial species list, to get just the correct names.
         if (isset($options['speciesNameFilterMode']) && !empty($options['listId'])) {
             $filterFields = array();
             $filterWheres = array();
             self::parse_species_name_filter_mode($options, $filterFields, $filterWheres);
             if (count($filterWheres)) {
                 $options['extraParams'] += array('query' => json_encode(array('where' => $filterWheres)));
             }
             $options['extraParams'] += $filterFields;
         }
     }
     data_entry_helper::$js_read_tokens = $options['readAuth'];
     data_entry_helper::$javascript .= "indiciaData['rowInclusionCheck-" . $options['id'] . "'] = '" . $options['rowInclusionCheck'] . "';\n";
     data_entry_helper::$javascript .= "indiciaData['copyDataFromPreviousRow-" . $options['id'] . "'] = '" . $options['copyDataFromPreviousRow'] . "';\n";
     data_entry_helper::$javascript .= "indiciaData['includeSpeciesGridLinkPage-" . $options['id'] . "'] = '" . $options['includeSpeciesGridLinkPage'] . "';\n";
     data_entry_helper::$javascript .= "indiciaData.speciesGridPageLinkUrl = '" . $options['speciesGridPageLinkUrl'] . "';\n";
     data_entry_helper::$javascript .= "indiciaData.speciesGridPageLinkParameter = '" . $options['speciesGridPageLinkParameter'] . "';\n";
     data_entry_helper::$javascript .= "indiciaData.speciesGridPageLinkTooltip = '" . $options['speciesGridPageLinkTooltip'] . "';\n";
     data_entry_helper::$javascript .= "indiciaData['editTaxaNames-" . $options['id'] . "'] = '" . $options['editTaxaNames'] . "';\n";
     data_entry_helper::$javascript .= "indiciaData['subSpeciesColumn-" . $options['id'] . "'] = '" . $options['subSpeciesColumn'] . "';\n";
     data_entry_helper::$javascript .= "indiciaData['subSamplePerRow-" . $options['id'] . "'] = " . ($options['subSamplePerRow'] ? 'true' : 'false') . ";\n";
     if ($options['copyDataFromPreviousRow']) {
         data_entry_helper::$javascript .= "indiciaData['previousRowColumnsToInclude-" . $options['id'] . "'] = '" . $options['previousRowColumnsToInclude'] . "';\n";
         data_entry_helper::$javascript .= "indiciaData.langAddAnother='" . lang::get('Add another') . "';\n";
     }
     if (count($options['mediaTypes'])) {
         data_entry_helper::add_resource('plupload');
         // store some globals that we need later when creating uploaders
         $relpath = data_entry_helper::getRootFolder() . data_entry_helper::client_helper_path();
         $interim_image_folder = isset(parent::$interim_image_folder) ? parent::$interim_image_folder : 'upload/';
         data_entry_helper::$javascript .= "indiciaData.uploadSettings = {\n";
         data_entry_helper::$javascript .= "  uploadScript: '" . $relpath . "upload.php',\n";
         data_entry_helper::$javascript .= "  destinationFolder: '" . $relpath . $interim_image_folder . "',\n";
         data_entry_helper::$javascript .= "  jsPath: '" . data_entry_helper::$js_path . "'";
         if (isset($options['resizeWidth'])) {
             data_entry_helper::$javascript .= ",\n  resizeWidth: " . $options['resizeWidth'];
         }
         if (isset($options['resizeHeight'])) {
             data_entry_helper::$javascript .= ",\n  resizeHeight: " . $options['resizeHeight'];
         }
         if (isset($options['resizeQuality'])) {
             data_entry_helper::$javascript .= ",\n  resizeQuality: " . $options['resizeQuality'];
         }
         data_entry_helper::$javascript .= "\n}\n";
         if ($indicia_templates['file_box'] != '') {
             data_entry_helper::$javascript .= "file_boxTemplate = '" . str_replace('"', '\\"', $indicia_templates['file_box']) . "';\n";
         }
         if ($indicia_templates['file_box_initial_file_info'] != '') {
             data_entry_helper::$javascript .= "file_box_initial_file_infoTemplate = '" . str_replace('"', '\\"', $indicia_templates['file_box_initial_file_info']) . "';\n";
         }
         if ($indicia_templates['file_box_uploaded_image'] != '') {
             data_entry_helper::$javascript .= "file_box_uploaded_imageTemplate = '" . str_replace('"', '\\"', $indicia_templates['file_box_uploaded_image']) . "';\n";
         }
     }
     $occAttrControls = array();
     $occAttrs = array();
     $occAttrControlsExisting = array();
     $taxonRows = array();
     $subSampleRows = array();
     if (!empty($options['useThirdLevelSamples']) && $options['useThirdLevelSamples'] == true) {
         $useThirdLevelSamples = true;
     } else {
         $useThirdLevelSamples = false;
     }
     // Load any existing sample's occurrence data into $entity_to_load
     if (isset(data_entry_helper::$entity_to_load['sample:id']) && $options['useLoadedExistingRecords'] === false) {
         self::preload_species_checklist_occurrences(data_entry_helper::$entity_to_load['sample:id'], $options['readAuth'], $options['mediaTypes'], $options['reloadExtraParams'], $subSampleRows, $options['speciesControlToUseSubSamples'], isset($options['subSampleSampleMethodID']) ? $options['subSampleSampleMethodID'] : '', $options['id'], $useThirdLevelSamples);
     }
     // load the full list of species for the grid, including the main checklist plus any additional species in the reloaded occurrences.
     $taxalist = data_entry_helper::get_species_checklist_taxa_list($options, $taxonRows);
     // If we managed to read the species list data we can proceed
     if (!array_key_exists('error', $taxalist)) {
         $attrOptions = array('id' => null, 'valuetable' => 'occurrence_attribute_value', 'attrtable' => 'occurrence_attribute', 'key' => 'occurrence_id', 'fieldprefix' => "sc:-idx-::occAttr", 'extraParams' => $options['readAuth'], 'survey_id' => array_key_exists('survey_id', $options) ? $options['survey_id'] : null);
         if (isset($options['attributeIds'])) {
             // make sure we load the grid ID attribute
             if (!empty($options['gridIdAttributeId']) && !in_array($options['gridIdAttributeId'], $options['attributeIds'])) {
                 $options['attributeIds'][] = $options['gridIdAttributeId'];
             }
             $attrOptions['extraParams'] += array('query' => json_encode(array('in' => array('id' => $options['attributeIds']))));
         }
         $attributes = data_entry_helper::getAttributes($attrOptions);
         // Merge in the attribute options passed into the control which can override the warehouse config
         if (isset($options['occAttrOptions'])) {
             foreach ($options['occAttrOptions'] as $attrId => $attr) {
                 if (isset($attributes[$attrId])) {
                     $attributes[$attrId] = array_merge($attributes[$attrId], $attr);
                 }
             }
         }
         // Get the attribute and control information required to build the custom occurrence attribute columns
         data_entry_helper::species_checklist_prepare_attributes($options, $attributes, $occAttrControls, $occAttrControlsExisting, $occAttrs);
         $beforegrid = '<span style="display: none;">Step 1</span>' . "\n";
         if (isset($options['lookupListId'])) {
             $subSampleImagesToLoad = array();
             //Cycle through sub-samples of the main parent sample
             foreach ($subSampleRows as $subSampleIdx => $subSampleRow) {
                 foreach (data_entry_helper::$entity_to_load as $key => $value) {
                     $keyParts = explode(':', $key);
                     //Get an array of sample media to load onto the grid
                     if (strpos($key, 'third-level-smp-occ-grid') !== false && strpos($key, ':sample_medium:id') !== false) {
                         if (!in_array($keyParts[3], $subSampleImagesToLoad)) {
                             $subSampleImagesToLoad[] = $keyParts[3];
                         }
                     }
                 }
             }
             //For each sub-sample, add a row to the occurrences grid with the image loaded, this is then ready for the user.
             //To create occurrences with
             if (isset($subSampleImagesToLoad)) {
                 $mediaIdArray = array();
                 foreach ($subSampleImagesToLoad as $subSampleImageIdx => $subSampleImageToLoad) {
                     $mediaIdArray[] = $subSampleImageToLoad;
                     $beforegrid .= self::get_species_checklist_empty_row_with_image($options, $occAttrControls, $attributes, $subSampleImageIdx, $subSampleImageToLoad);
                 }
                 $encodedMediaArray = json_encode($mediaIdArray);
                 data_entry_helper::$javascript .= "indiciaData.encodedMediaArray=" . json_encode($encodedMediaArray) . ";\n";
             }
             $beforegrid .= self::get_species_checklist_clonable_row($options, $occAttrControls, $attributes);
         }
         $onlyImages = true;
         if ($options['mediaTypes']) {
             foreach ($options['mediaTypes'] as $mediaType) {
                 if (substr($mediaType, 0, 6) !== 'Image:') {
                     $onlyImages = false;
                 }
             }
         }
         $grid = data_entry_helper::get_species_checklist_header($options, $occAttrs, $onlyImages);
         $rows = array();
         $imageRowIdxs = array();
         $taxonCounter = array();
         $rowIdx = 0;
         // tell the addTowToGrid javascript how many rows are already used, so it has a unique index for new rows
         data_entry_helper::$javascript .= "indiciaData['gridCounter-" . $options['id'] . "'] = " . count($taxonRows) . ";\n";
         data_entry_helper::$javascript .= "indiciaData['gridSampleCounter-" . $options['id'] . "'] = " . count($subSampleRows) . ";\n";
         // Loop through all the rows needed in the grid
         // Get the checkboxes (hidden or otherwise) that indicate a species is present
         if (is_array(data_entry_helper::$entity_to_load)) {
             $presenceValues = preg_grep("/^sc:[0-9]*:[0-9]*:present\$/", array_keys(data_entry_helper::$entity_to_load));
         }
         // if subspecies are stored, then need to load up the parent species info into the $taxonRows data
         if ($options['subSpeciesColumn']) {
             self::load_parent_species($taxalist, $options);
             if ($options['subSpeciesRemoveSspRank']) {
                 // remove subspecific rank information from the displayed subspecies names by passing a regex
                 data_entry_helper::$javascript .= "indiciaData.subspeciesRanksToStrip='" . lang::get('(form[a\\.]?|var\\.?|ssp\\.)') . "';\n";
             }
         }
         // track if there is a row we are editing in this grid
         $hasEditedRecord = false;
         if ($options['mediaTypes']) {
             $mediaBtnLabel = lang::get($onlyImages ? 'Add images' : 'Add media');
             $mediaBtnClass = 'sc' . $onlyImages ? 'Image' : 'Media' . 'Link';
         }
         foreach ($taxonRows as $txIdx => $rowIds) {
             $ttlId = $rowIds['ttlId'];
             $loadedTxIdx = isset($rowIds['loadedTxIdx']) ? $rowIds['loadedTxIdx'] : -1;
             $existing_record_id = isset($rowIds['occId']) ? $rowIds['occId'] : false;
             // Multi-column input does not work when image upload allowed
             $colIdx = count($options['mediaTypes']) ? 0 : (int) floor($rowIdx / (count($taxonRows) / $options['columns']));
             // Find the taxon in our preloaded list data that we want to output for this row
             $taxonIdx = 0;
             while ($taxonIdx < count($taxalist) && $taxalist[$taxonIdx]['id'] != $ttlId) {
                 $taxonIdx += 1;
             }
             if ($taxonIdx >= count($taxalist)) {
                 continue;
             }
             // next taxon, as this one was not found in the list
             $taxon = $taxalist[$taxonIdx];
             // If we are using the sub-species column then when the taxon has a parent (=species) this goes in the
             // first column and we put the subsp in the second column in a moment.
             if (isset($options['subSpeciesColumn']) && $options['subSpeciesColumn'] && !empty($taxon['parent'])) {
                 $firstColumnTaxon = $taxon['parent'];
             } else {
                 $firstColumnTaxon = $taxon;
             }
             // map field names if using a cached lookup
             if ($options['cacheLookup']) {
                 $firstColumnTaxon = $firstColumnTaxon + array('preferred_name' => $firstColumnTaxon['preferred_taxon'], 'common' => $firstColumnTaxon['default_common_name']);
             }
             // Get the cell content from the taxon_label template
             $firstCell = helper_base::mergeParamsIntoTemplate($firstColumnTaxon, 'taxon_label');
             // If the taxon label template is PHP, evaluate it.
             if ($options['PHPtaxonLabel']) {
                 $firstCell = eval($firstCell);
             }
             // Now create the table cell to contain this.
             $colspan = isset($options['lookupListId']) && $options['rowInclusionCheck'] != 'alwaysRemovable' ? ' colspan="2"' : '';
             $row = '';
             // Add a delete button if the user can remove rows, add an edit button if the user has the edit option set, add a page link if user has that option set.
             if ($options['rowInclusionCheck'] == 'alwaysRemovable') {
                 $imgPath = empty(helper_base::$images_path) ? helper_base::relative_client_helper_path() . "../media/images/" : helper_base::$images_path;
                 $speciesGridLinkPageIconSource = $imgPath . "nuvola/find-22px.png";
                 if ($options['editTaxaNames']) {
                     $row .= '<td class="row-buttons">
                  <img class="action-button remove-row" src=' . $imgPath . 'nuvola/cancel-16px.png>
                  <img class="action-button edit-taxon-name" src=' . $imgPath . 'nuvola/package_editors-16px.png>';
                     if ($options['includeSpeciesGridLinkPage']) {
                         $row .= '<img class="species-grid-link-page-icon" title="' . $options['speciesGridPageLinkTooltip'] . '" alt="Notes icon" src=' . $speciesGridLinkPageIconSource . '>';
                     }
                     $row .= '</td>';
                 } else {
                     $row .= '<td class="row-buttons"><img class="action-button remove-row" src=' . $imgPath . 'nuvola/cancel-16px.png>';
                     if ($options['includeSpeciesGridLinkPage']) {
                         $row .= '<img class="species-grid-link-page-icon" title="' . $options['speciesGridPageLinkTooltip'] . '" alt="Notes icon" src=' . $speciesGridLinkPageIconSource . '>';
                     }
                     $row .= '</td>';
                 }
             }
             // if editing a specific occurrence, mark it up
             $editedRecord = isset($_GET['occurrence_id']) && $_GET['occurrence_id'] == $existing_record_id;
             $editClass = $editedRecord ? ' edited-record ui-state-highlight' : '';
             $hasEditedRecord = $hasEditedRecord || $editedRecord;
             // Verified records can be flagged with an icon
             //Do an isset check as the npms_paths form for example uses the species checklist, but doesn't use an entity_to_load
             if (isset(data_entry_helper::$entity_to_load["sc:{$loadedTxIdx}:{$existing_record_id}:record_status"])) {
                 $status = data_entry_helper::$entity_to_load["sc:{$loadedTxIdx}:{$existing_record_id}:record_status"];
                 if (preg_match('/[VDR]/', $status)) {
                     $img = false;
                     switch ($status) {
                         case 'V':
                             $img = 'ok';
                             $statusLabel = 'verified';
                             break;
                         case 'D':
                             $img = 'dubious';
                             $statusLabel = 'queried';
                             break;
                         case 'R':
                             $img = 'cancel';
                             $statusLabel = 'rejected';
                             break;
                     }
                     if ($img) {
                         $label = lang::get($statusLabel);
                         $title = lang::get('This record has been {1}. Changing it will mean that it will need to be rechecked by an expert.', $label);
                         $firstCell .= "<img alt=\"{$label}\" title=\"{$title}\" src=\"{$imgPath}nuvola/{$img}-16px.png\">";
                     }
                 }
             }
             $row .= str_replace(array('{content}', '{colspan}', '{editClass}', '{tableId}', '{idx}'), array($firstCell, $colspan, $editClass, $options['id'], $colIdx), $indicia_templates['taxon_label_cell']);
             $hidden = $options['rowInclusionCheck'] == 'checkbox' ? '' : ' style="display:none"';
             // AlwaysFixed mode means all rows in the default checklist are included as occurrences. Same for
             // AlwayeRemovable except that the rows can be removed.
             // If we are reloading a record there will be an entity_to_load which will indicate whether present should be checked.
             // This has to be evaluated true or false if reloading a submission with errors.
             if ($options['rowInclusionCheck'] == 'alwaysFixed' || $options['rowInclusionCheck'] == 'alwaysRemovable' || data_entry_helper::$entity_to_load != null && array_key_exists("sc:{$loadedTxIdx}:{$existing_record_id}:present", data_entry_helper::$entity_to_load) && data_entry_helper::$entity_to_load["sc:{$loadedTxIdx}:{$existing_record_id}:present"] == true) {
                 $checked = ' checked="checked"';
             } else {
                 $checked = '';
             }
             $row .= "\n<td class=\"scPresenceCell\" headers=\"{$options['id']}-present-{$colIdx}\"{$hidden}>";
             $fieldname = "sc:{$options['id']}-{$txIdx}:{$existing_record_id}:present";
             if ($options['rowInclusionCheck'] === 'hasData') {
                 $row .= "<input type=\"hidden\" name=\"{$fieldname}\" id=\"{$fieldname}\" value=\"{$taxon['id']}\"/>";
             } else {
                 // this includes a control to force out a 0 value when the checkbox is unchecked.
                 $row .= "<input type=\"hidden\" class=\"scPresence\" name=\"{$fieldname}\" value=\"0\"/>" . "<input type=\"checkbox\" class=\"scPresence\" name=\"{$fieldname}\" id=\"{$fieldname}\" value=\"{$taxon['id']}\" {$checked} />";
             }
             // If we have a grid ID attribute, output a hidden
             if (!empty($options['gridIdAttributeId'])) {
                 $gridAttributeId = $options['gridIdAttributeId'];
                 if (empty($existing_record_id)) {
                     //If in add mode we don't need to include the occurrence attribute id
                     $fieldname = "sc:{$options['id']}-{$txIdx}::occAttr:{$gridAttributeId}";
                     $row .= "<input type=\"hidden\" name=\"{$fieldname}\" id=\"{$fieldname}\" value=\"{$options['id']}\"/>";
                 } else {
                     $search = preg_grep("/^sc:[0-9]*:{$existing_record_id}:occAttr:{$gridAttributeId}:" . '[0-9]*$/', array_keys(data_entry_helper::$entity_to_load));
                     if (!empty($search)) {
                         $match = array_pop($search);
                         $parts = explode(':', $match);
                         //The id of the existing occurrence attribute value is at the end of the data
                         $idxOfOccValId = count($parts) - 1;
                         //$txIdx is row number in the grid. We cannot simply take the data from entity_to_load as it doesn't contain the row number.
                         $fieldname = "sc:{$options['id']}-{$txIdx}:{$existing_record_id}:occAttr:{$gridAttributeId}:{$parts[$idxOfOccValId]}";
                         $row .= "<input type=\"hidden\" name=\"{$fieldname}\" id=\"{$fieldname}\" value=\"{$options['id']}\"/>";
                     }
                 }
             }
             $row .= "</td>";
             if ($options['speciesControlToUseSubSamples']) {
                 $row .= "\n<td class=\"scSampleCell\" style=\"display:none\">";
                 $fieldname = "sc:{$options['id']}-{$txIdx}:{$existing_record_id}:occurrence:sampleIDX";
                 $value = $options['subSamplePerRow'] ? $smpIdx : $rowIds['smpIdx'];
                 $row .= "<input type=\"hidden\" class=\"scSample\" name=\"{$fieldname}\" id=\"{$fieldname}\" value=\"{$value}\" />";
                 $row .= "</td>";
                 // always increment the sample index if 1 per row.
                 if ($options['subSamplePerRow']) {
                     $smpIdx++;
                 }
             }
             $idx = 0;
             if ($options['mediaTypes']) {
                 $existingImages = is_array(data_entry_helper::$entity_to_load) ? preg_grep("/^sc:{$loadedTxIdx}:{$existing_record_id}:occurrence_medium:id:[a-z0-9]*\$/", array_keys(data_entry_helper::$entity_to_load)) : array();
                 $row .= "\n<td class=\"ui-widget-content scAddMediaCell\">";
                 $style = count($existingImages) > 0 ? ' style="display: none"' : '';
                 $fieldname = "add-media:{$options['id']}-{$txIdx}:{$existing_record_id}";
                 $row .= "<a href=\"\"{$style} class=\"add-media-link button {$mediaBtnClass}\" id=\"{$fieldname}\">" . "{$mediaBtnLabel}</a>";
                 $row .= "</td>";
             }
             // Are we in the first column of a multicolumn grid, or doing single column grid? If so start new row.
             if ($colIdx === 0) {
                 $rows[$rowIdx] = $row;
             } else {
                 $rows[$rowIdx % ceil(count($taxonRows) / $options['columns'])] .= $row;
             }
             $rowIdx++;
             if ($options['mediaTypes'] && count($existingImages) > 0) {
                 $totalCols = ($options['lookupListId'] ? 2 : 1) + 1 + count($occAttrControls) + ($options['occurrenceComment'] ? 1 : 0) + ($options['occurrenceSensitivity'] ? 1 : 0) + (count($options['mediaTypes']) ? 1 : 0);
                 $rows[$rowIdx] = '<td colspan="' . $totalCols . '">' . data_entry_helper::file_box(array('table' => "sc:{$options['id']}-{$txIdx}:{$existing_record_id}:occurrence_medium", 'loadExistingRecordKey' => "sc:{$loadedTxIdx}:{$existing_record_id}:occurrence_medium", 'mediaTypes' => $options['mediaTypes'], 'readAuth' => $options['readAuth'])) . '</td>';
                 $imageRowIdxs[] = $rowIdx;
                 $rowIdx++;
             }
         }
         $grid .= "\n<tbody>\n";
         if (count($rows) > 0) {
             $grid .= data_entry_helper::species_checklist_implode_rows($rows, $imageRowIdxs);
         }
         $grid .= "</tbody>\n";
         $grid = str_replace(array('{class}', '{id}', '{content}'), array(' class="' . implode(' ', $classlist) . '"', " id=\"{$options['id']}\"", $grid), $indicia_templates['data-input-table']);
         // in hasData mode, the wrap_species_checklist method must be notified of the different default
         // way of checking if a row is to be made into an occurrence. This may differ between grids when
         // there are multiple grids on a page.
         if ($options['rowInclusionCheck'] == 'hasData') {
             $grid .= '<input name="rowInclusionCheck-' . $options['id'] . '" value="hasData" type="hidden" />';
             if (!empty($options['hasDataIgnoreAttrs'])) {
                 $grid .= '<input name="hasDataIgnoreAttrs-' . $options['id'] . '" value="' . implode(',', $options['hasDataIgnoreAttrs']) . '" type="hidden" />';
             }
         }
         // If the lookupListId parameter is specified then the user is able to add extra rows to the grid,
         // selecting the species from this list. Add the required controls for this.
         if (isset($options['lookupListId'])) {
             // Javascript to add further rows to the grid
             if (isset($indicia_templates['format_species_autocomplete_fn'])) {
                 data_entry_helper::$javascript .= 'formatter = ' . $indicia_templates['format_species_autocomplete_fn'];
             } else {
                 data_entry_helper::$javascript .= "formatter = '" . $indicia_templates['taxon_label'] . "';\n";
             }
             if (!empty(parent::$warehouse_proxy)) {
                 $url = parent::$warehouse_proxy . "index.php/services/data";
             } else {
                 $url = helper_base::$base_url . "index.php/services/data";
             }
             data_entry_helper::$javascript .= "if (typeof indiciaData.speciesGrid==='undefined') {indiciaData.speciesGrid={};}\n";
             data_entry_helper::$javascript .= "indiciaData.speciesGrid['{$options['id']}']={};\n";
             data_entry_helper::$javascript .= "indiciaData.speciesGrid['{$options['id']}'].cacheLookup=" . ($options['cacheLookup'] ? 'true' : 'false') . ";\n";
             data_entry_helper::$javascript .= "indiciaData.speciesGrid['{$options['id']}'].numValues=" . (!empty($options['numValues']) ? $options['numValues'] : 20) . ";\n";
             data_entry_helper::$javascript .= "indiciaData.speciesGrid['{$options['id']}'].selectMode=" . (!empty($options['selectMode']) && $options['selectMode'] ? 'true' : 'false') . ";\n";
             //encoded media array is just and array of media items that has been json_encoded.
             //Add a row to the occurrence grid for each media item.
             data_entry_helper::$javascript .= "\n        if (indiciaData.encodedMediaArray) {\n          var encodedMediaArray = eval(indiciaData.encodedMediaArray);\n          for (var i=0; i<encodedMediaArray.length; i++) {\n            makeImageRowOrSpareRow('" . $options['id'] . "', {'auth_token' : '" . $options['readAuth']['auth_token'] . "', 'nonce' : '" . $options['readAuth']['nonce'] . "'},'" . $options['lookupListId'] . "','{$url}', null, false, null, null, encodedMediaArray[i]);\n          }\n        }\n        \r\n";
             data_entry_helper::$javascript .= "makeImageRowOrSpareRow('" . $options['id'] . "', {'auth_token' : '" . $options['readAuth']['auth_token'] . "', 'nonce' : '" . $options['readAuth']['nonce'] . "'},'" . $options['lookupListId'] . "','{$url}', null, false, null, null);\r\n";
         }
         // If options contain a help text, output it at the end if that is the preferred position
         $options['helpTextClass'] = isset($options['helpTextClass']) ? $options['helpTextClass'] : 'helpTextLeft';
         $r = $beforegrid . $grid;
         data_entry_helper::$javascript .= "\$('#" . $options['id'] . "').find('input,select').keydown(keyHandler);\n";
         //nameFilter is an array containing all the parameters required to return data for each of the
         //"Choose species names available for selection" filter types
         data_entry_helper::species_checklist_filter_popup($options, $nameFilter);
         if ($options['subSamplePerRow']) {
             // output a hidden block to contain sub-sample hidden input values.
             $r .= '<div id="' . $options['id'] . '-blocks">' . data_entry_helper::get_subsample_per_row_hidden_inputs() . '</div>';
         }
         if ($hasEditedRecord) {
             data_entry_helper::$javascript .= "\$('#{$options['id']} tbody tr').hide();\n";
             data_entry_helper::$javascript .= "\$('#{$options['id']} tbody tr td.edited-record').parent().show();\n";
             data_entry_helper::$javascript .= "\$('#{$options['id']} tbody tr td.edited-record').parent().next('tr.supplementary-row').show();\n";
             $r .= '<p>' . lang::get('You are editing a single record that is part of a larger sample, so any changes to the sample\'s information such as edits to the date or map reference ' . 'will affect the whole sample.') . " <a id=\"species-grid-view-all-{$options['id']}\">" . lang::get('View all the records in this sample or add more records.') . '</a></p>';
             data_entry_helper::$javascript .= "\$('#species-grid-view-all-{$options['id']}').click(function(e) {\n  \$('#{$options['id']} tbody tr').show();\n  \$(e.currentTarget).hide();\n});\n";
             self::$onload_javascript .= "\nif (\$('#{$options['id']}').parents('.ui-tabs-panel').length) {\n  indiciaFns.activeTab(\$('#controls'), \$('#{$options['id']}').parents('.ui-tabs-panel')[0].id);\n}\n";
         }
         return $r;
     } else {
         return $taxalist['error'];
     }
 }
 /**
  * Draw the explore button on the page.
  * @return string The output HTML string.
  * 
  * @package    Client
  * @subpackage PrebuiltForms
  */
 protected static function get_control_explore($auth, $args)
 {
     if (!empty($args['explore_url']) && !empty($args['explore_param_name'])) {
         $url = $args['explore_url'];
         if (strcasecmp(substr($url, 0, 12), '{rootfolder}') !== 0 && strcasecmp(substr($url, 0, 4), 'http') !== 0) {
             $url = '{rootFolder}' . $url;
         }
         $pathParam = function_exists('variable_get') && variable_get('clean_url', 0) == '0' ? 'q' : '';
         $rootFolder = data_entry_helper::getRootFolder() . (empty($pathParam) ? '' : "?{$pathParam}=");
         $url = str_replace('{rootFolder}', $rootFolder, $url);
         $url .= strpos($url, '?') === false ? '?' : '&';
         $url .= $args['explore_param_name'] . '=' . self::$taxon_meaning_id;
         $r = '<a class="button" href="' . $url . '">' . lang::get('Explore records of {1}', self::get_best_name()) . '</a>';
     } else {
         throw new exception('The page has been setup to use an explore records button, but an "Explore URL" or "Explore Parameter Name" has not been specified.');
     }
     return $r;
 }
 public static function postcode_distance_limiter($auth, $args, $tabalias, $options, $path)
 {
     $r = '';
     //When then screen loads, attempt to add a point to the map showing the user's post code (which is in the $_GET).
     data_entry_helper::$javascript .= "\n      jQuery(document).ready(function(\$) {\n      mapInitialisationHooks.push(function (div) {\n           //Put into indicia data so we can see the map div elsewhere\n          indiciaData.mapdiv = div;\n          if (indiciaData.postCodeGeom) {\n            var feature = new OpenLayers.Feature.Vector(OpenLayers.Geometry.fromWKT(indiciaData.postCodeGeom));\n            indiciaData.mapdiv.map.editLayer.addFeatures([feature]);  \n          }\n      });\n    });\n    ";
     //Once the limiter is applied, the post code geom is passed to the URL and so is the indicia user id, so we need to pick these up from the URL
     if (!empty($_GET['dynamic-post_code_geom'])) {
         data_entry_helper::$javascript .= "\n        indiciaData.postCodeGeom='" . $_GET['dynamic-post_code_geom'] . "';\n      ";
     }
     if (!empty($_GET['dynamic-the_user_id'])) {
         $indiciaUserId = $_GET['dynamic-the_user_id'];
     } else {
         $indiciaUserId = 0;
     }
     //If the post code is in the URL params then it means a request has been made and the page is reloading.
     //Place the post code back into the field so the user doesn't have to re-enter it.
     if (!empty($_GET['post_code'])) {
         data_entry_helper::$javascript .= "\n        \$('#free-postcode').val('" . $_GET['post_code'] . "');\n      ";
     }
     //If the mileage is in the URL params then it means a request has been made and the page is reloading.
     //Place the mileage back into the field so the user doesn't have to re-enter it.
     if (!empty($_GET['mileage'])) {
         data_entry_helper::$javascript .= "\n        \$('#limit-value').val('" . $_GET['mileage'] . "');\n      ";
     }
     //If the page is loaded without a user id at all, it means the user will be working to see which user squares are closest
     //to their own post code.
     //This post code variable might later be overriden if a free text post code search is available (depending on whether option is set
     if (empty($postCode) && function_exists('hostsite_get_user_field') && hostsite_get_user_field('field_indicia_post_code')) {
         $postCode = hostsite_get_user_field('field_indicia_post_code');
     } else {
         $postCode = null;
     }
     if (!empty($options['postCodeSearchButtonLabel'])) {
         $postCodeSearchButtonLabel = $options['postCodeSearchButtonLabel'];
     } else {
         $postCodeSearchButtonLabel = 'Get Squares';
     }
     if (!empty($options['returnAllButtonLabel'])) {
         $returnAllButtonLabel = $options['returnAllButtonLabel'];
     } else {
         $returnAllButtonLabel = 'Return all squares';
     }
     //Message displayed if post code is invalid, or the the google api key has run out of requests available on the google account
     if (!empty($options['postCodeRequestIssueWarning'])) {
         $postCodeRequestIssueWarning = $options['postCodeRequestIssueWarning'];
     } else {
         $postCodeRequestIssueWarning = 'Sorry, there appears to be a problem searching with the post code. Please check your system\'s Google API key is operating correctly and that your post code is valid.';
     }
     //Only show the post code limiter if there is a post code to actually use as the origin point.
     if (!empty($postCode) || !empty($options['freeTextPostCode']) && $options['freeTextPostCode'] == true) {
         data_entry_helper::$javascript .= "\n        indiciaData.google_api_key='" . data_entry_helper::$google_api_key . "';\n        var georeferenceProxy='" . data_entry_helper::getRootFolder() . data_entry_helper::client_helper_path() . "proxy.php';\n        //Reload the screen with the limit applied\n        \$('#limit-submit').click(function(){\n          var postcode\n          //If post code is available as a free text box, then use that, else fall back on the account post code\n          if (\$('#free-postcode').val()) {\n            postcode=\$('#free-postcode').val();\n          } else {\n            postcode='" . $postCode . "';\n          }\n          limit_to_post_code(postcode,georeferenceProxy," . $indiciaUserId . ",\"" . $postCodeRequestIssueWarning . "\");\n        });\n        \$('#return-all-submit').click(function(){;\n          return_all_squares(" . $indiciaUserId . ");\n        });\n      ";
         if (!empty($options['instructionText'])) {
             $instructionText = $options['instructionText'];
         } else {
             $instructionText = "Only show locations within this distance (miles) of the user's post code.";
         }
         //Put a free text post code on the page if that option has been set by the user
         if (!empty($options['freeTextPostCode']) && $options['freeTextPostCode'] == true) {
             $r .= "<div>Please enter your post code here<br><input id='free-postcode' type='textbox'></div>\n";
         }
         $r .= "<div>" . $instructionText . "<br><input id='limit-value' type='textbox'><input id='limit-submit' type='button' value='" . $postCodeSearchButtonLabel . "'><input id='return-all-submit' type='button' value='" . $returnAllButtonLabel . "'></div>\n";
     } else {
         if (!empty($options['noPostCodeMessage'])) {
             $noPostCodeMessage = $options['noPostCodeMessage'];
         } else {
             $noPostCodeMessage = 'Unable to display post code distance limiter control. This is probably because there is no post code on your own user account, or on the account of the person you are editing.';
         }
         $r .= '<div><em>' . $noPostCodeMessage . '</em></div><br>';
     }
     return $r;
 }