forked from moodle/moodle
-
Notifications
You must be signed in to change notification settings - Fork 0
/
formslib.php
2977 lines (2696 loc) · 120 KB
/
formslib.php
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
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* formslib.php - library of classes for creating forms in Moodle, based on PEAR QuickForms.
*
* To use formslib then you will want to create a new file purpose_form.php eg. edit_form.php
* and you want to name your class something like {modulename}_{purpose}_form. Your class will
* extend moodleform overriding abstract classes definition and optionally defintion_after_data
* and validation.
*
* See examples of use of this library in course/edit.php and course/edit_form.php
*
* A few notes :
* form definition is used for both printing of form and processing and should be the same
* for both or you may lose some submitted data which won't be let through.
* you should be using setType for every form element except select, radio or checkbox
* elements, these elements clean themselves.
*
* @package core_form
* @copyright 2006 Jamie Pratt <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/** setup.php includes our hacked pear libs first */
require_once 'HTML/QuickForm.php';
require_once 'HTML/QuickForm/DHTMLRulesTableless.php';
require_once 'HTML/QuickForm/Renderer/Tableless.php';
require_once 'HTML/QuickForm/Rule.php';
require_once $CFG->libdir.'/filelib.php';
/**
* EDITOR_UNLIMITED_FILES - hard-coded value for the 'maxfiles' option
*/
define('EDITOR_UNLIMITED_FILES', -1);
/**
* Callback called when PEAR throws an error
*
* @param PEAR_Error $error
*/
function pear_handle_error($error){
echo '<strong>'.$error->GetMessage().'</strong> '.$error->getUserInfo();
echo '<br /> <strong>Backtrace </strong>:';
print_object($error->backtrace);
}
if ($CFG->debugdeveloper) {
//TODO: this is a wrong place to init PEAR!
$GLOBALS['_PEAR_default_error_mode'] = PEAR_ERROR_CALLBACK;
$GLOBALS['_PEAR_default_error_options'] = 'pear_handle_error';
}
/**
* Initalize javascript for date type form element
*
* @staticvar bool $done make sure it gets initalize once.
* @global moodle_page $PAGE
*/
function form_init_date_js() {
global $PAGE;
static $done = false;
if (!$done) {
$module = 'moodle-form-dateselector';
$function = 'M.form.dateselector.init_date_selectors';
$config = array(array(
'firstdayofweek' => get_string('firstdayofweek', 'langconfig'),
'mon' => date_format_string(strtotime("Monday"), '%a', 99),
'tue' => date_format_string(strtotime("Tuesday"), '%a', 99),
'wed' => date_format_string(strtotime("Wednesday"), '%a', 99),
'thu' => date_format_string(strtotime("Thursday"), '%a', 99),
'fri' => date_format_string(strtotime("Friday"), '%a', 99),
'sat' => date_format_string(strtotime("Saturday"), '%a', 99),
'sun' => date_format_string(strtotime("Sunday"), '%a', 99),
'january' => date_format_string(strtotime("January 1"), '%B', 99),
'february' => date_format_string(strtotime("February 1"), '%B', 99),
'march' => date_format_string(strtotime("March 1"), '%B', 99),
'april' => date_format_string(strtotime("April 1"), '%B', 99),
'may' => date_format_string(strtotime("May 1"), '%B', 99),
'june' => date_format_string(strtotime("June 1"), '%B', 99),
'july' => date_format_string(strtotime("July 1"), '%B', 99),
'august' => date_format_string(strtotime("August 1"), '%B', 99),
'september' => date_format_string(strtotime("September 1"), '%B', 99),
'october' => date_format_string(strtotime("October 1"), '%B', 99),
'november' => date_format_string(strtotime("November 1"), '%B', 99),
'december' => date_format_string(strtotime("December 1"), '%B', 99)
));
$PAGE->requires->yui_module($module, $function, $config);
$done = true;
}
}
/**
* Wrapper that separates quickforms syntax from moodle code
*
* Moodle specific wrapper that separates quickforms syntax from moodle code. You won't directly
* use this class you should write a class definition which extends this class or a more specific
* subclass such a moodleform_mod for each form you want to display and/or process with formslib.
*
* You will write your own definition() method which performs the form set up.
*
* @package core_form
* @copyright 2006 Jamie Pratt <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @todo MDL-19380 rethink the file scanning
*/
abstract class moodleform {
/** @var string name of the form */
protected $_formname; // form name
/** @var MoodleQuickForm quickform object definition */
protected $_form;
/** @var array globals workaround */
protected $_customdata;
/** @var object definition_after_data executed flag */
protected $_definition_finalized = false;
/**
* The constructor function calls the abstract function definition() and it will then
* process and clean and attempt to validate incoming data.
*
* It will call your custom validate method to validate data and will also check any rules
* you have specified in definition using addRule
*
* The name of the form (id attribute of the form) is automatically generated depending on
* the name you gave the class extending moodleform. You should call your class something
* like
*
* @param mixed $action the action attribute for the form. If empty defaults to auto detect the
* current url. If a moodle_url object then outputs params as hidden variables.
* @param mixed $customdata if your form defintion method needs access to data such as $course
* $cm, etc. to construct the form definition then pass it in this array. You can
* use globals for somethings.
* @param string $method if you set this to anything other than 'post' then _GET and _POST will
* be merged and used as incoming data to the form.
* @param string $target target frame for form submission. You will rarely use this. Don't use
* it if you don't need to as the target attribute is deprecated in xhtml strict.
* @param mixed $attributes you can pass a string of html attributes here or an array.
* @param bool $editable
*/
function moodleform($action=null, $customdata=null, $method='post', $target='', $attributes=null, $editable=true) {
global $CFG, $FULLME;
// no standard mform in moodle should allow autocomplete with the exception of user signup
if (empty($attributes)) {
$attributes = array('autocomplete'=>'off');
} else if (is_array($attributes)) {
$attributes['autocomplete'] = 'off';
} else {
if (strpos($attributes, 'autocomplete') === false) {
$attributes .= ' autocomplete="off" ';
}
}
if (empty($action)){
// do not rely on PAGE->url here because dev often do not setup $actualurl properly in admin_externalpage_setup()
$action = strip_querystring($FULLME);
if (!empty($CFG->sslproxy)) {
// return only https links when using SSL proxy
$action = preg_replace('/^http:/', 'https:', $action, 1);
}
//TODO: use following instead of FULLME - see MDL-33015
//$action = strip_querystring(qualified_me());
}
// Assign custom data first, so that get_form_identifier can use it.
$this->_customdata = $customdata;
$this->_formname = $this->get_form_identifier();
$this->_form = new MoodleQuickForm($this->_formname, $method, $action, $target, $attributes);
if (!$editable){
$this->_form->hardFreeze();
}
$this->definition();
$this->_form->addElement('hidden', 'sesskey', null); // automatic sesskey protection
$this->_form->setType('sesskey', PARAM_RAW);
$this->_form->setDefault('sesskey', sesskey());
$this->_form->addElement('hidden', '_qf__'.$this->_formname, null); // form submission marker
$this->_form->setType('_qf__'.$this->_formname, PARAM_RAW);
$this->_form->setDefault('_qf__'.$this->_formname, 1);
$this->_form->_setDefaultRuleMessages();
// we have to know all input types before processing submission ;-)
$this->_process_submission($method);
}
/**
* It should returns unique identifier for the form.
* Currently it will return class name, but in case two same forms have to be
* rendered on same page then override function to get unique form identifier.
* e.g This is used on multiple self enrollments page.
*
* @return string form identifier.
*/
protected function get_form_identifier() {
return get_class($this);
}
/**
* To autofocus on first form element or first element with error.
*
* @param string $name if this is set then the focus is forced to a field with this name
* @return string javascript to select form element with first error or
* first element if no errors. Use this as a parameter
* when calling print_header
*/
function focus($name=NULL) {
$form =& $this->_form;
$elkeys = array_keys($form->_elementIndex);
$error = false;
if (isset($form->_errors) && 0 != count($form->_errors)){
$errorkeys = array_keys($form->_errors);
$elkeys = array_intersect($elkeys, $errorkeys);
$error = true;
}
if ($error or empty($name)) {
$names = array();
while (empty($names) and !empty($elkeys)) {
$el = array_shift($elkeys);
$names = $form->_getElNamesRecursive($el);
}
if (!empty($names)) {
$name = array_shift($names);
}
}
$focus = '';
if (!empty($name)) {
$focus = 'forms[\''.$form->getAttribute('id').'\'].elements[\''.$name.'\']';
}
return $focus;
}
/**
* Internal method. Alters submitted data to be suitable for quickforms processing.
* Must be called when the form is fully set up.
*
* @param string $method name of the method which alters submitted data
*/
function _process_submission($method) {
$submission = array();
if ($method == 'post') {
if (!empty($_POST)) {
$submission = $this->_get_post_params();
}
} else {
$submission = array_merge_recursive($_GET, $this->_get_post_params()); // Emulate handling of parameters in xxxx_param().
}
// following trick is needed to enable proper sesskey checks when using GET forms
// the _qf__.$this->_formname serves as a marker that form was actually submitted
if (array_key_exists('_qf__'.$this->_formname, $submission) and $submission['_qf__'.$this->_formname] == 1) {
if (!confirm_sesskey()) {
print_error('invalidsesskey');
}
$files = $_FILES;
} else {
$submission = array();
$files = array();
}
$this->detectMissingSetType();
$this->_form->updateSubmission($submission, $files);
}
/**
* Internal method. Gets all POST variables, bypassing max_input_vars limit if needed.
*
* @return array All POST variables as an array, in the same format as $_POST.
*/
protected function _get_post_params() {
$enctype = $this->_form->getAttribute('enctype');
$max = (int)ini_get('max_input_vars');
if (empty($max) || count($_POST, COUNT_RECURSIVE) < $max || (!empty($enctype) && $enctype == 'multipart/form-data')) {
return $_POST;
}
// Large POST request with enctype supported by php://input.
// Parse php://input in chunks to bypass max_input_vars limit, which also applies to parse_str().
$allvalues = array();
$values = array();
$str = file_get_contents("php://input");
$delim = '&';
$fun = create_function('$p', 'return implode("'.$delim.'", $p);');
$chunks = array_map($fun, array_chunk(explode($delim, $str), $max));
foreach ($chunks as $chunk) {
parse_str($chunk, $values);
$allvalues = array_merge_recursive($allvalues, $values);
}
return $allvalues;
}
/**
* Internal method. Validates all old-style deprecated uploaded files.
* The new way is to upload files via repository api.
*
* @param array $files list of files to be validated
* @return bool|array Success or an array of errors
*/
function _validate_files(&$files) {
global $CFG, $COURSE;
$files = array();
if (empty($_FILES)) {
// we do not need to do any checks because no files were submitted
// note: server side rules do not work for files - use custom verification in validate() instead
return true;
}
$errors = array();
$filenames = array();
// now check that we really want each file
foreach ($_FILES as $elname=>$file) {
$required = $this->_form->isElementRequired($elname);
if ($file['error'] == 4 and $file['size'] == 0) {
if ($required) {
$errors[$elname] = get_string('required');
}
unset($_FILES[$elname]);
continue;
}
if (!empty($file['error'])) {
$errors[$elname] = file_get_upload_error($file['error']);
unset($_FILES[$elname]);
continue;
}
if (!is_uploaded_file($file['tmp_name'])) {
// TODO: improve error message
$errors[$elname] = get_string('error');
unset($_FILES[$elname]);
continue;
}
if (!$this->_form->elementExists($elname) or !$this->_form->getElementType($elname)=='file') {
// hmm, this file was not requested
unset($_FILES[$elname]);
continue;
}
/*
// TODO: rethink the file scanning MDL-19380
if ($CFG->runclamonupload) {
if (!clam_scan_moodle_file($_FILES[$elname], $COURSE)) {
$errors[$elname] = $_FILES[$elname]['uploadlog'];
unset($_FILES[$elname]);
continue;
}
}
*/
$filename = clean_param($_FILES[$elname]['name'], PARAM_FILE);
if ($filename === '') {
// TODO: improve error message - wrong chars
$errors[$elname] = get_string('error');
unset($_FILES[$elname]);
continue;
}
if (in_array($filename, $filenames)) {
// TODO: improve error message - duplicate name
$errors[$elname] = get_string('error');
unset($_FILES[$elname]);
continue;
}
$filenames[] = $filename;
$_FILES[$elname]['name'] = $filename;
$files[$elname] = $_FILES[$elname]['tmp_name'];
}
// return errors if found
if (count($errors) == 0){
return true;
} else {
$files = array();
return $errors;
}
}
/**
* Internal method. Validates filepicker and filemanager files if they are
* set as required fields. Also, sets the error message if encountered one.
*
* @return bool|array with errors
*/
protected function validate_draft_files() {
global $USER;
$mform =& $this->_form;
$errors = array();
//Go through all the required elements and make sure you hit filepicker or
//filemanager element.
foreach ($mform->_rules as $elementname => $rules) {
$elementtype = $mform->getElementType($elementname);
//If element is of type filepicker then do validation
if (($elementtype == 'filepicker') || ($elementtype == 'filemanager')){
//Check if rule defined is required rule
foreach ($rules as $rule) {
if ($rule['type'] == 'required') {
$draftid = (int)$mform->getSubmitValue($elementname);
$fs = get_file_storage();
$context = context_user::instance($USER->id);
if (!$files = $fs->get_area_files($context->id, 'user', 'draft', $draftid, 'id DESC', false)) {
$errors[$elementname] = $rule['message'];
}
}
}
}
}
// Check all the filemanager elements to make sure they do not have too many
// files in them.
foreach ($mform->_elements as $element) {
if ($element->_type == 'filemanager') {
$maxfiles = $element->getMaxfiles();
if ($maxfiles > 0) {
$draftid = (int)$element->getValue();
$fs = get_file_storage();
$context = context_user::instance($USER->id);
$files = $fs->get_area_files($context->id, 'user', 'draft', $draftid, '', false);
if (count($files) > $maxfiles) {
$errors[$element->getName()] = get_string('err_maxfiles', 'form', $maxfiles);
}
}
}
}
if (empty($errors)) {
return true;
} else {
return $errors;
}
}
/**
* Load in existing data as form defaults. Usually new entry defaults are stored directly in
* form definition (new entry form); this function is used to load in data where values
* already exist and data is being edited (edit entry form).
*
* note: $slashed param removed
*
* @param stdClass|array $default_values object or array of default values
*/
function set_data($default_values) {
if (is_object($default_values)) {
$default_values = (array)$default_values;
}
$this->_form->setDefaults($default_values);
}
/**
* Check that form was submitted. Does not check validity of submitted data.
*
* @return bool true if form properly submitted
*/
function is_submitted() {
return $this->_form->isSubmitted();
}
/**
* Checks if button pressed is not for submitting the form
*
* @staticvar bool $nosubmit keeps track of no submit button
* @return bool
*/
function no_submit_button_pressed(){
static $nosubmit = null; // one check is enough
if (!is_null($nosubmit)){
return $nosubmit;
}
$mform =& $this->_form;
$nosubmit = false;
if (!$this->is_submitted()){
return false;
}
foreach ($mform->_noSubmitButtons as $nosubmitbutton){
if (optional_param($nosubmitbutton, 0, PARAM_RAW)){
$nosubmit = true;
break;
}
}
return $nosubmit;
}
/**
* Check that form data is valid.
* You should almost always use this, rather than {@link validate_defined_fields}
*
* @return bool true if form data valid
*/
function is_validated() {
//finalize the form definition before any processing
if (!$this->_definition_finalized) {
$this->_definition_finalized = true;
$this->definition_after_data();
}
return $this->validate_defined_fields();
}
/**
* Validate the form.
*
* You almost always want to call {@link is_validated} instead of this
* because it calls {@link definition_after_data} first, before validating the form,
* which is what you want in 99% of cases.
*
* This is provided as a separate function for those special cases where
* you want the form validated before definition_after_data is called
* for example, to selectively add new elements depending on a no_submit_button press,
* but only when the form is valid when the no_submit_button is pressed,
*
* @param bool $validateonnosubmit optional, defaults to false. The default behaviour
* is NOT to validate the form when a no submit button has been pressed.
* pass true here to override this behaviour
*
* @return bool true if form data valid
*/
function validate_defined_fields($validateonnosubmit=false) {
static $validated = null; // one validation is enough
$mform =& $this->_form;
if ($this->no_submit_button_pressed() && empty($validateonnosubmit)){
return false;
} elseif ($validated === null) {
$internal_val = $mform->validate();
$files = array();
$file_val = $this->_validate_files($files);
//check draft files for validation and flag them if required files
//are not in draft area.
$draftfilevalue = $this->validate_draft_files();
if ($file_val !== true && $draftfilevalue !== true) {
$file_val = array_merge($file_val, $draftfilevalue);
} else if ($draftfilevalue !== true) {
$file_val = $draftfilevalue;
} //default is file_val, so no need to assign.
if ($file_val !== true) {
if (!empty($file_val)) {
foreach ($file_val as $element=>$msg) {
$mform->setElementError($element, $msg);
}
}
$file_val = false;
}
$data = $mform->exportValues();
$moodle_val = $this->validation($data, $files);
if ((is_array($moodle_val) && count($moodle_val)!==0)) {
// non-empty array means errors
foreach ($moodle_val as $element=>$msg) {
$mform->setElementError($element, $msg);
}
$moodle_val = false;
} else {
// anything else means validation ok
$moodle_val = true;
}
$validated = ($internal_val and $moodle_val and $file_val);
}
return $validated;
}
/**
* Return true if a cancel button has been pressed resulting in the form being submitted.
*
* @return bool true if a cancel button has been pressed
*/
function is_cancelled(){
$mform =& $this->_form;
if ($mform->isSubmitted()){
foreach ($mform->_cancelButtons as $cancelbutton){
if (optional_param($cancelbutton, 0, PARAM_RAW)){
return true;
}
}
}
return false;
}
/**
* Return submitted data if properly submitted or returns NULL if validation fails or
* if there is no submitted data.
*
* note: $slashed param removed
*
* @return object submitted data; NULL if not valid or not submitted or cancelled
*/
function get_data() {
$mform =& $this->_form;
if (!$this->is_cancelled() and $this->is_submitted() and $this->is_validated()) {
$data = $mform->exportValues();
unset($data['sesskey']); // we do not need to return sesskey
unset($data['_qf__'.$this->_formname]); // we do not need the submission marker too
if (empty($data)) {
return NULL;
} else {
return (object)$data;
}
} else {
return NULL;
}
}
/**
* Return submitted data without validation or NULL if there is no submitted data.
* note: $slashed param removed
*
* @return object submitted data; NULL if not submitted
*/
function get_submitted_data() {
$mform =& $this->_form;
if ($this->is_submitted()) {
$data = $mform->exportValues();
unset($data['sesskey']); // we do not need to return sesskey
unset($data['_qf__'.$this->_formname]); // we do not need the submission marker too
if (empty($data)) {
return NULL;
} else {
return (object)$data;
}
} else {
return NULL;
}
}
/**
* Save verified uploaded files into directory. Upload process can be customised from definition()
*
* @deprecated since Moodle 2.0
* @todo MDL-31294 remove this api
* @see moodleform::save_stored_file()
* @see moodleform::save_file()
* @param string $destination path where file should be stored
* @return bool Always false
*/
function save_files($destination) {
debugging('Not used anymore, please fix code! Use save_stored_file() or save_file() instead');
return false;
}
/**
* Returns name of uploaded file.
*
* @param string $elname first element if null
* @return string|bool false in case of failure, string if ok
*/
function get_new_filename($elname=null) {
global $USER;
if (!$this->is_submitted() or !$this->is_validated()) {
return false;
}
if (is_null($elname)) {
if (empty($_FILES)) {
return false;
}
reset($_FILES);
$elname = key($_FILES);
}
if (empty($elname)) {
return false;
}
$element = $this->_form->getElement($elname);
if ($element instanceof MoodleQuickForm_filepicker || $element instanceof MoodleQuickForm_filemanager) {
$values = $this->_form->exportValues($elname);
if (empty($values[$elname])) {
return false;
}
$draftid = $values[$elname];
$fs = get_file_storage();
$context = context_user::instance($USER->id);
if (!$files = $fs->get_area_files($context->id, 'user', 'draft', $draftid, 'id DESC', false)) {
return false;
}
$file = reset($files);
return $file->get_filename();
}
if (!isset($_FILES[$elname])) {
return false;
}
return $_FILES[$elname]['name'];
}
/**
* Save file to standard filesystem
*
* @param string $elname name of element
* @param string $pathname full path name of file
* @param bool $override override file if exists
* @return bool success
*/
function save_file($elname, $pathname, $override=false) {
global $USER;
if (!$this->is_submitted() or !$this->is_validated()) {
return false;
}
if (file_exists($pathname)) {
if ($override) {
if (!@unlink($pathname)) {
return false;
}
} else {
return false;
}
}
$element = $this->_form->getElement($elname);
if ($element instanceof MoodleQuickForm_filepicker || $element instanceof MoodleQuickForm_filemanager) {
$values = $this->_form->exportValues($elname);
if (empty($values[$elname])) {
return false;
}
$draftid = $values[$elname];
$fs = get_file_storage();
$context = context_user::instance($USER->id);
if (!$files = $fs->get_area_files($context->id, 'user', 'draft', $draftid, 'id DESC', false)) {
return false;
}
$file = reset($files);
return $file->copy_content_to($pathname);
} else if (isset($_FILES[$elname])) {
return copy($_FILES[$elname]['tmp_name'], $pathname);
}
return false;
}
/**
* Returns a temporary file, do not forget to delete after not needed any more.
*
* @param string $elname name of the elmenet
* @return string|bool either string or false
*/
function save_temp_file($elname) {
if (!$this->get_new_filename($elname)) {
return false;
}
if (!$dir = make_temp_directory('forms')) {
return false;
}
if (!$tempfile = tempnam($dir, 'tempup_')) {
return false;
}
if (!$this->save_file($elname, $tempfile, true)) {
// something went wrong
@unlink($tempfile);
return false;
}
return $tempfile;
}
/**
* Get draft files of a form element
* This is a protected method which will be used only inside moodleforms
*
* @param string $elname name of element
* @return array|bool|null
*/
protected function get_draft_files($elname) {
global $USER;
if (!$this->is_submitted()) {
return false;
}
$element = $this->_form->getElement($elname);
if ($element instanceof MoodleQuickForm_filepicker || $element instanceof MoodleQuickForm_filemanager) {
$values = $this->_form->exportValues($elname);
if (empty($values[$elname])) {
return false;
}
$draftid = $values[$elname];
$fs = get_file_storage();
$context = context_user::instance($USER->id);
if (!$files = $fs->get_area_files($context->id, 'user', 'draft', $draftid, 'id DESC', false)) {
return null;
}
return $files;
}
return null;
}
/**
* Save file to local filesystem pool
*
* @param string $elname name of element
* @param int $newcontextid id of context
* @param string $newcomponent name of the component
* @param string $newfilearea name of file area
* @param int $newitemid item id
* @param string $newfilepath path of file where it get stored
* @param string $newfilename use specified filename, if not specified name of uploaded file used
* @param bool $overwrite overwrite file if exists
* @param int $newuserid new userid if required
* @return mixed stored_file object or false if error; may throw exception if duplicate found
*/
function save_stored_file($elname, $newcontextid, $newcomponent, $newfilearea, $newitemid, $newfilepath='/',
$newfilename=null, $overwrite=false, $newuserid=null) {
global $USER;
if (!$this->is_submitted() or !$this->is_validated()) {
return false;
}
if (empty($newuserid)) {
$newuserid = $USER->id;
}
$element = $this->_form->getElement($elname);
$fs = get_file_storage();
if ($element instanceof MoodleQuickForm_filepicker) {
$values = $this->_form->exportValues($elname);
if (empty($values[$elname])) {
return false;
}
$draftid = $values[$elname];
$context = context_user::instance($USER->id);
if (!$files = $fs->get_area_files($context->id, 'user' ,'draft', $draftid, 'id DESC', false)) {
return false;
}
$file = reset($files);
if (is_null($newfilename)) {
$newfilename = $file->get_filename();
}
if ($overwrite) {
if ($oldfile = $fs->get_file($newcontextid, $newcomponent, $newfilearea, $newitemid, $newfilepath, $newfilename)) {
if (!$oldfile->delete()) {
return false;
}
}
}
$file_record = array('contextid'=>$newcontextid, 'component'=>$newcomponent, 'filearea'=>$newfilearea, 'itemid'=>$newitemid,
'filepath'=>$newfilepath, 'filename'=>$newfilename, 'userid'=>$newuserid);
return $fs->create_file_from_storedfile($file_record, $file);
} else if (isset($_FILES[$elname])) {
$filename = is_null($newfilename) ? $_FILES[$elname]['name'] : $newfilename;
if ($overwrite) {
if ($oldfile = $fs->get_file($newcontextid, $newcomponent, $newfilearea, $newitemid, $newfilepath, $newfilename)) {
if (!$oldfile->delete()) {
return false;
}
}
}
$file_record = array('contextid'=>$newcontextid, 'component'=>$newcomponent, 'filearea'=>$newfilearea, 'itemid'=>$newitemid,
'filepath'=>$newfilepath, 'filename'=>$newfilename, 'userid'=>$newuserid);
return $fs->create_file_from_pathname($file_record, $_FILES[$elname]['tmp_name']);
}
return false;
}
/**
* Get content of uploaded file.
*
* @param string $elname name of file upload element
* @return string|bool false in case of failure, string if ok
*/
function get_file_content($elname) {
global $USER;
if (!$this->is_submitted() or !$this->is_validated()) {
return false;
}
$element = $this->_form->getElement($elname);
if ($element instanceof MoodleQuickForm_filepicker || $element instanceof MoodleQuickForm_filemanager) {
$values = $this->_form->exportValues($elname);
if (empty($values[$elname])) {
return false;
}
$draftid = $values[$elname];
$fs = get_file_storage();
$context = context_user::instance($USER->id);
if (!$files = $fs->get_area_files($context->id, 'user', 'draft', $draftid, 'id DESC', false)) {
return false;
}
$file = reset($files);
return $file->get_content();
} else if (isset($_FILES[$elname])) {
return file_get_contents($_FILES[$elname]['tmp_name']);
}
return false;
}
/**
* Print html form.
*/
function display() {
//finalize the form definition if not yet done
if (!$this->_definition_finalized) {
$this->_definition_finalized = true;
$this->definition_after_data();
}
$this->_form->display();
}
/**
* Renders the html form (same as display, but returns the result).
*
* Note that you can only output this rendered result once per page, as
* it contains IDs which must be unique.
*
* @return string HTML code for the form
*/
public function render() {
ob_start();
$this->display();
$out = ob_get_contents();
ob_end_clean();
return $out;
}
/**
* Form definition. Abstract method - always override!
*/
protected abstract function definition();
/**
* Dummy stub method - override if you need to setup the form depending on current
* values. This method is called after definition(), data submission and set_data().
* All form setup that is dependent on form values should go in here.
*/
function definition_after_data(){
}
/**
* Dummy stub method - override if you needed to perform some extra validation.
* If there are errors return array of errors ("fieldname"=>"error message"),
* otherwise true if ok.
*
* Server side rules do not work for uploaded files, implement serverside rules here if needed.
*
* @param array $data array of ("fieldname"=>value) of submitted data
* @param array $files array of uploaded files "element_name"=>tmp_file_path
* @return array of "element_name"=>"error_description" if there are errors,
* or an empty array if everything is OK (true allowed for backwards compatibility too).
*/
function validation($data, $files) {
return array();
}
/**
* Helper used by {@link repeat_elements()}.
*