Textpattern PHP Cross Reference Content Management Systems

Source: /textpattern/include/txp_file.php - 1295 lines - 44983 bytes - Summary - Text - Print

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('&#124;', 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('&#124;', array('role' => 'separator')).
 446                              sp.popTag($tagName, 'Textpattern', array('type' => 'textpattern') + $tag_url).
 447                              sp.span('&#124;', 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

title

Description

title

Description

title

title

Body