This repository was archived by the owner on Feb 3, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlims.module
More file actions
1082 lines (961 loc) · 39.4 KB
/
Copy pathlims.module
File metadata and controls
1082 lines (961 loc) · 39.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?php
/**
* @file
* The bones of the LIMS system.
*/
require_once 'includes/LIMSrawseq.inc';
/**
* Implements hook_menu().
*/
function lims_menu() {
$items['node/%node/download-index'] = array(
'title' => 'Index File Download',
'description' => 'For hosting a plain text, tab-delimited mapping of samples to indices.',
// permissions required to view page
'access arguments' => array('access content'),
'page callback' => 'lims_download_samples_indices_callback',
'page arguments' => array(1),
);
return $items;
}
/**
* Implements hook_permission().
*/
function lims_permission() {
return array(
'access lims' => array(
'title' => t('Access LIMS'),
'description' => t('Apply access permission to LIMS view page.'),
)
);
}
/**
* Implements hook_node_access().
*/
function lims_node_access($node, $op, $account) {
if (isset($node->type) AND ($node->type == 'seq_run')) {
return (user_access('access lims')) ? NODE_ACCESS_ALLOW : NODE_ACCESS_DENY;
}
return NODE_ACCESS_IGNORE;
}
/**
* Page callback function for hook_menu()
*/
function lims_download_samples_indices_callback($node) {
$output = '';
if (isset($node->samples[0]->index_seq)) {
// Remove Knowpulse header and menu
drupal_add_http_header('Content-Type', 'text/plain');
// Automatically download as a file
header("Content-Disposition: attachment; filename=indices.txt");
// Iterate through each sample for this node and extract the sample name and index sequence
foreach ($node->samples as $sample) {
$output .= "$sample->sample_name\t$sample->index_seq\n";
}
print $output;
// This effectively "breaks drupal" (as bad as killing kittens?) right after we get what
// we need to prevent any theming from appearing on the page
drupal_exit();
}
else {
// CSS to format the link on the page
$output .= '<style>
a.indices-link-download-redirect {
float:right;
margin-top: 17px;
margin-right: 17px;
} </style>';
// The error message formated as a drupal error
$output .= '<div class="messages error">There are no indices for this sequencing run, therefore no file can be downloaded.</div>';
// Provide a link to return the user to the Sequencing Run for this node
$output .= l('Return to Sequencing Run', 'node/' . $node->nid, array('attributes' => array('class' => array('indices-link-download-redirect'))));
}
return $output;
}
/**
* Implements hook_theme().
*/
function lims_theme($existing, $type, $theme, $path) {
$items = array();
$items['lims_seqrun_node'] = array(
'template' => 'lims-seqrun-node',
'render element' => 'node',
'path' => "$path/theme"
);
$items['lims_seqrun_teaser'] = array(
'template' => 'lims-seqrun-teaser',
'render element' => 'node',
'path' => "$path/theme"
);
return $items;
}
/**
* @section
* "Sequencing Run" Node/Content type.
*/
/**
* Implements hook_node_info().
* Register the "Sequencing Run" node/content type with Drupal.
*/
function lims_node_info() {
$node_types = array();
$node_types['seq_run'] = array(
'name' => t('Sequencing Run'),
'base' => 'seqrun',
'description' => t('Information about a run of sequencing. This is used to keep track of raw sequence that will not be loading into KnowPulse directly but for which meta data including file locations need to be stored.'),
'title_label' => t('Run Name'),
'locked' => TRUE
);
return $node_types;
}
/**
* Implements hook_load().
* Loads our custom values into the node object for viewing and updating.
*/
function seqrun_load(&$nodes) {
// Keep track of the sample file ids while populating the main data
// to make for more efficient retrieval of samples later.
$sample_fids = array();
$sample_ids = array();
// Select all the details to populate the nodes.
$nids = array_keys($nodes);
if (count($nids) > 1) {
$results = db_query('
SELECT *
FROM {lims_seqrun} run
LEFT JOIN {lims_seqrun_species} org ON org.run_id=run.run_id
WHERE nid IN (:nids)',
array(':nids' => $nids)
);
}
else {
$results = db_query('
SELECT *
FROM {lims_seqrun} run
LEFT JOIN {lims_seqrun_species} org ON org.run_id=run.run_id
WHERE nid=:nid',
array(':nid' => $nids)
);
}
// Now add them all to the nodes.
foreach($results as $r) {
$nodes[$r->nid]->seqrun = (isset($nodes[$r->nid]->seqrun)) ? $nodes[$r->nid]->seqrun : new stdClass();
$nodes[$r->nid]->seqrun->run_id = $r->run_id;
$nodes[$r->nid]->seqrun->run_name = $r->run_name;
$nodes[$r->nid]->seqrun->filename = $r->filename;
$nodes[$r->nid]->seqrun->md5sum = $r->md5sum;
$nodes[$r->nid]->seqrun->library_type = $r->library_type;
$nodes[$r->nid]->seqrun->insert_length = $r->insert_length;
$nodes[$r->nid]->seqrun->fragment_length = $r->fragment_length;
$nodes[$r->nid]->seqrun->index_type = $r->index_type;
$nodes[$r->nid]->seqrun->technology = $r->technology;
$nodes[$r->nid]->seqrun->read_type = $r->read_type;
$nodes[$r->nid]->seqrun->description = $r->description;
$nodes[$r->nid]->seqrun->samples_fid = $r->samples_fid;
$nodes[$r->nid]->samples = array();
// Keep track of the sample file id for each loaded node
// so that we can attach the samples later.
if ($r->samples_fid) {
$sample_fids[$r->samples_fid] = $r->nid;
}
// Keep track of nodes with single samples
if ($r->sample_id) {
$sample_ids[$r->sample_id] = $r->nid;
}
// Since there can be more than one species, the join above will actually
// return multiple rows (r's) for a single node. Thus we need to check if
// this node already has a species set and if it does then add to not
// override the array. Above we just override the values since they
// will not change between rows and that's faster then checking first.
if (isset($nodes[$r->nid]->species)) {
$nodes[$r->nid]->species[$r->organism_id] = chado_generate_var('organism', array('organism_id' => $r->organism_id));
}
else {
$nodes[$r->nid]->species = array($r->organism_id => chado_generate_var('organism', array('organism_id' => $r->organism_id)));
}
}
// Now retrieve and add samples.
// CASE 1: Single Sample to retrieve
if (sizeof($sample_ids) > 0) {
$results = db_query('
SELECT *
FROM {lims_seqrun_samples}
WHERE sample_id IN (:ids)
', array(':ids' => array_keys($sample_ids)));
// Add each sample to the appropriate node based on the sample fids array
// we created above which maps fid to nid.
foreach($results as $r) {
$nid = $sample_ids[$r->sample_id];
$sample = array(
'sample_id' => $r->sample_id,
'sample_name' => $r->sample_name,
'sample_accession' => $r->sample_accession,
'sample_stock_id' => $r->sample_stock_id,
'sample_description' => $r->sample_description,
);
$nodes[$nid]->samples[] = (object) $sample;
}
}
// CASE 2: Multiple samples grouped by a sample_fid.
if (sizeof($sample_fids) > 0) {
$results = db_query('
SELECT *
FROM {lims_seqrun_samples}
WHERE samples_fid IN (:fids)
ORDER BY sample_id ASC
', array(':fids' => array_keys($sample_fids)));
// Add each sample to the appropriate node based on the sample fids array
// we created above which maps fid to nid.
foreach($results as $r) {
$nid = $sample_fids[$r->samples_fid];
$sample = array(
'sample_id' => $r->sample_id,
'index_id' => $r->index_id,
'index_seq' => $r->index_seq,
'sample_name' => $r->sample_name,
'sample_accession' => $r->sample_accession,
'sample_stock_id' => $r->sample_stock_id,
'sample_description' => $r->sample_description,
'plate_well' => $r->plate_well,
);
$nodes[$nid]->samples[] = (object) $sample;
}
}
// Add quality Information to any samples which were collected.
foreach ($nodes as $nid => $node) {
foreach ($node->samples as $k => $sample) {
$sample_id = $sample->sample_id;
$results = db_select('lims_seqrun_samples_info', 'info')
->fields('info', ['sample_info_id', 'info_label', 'value'])
->condition('info.sample_id', $sample_id)
->orderBy('info.sample_info_id', 'ASC')
->execute();
foreach($results as $r) {
$node->samples[$k]->quality_info[$r->info_label] = $r->value;
}
}
}
}
/**
* Implement hook_form().
* Creates the node/page add/edit form for a "Sequencing Run".
*/
function seqrun_form($node, $form_state) {
$form = node_content_form($node, $form_state);
$form['general'] = array(
'#type' => 'fieldset'
);
$form['title']['#description'] = t('Choose a short, descriptive name for the run.');
$form['general']['title'] = $form['title'];
unset($form['title']);
// Hard-coded options.
// Change the arrays below to change the options available for each select.
// NOTE: species are based on those in the chado organism table so cannot
// be changed manually.
$options = array(
'species' => array(),
'technology' => array(
'Sanger',
'Illumina GAIIx',
'Illumina HiSeq 2500',
'Illumina MiSeq',
'Illumina HiSeq X Ten',
'Illumina NovaSeq6000',
'Roche 454',
'PacBio RS',
'PacBio HiFi',
'Pacbio Revio',
'Oxford Nanopore',
),
'read_type' => array(
'Single-end',
'Paired-end',
'Mate Pair (FR)',
'Mate Pair (RF)',
'Other',
),
'library_type' => array(
'Genomic',
'RNA-seq',
'3\' transcript',
'Single-enzyme GBS',
'Dual-enzyme GBS',
'ChIPseq',
'Exome capture',
'10X Chromium',
'Oxford Nanopore: ligation',
'Oxford Nanopore: rapid',
'Oxford Nanopore: PCR-cDNA',
'CRISPRclean Ribosomal RNA Depletion (Jumpcode Genomics)',
),
'index_type' => array(
'None',
'Illumina indexing',
'GBS 96-plex',
'NextFlex 96-plex',
'10X Chromium index set',
'Custom',
'Other',
),
);
// Add Species options.
$results = chado_query("SELECT organism_id, genus||' '||species as scientific_name FROM {organism} ORDER BY genus ASC, species ASC");
foreach ($results as $r) {
$options['species'][$r->organism_id] = $r->scientific_name;
}
$form['run_id'] = array(
'#type' => 'hidden',
'#value' => (isset($node->seqrun->run_id)) ? $node->seqrun->run_id : NULL,
);
// Ensure keys = values for options.
foreach ($options as $category => $opts) {
if ($category != 'species') {
$options[$category] = array_merge(
// Add NULL option to force users to select.
array(NULL => ''),
// Use the previous values as both the keys and values.
array_combine($opts, $opts)
);
}
}
// Add a little bit of theme-ing for the form.
$form['#attached']['css'][] = array(
'type' => 'inline',
'data' => '
select {
width: 370px;
}
em {
font-weight: bold;
font-style: italic;
}
.footnotes {
margin-left: 25px;
}
'
);
// This is needed to remove error messages associated with a previously
// uploaded file which has since been removed.
$form['#attached']['js'][] = array(
'type' => 'inline',
'data' => "
Drupal.behaviors.limsSeqRun_nodeFormSamplesErrorRemove = {
attach: function (context, settings) {
if(document.getElementById('edit-sample-file-upload-button')) {
jQuery('div.messages.error').remove();
}
}};
"
);
$form['general']['species'] = array(
'#type' => 'select',
'#title' => 'Species',
'#description' => 'Please select the species the sequencing was done on. If your samples are from multiple species, then ctrl + click to select all species represented. Furethermore, if your species is not in this list, please contact Larissa or Lacey before entering data.',
'#options' => $options['species'],
'#multiple' => TRUE,
'#required' => TRUE,
'#default_value' => (isset($node->species)) ? array_keys($node->species) : NULL,
);
$form['library'] = array(
'#type' => 'fieldset',
'#title' => t('Library-Specific Details'),
);
$form['library']['library_type'] = array(
'#type' => 'select',
'#title' => t('Library Type'),
'#description' => t('The method of library preparation for sequencing.'),
'#options' => $options['library_type'],
'#required' => TRUE,
'#default_value' => (isset($node->seqrun->library_type)) ? $node->seqrun->library_type : '',
);
$form['library']['insert_len'] = array(
'#type' => 'textfield',
'#title' => t('Insert Length'),
'#description' => 'Enter the length of the insert in base pairs. This will not apply to all sequencing technologies.',
'#element_validate' => array('element_validate_integer_positive'),
'#default_value' => (!empty($node->seqrun->insert_length)) ? $node->seqrun->insert_length : '',
);
$form['library']['frag_len'] = array(
'#type' => 'textfield',
'#title' => t('Fragment Length'),
'#description' => 'Enter the length of the fragment in base pairs. This will not apply to all sequencing technologies.',
'#element_validate' => array('element_validate_integer_positive'),
'#default_value' => (!empty($node->seqrun->fragment_length)) ? $node->seqrun->fragment_length : '',
);
$form['library']['index_type'] = array(
'#type' => 'select',
'#title' => t('Index Type'),
'#description' => t('The collection of barcodes used to identify samples. If "Other", please elaborate in the "Run Description".'),
'#options' => $options['index_type'],
'#required' => TRUE,
'#default_value' => (isset($node->seqrun->index_type)) ? $node->seqrun->index_type : '',
'#ajax' => array(
'callback' => 'ajax_lims_seqrun_index_type_callback',
'wrapper' => 'samples-section',
),
);
$form['sequencing'] = array(
'#type' => 'fieldset',
'#title' => t('Sequencing-Specific Details')
);
$form['sequencing']['technology'] = array(
'#type' => 'select',
'#title' => 'Sequencing Technology',
'#description' => 'Choose the sequencing platform.',
'#options' => $options['technology'],
'#required' => TRUE,
'#default_value' => (isset($node->seqrun->technology)) ? $node->seqrun->technology : '',
);
$form['sequencing']['read_type'] = array(
'#type' => 'select',
'#title' => t('Read Type'),
'#description' => t('Choose the method used to generate the reads.'),
'#options' => $options['read_type'],
'#required' => TRUE,
'#default_value' => (isset($node->seqrun->read_type)) ? $node->seqrun->read_type : '',
);
$form['file'] = array(
'#type' => 'fieldset',
'#title' => 'File-Specific Details',
);
$form['file']['filename'] = array(
'#type' => 'textfield',
'#title' => 'File Name',
'#description' => 'Specify the full name of the file <em>including the path and extension</em>.',
'#default_value' => (isset($node->seqrun->filename)) ? $node->seqrun->filename : '',
);
$form['file']['md5sum'] = array(
'#type' => 'textfield',
'#title' => 'MD5 Checksum',
'#description' => 'The MD5 hash describing the file (used to verify the file is complete & intact).',
'#default_value' => (isset($node->seqrun->md5sum)) ? $node->seqrun->md5sum : '',
);
$form['sample'] = array(
'#type' => 'fieldset',
'#title' => t('Sample Details'),
'#prefix' => '<div id="samples-section">',
'#suffix' => '</div>'
);
$index_type = (isset($node->seqrun->index_type)) ? $node->seqrun->index_type : NULL;
$index_type = (isset($form_state['values']['index_type'])) ? $form_state['values']['index_type'] : $index_type;
// Samples can be submitted as either a single sample or a file containing multiple samples
// depending on whether they are indexed or not. Unfortunatly doing a simple if indexed
// then single sample otherwise, file upload causes Drupal Magic to go awry... Thus we
// are assuming file upload to appease the Drupal Gods but if there is no indexing we
// we will later delete the file upload elements in favour of single sample entry fields.
// File Upload for indexed samples.
// --------------------------------------------
$warning = '<div class="messages warning">
<h2 class="element-invisible">Warning message</h2>
File Upload is specific to multiplexed samples. If you only have a single sample, select "None" as the "Index Type" above.
</div>';
$xls_template = drupal_get_path('module','lims') . '/LIMS-Sequence_Run-samples_template.xls';
$tsv_template = drupal_get_path('module','lims') . '/LIMS-Sequence_Run-samples_template.tsv';
$help = '<br /><strong>Samples File Template:</strong> <a href="' . url($xls_template) . '">XLS</a>, <a href="' . url($tsv_template) . '">TSV</a><br /><br />
<strong>Samples File Specifications:</strong><br />
A tab-delimited file where each line describes a single well/lane of sequencing. Each line should have the the following columns:
<ol>
<li><em>Index ID<span style="color:red" title="required">*</span>:</em> The name of the index used for this particular sample (ie: GBS-Idx1).</li>
<li><em>Index Sequence<span style="color:red" title="required">*</span>:</em> The sequence of the index (ie: CTCC).</li>
<li><em>Sample Name<span style="color:red" title="required">*</span>:</em> The name you gave your sample. NOTE: IF your sample name exactly matches that of the germplasm you extracted it from then the KP:GERM ID will be looked-up automatically.</li>
<li><em>Sample Accession<span style="color:red" title="required"><sup>++</sup></span>:</em> The KP:GERM ID in KnowPulse for the germplasm your sample was extracted from (ie: KP:GERM58 if your sample is CDC Redberry).</li>
<li><em>Plate Well:</em> The location of the sample in the plate (ie: A1).</li>
<li><em>Sample Description:</em> Any additional notes or description of the sample (ie: seed source, number of plants pooled, etc.).</li>
</ol>
<div class="footnotes"><span style="color:red" title="required">* Required Columns.</span></div>
<div class="footnotes"><span style="color:red" title="required"><sup>++</sup> Accession is required unless your sample name exactly matches germplasm in KnowPulse. The accession in the file (if supplied) will always take precedence.</span></div>
<div class="footnotes"><em>NOTE: The file should contain 6 columns. If the column is not required then leave it empty.</em></div>
<br />';
$form['sample']['file_help'] = array(
'#type' => 'markup',
'#markup' => $warning.$help
);
// Needed for file upload.
$form['#attributes']['enctype'] = 'multipart/form-data';
// Provide field to upload file.
$samples_fid = (isset($node->seqrun->samples_fid)) ? $node->seqrun->samples_fid : NULL;
$samples_fid = (isset($form_state['values']['sample_file'])) ? $form_state['values']['sample_file'] : $samples_fid;
$samples_fid = (isset($form_state['values']) AND empty($form_state['values']['sample_file'])) ? 0 : $samples_fid;
$form['sample']['sample_file'] = array(
'#type' => 'managed_file',
'#title' => 'Samples File',
'#process' => array('seqrun_file_element_process'),
'#upload_validators' => array('file_validate_extensions' => array('txt tsv')),
'#default_value' => $samples_fid,
'#ajax' => array(
'callback' => 'ajax_samples_file_show_snippet_callback',
'wrapper' => 'samples-table',
)
);
$form['sample']['sample_snippet'] = array(
'#type' => 'markup',
'#markup' => ''
);
if (!empty($samples_fid)) {
// Generate Samples Snippet to allow users to evaluate
// whether their file was parsed correctly.
$header = array(
array('data' => 'Index', 'colspan' => 2, 'style' => 'text-align:center;'),
array('data' => 'Sample', 'colspan' => 4, 'style' => 'text-align:center;'),
);
$rows = array();
// Second header.
$rows[] = array(
array( 'data' => 'ID', 'header' => TRUE),
array( 'data' => 'Sequence', 'header' => TRUE),
array( 'data' => 'Name', 'header' => TRUE),
array( 'data' => 'Accession', 'header' => TRUE),
array( 'data' => 'Plate Well', 'header' => TRUE),
array( 'data' => 'Description', 'header' => TRUE),
);
$i = 0;
$num_samples = db_query('SELECT count(*) FROM {lims_seqrun_samples} WHERE samples_fid=:fid', array(':fid' => $samples_fid))->fetchField();
$sql = 'SELECT * FROM {lims_seqrun_samples} WHERE samples_fid=:fid ORDER BY sample_id ASC';
$samples = db_query($sql, array(':fid' => $samples_fid));
foreach ($samples as $sample) {
$i++;
if ($i >= 20) { break; }
$rows[] = array(
$sample->index_id,
$sample->index_seq,
$sample->sample_name,
$sample->sample_accession,
$sample->plate_well,
$sample->sample_description
);
}
if ($num_samples > 0) {
$form['sample']['sample_snippet']['#markup'] =
'<br /><br /><strong>Table 1: A subset of parsed samples from the uploaded file.</strong>'
. '<br />Please use the following truncated sample list to ensure your file has been parsed correctly.'
. theme('table',array(
'header' => $header,
'rows' => $rows,
'attributes' => array('style' => 'width:auto')
))
.'<strong>Total Samples Parsed: ' . $num_samples . '</strong> (only a subset of which are shown above)';
}
}
// Input fields for Single Samples.
// --------------------------------------------
// If the samples are un-indexed then we can use simple form fields instead of the
// complicated file upload we just implemented. As such we are going to remove said
// complicated file upload and replace with more user-friendly fields.
// If you are confused as to why the file upload needed to be set if it wasn't going to
// be used, ask the Drupal Gods or see the node above the file upload section if they
// are not forthcomming.
if ($index_type == 'None') {
unset($form['sample']['sample_file']);
$form['sample']['file_help']['#type'] = 'hidden';
$form['sample']['sample_snippet']['#type'] = 'hidden';
// Retrieve the default.
if (!empty($node->samples) AND !isset($node->samples[0]->index_id)) {
$sample = $node->samples[0];
$sample->germplasm_name = str_replace(array(':id',':name'), array($sample->sample_stock_id, $sample->sample_accession), '(:id) :name');
}
elseif (isset($form_state['values']) AND isset($form_state['values']['sample_name'])) {
$sample = new stdclass();
$sample->sample_name = $form_state['values']['sample_name'];
$sample->germplasm_name = $form_state['values']['germplasm_name'];
$sample->sample_description = $form_state['values']['sample_description'];
}
else {
$sample = new stdclass();
$sample->sample_name = '';
$sample->germplasm_name = '';
$sample->sample_description = '';
}
$form['sample']['sample_name'] = array(
'#type' => 'textfield',
'#title' => 'Sample Name',
'#description' => 'The name you gave your sample.',
'#default_value' => $sample->sample_name,
);
$form['sample']['germplasm_name'] = array(
'#type' => 'textfield',
'#title' => 'Germplasm Name',
'#description' => 'The name of the germplasm your sample was extracted from.',
'#autocomplete_path' => 'tripal_ajax/tripal_germplasm/name_to_id/ALL',
'#default_value' => $sample->germplasm_name,
);
$form['sample']['sample_description'] = array(
'#type' => 'textarea',
'#rows' => 2,
'#title' => 'Sample Description',
'#description' => 'Any additional notes or description of the sample (ie: seed source, number of plants pooled, etc.).',
'#default_value' => $sample->sample_description,
);
}
$form['description'] = array(
'#type' => 'textarea',
'#title' => t('Run Description'),
'#description' => t('Any additional details related to this sequencing run.'),
'#default_value' => (isset($node->seqrun->description)) ? $node->seqrun->description : '',
);
return $form;
}
/**
* AJAX: Replace samples section based on index type.
*/
function ajax_lims_seqrun_index_type_callback($form, $form_state) {
return $form['sample'];
}
/**
* A custom process function for the seqrun sample details file form element.
*/
function seqrun_file_element_process($element, &$form_state, &$form) {
// Pre-render the managed file field in order to gain access to the button.
$element = file_managed_file_process($element, $form_state, $form);
// Move the samples table into the file element so it can piggy-back
// on the file upload/remove ajax.
$element['sample_snippet'] = $form['sample']['sample_snippet'];
unset($form['sample']['sample_snippet']);
// Add a validate handler so we can do stuff!
$element['upload_button']['#submit'][] = 'seqrun_sample_file_submit';
$element['remove_button']['#submit'][] = 'seqrun_sample_file_remove_submit';
return $element;
}
/**
* Custom handler for Sequencing Run samples file.
*/
function seqrun_sample_file_submit($form, &$form_state) {
$error = FALSE;
ini_set('auto_detect_line_endings', true);
// First save the file.
$file = file_save_upload('sample_file');
// As long as the file was saved properly (ie: no validation issues so far)
// then we can start processing it.
if (!empty($file)) {
// Open the file for processing.
$sample_file = fopen($file->uri, 'r');
$header = fgetcsv($sample_file, 0, "\t");
if (sizeof($header) < 6) {
$error = TRUE;
drupal_set_message('Six columns are required for every line.', 'error');
//form_set_error('sample_file', 'Line '.$i.': 6 columns are required for every line.');
}
// For each line:
$i = 1;
while( $line = fgetcsv($sample_file, 0, "\t") ) {
$i++;
$error = FALSE;
if (is_array($line)) {
// Reset extra fields
$sample_stock_id = NULL;
// Trim whitespace for each column.
foreach ($line as $k => $v) {
$line[$k] = trim($v);
}
// Determine if the line is empty.
$empty = FALSE;
if (sizeof($line) == 1 AND empty($line[0])) {
$empty = TRUE;
}
// Validate the line.
if (!$empty) {
// -- Check no column is longer than the database allows.
$expected = array(80, 80, 80, 80, 10);
foreach ($expected as $k => $max) {
if (isset($line[$k]) AND (strlen($line[$k]) > $max)) {
$error = TRUE;
$column = ($k == 0) ? 'Index ID' : NULL;
$column = ($k == 1) ? 'Index Sequence' : $column;
$column = ($k == 2) ? 'Sample Name' : $column;
$column = ($k == 3) ? 'Sample Accession' : $column;
$column = ($k == 4) ? 'Plate Well' : $column;
drupal_set_message('Line '.$i.': The <i>'.$column.'</i> is too long (maximum length: '.$max.' characters).', 'error');
}
}
// -- Index ID not empty.
if (empty($line[0])) {
$error = TRUE;
drupal_set_message('Line '.$i.': The Index ID is required and thus should not be empty.','error');
//form_set_error('sample_file', 'Line '.$i.': The first three columns (Index ID, Index Sequence & Sample Name) are required and thus should not be empty.');
}
// -- Index Sequence not empty
// EXCEPTION: Index ID is "unknown".
if (empty($line[1]) AND !preg_match('/[uU]nknown/',$line[0])) {
$error = TRUE;
drupal_set_message('Line '.$i.': The Index Sequence required and thus should not be empty.','error');
//form_set_error('sample_file', 'Line '.$i.': The first three columns (Index ID, Index Sequence & Sample Name) are required and thus should not be empty.');
}
// -- Sample Name not empty.
if (empty($line[2])) {
$error = TRUE;
drupal_set_message('Line '.$i.': The Sample Name is required and thus should not be empty.','error');
//form_set_error('sample_file', 'Line '.$i.': The first three columns (Index ID, Index Sequence & Sample Name) are required and thus should not be empty.');
}
// -- 2nd column is all sequence.
// EXCEPTION: Index ID is "unknown".
if (!empty($line[1]) AND !preg_match('/^[ATGCatgc\,]+$/', $line[1]) AND !preg_match('/[uU]nknown/',$line[0])) {
$error = TRUE;
drupal_set_message('Line '.$i.': The index sequence should consist of only ATGC.','error');
//form_set_error('sample_file', 'Line '.$i.': The index sequence should consist of only ATGC.');
}
// -- Accession should always be KP:GERM###.
if (!empty($line[3]) AND !preg_match('/KP:GERM\d+/', $line[3])) {
$error = TRUE;
drupal_set_message('Line '.$i.': The accession should always be KP:GERM### in full capitals.','error');
}
// -- Ensure KP:GERM exists.
elseif (!empty($line[3])) {
$kpgerm = chado_query('SELECT stock_id FROM {stock} WHERE uniquename=:accession', array(':accession' => $line[3]))->fetchField();
if (empty($kpgerm)) {
$error = TRUE;
drupal_set_message('Line '.$i.': The accession provided, <i>'.$line[3].'</i>, does not exist in KnowPulse.', 'error');
}
else {
$sample_stock_id = $kpgerm;
}
}
// -- Attempt Auto-detect Accession based on sample name.
else {
// First TRY to look it up based on sample name.
$kpgerm = chado_query('SELECT uniquename, stock_id FROM {stock} WHERE name=:sample', array(':sample' => $line[2]))->fetchObject();
if (!empty($kpgerm)) {
$line[3] = $kpgerm->uniquename;
$sample_stock_id = $kpgerm->stock_id;
}
// If unable to then provide error message. :(
else {
$error = TRUE;
drupal_set_message('Line '.$i.': The accession is required and could not be automatically determined by the sample name. Please provide it in the file.','error');
//form_set_error('sample_file', 'Line '.$i.': The accession should always be KP:GERM### in full capitals.');
}
}
// Save it to the database.
if (!$error) {
$sample = array(
'samples_fid' => $file->fid,
'index_id' => $line[0],
'index_seq' => $line[1],
'sample_name' => $line[2],
'sample_accession' => $line[3],
'plate_well' => (isset($line[4])) ? $line[4] : '',
'sample_description' => (isset($line[5])) ? $line[5] : '',
'sample_stock_id' => $sample_stock_id,
);
drupal_write_record('lims_seqrun_samples', $sample);
}
}
}
}
}
}
/**
* Custom handler for Sequencing Run samples file.
*/
function seqrun_sample_file_remove_submit($form, &$form_state) {
// Clean-up sample table to ensure that no samples from previously
// uploaded files hang around.
db_query('DELETE FROM {lims_seqrun_samples} WHERE samples_fid NOT IN (SELECT samples_fid FROM {lims_seqrun})');
// Remove the fid from this node.
if (isset($form_state['node']->nid)) {
db_query("UPDATE {lims_seqrun} SET samples_fid=NULL WHERE nid=:nid",
array(':nid' => $form_state['node']->nid));
}
}
/**
*
*/
function ajax_samples_file_show_snippet_callback($form, $form_state) {
return $form['sample']['sample_snippet'];
}
/**
* Implement hook_validate().
* Validates the data entered into the create/edit "Sequencing Run" form
* before either inserting, seqrun_updating(), or updating, seqrun_insert().
*/
function seqrun_validate(&$node, $form, &$form_state) {
// Ensure the insert & fragment length are integers.
// Taken care of by #element_validate.
// Ensure the file name has not already been associated with a sequencing run.
$filename = NULL;
if (isset($node->nid) AND !empty($form_state['values']['filename'])) {
$filename = db_query("SELECT nid from {lims_seqrun} WHERE filename = :filename AND nid != :nid LIMIT 1", array(":filename" => $form_state['values']['filename'], ':nid' => $node->nid))->fetchField();
}
elseif (!empty($form_state['values']['filename'])) {
$filename = db_query("SELECT nid from {lims_seqrun} WHERE filename = :filename LIMIT 1", array(":filename" => $form_state['values']['filename']))->fetchField();
}
if (!empty($filename)) {
form_set_error('filename', t('This file has already been associated with <a href="@url">a sequencing run</a>.', array('@url' => 'node/'.$filename)));
}
// Ensure the file exists
// @todo: unsure how to do this when storage is not yet mounted.
// The following validation only applies to a single unindexed sample.
if ($node->index_type == 'None' AND isset($node->germplasm_name)) {
// Ensure the autocomplete was used for the germplasm name.
if (!preg_match('/\((\d+)\).*/', $node->germplasm_name, $matches)) {
form_set_error('germplasm_name', 'You must choose the germplasm name from the autocomplete drop-down.');
}
// Ensure the stock_id from the autocomplete exists.
else {
$form_state['values']['germplasm_id'] = $matches[1];
$present = chado_query('SELECT true FROM {stock} WHERE stock_id=:id',
array(':id' => $form_state['values']['germplasm_id'])) ->fetchField();
if (!$present) {
form_set_error('germplasm_name', 'The germplasm selected does not exist. Please re-select the <em>Germplasm Name</em> from the drop-down.');
}
}
}
}
/**
* Implements hook_insert().
* Handles insertion of our custom content when a new "Sequencing Run"
* has been created.
*/
function seqrun_insert($node) {
// If the index type is set to None then we have to
// insert the single sample since it won't be added with a file.
// Do this first so that we can reference it in the core Sequencing Run table.
if ($node->index_type == 'None') {
preg_match('/\((\d+)\)/', $node->germplasm_name, $matches);
// NOTE: We're ignoring the sample_fid, index_id, index_seq & plate_well
// since they don't apply to a single sample sequencing run.
$sample = array(
'sample_name' => $node->sample_name,
'sample_accession' => 'KP:GERM' . $matches[1],
'sample_description' => $node->sample_description,
'sample_stock_id' => $node->germplasm_id,
);
drupal_write_record('lims_seqrun_samples', $sample);
}
// Now add data for the core sequencing run.
$record = array(
'nid' => $node->nid,
'run_name' => $node->title,
'filename' => $node->filename,
'md5sum' => $node->md5sum,
'library_type' => $node->library_type,
'insert_length' => $node->insert_len,
'fragment_length' => $node->frag_len,
'index_type' => $node->index_type,
'technology' => $node->technology,
'read_type' => $node->read_type,
'description' => $node->description,
);
if (isset($node->sample_file)) {
$record['samples_fid'] = $node->sample_file;
}
if (isset($sample['sample_id'])) {
$record['sample_id'] = $sample['sample_id'];
}
drupal_write_record('lims_seqrun', $record);
// Now associate the organisms/species.
foreach($node->species as $organism_id) {
$species = array(
'run_id' => $record['run_id'],
'organism_id' => $organism_id
);
drupal_write_record('lims_seqrun_species', $species);
}
// Clean-up sample table to ensure that no samples from previously
// uploaded files hang around.
db_query('DELETE FROM {lims_seqrun_samples} WHERE samples_fid NOT IN (SELECT samples_fid FROM {lims_seqrun})');
}
/**
* Implements hook_update().
* Handles updating our custom content when a pre-existing "Sequencing Run"
* is edited.
*/
function seqrun_update($node) {
// If the index type is set to None then we have to
// insert the single sample since it won't be added with a file.
// Do this first so that we can reference it in the core Sequencing Run table.
if ($node->index_type == 'None') {
// NOTE: We're ignoring the sample_fid, index_id, index_seq & plate_well
// since they don't apply to a single sample sequencing run.
$sample = array(
'sample_name' => $node->sample_name,
'sample_accession' => 'KP:GERM' . $node->germplasm_id,
'sample_description' => $node->sample_description,
'sample_stock_id' => $node->germplasm_id,
);
// Check to see if a sample already exists for this sequencing run
// and then add or update as appropriate.
$sample_id = db_query('SELECT sample_id FROM {lims_seqrun} WHERE nid=:nid',
array(':nid' => $node->nid))->fetchField();
if ($sample_id) {
$sample['sample_id'] = $sample_id;
drupal_write_record('lims_seqrun_samples', $sample, 'sample_id');
}
else {
drupal_write_record('lims_seqrun_samples', $sample);
}
}
// Now update data for the core sequencing run.
$record = array(
'nid' => $node->nid,
'run_id' => $node->run_id,
'run_name' => $node->title,
'filename' => $node->filename,
'md5sum' => $node->md5sum,