Textpattern | PHP Cross Reference | Content Management Systems |
Description: Files panel.
1 <?php 2 3 /* 4 * Textpattern Content Management System 5 * https://textpattern.com/ 6 * 7 * Copyright (C) 2020 The Textpattern Development Team 8 * 9 * "Mod File Upload" by Michael Manfre 10 * Copyright (C) 2004 Michael Manfre 11 * 12 * This file is part of Textpattern. 13 * 14 * Textpattern is free software; you can redistribute it and/or 15 * modify it under the terms of the GNU General Public License 16 * as published by the Free Software Foundation, version 2. 17 * 18 * Textpattern is distributed in the hope that it will be useful, 19 * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 * GNU General Public License for more details. 22 * 23 * You should have received a copy of the GNU General Public License 24 * along with Textpattern. If not, see <https://www.gnu.org/licenses/>. 25 */ 26 27 /** 28 * Files panel. 29 * 30 * @package Admin\File 31 */ 32 33 use Textpattern\Validator\CategoryConstraint; 34 use Textpattern\Validator\ChoiceConstraint; 35 use Textpattern\Validator\Validator; 36 use Textpattern\Search\Filter; 37 38 if (!defined('txpinterface')) { 39 die('txpinterface is undefined.'); 40 } 41 42 $levels = array( 43 1 => gTxt('private'), 44 0 => gTxt('public'), 45 ); 46 47 global $file_statuses; 48 $file_statuses = status_list(true, array(STATUS_DRAFT, STATUS_STICKY)); 49 50 if ($event == 'file') { 51 require_privs('file'); 52 53 global $all_file_cats, $all_file_authors; 54 $all_file_cats = getTree('root', 'file'); 55 $all_file_authors = the_privileged('file.edit.own', true); 56 57 $available_steps = array( 58 'file_change_pageby' => true, 59 'file_multi_edit' => true, 60 'file_edit' => false, 61 'file_insert' => true, 62 'file_list' => false, 63 'file_replace' => true, 64 'file_save' => true, 65 'file_create' => true, 66 ); 67 68 if ($step && bouncer($step, $available_steps)) { 69 $step(); 70 } else { 71 file_list(); 72 } 73 } 74 75 /** 76 * The main panel listing all files. 77 * 78 * @param string|array $message The activity message 79 */ 80 81 function file_list($message = '', $ids = array()) 82 { 83 global $file_base_path, $file_statuses, $txp_user, $event; 84 85 pagetop(gTxt('tab_file'), $message); 86 87 extract(gpsa(array( 88 'page', 89 'sort', 90 'dir', 91 'crit', 92 'search_method', 93 ))); 94 95 if ($sort === '') { 96 $sort = get_pref('file_sort_column', 'filename'); 97 } else { 98 if (!in_array($sort, array('id', 'category', 'title', 'date', 'downloads', 'author', 'size'))) { 99 $sort = 'filename'; 100 } 101 102 set_pref('file_sort_column', $sort, 'file', PREF_HIDDEN, '', 0, PREF_PRIVATE); 103 } 104 105 if ($dir === '') { 106 $dir = get_pref('file_sort_dir', 'asc'); 107 } else { 108 $dir = ($dir == 'asc') ? "asc" : "desc"; 109 set_pref('file_sort_dir', $dir, 'file', PREF_HIDDEN, '', 0, PREF_PRIVATE); 110 } 111 112 switch ($sort) { 113 case 'id': 114 $sort_sql = "txp_file.id $dir"; 115 break; 116 case 'date': 117 $sort_sql = "txp_file.created $dir, txp_file.id ASC"; 118 break; 119 case 'category': 120 $sort_sql = "txp_category.title $dir, txp_file.filename DESC"; 121 break; 122 case 'title': 123 $sort_sql = "txp_file.title $dir, txp_file.filename DESC"; 124 break; 125 case 'size': 126 $sort_sql = "txp_file.size $dir, txp_file.id ASC"; 127 break; 128 case 'downloads': 129 $sort_sql = "txp_file.downloads $dir, txp_file.filename DESC"; 130 break; 131 case 'author': 132 $sort_sql = "txp_users.RealName $dir, txp_file.id ASC"; 133 break; 134 default: 135 $sort = 'filename'; 136 $sort_sql = "txp_file.filename $dir"; 137 break; 138 } 139 140 $switch_dir = ($dir == 'desc') ? 'asc' : 'desc'; 141 142 $search = new Filter($event, 143 array( 144 'id' => array( 145 'column' => 'txp_file.id', 146 'label' => gTxt('id'), 147 'type' => 'integer', 148 ), 149 'filename' => array( 150 'column' => 'txp_file.filename', 151 'label' => gTxt('name'), 152 ), 153 'title' => array( 154 'column' => 'txp_file.title', 155 'label' => gTxt('title'), 156 ), 157 'description' => array( 158 'column' => 'txp_file.description', 159 'label' => gTxt('description'), 160 ), 161 'category' => array( 162 'column' => array('txp_file.category', 'txp_category.title'), 163 'label' => gTxt('category'), 164 ), 165 'status' => array( 166 'column' => array('txp_file.status'), 167 'label' => gTxt('status'), 168 'type' => 'boolean', 169 ), 170 'author' => array( 171 'column' => array('txp_file.author', 'txp_users.RealName'), 172 'label' => gTxt('author'), 173 ), 174 ) 175 ); 176 177 $search->setAliases('status', $file_statuses); 178 179 list($criteria, $crit, $search_method) = $search->getFilter(array('id' => array('can_list' => true))); 180 181 $search_render_options = array('placeholder' => 'search_files'); 182 183 $sql_from = 184 safe_pfx_j('txp_file')." 185 LEFT JOIN ".safe_pfx_j('txp_category')." ON txp_category.name = txp_file.category AND txp_category.type = 'file' 186 LEFT JOIN ".safe_pfx_j('txp_users')." ON txp_users.name = txp_file.author"; 187 188 if ($crit === '') { 189 $total = safe_count('txp_file', $criteria); 190 } else { 191 $total = getThing("SELECT COUNT(*) FROM $sql_from WHERE $criteria"); 192 } 193 194 $searchBlock = 195 n.tag( 196 $search->renderForm('file_list', $search_render_options), 197 'div', array( 198 'class' => 'txp-layout-4col-3span', 199 'id' => $event.'_control', 200 'style' => $total || $crit === '' ? false : 'display:none', 201 ) 202 ); 203 204 $createBlock = array(); 205 206 if (!is_dir($file_base_path) || !is_writeable($file_base_path)) { 207 $createBlock[] = 208 graf( 209 span(null, array('class' => 'ui-icon ui-icon-alert')).' '. 210 gTxt('file_dir_not_writeable', array('{filedir}' => $file_base_path)), 211 array('class' => 'alert-block warning') 212 ); 213 } elseif (has_privs('file.edit.own')) { 214 $categories = event_category_popup('file', '', 'file_category'); 215 $createBlock[] = 216 n.tag_start('div', array('class' => 'txp-control-panel')). 217 n.file_upload_form('upload_file', 'upload', 'file_insert[]', '', '', 'async', '', 218 array('postinput' => ($categories 219 ? n.tag( 220 n.tag(gTxt('category'), 'label', array('for' => 'file_category')).$categories.n, 221 'span', 222 array('class' => 'inline-file-uploader-actions')) 223 : '' 224 )) 225 ); 226 227 if ($existing_files = get_filenames()) { 228 $selected = count($existing_files) >= 5 ? array() : null; 229 $createBlock[] = 230 form( 231 eInput('file'). 232 sInput('file_create'). 233 tag(gTxt('existing_file'), 'label', array('for' => 'file-existing')). 234 selectInput('filename', $existing_files, $selected, false, '', 'file-existing'). 235 fInput('submit', '', gTxt('import')), 236 '', '', 'post', 'assign-existing-form', '', 'assign_file'); 237 } 238 239 $createBlock[] = tag_end('div'); 240 } 241 242 $createBlock = implode(n, $createBlock); 243 $contentBlock = ''; 244 245 $paginator = new \Textpattern\Admin\Paginator(); 246 $limit = $paginator->getLimit(); 247 248 list($page, $offset, $numPages) = pager($total, $limit, $page); 249 250 if ($total < 1) { 251 $contentBlock .= graf( 252 span(null, array('class' => 'ui-icon ui-icon-info')).' '. 253 gTxt($crit === '' ? 'no_files_recorded' : 'no_results_found'), 254 array('class' => 'alert-block information') 255 ); 256 } else { 257 $rs = safe_query( 258 "SELECT 259 txp_file.id, 260 txp_file.filename, 261 txp_file.title, 262 txp_file.category, 263 txp_file.description, 264 UNIX_TIMESTAMP(txp_file.created) AS uDate, 265 txp_file.downloads, 266 txp_file.status, 267 txp_file.author, 268 txp_file.size, 269 txp_users.RealName AS realname, 270 txp_category.Title AS category_title 271 FROM $sql_from WHERE $criteria ORDER BY $sort_sql LIMIT $offset, $limit" 272 ); 273 274 if ($rs && numRows($rs)) { 275 $show_authors = !has_single_author('txp_file'); 276 277 $contentBlock .= n.tag_start('form', array( 278 'class' => 'multi_edit_form', 279 'id' => 'files_form', 280 'name' => 'longform', 281 'method' => 'post', 282 'action' => 'index.php', 283 )). 284 n.tag_start('div', array( 285 'class' => 'txp-listtables', 286 'tabindex' => 0, 287 'aria-label' => gTxt('list'), 288 )). 289 n.tag_start('table', array('class' => 'txp-list')). 290 n.tag_start('thead'). 291 tr( 292 hCell( 293 fInput('checkbox', 'select_all', 0, '', '', '', '', '', 'select_all'), 294 '', ' class="txp-list-col-multi-edit" scope="col" title="'.gTxt('toggle_all_selected').'"' 295 ). 296 column_head( 297 'ID', 'id', 'file', true, $switch_dir, $crit, $search_method, 298 (('id' == $sort) ? "$dir " : '').'txp-list-col-id' 299 ). 300 column_head( 301 'name', 'filename', 'file', true, $switch_dir, $crit, $search_method, 302 (('filename' == $sort) ? "$dir " : '').'txp-list-col-filename' 303 ). 304 column_head( 305 'title', 'title', 'file', true, $switch_dir, $crit, $search_method, 306 (('title' == $sort) ? "$dir " : '').'txp-list-col-title' 307 ). 308 column_head( 309 'date', 'date', 'file', true, $switch_dir, $crit, $search_method, 310 (('date' == $sort) ? "$dir " : '').'txp-list-col-created date' 311 ). 312 column_head( 313 'category', 'category', 'file', true, $switch_dir, $crit, $search_method, 314 (('category' == $sort) ? "$dir " : '').'txp-list-col-category category' 315 ). 316 (has_privs('tag') 317 ? hCell(gTxt( 318 'tags'), '', ' class="txp-list-col-tag-build" scope="col"' 319 ) 320 : '' 321 ). 322 hCell(gTxt( 323 'status'), '', ' class="txp-list-col-status" scope="col"' 324 ). 325 hCell(gTxt( 326 'condition'), '', ' class="txp-list-col-condition" scope="col"' 327 ). 328 column_head( 329 'file_size', 'size', 'file', true, $switch_dir, $crit, $search_method, 330 (('size' == $sort) ? "$dir " : '').'txp-list-col-filesize' 331 ). 332 column_head( 333 'downloads', 'downloads', 'file', true, $switch_dir, $crit, $search_method, 334 (('downloads' == $sort) ? "$dir " : '').'txp-list-col-downloads' 335 ). 336 ( 337 $show_authors 338 ? column_head('author', 'author', 'file', true, $switch_dir, $crit, $search_method, 339 (('author' == $sort) ? "$dir " : '').'txp-list-col-author name') 340 : '' 341 ) 342 ). 343 n.tag_end('thead'). 344 n.tag_start('tbody'); 345 346 $validator = new Validator(); 347 348 while ($a = nextRow($rs)) { 349 extract($a); 350 $filename = sanitizeForFile($filename); 351 352 $edit_url = array( 353 'event' => 'file', 354 'step' => 'file_edit', 355 'id' => $id, 356 'sort' => $sort, 357 'dir' => $dir, 358 'page' => $page, 359 'search_method' => $search_method, 360 'crit' => $crit, 361 ); 362 363 $tagName = 'file_download_link'; 364 $tag_url = array( 365 'id' => $id, 366 'description' => $description, 367 'filename' => $filename, 368 'step' => 'build', 369 ); 370 371 $file_exists = file_exists(build_file_path($file_base_path, $filename)); 372 $can_edit = has_privs('file.edit') || ($author === $txp_user && has_privs('file.edit.own')); 373 $validator->setConstraints(array(new CategoryConstraint($category, array('type' => 'file')))); 374 375 if ($validator->validate()) { 376 $vc = ''; 377 } else { 378 $vc = ' error'; 379 } 380 381 if ($file_exists) { 382 $downloads = make_download_link($id, $downloads, $filename); 383 $condition = tag(gTxt('status_ok'), 'small', array('class' => 'alert-block alert-pill success')); 384 } else { 385 $condition = tag(gTxt('status_missing'), 'small', array('class' => 'alert-block alert-pill error')); 386 } 387 388 if ($category) { 389 $category = span(txpspecialchars($category_title), array('title' => $category)); 390 } 391 392 if ($can_edit) { 393 $name = href(txpspecialchars($filename), $edit_url, array('title' => gTxt('edit'))); 394 } else { 395 $name = txpspecialchars($filename); 396 } 397 398 if ($can_edit) { 399 $id_column = href($id, $edit_url, array('title' => gTxt('edit'))); 400 $multi_edit = checkbox('selected[]', $id, in_array($id, $ids)); 401 } else { 402 $id_column = $id; 403 $multi_edit = ''; 404 } 405 406 if ($file_exists) { 407 $id_column .= span( 408 sp.span('|', array('role' => 'separator')). 409 sp.make_download_link($id, gTxt('download'), $filename), 410 array('class' => 'txp-option-link') 411 ); 412 } 413 414 if (isset($file_statuses[$status])) { 415 $status = $file_statuses[$status]; 416 } else { 417 $status = span(gTxt('none'), array('class' => 'error')); 418 } 419 420 $contentBlock .= tr( 421 td( 422 $multi_edit, '', 'txp-list-col-multi-edit' 423 ). 424 hCell( 425 $id_column, '', array( 426 'class' => 'txp-list-col-id', 427 'scope' => 'row', 428 ) 429 ). 430 td( 431 $name, '', 'txp-list-col-filename txp-contain' 432 ). 433 td( 434 txpspecialchars($title), '', 'txp-list-col-title' 435 ). 436 td( 437 gTime($uDate), '', 'txp-list-col-created date' 438 ). 439 td( 440 $category, '', 'txp-list-col-category category'.$vc 441 ). 442 (has_privs('tag') 443 ? td( 444 popTag($tagName, 'Textile', array('type' => 'textile') + $tag_url). 445 sp.span('|', array('role' => 'separator')). 446 sp.popTag($tagName, 'Textpattern', array('type' => 'textpattern') + $tag_url). 447 sp.span('|', array('role' => 'separator')). 448 sp.popTag($tagName, 'HTML', array('type' => 'html') + $tag_url), '', 'txp-list-col-tag-build' 449 ) 450 : '' 451 ). 452 td( 453 $status, '', 'txp-list-col-status' 454 ). 455 td( 456 $condition, '', 'txp-list-col-condition' 457 ). 458 td( 459 format_filesize($size), '', 'txp-list-col-filesize' 460 ). 461 td( 462 $downloads, '', 'txp-list-col-downloads' 463 ). 464 ( 465 $show_authors 466 ? td(span(txpspecialchars($realname), array('title' => $author)), '', 'txp-list-col-author name') 467 : '' 468 ) 469 ); 470 } 471 472 $contentBlock .= 473 n.tag_end('tbody'). 474 n.tag_end('table'). 475 n.tag_end('div'). // End of .txp-listtables. 476 file_multiedit_form($page, $sort, $dir, $crit, $search_method). 477 tInput(). 478 n.tag_end('form'); 479 } 480 } 481 482 $pageBlock = $paginator->render(). 483 nav_form($event, $page, $numPages, $sort, $dir, $crit, $search_method, $total, $limit); 484 485 $table = new \Textpattern\Admin\Table($event); 486 echo $table->render(compact('total', 'crit'), $searchBlock, $createBlock, $contentBlock, $pageBlock). 487 n.tag( 488 null, 489 'div', array( 490 'class' => 'txp-tagbuilder-content', 491 'id' => 'tagbuild_links', 492 'aria-label' => gTxt('tagbuilder'), 493 'title' => gTxt('tagbuilder'), 494 )); 495 } 496 497 // ------------------------------------------------------------- 498 499 function file_multiedit_form($page, $sort, $dir, $crit, $search_method) 500 { 501 global $file_statuses, $all_file_cats, $all_file_authors; 502 503 $categories = $all_file_cats ? treeSelectInput('category', $all_file_cats, '') : ''; 504 $authors = $all_file_authors ? selectInput('author', $all_file_authors, '', true) : ''; 505 $status = selectInput('status', $file_statuses, '', true); 506 507 $methods = array( 508 'changestatus' => array( 509 'label' => gTxt('changestatus'), 510 'html' => $status, 511 ), 512 'changecategory' => array( 513 'label' => gTxt('changecategory'), 514 'html' => $categories, 515 ), 516 'changeauthor' => array( 517 'label' => gTxt('changeauthor'), 518 'html' => $authors, 519 ), 520 'changecount' => array('label' => gTxt('reset_download_count')), 521 'delete' => gTxt('delete'), 522 ); 523 524 if (!$categories) { 525 unset($methods['changecategory']); 526 } 527 528 if (has_single_author('txp_file') || !has_privs('file.edit')) { 529 unset($methods['changeauthor']); 530 } 531 532 if (!has_privs('file.delete.own') && !has_privs('file.delete')) { 533 unset($methods['delete']); 534 } 535 536 return multi_edit($methods, 'file', 'file_multi_edit', $page, $sort, $dir, $crit, $search_method); 537 } 538 539 // ------------------------------------------------------------- 540 541 function file_multi_edit() 542 { 543 global $txp_user, $all_file_cats, $all_file_authors; 544 545 // Empty entry to permit clearing the category. 546 $categories = array(''); 547 548 foreach ($all_file_cats as $row) { 549 $categories[] = $row['name']; 550 } 551 552 $selected = ps('selected'); 553 554 if (!$selected || !is_array($selected)) { 555 return file_list(); 556 } 557 558 $selected = array_map('assert_int', $selected); 559 $method = ps('edit_method'); 560 $changed = array(); 561 $key = ''; 562 563 switch ($method) { 564 case 'delete': 565 return file_delete($selected); 566 break; 567 case 'changecategory': 568 $val = ps('category'); 569 if (in_array($val, $categories)) { 570 $key = 'category'; 571 } 572 break; 573 case 'changeauthor': 574 $val = ps('author'); 575 if (has_privs('file.edit') && isset($all_file_authors[$val])) { 576 $key = 'author'; 577 } 578 break; 579 case 'changecount': 580 $key = 'downloads'; 581 $val = 0; 582 break; 583 case 'changestatus': 584 $key = 'status'; 585 $val = ps('status'); 586 587 // Do not allow to be set to an empty value. 588 if (!$val) { 589 $selected = array(); 590 } 591 break; 592 default: 593 $key = ''; 594 $val = ''; 595 break; 596 } 597 598 // Remove bogus (false) entries to prevent SQL syntax errors being thrown. 599 $selected = array_filter($selected); 600 601 if (!has_privs('file.edit')) { 602 if ($selected && has_privs('file.edit.own')) { 603 $selected = safe_column("id", 'txp_file', "id IN (".join(',', $selected).") AND author = '".doSlash($txp_user)."'"); 604 } else { 605 $selected = array(); 606 } 607 } 608 609 if ($selected && $key) { 610 foreach ($selected as $id) { 611 if (safe_update('txp_file', "$key = '".doSlash($val)."'", "id = '$id'")) { 612 $changed[] = $id; 613 } 614 } 615 } 616 617 if ($changed) { 618 update_lastmod('file_updated', $changed); 619 620 return file_list(gTxt('file_updated', array('{name}' => join(', ', $changed)))); 621 } 622 623 return file_list(); 624 } 625 626 /** 627 * Renders and outputs the file editor panel. 628 * 629 * @param string|array $message The activity message 630 * @param int $id The file ID 631 */ 632 633 function file_edit($message = '', $id = '') 634 { 635 global $file_base_path, $levels, $file_statuses, $txp_user, $event, $all_file_cats; 636 637 extract(gpsa(array( 638 'name', 639 'title', 640 'category', 641 'permissions', 642 'description', 643 'sort', 644 'dir', 645 'page', 646 'crit', 647 'search_method', 648 'publish_now', 649 ))); 650 651 if (!$id) { 652 $id = gps('id'); 653 } 654 655 $id = assert_int($id); 656 $rs = safe_row("*, UNIX_TIMESTAMP(created) AS created, UNIX_TIMESTAMP(modified) AS modified", 'txp_file', "id = '$id'"); 657 658 if ($rs) { 659 extract($rs); 660 $filename = sanitizeForFile($filename); 661 662 if (!has_privs('file.edit') && !($author === $txp_user && has_privs('file.edit.own'))) { 663 require_privs(); 664 } 665 666 pagetop(gTxt('edit_file'), $message); 667 668 if ($permissions == '') { 669 $permissions = '-1'; 670 } 671 672 if (!has_privs('file.publish') && $status >= STATUS_LIVE) { 673 $status = STATUS_PENDING; 674 } 675 676 $file_exists = file_exists(build_file_path($file_base_path, $filename)); 677 $existing_files = get_filenames(); 678 679 if (!is_dir($file_base_path) || !is_writeable($file_base_path)) { 680 $replace = ''; 681 } else { 682 $replace = ($file_exists) 683 ? file_upload_form('replace_file', 'file_replace', 'file_replace', $id, 'file_replace', ' replace-file') 684 : file_upload_form('file_relink', 'file_reassign', 'file_replace', $id, 'file_reassign', ' upload-file'); 685 } 686 687 $condition = span((($file_exists) 688 ? gTxt('status_ok') 689 : gTxt('status_missing') 690 ), array('class' => 'alert-block alert-pill '.(($file_exists) ? 'success' : 'error'))); 691 692 $downloadlink = ($file_exists) ? make_download_link($id, txpspecialchars($filename), $filename) : txpspecialchars($filename); 693 694 $created = 695 inputLabel( 696 'year', 697 tsi('year', '%Y', $rs['created'], '', 'year'). 698 ' <span role="separator">/</span> '. 699 tsi('month', '%m', $rs['created'], '', 'month'). 700 ' <span role="separator">/</span> '. 701 tsi('day', '%d', $rs['created'], '', 'day'), 702 'publish_date', 703 array('timestamp_file', 'instructions_file_date'), 704 array('class' => 'txp-form-field date posted') 705 ). 706 inputLabel( 707 'hour', 708 tsi('hour', '%H', $rs['created'], '', 'hour'). 709 ' <span role="separator">:</span> '. 710 tsi('minute', '%M', $rs['created'], '', 'minute'). 711 ' <span role="separator">:</span> '. 712 tsi('second', '%S', $rs['created'], '', 'second'), 713 'publish_time', 714 array('', 'instructions_file_time'), 715 array('class' => 'txp-form-field time posted') 716 ). 717 n.tag( 718 checkbox('publish_now', '1', $publish_now, '', 'publish_now'). 719 n.tag(gTxt('set_to_now'), 'label', array('for' => 'publish_now')), 720 'div', array('class' => 'txp-form-field-shim posted-now') 721 ); 722 723 echo n.tag_start('div', array('class' => 'txp-edit')). 724 hed(gTxt('edit_file'), 2). 725 $replace. 726 inputLabel( 727 'condition', 728 $condition, 729 '', '', array('class' => 'txp-form-field edit-file-condition') 730 ). 731 inputLabel( 732 'id', 733 $id, 734 'id', '', array('class' => 'txp-form-field edit-file-id') 735 ). 736 inputLabel( 737 'name', 738 $downloadlink, 739 '', '', array('class' => 'txp-form-field edit-file-name') 740 ). 741 inputLabel( 742 'download_count', 743 $downloads, 744 '', '', array('class' => 'txp-form-field edit-file-download-count') 745 ). 746 form( 747 (($file_exists) 748 ? inputLabel( 749 'file_status', 750 selectInput('status', $file_statuses, $status, false, '', 'file_status'), 751 'file_status', '', array('class' => 'txp-form-field edit-file-status') 752 ). 753 $created. 754 inputLabel( 755 'file_title', 756 fInput('text', 'title', $title, '', '', '', INPUT_REGULAR, '', 'file_title'), 757 'title', '', array('class' => 'txp-form-field edit-file-title') 758 ). 759 inputLabel( 760 'file_category', 761 event_category_popup('file', $category, 'file_category'). 762 n.eLink('category', 'list', '', '', gTxt('edit'), '', '', '', 'txp-option-link'), 763 'category', '', array('class' => 'txp-form-field edit-file-category') 764 ). 765 // inputLabel( 766 // 'perms', 767 // selectInput('perms', $levels, $permissions), 768 // 'permissions' 769 // ). 770 inputLabel( 771 'file_description', 772 '<textarea id="file_description" name="description" cols="'.INPUT_LARGE.'" rows="'.TEXTAREA_HEIGHT_SMALL.'">'.htmlspecialchars($description, ENT_NOQUOTES).'</textarea>', 773 'description', '', array('class' => 'txp-form-field txp-form-field-textarea edit-file-description') 774 ). 775 pluggable_ui('file_ui', 'extend_detail_form', '', $rs). 776 graf( 777 sLink('file', '', gTxt('cancel'), 'txp-button'). 778 fInput('submit', '', gTxt('save'), 'publish'), 779 array('class' => 'txp-edit-actions') 780 ). 781 hInput('filename', $filename) 782 : (empty($existing_files) 783 ? '' 784 : gTxt('existing_file').selectInput('filename', $existing_files, '', 1) 785 ). 786 pluggable_ui('file_ui', 'extend_detail_form', '', $rs). 787 graf( 788 sLink('file', '', gTxt('cancel'), 'txp-button'). 789 fInput('submit', '', gTxt('save'), 'publish'), 790 array('class' => 'txp-edit-actions') 791 ). 792 hInput('perms', ($permissions == '-1') ? '' : $permissions). 793 hInput(compact('category', 'title', 'description', 'status')) 794 ). 795 eInput('file'). 796 sInput('file_save'). 797 hInput(compact('id', 'sort', 'dir', 'page', 'search_method', 'crit')), 798 '', '', 'post', 'file-detail '.(($file_exists) ? '' : 'not-').'exists', '', (($file_exists) ? 'file_details' : 'assign_file')). 799 n.tag_end('div'); 800 } 801 } 802 803 // ------------------------------------------------------------- 804 805 function file_db_add($filename, $category, $permissions, $description, $size, $title = '') 806 { 807 global $txp_user; 808 809 if (trim($filename) === '') { 810 return false; 811 } 812 813 $qs = quote_list( 814 array('author' => $txp_user) + compact('filename', 'title', 'category', 'permissions', 'description', 'size') 815 ) + array( 816 'created' => 'NOW()', 817 'modified' => 'NOW()', 818 ); 819 820 $rs = safe_insert('txp_file', join_qs($qs, ',')); 821 822 if ($rs) { 823 return $GLOBALS['ID'] = $rs; 824 } 825 826 return false; 827 } 828 829 // ------------------------------------------------------------- 830 831 function file_create() 832 { 833 global $txp_user, $file_base_path; 834 835 require_privs('file.edit.own'); 836 837 extract(array_map('assert_string', gpsa(array( 838 'title', 839 'category', 840 'permissions', 841 'description', 842 )))); 843 844 $filename = array_filter((array) gps('filename')); 845 846 $success = $failed = $notFound = $invalid = $ids = array(); 847 848 foreach ($filename as $file) { 849 $safe_filename = sanitizeForFile($file); 850 if ($safe_filename != $file) { 851 $invalid[] = $file; 852 continue; 853 } 854 855 $size = filesize(build_file_path($file_base_path, $safe_filename)); 856 $id = file_db_add($safe_filename, $category, $permissions, $description, $size, $title); 857 858 if ($id === false) { 859 $failed[] = $safe_filename; 860 } else { 861 $newpath = build_file_path($file_base_path, $safe_filename); 862 863 if (is_file($newpath)) { 864 file_set_perm($newpath); 865 $ids[] = $id; 866 $success[] = $safe_filename; 867 } else { 868 $notFound[] = $safe_filename; 869 } 870 } 871 } 872 873 $messages = array(); 874 875 if ($success) { 876 $messages[] = array(gTxt('linked_to_file', array('{list}' => join(', ', $success))), 0); 877 } 878 879 if ($failed) { 880 $messages[] = array(gTxt('file_upload_failed', array('{list}' => join(', ', $failed))), E_ERROR); 881 } 882 883 if ($notFound) { 884 $messages[] = array(gTxt('file_not_found', array('{list}' => join(', ', $notFound))), E_ERROR); 885 } 886 887 if ($invalid) { 888 $messages[] = array(gTxt('invalid_filename', array('{list}' => join(', ', $invalid))), E_ERROR); 889 } 890 891 if ($ids) { 892 now('created', true); 893 update_lastmod('file_created', compact('ids', 'filename', 'title', 'category', 'description')); 894 } 895 896 $response = ''; 897 898 foreach ($messages as $message) { 899 $response .= 'textpattern.Console.addMessage('.json_encode($message, TEXTPATTERN_JSON).');'.n; 900 } 901 902 script_js($response, false); 903 file_list(); 904 } 905 906 // ------------------------------------------------------------- 907 908 function file_insert() 909 { 910 global $txp_user, $file_base_path, $file_max_upload_size, $app_mode; 911 912 require_privs('file.edit.own'); 913 $messages = $ids = array(); 914 $fileshandler = Txp::get('\Textpattern\Server\Files'); 915 $files = $fileshandler->refactor($_FILES['thefile']); 916 $titles = gps('title'); 917 918 extract(array_map('assert_string', gpsa(array( 919 'category', 920 'permissions', 921 'description', 922 )))); 923 924 foreach ($files as $i => $file) { 925 $chunked = $fileshandler->dechunk($file); 926 extract($file); 927 $newname = sanitizeForFile($name); 928 $newpath = build_file_path($file_base_path, $newname); 929 930 if ($error || !$size && $chunked) { 931 $messages[] = array(gTxt('file_upload_failed', array('{list}' => $newname))." - ".upload_get_errormsg($error ? $error : UPLOAD_ERR_PARTIAL), E_ERROR); 932 } elseif ($file_max_upload_size < $size) { 933 $messages[] = array(gTxt('file_upload_failed', array('{list}' => $newname))." - ".upload_get_errormsg(UPLOAD_ERR_FORM_SIZE), E_ERROR); 934 } elseif (!is_file($newpath) && !safe_count('txp_file', "filename = '".doSlash($newname)."'")) { 935 $hash = isset($titles[$i]) ? $i : md5($name); 936 $title = isset($titles[$hash]) ? $titles[$hash] : ''; 937 $id = file_db_add($newname, $category, $permissions, $description, $size, $title); 938 939 if (!$id) { 940 $messages[] = array(gTxt('file_upload_failed', array('{list}' => $newname)).' (db_add)', E_ERROR); 941 } else { 942 $id = assert_int($id); 943 944 if (!shift_uploaded_file($tmp_name, $newpath)) { 945 safe_delete('txp_file', "id = '$id'"); 946 safe_alter('txp_file', "auto_increment = '$id'"); 947 $messages[] = array(gTxt('directory_permissions', array('{path}' => $newpath)), E_ERROR); 948 } else { 949 file_set_perm($newpath); 950 $ids[] = $GLOBALS['ID'] = $id; 951 $messages[] = array(gTxt('file_uploaded', array('{name}' => href(txpspecialchars($newname), '?event=file&step=file_edit&id='.$id, array('title' => gTxt('edit')))), false), 0); 952 } 953 } 954 } else { 955 $messages[] = array(gTxt('file_already_exists', array('{name}' => $newname)), E_WARNING); 956 } 957 958 // Clean up file. 959 @unlink($tmp_name); 960 } 961 962 if ($ids) { 963 update_lastmod('file_uploaded', compact('ids', 'title', 'category', 'description')); 964 now('created', true); 965 } 966 967 if ($app_mode == 'async') { 968 $response = $ids ? 'textpattern.Relay.data.fileid = ["'.implode('","', $ids).'"].concat(textpattern.Relay.data.fileid || []);'.n : ''; 969 970 foreach ($messages as $message) { 971 $response .= 'textpattern.Console.addMessage('.json_encode($message, TEXTPATTERN_JSON).', "uploadEnd");'.n; 972 } 973 974 send_script_response($response); 975 976 // Bail out. 977 return; 978 } 979 980 $status = $ids ? (count($ids) < count($messages) ? E_WARNING : 0) : E_ERROR; 981 $message = array(); 982 983 foreach ($messages as $row) { 984 $message[] = $row[0]; 985 } 986 987 $messages = implode(br, $message); 988 989 if ($ids && count($files) == 1) { 990 file_edit(array($messages, $status), $ids[0]); 991 } else { 992 unset($GLOBALS['ID']); 993 file_list($files ? array($messages, $status) : '', $ids); 994 } 995 } 996 997 // ------------------------------------------------------------- 998 999 function file_replace() 1000 { 1001 global $txp_user, $file_base_path; 1002 1003 $id = assert_int(gps('id')); 1004 $rs = safe_row("filename, author", 'txp_file', "id = '$id'"); 1005 1006 if (!$rs) { 1007 file_list(array(gTxt('invalid_id', array('{id}' => $id)), E_ERROR)); 1008 1009 return; 1010 } 1011 1012 extract($rs); 1013 $filename = sanitizeForFile($filename); 1014 1015 if (!has_privs('file.edit') && !($author === $txp_user && has_privs('file.edit.own'))) { 1016 require_privs(); 1017 } 1018 1019 $file = file_get_uploaded(); 1020 $name = file_get_uploaded_name(); 1021 1022 if ($file === false) { 1023 // Could not get uploaded file. 1024 file_list(array(gTxt('file_upload_failed')." $name ".upload_get_errormsg($_FILES['thefile']['error']), E_ERROR)); 1025 1026 return; 1027 } 1028 1029 if (!$filename) { 1030 file_list(array(gTxt('invalid_filename'), E_ERROR)); 1031 } else { 1032 $newpath = build_file_path($file_base_path, $filename); 1033 1034 if (is_file($newpath)) { 1035 rename($newpath, $newpath.'.tmp'); 1036 } 1037 1038 if (!shift_uploaded_file($file, $newpath)) { 1039 file_list(array(gTxt('directory_permissions', array('{path}' => $newpath)), E_ERROR)); 1040 1041 // Rename tmp back. 1042 rename($newpath.'.tmp', $newpath); 1043 1044 // Remove tmp upload. 1045 unlink($file); 1046 } else { 1047 file_set_perm($newpath); 1048 update_lastmod('file_replaced', compact('id', 'filename')); 1049 now('created', true); 1050 1051 if ($size = filesize($newpath)) { 1052 safe_update('txp_file', "size = $size, modified = NOW()", "id = '$id'"); 1053 } 1054 1055 file_edit(gTxt('file_uploaded', array('{name}' => $name)), $id); 1056 1057 // Clean up old. 1058 if (is_file($newpath.'.tmp')) { 1059 unlink($newpath.'.tmp'); 1060 } 1061 } 1062 } 1063 } 1064 1065 // ------------------------------------------------------------- 1066 1067 function file_save() 1068 { 1069 global $file_base_path, $file_statuses, $txp_user; 1070 1071 $varray = array_map('assert_string', gpsa(array( 1072 'id', 1073 'category', 1074 'title', 1075 'description', 1076 'status', 1077 'publish_now', 1078 'year', 1079 'month', 1080 'day', 1081 'hour', 1082 'minute', 1083 'second', 1084 ))); 1085 1086 extract(doSlash($varray)); 1087 $filename = $varray['filename'] = sanitizeForFile(gps('filename')); 1088 1089 if ($filename == '') { 1090 file_list(array(gTxt('file_not_updated', array('{name}' => $filename)), E_ERROR)); 1091 1092 return; 1093 } 1094 1095 $id = $varray['id'] = assert_int($id); 1096 $permissions = gps('perms'); 1097 1098 if (is_array($permissions)) { 1099 asort($permissions); 1100 $permissions = implode(",", $permissions); 1101 } 1102 1103 $varray['permissions'] = $permissions; 1104 $perms = doSlash($permissions); 1105 $rs = safe_row("filename, author", 'txp_file', "id = '$id'"); 1106 1107 if (!has_privs('file.edit') && !($rs['author'] === $txp_user && has_privs('file.edit.own'))) { 1108 require_privs(); 1109 } 1110 1111 $old_filename = $varray['old_filename'] = sanitizeForFile($rs['filename']); 1112 1113 if ($old_filename != false && strcmp($old_filename, $filename) != 0) { 1114 $old_path = build_file_path($file_base_path, $old_filename); 1115 $new_path = build_file_path($file_base_path, $filename); 1116 1117 if (file_exists($old_path) && shift_uploaded_file($old_path, $new_path) === false) { 1118 file_list(array(gTxt('file_cannot_rename', array('{name}' => $filename)), E_ERROR)); 1119 1120 return; 1121 } else { 1122 file_set_perm($new_path); 1123 } 1124 } 1125 1126 $created_ts = @safe_strtotime($year.'-'.$month.'-'.$day.' '.$hour.':'.$minute.':'.$second); 1127 1128 if ($publish_now) { 1129 $created = "NOW()"; 1130 } elseif ($created_ts > 0) { 1131 $created = "FROM_UNIXTIME('".$created_ts."')"; 1132 } else { 1133 $created = ''; 1134 } 1135 1136 $size = filesize(build_file_path($file_base_path, $filename)); 1137 1138 $constraints = array( 1139 'category' => new CategoryConstraint(gps('category'), array('type' => 'file')), 1140 'status' => new ChoiceConstraint(gps('status'), array( 1141 'choices' => array_keys($file_statuses), 1142 'message' => 'invalid_status', 1143 )), 1144 ); 1145 callback_event_ref('file_ui', 'validate_save', 0, $varray, $constraints); 1146 $validator = new Validator($constraints); 1147 1148 $rs = $validator->validate() && safe_update('txp_file', " 1149 filename = '".doSlash($filename)."', 1150 title = '$title', 1151 category = '$category', 1152 permissions = '$perms', 1153 description = '$description', 1154 status = '$status', 1155 size = '$size', 1156 modified = NOW()" 1157 .($created ? ", created = $created" : ''), "id = '$id'"); 1158 1159 if (!$rs) { 1160 // Update failed, rollback name. 1161 if (isset($old_path) && shift_uploaded_file($new_path, $old_path) === false) { 1162 file_list(array(gTxt('file_unsynchronized', array('{name}' => $filename)), E_ERROR)); 1163 1164 return; 1165 } else { 1166 file_list(array(gTxt('file_not_updated', array('{name}' => $filename)), E_ERROR)); 1167 1168 return; 1169 } 1170 } 1171 1172 update_lastmod('file_saved', compact('id', 'filename', 'title', 'category', 'description', 'status', 'size')); 1173 now('created', true); 1174 file_list(gTxt('file_updated', array('{name}' => $filename))); 1175 } 1176 1177 // ------------------------------------------------------------- 1178 1179 function file_delete($ids = array()) 1180 { 1181 global $file_base_path, $txp_user; 1182 1183 // Fetch ids and remove bogus (false) entries to prevent SQL syntax errors being thrown. 1184 $ids = $ids ? array_map('assert_int', $ids) : array(assert_int(ps('id'))); 1185 $ids = array_filter($ids); 1186 1187 if (!has_privs('file.delete')) { 1188 if ($ids && has_privs('file.delete.own')) { 1189 $ids = safe_column("id", 'txp_file', "id IN (".join(',', $ids).") AND author = '".doSlash($txp_user)."'"); 1190 } else { 1191 $ids = array(); 1192 } 1193 } 1194 1195 if (!empty($ids)) { 1196 $fail = array(); 1197 1198 $rs = safe_rows_start("id, filename", 'txp_file', "id IN (".join(',', $ids).")"); 1199 1200 if ($rs) { 1201 while ($a = nextRow($rs)) { 1202 extract($a); 1203 $id = assert_int($id); 1204 $filepath = build_file_path($file_base_path, $filename); 1205 1206 // Notify plugins of pending deletion, pass file's id and path. 1207 callback_event('file_deleted', '', false, $id, $filepath); 1208 1209 $rsd = safe_delete('txp_file', "id = '$id'"); 1210 $ul = false; 1211 1212 if ($rsd && is_file($filepath)) { 1213 $ul = unlink($filepath); 1214 } 1215 1216 if (!$rsd or !$ul) { 1217 $fail[] = $id; 1218 } 1219 } 1220 if ($fail) { 1221 file_list(array(gTxt('file_delete_failed', array('{list}' => join(', ', $fail))), E_ERROR)); 1222 1223 return; 1224 } else { 1225 update_lastmod('file_deleted', $ids); 1226 now('created', true); 1227 file_list(gTxt('file_deleted', array('{name}' => join(', ', $ids)))); 1228 1229 return; 1230 } 1231 } else { 1232 file_list(array(gTxt('file_not_found', array('{list}' => join(', ', $ids))), E_ERROR)); 1233 1234 return; 1235 } 1236 } 1237 file_list(); 1238 } 1239 1240 // ------------------------------------------------------------- 1241 1242 function file_get_uploaded_name() 1243 { 1244 return $_FILES['thefile']['name']; 1245 } 1246 1247 // ------------------------------------------------------------- 1248 1249 function file_get_uploaded() 1250 { 1251 return get_uploaded_file($_FILES['thefile']['tmp_name']); 1252 } 1253 1254 // ------------------------------------------------------------- 1255 1256 function file_set_perm($file) 1257 { 1258 return @chmod($file, 0644); 1259 } 1260 1261 /** 1262 * Renders a specific file upload form. 1263 * 1264 * @param string $label File name label. May be empty 1265 * @param string $pophelp Help item 1266 * @param string $step Step 1267 * @param string $id File id 1268 * @param string $label_id HTML id attribute for the filename input element 1269 * @param string $class HTML class attribute for the form element 1270 * @param string|array $wraptag_val Tag to wrap the value / label in, or empty to omit 1271 * @param array $extra array('postinput' => $categories ...) 1272 * @param string|array $accept Comma separated list of allowed file types, or empty to omit 1273 * @return string HTML 1274 */ 1275 1276 function file_upload_form($label, $pophelp, $step, $id = '', $label_id = '', $class = '', $wraptag_val = array('div', 'div'), $extra = null, $accept = '') 1277 { 1278 global $file_max_upload_size; 1279 1280 if (!$file_max_upload_size || intval($file_max_upload_size) == 0) { 1281 $file_max_upload_size = 2 * (1024 * 1024); 1282 } 1283 1284 $max_file_size = (intval($file_max_upload_size) == 0) ? '' : intval($file_max_upload_size); 1285 1286 return upload_form($label, $pophelp, $step, 'file', $id, $max_file_size, $label_id, $class, $wraptag_val, $extra, $accept); 1287 } 1288 1289 // ------------------------------------------------------------- 1290 1291 function file_change_pageby() 1292 { 1293 Txp::get('\Textpattern\Admin\Paginator')->change(); 1294 file_list(); 1295 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
title