-
-
Notifications
You must be signed in to change notification settings - Fork 110
Expand file tree
/
Copy pathtxplib_admin.php
More file actions
1859 lines (1559 loc) · 51.4 KB
/
Copy pathtxplib_admin.php
File metadata and controls
1859 lines (1559 loc) · 51.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
/*
* Textpattern Content Management System
* https://textpattern.com/
*
* Copyright (C) 2026 The Textpattern Development Team
*
* This file is part of Textpattern.
*
* Textpattern 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, version 2.
*
* Textpattern 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 Textpattern. If not, see <https://www.gnu.org/licenses/>.
*/
/**
* Collection of password handling functions.
*
* @package User
*/
/**
* Emails a new user with account details and requests they set a password.
*
* @param string $name The login name
* @return bool FALSE on error.
*/
function send_account_activation($name)
{
global $sitename;
require_privs('admin.edit');
$type = 'account_activation';
$rs = safe_row("user_id, email, nonce, RealName, pass", 'txp_users', "name = '".doSlash($name)."'");
if ($rs) {
extract($rs);
$expiryTimestamp = time() + (60 * 60 * ACTIVATION_EXPIRY_HOURS);
$txpToken = \Txp::get('\Textpattern\Security\Token');
$activation_code = $txpToken->generate($user_id, $type, $expiryTimestamp, $pass, $nonce);
$expiryYear = safe_strftime('%Y', $expiryTimestamp);
$expiryMonth = safe_strftime('%B', $expiryTimestamp);
$expiryDay = safe_strftime('%Oe', $expiryTimestamp);
$expiryTime = safe_strftime('%H:%M %Z', $expiryTimestamp);
$authorLang = safe_field('val', 'txp_prefs', "name='language_ui' AND user_name = '".doSlash($name)."'");
$authorLang = ($authorLang) ? $authorLang : TEXTPATTERN_DEFAULT_LANG;
$txpLang = Txp::get('\Textpattern\L10n\Lang');
$txpLang->swapStrings($authorLang, 'admin, common');
$message = gTxt('salutation', array('{name}' => $RealName)).
n.n.gTxt('you_have_been_registered').' '.$sitename.
n.n.gTxt('your_login_is').' '.$name.
n.n.gTxt('account_activation_confirmation').
n.ahu.'index.php?lang='.$authorLang.'&activate='.$activation_code.
n.n.gTxt('link_expires', array(
'{year}' => $expiryYear,
'{month}' => $expiryMonth,
'{day}' => $expiryDay,
'{time}' => $expiryTime,
));
// Tidy up expired activation requests.
$txpToken->remove($type, null, (ACTIVATION_EXPIRY_HOURS + 1).' HOUR');
$subject = gTxt('account_activation');
$txpLang->swapStrings(null);
if (txpMail($email, "[$sitename] ".$subject, $message)) {
return gTxt('login_sent_to', array('{email}' => $email));
} else {
return array(gTxt('could_not_mail'), E_ERROR);
}
}
}
/**
* Sends a password reset link to a user's email address.
*
* This function will return a success message even when the specified user
* doesn't exist. Though an error message could be thrown when a user isn't
* found, security best practice prevents leaking existing account names.
*
* @param string $name The login name
* @return string A localized message string
* @see send_new_password()
* @see reset_author_pass()
* @example
* echo send_reset_confirmation_request('username');
*/
function send_reset_confirmation_request($name)
{
global $sitename;
$expiryTimestamp = time() + (60 * RESET_EXPIRY_MINUTES);
$safeName = doSlash($name);
$type = 'password_reset';
$rs = safe_query(
"SELECT
txp_users.user_id, txp_users.email,
txp_users.nonce, txp_users.pass,
txp_token.expires
FROM ".safe_pfx('txp_users')." txp_users
LEFT JOIN ".safe_pfx('txp_token')." txp_token
ON txp_users.user_id = txp_token.reference_id
AND txp_token.type = 'password_reset'
WHERE txp_users.name = '$safeName'"
);
$row = nextRow($rs);
if ($row) {
extract($row);
// Rate limit the reset requests.
if ($expires) {
$originalExpiry = strtotime($expires);
if (($expiryTimestamp - $originalExpiry) < (60 * RESET_RATE_LIMIT_MINUTES)) {
return gTxt('password_reset_confirmation_request_sent');
}
}
$txpToken = \Txp::get('\Textpattern\Security\Token');
$confirm = $txpToken->generate($user_id, $type, $expiryTimestamp, $pass, $nonce);
$expiryYear = safe_strftime('%Y', $expiryTimestamp);
$expiryMonth = safe_strftime('%B', $expiryTimestamp);
$expiryDay = safe_strftime('%Oe', $expiryTimestamp);
$expiryTime = safe_strftime('%H:%M %Z', $expiryTimestamp);
$authorLang = safe_field('val', 'txp_prefs', "name='language_ui' AND user_name = '$safeName'");
$authorLang = ($authorLang) ? $authorLang : TEXTPATTERN_DEFAULT_LANG;
$txpLang = Txp::get('\Textpattern\L10n\Lang');
$txpLang->swapStrings($authorLang, 'admin, common');
$message = gTxt('salutation', array('{name}' => $name)).
n.n.gTxt('password_reset_confirmation').
n.ahu.'index.php?lang='.$authorLang.'&confirm='.$confirm.
n.n.gTxt('link_expires', array(
'{year}' => $expiryYear,
'{month}' => $expiryMonth,
'{day}' => $expiryDay,
'{time}' => $expiryTime,
));
// Tidy up expired password reset requests.
$txpToken->remove($type, null, (RESET_EXPIRY_MINUTES + 1).' MINUTE');
$subject = gTxt('password_reset_confirmation_request');
$txpLang->swapStrings(null);
if (txpMail($email, "[$sitename] ".$subject, $message)) {
return gTxt('password_reset_confirmation_request_sent');
} else {
return array(gTxt('could_not_mail'), E_ERROR);
}
} else {
// Send generic 'request_sent' message so that (non-)existence of
// account names are not leaked. Since this is a short circuit, there's
// a possibility of a timing attack revealing the existence of an
// account, which we could defend against to some degree.
return gTxt('password_reset_confirmation_request_sent');
}
}
/**
* Loads client-side localisation scripts.
*
* Passes localisation strings from the database to JavaScript.
*
* Only works on the admin-side pages.
*
* @param string|array $var Scalar or array of string keys
* @param array $atts Array or array of arrays of variable substitution pairs
* @param array $route Optional events/steps upon which to add the strings
* @since 4.5.0
* @package L10n
* @example
* gTxtScript(array('string1', 'string2', 'string3'));
*/
function gTxtScript($var, $atts = array(), $route = array())
{
global $textarray_script, $event, $step;
$targetEvent = empty($route[0]) ? null : (array)$route[0];
$targetStep = empty($route[1]) ? null : (array)$route[1];
if (($targetEvent === null || in_array($event, $targetEvent)) && ($targetStep === null || in_array($step, $targetStep))) {
if (!is_array($textarray_script)) {
$textarray_script = array();
}
$data = is_array($var) ? array_map('gTxt', $var, $atts) : (array) gTxt($var, $atts);
$textarray_script += array_combine((array) $var, $data);
}
}
/**
* Handle refreshing the passed AJAX content to the UI.
*
* @param array $partials Partials array
* @param array $rs Record set of the edited content
*/
function updatePartials($partials, $rs, $types)
{
if (!is_array($types)) {
$types = array($types);
}
foreach ($partials as $k => $p) {
if (in_array($p['mode'], $types)) {
$cb = $p['cb'];
$partials[$k]['html'] = (is_array($cb) ? call_user_func($cb, $rs, $k) : $cb($rs, $k));
}
}
return $partials;
}
/**
* Handle refreshing the passed AJAX content to the UI.
*
* @param array $partials Partials array
* @return array Response to send back to the browser
*/
function updateVolatilePartials($partials)
{
$response = array();
// Update the volatile partials.
foreach ($partials as $k => $p) {
// Volatile partials need a target DOM selector.
if (empty($p['selector']) && $p['mode'] != PARTIAL_STATIC) {
trigger_error(gTxt('empty_partial_selector', array('{name}' => $k)));
} else {
// Build response script.
list($selector, $fragment) = (array)$p['selector'] + array(null, null);
if ($p['mode'] == PARTIAL_VOLATILE) {
// Volatile partials replace *all* of the existing HTML
// fragment for their selector with the new one.
$selector = do_list($selector);
$fragment = isset($fragment) ? do_list($fragment) + $selector : $selector;
$response[] = 'var $html = $("<div>'.escape_js($p['html']).'</div>")';
foreach ($selector as $i => $sel) {
$response[] = '$("'.$sel.'").replaceWith($html.find("'.$fragment[$i].'"))';
}
} elseif ($p['mode'] == PARTIAL_VOLATILE_VALUE) {
// Volatile partial values replace the *value* of elements
// matching their selector.
$response[] = '$("'.$selector.'").val("'.escape_js($p['html']).'")';
}
}
}
return $response;
}
function selectCustom ($table_id = 1) {
$options = array();
foreach ($entityLabels = \Txp::get('\Textpattern\Meta\ContentType')->getEntities($table_id, 'label') as $value => $label) {
$fields = safe_field('GROUP_CONCAT(meta_id)', 'txp_meta_fieldsets', "type_id = $value");
$options[$value] = array('title' => $label, 'data-fields' => $fields);
}
return $options;
}
/**
* Checks if GD supports the given image type.
*
* @param string $image_type Either '.gif', '.jpg', '.png', '.svg'
* @return bool TRUE if the type is supported
* @package Image
*/
function check_gd($image_type)
{
if (!function_exists('gd_info')) {
return false;
}
$gd_info = gd_info();
switch ($image_type) {
case '.gif':
return ($gd_info['GIF Create Support'] == true);
break;
case '.jpg':
case '.jpeg':
return ($gd_info['JPEG Support'] == true);
break;
case '.png':
return ($gd_info['PNG Support'] == true);
break;
case '.svg':
if (has_privs('image.create.trusted')) {
return true;
}
break;
case '.webp':
return (!empty($gd_info['WebP Support']));
break;
case '.avif':
return (!empty($gd_info['AVIF Support']));
break;
}
return false;
}
/**
* Uploads an image.
*
* Can be used to upload a new image or replace an existing one.
* If $id is specified, the image will be replaced. If $uploaded is set FALSE,
* $file can take a local file instead of HTTP file upload variable.
*
* All uploaded files will included on the Images panel.
*
* Thumbnails default to 'auto' if no other system is currently in force.
*
* @param array $file HTTP file upload variables
* @param array $meta Image meta data, allowed keys 'caption', 'alt', 'category'
* @param int $id Existing image's ID
* @param bool $uploaded If FALSE, $file takes a filename instead of upload vars
* @return array|string An array of array(message, id) on success, localized error string on error
* @package Image
* @example
* print_r(image_data(
* $_FILES['myfile'],
* array(
* 'caption' => '',
* 'alt' => '',
* 'category' => '',
* )
* ));
*/
function image_data($file, $meta = array(), $id = 0, $uploaded = true)
{
global $txp_user, $event;
$name = $file['name'];
$error = $file['error'];
$file = $file['tmp_name'];
$thumbtype = (int) get_pref('thumbnail_type', THUMB_AUTO);
if ($uploaded) {
if ($error !== UPLOAD_ERR_OK) {
return upload_get_errormsg($error);
}
$file = get_uploaded_file($file);
}
if (empty($file)) {
return upload_get_errormsg(UPLOAD_ERR_NO_FILE);
}
if (get_pref('file_max_upload_size') < filesize($file)) {
unlink($file);
return upload_get_errormsg(UPLOAD_ERR_FORM_SIZE);
}
if (!($data = txpimagesize($file))) {
return gTxt('only_graphic_files_allowed', array('{formats}' => join(', ', get_safe_image_types())));
}
list($w, $h) = $data;
$ext = $data['ext'];
$name = substr($name, 0, strrpos($name, '.')).$ext;
$safename = doSlash($name);
$meta = lAtts(array(
'category' => '',
'caption' => '',
'alt' => '',
), (array) $meta, false);
extract(doSlash($meta));
$q = "
name = '$safename',
ext = '$ext',
w = $w,
h = $h,
alt = '$alt',
caption = '$caption',
category = '$category',
date = NOW(),
thumbnail = '$thumbtype',
author = '".doSlash($txp_user)."'
";
if (empty($id)) {
$rs = safe_insert('txp_image', $q);
if ($rs) {
$id = $GLOBALS['ID'] = $rs;
} else {
return gTxt('image_save_error');
}
} else {
$id = assert_int($id);
}
$newpath = IMPATH.$id.$ext;
if (shift_uploaded_file($file, $newpath, $ext == '.svg') == false) {
if (!empty($rs)) {
safe_delete('txp_image', "id = '$id'");
unset($GLOBALS['ID']);
}
return gTxt('directory_permissions', array('{path}' => $newpath));
} elseif (empty($rs)) {
$rs = safe_update('txp_image', $q, "id = $id");
if (!$rs) {
return gTxt('image_save_error');
}
// Invalidate (delete) any old thumbnails.
deleteThumbnails($id);
}
chmod($newpath, 0644);
// GD is supported
if (check_gd($ext)) {
// Auto-generate a thumbnail using the last settings
if ($thumbtype == THUMB_CUSTOM && (get_pref('thumb_w') > 0 || get_pref('thumb_h') > 0)) {
$t = new txp_thumb($id);
$t->crop = (bool) get_pref('thumb_crop');
$t->hint = '0';
$t->width = (int) get_pref('thumb_w');
$t->height = (int) get_pref('thumb_h');
$t->write();
}
}
$message = gTxt('image_uploaded', array('{name}' => $name));
update_lastmod('image_uploaded', compact('id', 'name', 'ext', 'w', 'h', 'alt', 'caption', 'category', 'txp_user'));
// call post-upload plugins with new image's $id
callback_event('image_uploaded', $event, false, $id);
return array($message, $id);
}
/**
* Error handler for admin-side pages.
*
* @param int $errno
* @param string $errstr
* @param string $errfile
* @param int $errline
* @access private
* @package Debug
*/
function adminErrorHandler($errno, $errstr, $errfile, $errline)
{
global $production_status, $theme, $event, $step;
$error = array();
if ($production_status == 'testing') {
$error = array(
E_WARNING => 'Warning',
E_RECOVERABLE_ERROR => 'Catchable fatal error',
E_USER_ERROR => 'User_Error',
E_USER_WARNING => 'User_Warning',
);
} elseif ($production_status == 'debug') {
$error = array(
E_WARNING => 'Warning',
E_NOTICE => 'Notice',
E_RECOVERABLE_ERROR => 'Catchable fatal error',
E_USER_ERROR => 'User_Error',
E_USER_WARNING => 'User_Warning',
E_USER_NOTICE => 'User_Notice',
);
if (!isset($error[$errno])) {
$error[$errno] = $errno;
}
}
if (!isset($error[$errno]) || !error_reporting()) {
return;
}
// When even a minimum environment is missing.
if (!isset($production_status)) {
echo '<pre dir="auto">'.gTxt('internal_error').' "'.$errstr.'"'.n."in $errfile at line $errline".'</pre>';
return;
}
$backtrace = '';
if (has_privs('debug.verbose')) {
$msg = $error[$errno].' "'.$errstr.'"';
} else {
$msg = gTxt('internal_error');
}
if ($production_status == 'debug' /*&& has_privs('debug.backtrace')*/) {
$msg .= n."in $errfile at line $errline";
$backtrace = join(n, get_caller(10, 1));
}
if ($errno == E_ERROR || $errno == E_USER_ERROR) {
$httpstatus = 500;
} else {
$httpstatus = 200;
}
$out = "$msg.\n$backtrace";
if (http_accept_format('html')) {
if ($backtrace) {
echo "<pre dir=\"auto\">$msg.</pre>".
n.'<pre class="backtrace" dir="ltr"><code>'.
txpspecialchars($backtrace).'</code></pre>';
} elseif (is_object($theme)) {
echo $theme->announce(array($out, E_ERROR), true);
} else {
echo "<pre dir=\"auto\">$out</pre>";
}
} elseif (http_accept_format('js')) {
if (is_object($theme)) {
send_script_response($theme->announce_async(array($out, E_ERROR), true));
} else {
send_script_response('/* '.$out.'*/');
}
} elseif (http_accept_format('xml')) {
send_xml_response(array(
'http-status' => $httpstatus,
'internal_error' => "$out",
));
} else {
txp_die($msg, 500);
}
}
/**
* Error handler for update scripts.
*
* @param int $errno
* @param string $errstr
* @param string $errfile
* @param int $errline
* @access private
* @package Debug
*/
function updateErrorHandler($errno, $errstr, $errfile, $errline)
{
global $production_status;
$old = $production_status;
$production_status = 'debug';
adminErrorHandler($errno, $errstr, $errfile, $errline);
$production_status = $old;
throw new Exception('update failed');
}
/**
* Registers an admin-side extension page.
*
* For now this just does the same as register_callback().
*
* @param callback $func The callback function
* @param string $event The callback event
* @param string $step The callback step
* @param bool $top The top or the bottom of the page
* @access private
* @see register_callback()
* @package Callback
*/
function register_page_extension($func, $event, $step = '', $top = 0)
{
register_callback($func, $event, $step, $top);
}
/**
* Registers a new admin-side panel and adds a navigation link to the menu.
*
* @param string $area The menu the panel appears in, e.g. "home", "content", "presentation", "admin", "extensions"
* @param string $panel The panel's event
* @param string $title The menu item's label
* @package Callback
* @example
* add_privs('abc_admin_event', '1,2');
* register_tab('extensions', 'abc_admin_event', 'My Panel');
* register_callback('abc_admin_function', 'abc_admin_event');
*/
function register_tab($area, $panel, $title)
{
global $plugin_areas, $event;
if ($event !== 'plugin') {
$plugin_areas[$area][$title] = $panel;
}
}
/**
* Call an event's pluggable UI function.
*
* @param string $event The event
* @param string $element The element selector
* @param string $default The default interface markup
* @return mixed Returned value from a callback handler, or $default if no custom UI was provided
* @package Callback
*/
function pluggable_ui($event, $element, $default = '')
{
$argv = func_get_args();
$argv = array_merge(array(
$event,
$element,
(string) $default === '' ? 0 : array(0, 0)
), array_slice($argv, 2));
// Custom user interface, anyone?
// Signature for called functions:
// string my_called_func(string $event, string $step, string $default_markup[, mixed $context_data...])
$ui = call_user_func_array('callback_event', $argv);
// Either plugins provided a user interface, or we render our own.
return ($ui === '') ? $default : $ui;
}
/**
* Gets a list of form types.
*
* The list of form types can be extended with a 'form.types > types'
* callback event. Callback functions get passed three arguments: '$event',
* '$step' and '$types'. The third parameter contains a reference to an
* array of 'type => label' pairs.
*
* @return array An array of form types
* @since 4.6.0
* @deprecated 4.8.6
* @see Textpattern\Skin\Form->getTypes()
* @todo Move callback to Textpattern\Skin\Form->getTypes()?
* @package Template
*/
function get_form_types()
{
static $types = null;
if ($types === null) {
foreach (Txp::get('Textpattern\Skin\Form')->getTypes() as $type) {
$types[$type] = gTxt($type);
}
callback_event_ref('form.types', 'types', 0, $types);
}
return $types;
}
/**
* Gets a list of essential form templates.
*
* These forms can not be deleted or renamed. The array keys hold
* the form names, the array values their group.
*
* The list forms can be extended with a 'form.essential > forms'
* callback event. Callback functions get passed three arguments: '$event',
* '$step' and '$essential'. The third parameter contains a reference to an
* array of forms.
*
* @return array An array of form names
* @since 4.6.0
* @package Template
*/
function get_essential_forms()
{
static $essential = null;
if ($essential === null) {
$essential = array(
'comments' => 'comment',
'comments_display' => 'comment',
'comment_form' => 'comment',
'default' => 'article',
'plainlinks' => 'link',
'files' => 'file',
);
callback_event_ref('form.essential', 'forms', 0, $essential);
}
return $essential;
}
/**
* Renders a HTML <select> list of supported permanent link URL formats.
*
* @param string $name HTML name and id of the list
* @param string $val Initial (or current) selected item
* @return string HTML
*/
function permlinkmodes($name, $val, $blank = false)
{
$vals = array(
'messy' => gTxt('messy'),
'id_title' => gTxt('id_title'),
'section_id_title' => gTxt('section_id_title'),
'section_category_title' => gTxt('section_category_title'),
'year_month_day_title' => gTxt('year_month_day_title'),
'breadcrumb_title' => gTxt('breadcrumb_title'),
'section_title' => gTxt('section_title'),
'title_only' => gTxt('title_only')
);
return selectInput($name, $vals, $val, $blank, '', $name);
}
/**
* Gets the name of the default publishing section.
*
* @return string The section
*/
function getDefaultSection()
{
global $txp_sections;
$name = get_pref('default_section');
if (!isset($txp_sections[$name])) {
foreach ($txp_sections as $name => $section) {
if ($name != 'default') {
break;
}
}
set_pref('default_section', $name, 'section', PREF_HIDDEN);
}
return $name;
}
/**
* Updates a list's per page number.
*
* Gets the per page number from a "qty" HTTP POST/GET parameter and
* creates a user-specific preference value "$name_list_pageby".
*
* @param string|null $name The name of the list
* @deprecated in 4.7.0
*/
function event_change_pageby($name = null)
{
global $event;
Txp::get('\Textpattern\Admin\Paginator', $event, $name)->change();
}
/**
* Generic multi-edit form's edit handler shared across panels.
*
* Receives an action from a multi-edit form and runs it in the given
* database table.
*
* @param string $table The database table
* @param string $id_key The database column selected items match to. Column should be integer type
* @return string Comma-separated list of affected items
* @see multi_edit()
*/
function event_multi_edit($table, $id_key)
{
$method = ps('edit_method');
$selected = ps('selected');
if ($selected) {
if ($method == 'delete') {
foreach ($selected as $id) {
$id = assert_int($id);
if (safe_delete($table, "$id_key = '$id'")) {
$ids[] = $id;
}
}
return join(', ', $ids);
}
}
return '';
}
/**
* Verifies temporary directory existence and that it's writeable.
*
* @return bool|null NULL on error, TRUE on success
* @package Debug
*/
function find_temp_dir()
{
global $path_to_site, $img_dir;
if (IS_WIN) {
$guess = array(
txpath.DS.'tmp',
getenv('TMP'),
getenv('TEMP'),
getenv('SystemRoot').DS.'Temp',
'C:'.DS.'Temp',
$path_to_site.DS.$img_dir,
);
foreach ($guess as $k => $v) {
if (empty($v)) {
unset($guess[$k]);
}
}
} else {
$guess = array(
txpath.DS.'tmp',
sys_get_temp_dir(),
DS.'tmp',
$path_to_site.DS.$img_dir,
);
}
foreach ($guess as $dir) {
if (is_writable($dir)) {
$tf = tempnam($dir, 'txp_');
if ($tf) {
$tf = realpath($tf);
}
if ($tf and file_exists($tf)) {
unlink($tf);
return dirname($tf);
}
}
}
return false;
}
/**
* Moves an uploaded file and returns its new location.
*
* @param string $f The filename of the uploaded file
* @param string $dest The destination of the moved file. If omitted, the file is moved to the temp directory
* @return string|bool The new path or FALSE on error
* @package File
*/
function get_uploaded_file($f, $dest = '')
{
global $tempdir;
if (!is_uploaded_file($f)) {
return false;
}
if ($dest) {
$newfile = $dest;
} else {
$newfile = tempnam($tempdir, 'txp_');
if (!$newfile) {
return false;
}
}
// $newfile is created by tempnam(), but move_uploaded_file will overwrite it.
if (move_uploaded_file($f, $newfile)) {
return $newfile;
}
}
/**
* Gets an array of files in the Files directory that weren't uploaded
* from Textpattern.
*
* Used for importing existing files on the server to Textpattern's files panel.
*
* @param string $path The directory to scan
* @param int $options glob() options
* @return array An array of file paths
* @package File
*/
function get_filenames($path = null, $options = GLOB_NOSORT)
{
global $file_base_path;
$files = array();
$file_path = isset($path) ? $path : $file_base_path;
$is_file = ($options & GLOB_ONLYDIR) ? 'is_dir' : 'is_file';
if (!is_dir($file_path) || !is_readable($file_path)) {
return array();
}
$cwd = getcwd();
if (chdir($file_path)) {
$directory = glob('*', $options);
if ($directory) {
foreach ($directory as $filename) {
if ($is_file($filename) && is_readable($filename)) {
$files[$filename] = $filename;
}
}
unset($directory);
}
if ($cwd) {
chdir($cwd);
}
}
if (!$files || isset($path)) {
return $files;
}
$rs = safe_rows_start("filename", 'txp_file', "1 = 1");
if ($rs && numRows($rs)) {
while ($a = nextRow($rs)) {
unset($files[$a['filename']]);
}
}
return $files;
}
/**
* Moves a file.
*
* @param string $f The file to move
* @param string $dest The destination
* @param bool $issvg Image type is SVG
* @return bool TRUE on success, or FALSE on error
* @package File
*/
function shift_uploaded_file($f, $dest, $issvg = false)
{
if ($issvg) {
if (($svg = imagecreatefromsvg($f)) !== false) {
unlink($f);
if (file_put_contents($dest, $svg) !== false)
return true;
}
}
if (rename($f, $dest)) {
return true;