├── README.md ├── gravityformstimesheet └── gravityformstimesheet.php ├── screenshot-01.jpg ├── screenshot-02.jpg └── screenshot-03.jpg /README.md: -------------------------------------------------------------------------------- 1 | Gravity Forms Timesheet Field 2 | ============================= 3 | 4 | A new type of field that allows timesheets to be submitted via Gravity Forms. 5 | 6 | Features 7 | -------- 8 | 9 | - Date calendar popup and various date and time formats support 10 | - Sane input validation 11 | - Comments per time entry 12 | - Time totals in Form Entry list, detail, notifications and confirmations 13 | - Works with the [Saved Forms](https://github.com/soulseekah/Gravity-Forms-Saved-Forms-Addon) plugin correctly 14 | - Ready to be styled to suit any theme out there 15 | 16 | Screenshots 17 | ----------- 18 | 19 | ![Timesheet Field](https://github.com/soulseekah/gravityformstimesheet/raw/master/screenshot-01.jpg) 20 | ![Timesheet Output](https://github.com/soulseekah/gravityformstimesheet/raw/master/screenshot-02.jpg) 21 | ![Timesheet Detail](https://github.com/soulseekah/gravityformstimesheet/raw/master/screenshot-03.jpg) 22 | 23 | Installation 24 | ------------ 25 | 26 | Download the latest release from the [Releases](https://github.com/soulseekah/gravityformstimesheet/releases) page, unzip and copy the `gravityformstimesheet/` directory into your `wp-content/plugins` directory. Activate the plugin and enjoy. 27 | 28 | Requires at least Gravity Forms 1.9 and WordPress 4.0. 29 | -------------------------------------------------------------------------------- /gravityformstimesheet/gravityformstimesheet.php: -------------------------------------------------------------------------------- 1 | timeFormat == '24' ? 6 : 8; 45 | $values = array_chunk( $value, $chunksize ); 46 | 47 | $value = array(); 48 | foreach ( $values as $chunk ) { 49 | while ( count( $chunk ) < $chunksize ) { 50 | $chunk []= ''; 51 | } 52 | $value = array( 53 | 'date' => array_shift( $chunk ), 54 | 'checkin_hour' => array_shift( $chunk ), 'checkin_minute' => array_shift( $chunk ), 'checkin_24' => $this->timeFormat == '24' ? '' : array_shift( $chunk ), 55 | 'checkout_hour' => array_shift( $chunk ), 'checkout_minute' => array_shift( $chunk ), 'checkout_24' => $this->timeFormat == '24' ? '' : array_shift( $chunk ), 56 | 'comment' => array_shift( $chunk ) 57 | ); 58 | 59 | if ( !$this->isRequired ) { 60 | if ( empty( $value['date'] ) && empty( $value['checkin_hour'] ) && empty( $value['checkin_minute'] ) && empty( $value['checkout_hour'] ) && empty( $value['checkout_minute'] ) && empty( $value['comment'] ) ) { 61 | continue; 62 | } 63 | } 64 | 65 | $date_info = GFCommon::parse_date( $value['date'], $this->dateFormat ); 66 | if ( GFCommon::is_empty_array( $date_info ) || !checkdate( $date_info['month'], $date_info['day'], $date_info['year'] ) ) { 67 | $this->failed_validation = true; 68 | $this->validation_message = __( 'Invalid date or date format', self::$_translation_domain ); 69 | return; 70 | } 71 | 72 | if ( !is_numeric( $value['checkin_hour'] ) || !is_numeric( $value['checkin_minute'] ) 73 | || !is_numeric( $value['checkout_hour'] ) || !is_numeric( $value['checkout_minute'] ) 74 | ) { 75 | 76 | $this->failed_validation = true; 77 | $this->validation_message = __( 'Invalid time or time format', self::$_translation_domain ); 78 | return; 79 | } 80 | 81 | if ( $this->timeFormat != '24' ) { 82 | if ( $value['checkin_24'] == 'am' && intval( $value['checkin_hour'] ) == 12 ) $value['checkin_hour'] = 0; 83 | if ( $value['checkin_24'] == 'pm' && intval( $value['checkin_hour'] ) != 12 ) $value['checkin_hour'] += 12; 84 | if ( $value['checkout_24'] == 'pm' && intval( $value['checkout_hour'] ) != 12 ) $value['checkout_hour'] += 12; 85 | } 86 | 87 | if ( 88 | ( intval( $value['checkin_hour'] ) < 0 || intval( $value['checkin_hour'] ) > 23 ) 89 | || ( intval( $value['checkout_hour'] ) < 0 || intval( $value['checkout_hour'] ) > 23 ) 90 | || ( intval( $value['checkin_minute'] ) < 0 || intval( $value['checkin_minute'] ) > 59 ) 91 | || ( intval( $value['checkout_minute'] ) < 0 || intval( $value['checkout_minute'] ) > 59 ) 92 | ) { 93 | $this->failed_validation = true; 94 | $this->validation_message = __( 'Invalid time or time format', self::$_translation_domain ); 95 | return; 96 | } 97 | 98 | if ( intval( $value['checkin_hour'] ) >= intval( $value['checkout_hour'] ) ) { 99 | if ( intval( $value['checkin_minute'] ) >= intval( $value['checkout_minute'] ) ) { 100 | $this->failed_validation = true; 101 | $this->validation_message = __( 'Invalid time or time format', self::$_translation_domain ); 102 | return; 103 | } 104 | } 105 | } 106 | } 107 | 108 | 109 | /** 110 | * Rendering the UI of the timesheet field. 111 | */ 112 | public function get_field_input( $form, $value = '', $entry = null ) { 113 | 114 | wp_enqueue_style( 'gforms_datepicker_css', GFCommon::get_base_url() . '/css/datepicker.css', null, GFCommon::$version ); 115 | wp_enqueue_script( 'gform_datepicker_init' ); 116 | 117 | if ( is_admin() ) { 118 | ob_start(); 119 | 120 | echo esc_html__( 'Timesheet fields are not editable.' ); 121 | ?> 122 | 123 | '', 134 | 'checkin_hour' => '', 'checkin_minute' => '', 'checkin_24' => '', 135 | 'checkout_hour' => '', 'checkout_minute' => '', 'checkout_24' => '', 136 | 'comment' => '' 137 | ) ); 138 | else { 139 | $value = $this->unserialize( $value ); 140 | } 141 | 142 | ob_start(); 143 | ?> 144 |
145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | dateFormat ) ? 'mdy' : esc_attr( $this->dateFormat ); 167 | $add_icon = $this->addIconUrl ? : GFCommon::get_base_url() . '/images/add.png'; 168 | $delete_icon = $this->deleteIconUrl ? : GFCommon::get_base_url() . '/images/remove.png'; 169 | $maxrows = !empty( $this->maxRows ) && is_numeric( $this->maxRows ) && $this->maxRows > -1 ? $this->maxRows : 0; 170 | 171 | foreach ( $value as $item ): 172 | ?> 173 | 174 | 177 | 184 | 191 | 194 | 198 | 199 | 203 | 204 |
175 | 176 | 178 | display_time_field( array( 179 | 'hour' => $item['checkin_hour'], 180 | 'minute' => $item['checkin_minute'], 181 | '24' => $item['checkin_24'] 182 | ) ); ?> 183 | 185 | display_time_field( array( 186 | 'hour' => $item['checkout_hour'], 187 | 'minute' => $item['checkout_minute'], 188 | '24' => $item['checkout_24'] 189 | ) ); ?> 190 | 192 | 193 | 195 | 196 | 197 |
205 | 206 | 242 |
243 | calendarIconType = wp_strip_all_tags( $this->calendarIconType ); 267 | $this->calendarIconUrl = wp_strip_all_tags( $this->calendarIconUrl ); 268 | if ( ! $this->dateFormat || ! in_array( $this->dateFormat, array( 'mdy', 'dmy', 'dmy_dash', 'dmy_dot', 'ymd_slash', 'ymd_dash', 'ymd_dot' ) ) ) { 269 | $this->dateFormat = 'mdy'; 270 | } 271 | } 272 | 273 | /** 274 | * Render the lead view of the timesheet field. 275 | */ 276 | public function get_value_entry_detail( $value, $currency = '', $use_text = false, $format = 'html', $media = 'screen' ) { 277 | $value = maybe_unserialize( $value ); 278 | $chunksize = $this->timeFormat == '24' ? 6 : 8; 279 | $values = array_chunk( $value, $chunksize ); 280 | $value = array(); 281 | foreach ( $values as $chunk ) { 282 | while ( count( $chunk ) < $chunksize ) { 283 | $chunk []= ''; 284 | } 285 | $entry = array( 286 | 'date' => array_shift( $chunk ), 287 | 'checkin_hour' => array_shift( $chunk ), 'checkin_minute' => array_shift( $chunk ), 'checkin_24' => $this->timeFormat == '24' ? '' : array_shift( $chunk ), 288 | 'checkout_hour' => array_shift( $chunk ), 'checkout_minute' => array_shift( $chunk ), 'checkout_24' => $this->timeFormat == '24' ? '' : array_shift( $chunk ), 289 | 'comment' => array_shift( $chunk ) 290 | ); 291 | if ( !empty( $entry['date'] ) ) 292 | $value []= $entry; 293 | } 294 | 295 | ob_start(); 296 | ?> 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | timeFormat != '24' ) { 319 | if ( $entry['checkin_24'] == 'am' && intval( $entry['checkin_hour'] ) == 12 ) $entry['checkin_hour'] = 0; 320 | if ( $entry['checkin_24'] == 'pm' && $entry['checkin_hour'] != 12 ) $entry['checkin_hour'] += 12; 321 | if ( $entry['checkout_24'] == 'pm' && $entry['checkout_hour'] != 12 ) $entry['checkout_hour'] += 12; 322 | } 323 | 324 | $minutes += ( ( $entry['checkout_hour'] * 60 ) + $entry['checkout_minute'] ) - ( ( $entry['checkin_hour'] * 60 ) + $entry['checkin_minute'] ); 325 | endforeach; 326 | ?> 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 |
timeFormat == '24' ? '' : ' ' .$entry['checkin_24'] ); ?>timeFormat == '24' ? '' : ' ' .$entry['checkout_24'] ); ?>
335 | unserialize( html_entity_decode( $value ) ); 345 | $minutes = 0; 346 | foreach ( $value as $entry ): 347 | if ( $this->timeFormat != '24' ) { 348 | if ( $entry['checkin_24'] == 'am' && intval( $entry['checkin_hour'] ) == 12 ) $entry['checkin_hour'] = 0; 349 | if ( $entry['checkin_24'] == 'pm' && $entry['checkin_hour'] != 12 ) $entry['checkin_hour'] += 12; 350 | if ( $entry['checkout_24'] == 'pm' && $entry['checkout_hour'] != 12 ) $entry['checkout_hour'] += 12; 351 | } 352 | $minutes += ( ( $entry['checkout_hour'] * 60 ) + $entry['checkout_minute'] ) - ( ( $entry['checkin_hour'] * 60 ) + $entry['checkin_minute'] ); 353 | endforeach; 354 | 355 | return sprintf( '%d entries, %02d:%02d total', count( $value ), $minutes/60, $minutes % 60 ); 356 | } 357 | 358 | /** 359 | * Renders the timesheet table wherever used; mostly email 360 | * notifications and confirmations. But who knows where else... 361 | */ 362 | public function get_value_merge_tag( $value, $input_id, $entry, $form, $modifier, $raw_value, $url_encode, $esc_html, $format, $nl2br ) { 363 | $value = $this->unserialize( $raw_value ); 364 | ob_start(); 365 | $minutes = 0; 366 | ?> 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | timeFormat != '24' ) { 389 | if ( $entry['checkin_24'] == 'am' && intval( $entry['checkin_hour'] ) == 12 ) $entry['checkin_hour'] = 0; 390 | if ( $entry['checkin_24'] == 'pm' && $entry['checkin_hour'] != 12 ) $entry['checkin_hour'] += 12; 391 | if ( $entry['checkout_24'] == 'pm' && $entry['checkout_hour'] != 12 ) $entry['checkout_hour'] += 12; 392 | } 393 | 394 | $minutes += ( ( $entry['checkout_hour'] * 60 ) + $entry['checkout_minute'] ) - ( ( $entry['checkin_hour'] * 60 ) + $entry['checkin_minute'] ); 395 | endforeach; 396 | ?> 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 |
timeFormat == '24' ? '' : ' ' .$entry['checkin_24'] ); ?>timeFormat == '24' ? '' : ' ' .$entry['checkout_24'] ); ?>
405 | 415 |
416 |
417 | 418 | : 419 | 420 |
421 |
422 | 423 | 424 |
425 | timeFormat != '24' ) { 427 | ?> 428 |
429 | 433 |
434 | 437 |
438 | timeFormat == '24' ? 6 : 8; 446 | $values = array_chunk( maybe_unserialize( $value ), $chunksize ); 447 | $value = array(); 448 | foreach ( $values as $chunk ) { 449 | while ( count( $chunk ) < $chunksize ) { 450 | $chunk []= ''; 451 | } 452 | $entry = array( 453 | 'date' => array_shift( $chunk ), 454 | 'checkin_hour' => array_shift( $chunk ), 'checkin_minute' => array_shift( $chunk ), 'checkin_24' => $this->timeFormat == '24' ? '' : array_shift( $chunk ), 455 | 'checkout_hour' => array_shift( $chunk ), 'checkout_minute' => array_shift( $chunk ), 'checkout_24' => $this->timeFormat == '24' ? '' : array_shift( $chunk ), 456 | 'comment' => array_shift( $chunk ) 457 | ); 458 | if ( !empty( $entry['date'] ) ) 459 | $value []= $entry; 460 | } 461 | return $value; 462 | } 463 | 464 | } 465 | 466 | GF_Fields::register( new GF_Field_Timesheet() ); 467 | -------------------------------------------------------------------------------- /screenshot-01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soulseekah/gravityformstimesheet/2e8294e3f4cccb8fcf0d108c12f62127284040ea/screenshot-01.jpg -------------------------------------------------------------------------------- /screenshot-02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soulseekah/gravityformstimesheet/2e8294e3f4cccb8fcf0d108c12f62127284040ea/screenshot-02.jpg -------------------------------------------------------------------------------- /screenshot-03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soulseekah/gravityformstimesheet/2e8294e3f4cccb8fcf0d108c12f62127284040ea/screenshot-03.jpg --------------------------------------------------------------------------------