Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]

   1  <?php
   2  ///////////////////////////////////////////////////////////////////////////
   3  //                                                                       //
   4  // NOTICE OF COPYRIGHT                                                   //
   5  //                                                                       //
   6  // Moodle - Modular Object-Oriented Dynamic Learning Environment         //
   7  //          http://moodle.org                                            //
   8  //                                                                       //
   9  // Copyright (C) 1999-onwards Moodle Pty Ltd  http://moodle.com          //
  10  //                                                                       //
  11  // This program is free software; you can redistribute it and/or modify  //
  12  // it under the terms of the GNU General Public License as published by  //
  13  // the Free Software Foundation; either version 2 of the License, or     //
  14  // (at your option) any later version.                                   //
  15  //                                                                       //
  16  // This program is distributed in the hope that it will be useful,       //
  17  // but WITHOUT ANY WARRANTY; without even the implied warranty of        //
  18  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         //
  19  // GNU General Public License for more details:                          //
  20  //                                                                       //
  21  //          http://www.gnu.org/copyleft/gpl.html                         //
  22  //                                                                       //
  23  ///////////////////////////////////////////////////////////////////////////
  24  
  25  class data_field_latlong extends data_field_base {
  26      var $type = 'latlong';
  27  
  28      // This is an array of URL schemes for linking out to services, using the float values of lat and long.
  29      // In each scheme, the special markers @lat@ and @long@ will be replaced by the float values.
  30      // The config options for the field store each service name that should be displayed, in a comma-separated
  31      // field. Therefore please DO NOT include commas in the service names if you are adding extra services.
  32  
  33      // Parameter data used:
  34      // "param1" is a comma-separated list of the linkout service names that are enabled for this instance
  35      // "param2" indicates the label that will be used in generating Google Earth KML files: -1 for item #, -2 for lat/long, positive number for the (text) field to use.
  36  
  37      var $linkoutservices = array(
  38            "Google Maps" => "http://maps.google.com/maps?q=@lat@,+@long@&iwloc=A&hl=en",
  39            "Google Earth" => "@wwwroot@/mod/data/field/latlong/kml.php?d=@dataid@&fieldid=@fieldid@&rid=@recordid@",
  40            "Geabios" => "http://www.geabios.com/html/services/maps/PublicMap.htm?lat=@lat@&lon=@long@&fov=0.3&title=Moodle%20data%20item",
  41            "OpenStreetMap" => "http://www.openstreetmap.org/index.html?lat=@lat@&lon=@long@&zoom=11",
  42            "Multimap" => "http://www.multimap.com/map/browse.cgi?scale=200000&lon=@long@&lat=@lat@&icon=x"
  43      );
  44      // Other map sources listed at http://kvaleberg.com/extensions/mapsources/index.php?params=51_30.4167_N_0_7.65_W_region:earth
  45  
  46      public function supports_preview(): bool {
  47          return true;
  48      }
  49  
  50      public function get_data_content_preview(int $recordid): stdClass {
  51          return (object)[
  52              'id' => 0,
  53              'fieldid' => $this->field->id,
  54              'recordid' => $recordid,
  55              'content' => 41.391205,
  56              'content1' => 2.163873,
  57              'content2' => null,
  58              'content3' => null,
  59              'content4' => null,
  60          ];
  61      }
  62  
  63      function display_add_field($recordid = 0, $formdata = null) {
  64          global $CFG, $DB, $OUTPUT;
  65  
  66          $lat = '';
  67          $long = '';
  68          if ($formdata) {
  69              $fieldname = 'field_' . $this->field->id . '_0';
  70              $lat = $formdata->$fieldname;
  71              $fieldname = 'field_' . $this->field->id . '_1';
  72              $long = $formdata->$fieldname;
  73          } else if ($recordid) {
  74              if ($content = $DB->get_record('data_content', array('fieldid'=>$this->field->id, 'recordid'=>$recordid))) {
  75                  $lat  = $content->content;
  76                  $long = $content->content1;
  77              }
  78          }
  79          $str = '<div title="'.s($this->field->description).'">';
  80          $str .= '<fieldset><legend><span class="accesshide">'.$this->field->name.'</span></legend>';
  81          $str .= '<table class="form-inline"><tr><td align="right">';
  82          $classes = 'mod-data-input form-control-static';
  83          $str .= '<label for="field_'.$this->field->id.'_0" class="' . $classes . '">' . get_string('latitude', 'data');
  84          if ($this->field->required) {
  85              $str .= $OUTPUT->pix_icon('req', get_string('requiredelement', 'form'));
  86          }
  87          $classes = 'form-control mx-1';
  88          $str .= '</label></td><td>';
  89          $str .= '<input class="' . $classes . '" type="text" name="field_'.$this->field->id.'_0" ';
  90          $str .= ' id="field_'.$this->field->id.'_0" value="';
  91          $str .= s($lat).'" size="10" />°N</td></tr>';
  92          $classes = 'mod-data-input form-control-static';
  93          $str .= '<tr><td align="right"><label for="field_'.$this->field->id.'_1" class="' . $classes . '">';
  94          $str .= get_string('longitude', 'data');
  95          if ($this->field->required) {
  96              $str .= $OUTPUT->pix_icon('req', get_string('requiredelement', 'form'));
  97          }
  98          $classes = 'form-control mx-1';
  99          $str .= '</label></td><td><input class="' . $classes . '" type="text" ';
 100          $str .= 'name="field_'.$this->field->id.'_1" id="field_'.$this->field->id.'_1" value="';
 101          $str .= s($long).'" size="10" />°E</td>';
 102          $str .= '</tr>';
 103          $str .= '</table>';
 104          $str .= '</fieldset>';
 105          $str .= '</div>';
 106          return $str;
 107      }
 108  
 109      function display_search_field($value = '') {
 110          global $CFG, $DB;
 111  
 112          $varcharlat = $DB->sql_compare_text('content');
 113          $varcharlong= $DB->sql_compare_text('content1');
 114          $latlongsrs = $DB->get_recordset_sql(
 115              "SELECT DISTINCT $varcharlat AS la, $varcharlong AS lo
 116                 FROM {data_content}
 117                WHERE fieldid = ?
 118               ORDER BY $varcharlat, $varcharlong", array($this->field->id));
 119  
 120          $options = array();
 121          foreach ($latlongsrs as $latlong) {
 122              $latitude = format_float($latlong->la, 4);
 123              $longitude = format_float($latlong->lo, 4);
 124              if ($latitude && $longitude) {
 125                  $options[$latlong->la . ',' . $latlong->lo] = $latitude . ' ' . $longitude;
 126              }
 127          }
 128          $latlongsrs->close();
 129  
 130          $classes = array('class' => 'accesshide');
 131          $return = html_writer::label(get_string('latlong', 'data'), 'menuf_'.$this->field->id, false, $classes);
 132          $classes = array('class' => 'custom-select');
 133          $return .= html_writer::select($options, 'f_'.$this->field->id, $value, array('' => get_string('menuchoose', 'data')),
 134              $classes);
 135         return $return;
 136      }
 137  
 138      public function parse_search_field($defaults = null) {
 139          $param = 'f_'.$this->field->id;
 140          if (empty($defaults[$param])) {
 141              $defaults = array($param => '');
 142          }
 143          return optional_param($param, $defaults[$param], PARAM_NOTAGS);
 144      }
 145  
 146      function generate_sql($tablealias, $value) {
 147          global $DB;
 148  
 149          static $i=0;
 150          $i++;
 151          $name1 = "df_latlong1_$i";
 152          $name2 = "df_latlong2_$i";
 153          $varcharlat = $DB->sql_compare_text("{$tablealias}.content");
 154          $varcharlong= $DB->sql_compare_text("{$tablealias}.content1");
 155  
 156  
 157          $latlong[0] = '';
 158          $latlong[1] = '';
 159          $latlong = explode (',', $value, 2);
 160          return array(" ({$tablealias}.fieldid = {$this->field->id} AND $varcharlat = :$name1 AND $varcharlong = :$name2) ",
 161                       array($name1=>$latlong[0], $name2=>$latlong[1]));
 162      }
 163  
 164      function display_browse_field($recordid, $template) {
 165          global $CFG;
 166  
 167          $content = $this->get_data_content($recordid);
 168          if (!$content) {
 169              return '';
 170          }
 171  
 172          $lat = $content->content;
 173          if (strlen($lat ?? '') < 1) {
 174              return '';
 175          }
 176          $long = $content->content1;
 177          if (strlen($long ?? '') < 1) {
 178              return '';
 179          }
 180          // We use format_float to display in the regional format.
 181          if ($lat < 0) {
 182              $compasslat = format_float(-$lat, 4) . '°S';
 183          } else {
 184              $compasslat = format_float($lat, 4) . '°N';
 185          }
 186          if ($long < 0) {
 187              $compasslong = format_float(-$long, 4) . '°W';
 188          } else {
 189              $compasslong = format_float($long, 4) . '°E';
 190          }
 191  
 192          // Now let's create the jump-to-services link.
 193          $servicesshown = explode(',', $this->field->param1);
 194  
 195          // These are the different things that can be magically inserted into URL schemes.
 196          $urlreplacements = array(
 197              '@lat@' => $lat,
 198              '@long@' => $long,
 199              '@wwwroot@' => $CFG->wwwroot,
 200              '@contentid@' => $content->id,
 201              '@dataid@' => $this->data->id,
 202              '@courseid@' => $this->data->course,
 203              '@fieldid@' => $content->fieldid,
 204              '@recordid@' => $content->recordid,
 205          );
 206  
 207          if (count($servicesshown) == 1 && $servicesshown[0]) {
 208              $str = " <a class=\"data-field-link\" href='"
 209                          . str_replace(
 210                              array_keys($urlreplacements),
 211                              array_values($urlreplacements),
 212                              $this->linkoutservices[$servicesshown[0]]
 213                          ) . "' title='$servicesshown[0]'>$compasslat $compasslong</a>";
 214          } else if (count($servicesshown) > 1) {
 215              $str = '<form id="latlongfieldbrowse" class="data-field-html">';
 216              $str .= "$compasslat, $compasslong\n";
 217              $str .= "<label class='accesshide' for='jumpto'>". get_string('jumpto') ."</label>";
 218              $str .= '<select id="jumpto" name="jumpto" class="custom-select">';
 219              foreach ($servicesshown as $servicename) {
 220                  // Add a link to a service.
 221                  $str .= "\n  <option value='"
 222                              . str_replace(
 223                                  array_keys($urlreplacements),
 224                                  array_values($urlreplacements),
 225                                  $this->linkoutservices[$servicename]
 226                              ) . "'>".htmlspecialchars($servicename, ENT_COMPAT)."</option>";
 227              }
 228              // NB! If you are editing this, make sure you don't break the javascript reference "previousSibling"
 229              // which allows the "Go" button to refer to the drop-down selector.
 230              $str .= '\n</select><input type="button" class="btn ml-1 btn-secondary" value="' . get_string('go');
 231              $str .= '" onclick="if(previousSibling.value){self.location=previousSibling.value}"/>';
 232              $str .= '</form>';
 233          } else {
 234              $str = "$compasslat, $compasslong";
 235          }
 236  
 237          return $str;
 238      }
 239  
 240      function update_content_import($recordid, $value, $name='') {
 241          $values = explode(" ", $value, 2);
 242  
 243          foreach ($values as $index => $value) {
 244              $this->update_content($recordid, $value, $name . '_' . $index);
 245          }
 246      }
 247  
 248      function update_content($recordid, $value, $name='') {
 249          global $DB;
 250  
 251          $content = new stdClass();
 252          $content->fieldid = $this->field->id;
 253          $content->recordid = $recordid;
 254          // When updating these values (which might be region formatted) we should format
 255          // the float to allow for a consistent float format in the database.
 256          $value = unformat_float($value);
 257          $value = trim($value ?? '');
 258          if (strlen($value) > 0) {
 259              $value = floatval($value);
 260          } else {
 261              $value = null;
 262          }
 263          $names = explode('_', $name);
 264          switch ($names[2]) {
 265              case 0:
 266                  // update lat
 267                  $content->content = $value;
 268                  break;
 269              case 1:
 270                  // update long
 271                  $content->content1 = $value;
 272                  break;
 273              default:
 274                  break;
 275          }
 276          if ($oldcontent = $DB->get_record('data_content', array('fieldid'=>$this->field->id, 'recordid'=>$recordid))) {
 277              $content->id = $oldcontent->id;
 278              return $DB->update_record('data_content', $content);
 279          } else {
 280              return $DB->insert_record('data_content', $content);
 281          }
 282      }
 283  
 284      function get_sort_sql($fieldname) {
 285          global $DB;
 286          return $DB->sql_cast_char2real($fieldname, true);
 287      }
 288  
 289      function export_text_value($record) {
 290          // The content here is from the database and does not require location formating.
 291          return sprintf('%01.4f', $record->content) . ' ' . sprintf('%01.4f', $record->content1);
 292      }
 293  
 294      /**
 295       * Check if a field from an add form is empty
 296       *
 297       * @param mixed $value
 298       * @param mixed $name
 299       * @return bool
 300       */
 301      function notemptyfield($value, $name) {
 302          return isset($value) && !($value == '');
 303      }
 304  
 305      /**
 306       * Validate values for this field.
 307       * Both the Latitude and the Longitude fields need to be filled in.
 308       *
 309       * @param array $values The entered values for the lat. and long.
 310       * @return string|bool Error message or false.
 311       */
 312      public function field_validation($values) {
 313          $valuecount = 0;
 314          // The lat long class has two values that need to be checked.
 315          foreach ($values as $value) {
 316              if (isset($value->value) && !($value->value == '')) {
 317                  $valuecount++;
 318              }
 319          }
 320          // If we have nothing filled in or both filled in then everything is okay.
 321          if ($valuecount == 0 || $valuecount == 2) {
 322              return false;
 323          }
 324          // If we get here then only one field has been filled in.
 325          return get_string('latlongboth', 'data');
 326      }
 327  
 328      /**
 329       * Return the plugin configs for external functions.
 330       *
 331       * @return array the list of config parameters
 332       * @since Moodle 3.3
 333       */
 334      public function get_config_for_external() {
 335          // Return all the config parameters.
 336          $configs = [];
 337          for ($i = 1; $i <= 10; $i++) {
 338              $configs["param$i"] = $this->field->{"param$i"};
 339          }
 340          return $configs;
 341      }
 342  }