Subversion Repositories web.creative

Rev

Blame | Last modification | View Log | Download

<?php namespace ProcessWire;

/**
 * Recent pages list for admin
 *
 * ProcessWire 3.x, Copyright 2016 by Ryan Cramer
 * 
 * @todo: make everything configurable
 * 
 * https://processwire.com
 * 
 * Translatable labels:
 *
 * __('Edited');
 * __('Created');
 * __('Edited by me');
 * __('Created by me');
 * __('Add another'); 
 *
 */

class ProcessRecentPages extends Process {

  public static function getModuleInfo() {
    return array(
      'title' => 'Recent Pages', 
      'summary' => 'Shows a list of recently edited pages in your admin.', 
      'version' => 2, 
      'author' => 'Ryan Cramer', 
      'href' => 'http://modules.processwire.com/', 
      'icon' => 'clock-o',
      'permission' => 'page-edit-recent', 
      'permissions' => array(
        'page-edit-recent' => 'Can see recently edited pages'
        ),
      'page' => array(
        'name' => 'recent-pages', 
        'parent' => 'page',
        'title' => 'Recent', 
        ),
      'useNavJSON' => true, 
      'nav' => array(
        array(
          'url' => '?edited=1',
          'label' => 'Edited',
          'icon' => 'users',
          'navJSON' => 'navJSON/?edited=1',
        ),
        array(
          'url' => '?added=1',
          'label' => 'Created',
          'icon' => 'users',
          'navJSON' => 'navJSON/?added=1',
        ),
        array(
          'url' => '?edited=1&me=1', 
          'label' => 'Edited by me', 
          'icon' => 'user',
          'navJSON' => 'navJSON/?edited=1&me=1', 
        ),
        array(
          'url' => '?added=1&me=1', 
          'label' => 'Created by me',
          'icon' => 'user',
          'navJSON' => 'navJSON/?added=1&me=1', 
        ),
        array(
          'url' => 'another/',
          'label' => 'Add another',
          'icon' => 'plus-circle',
          'navJSON' => 'anotherNavJSON/',
        )
      )
    );
  }

  /**
   * Shared translation labels
   * 
   * @var array
   * 
   */
  protected $labels = array();

  /**
   * Oldest date to match or pages (hopefully equal to site installation date)
   * 
   * @var int
   * 
   */
  protected $oldestDate = 0;
  
  public function __construct() {
    $this->set('itemLimit', 15); 
  }
  
  public function init() {
    $this->addHookProperty('Page::recentTimeStr', $this, 'hookPageRecentTimeStr'); 
    parent::init();
    $this->labels['nothing'] = $this->_('No pages match (yet)'); 
    $this->oldestDate = (int) $this->wire('config')->installed; 
    if(!$this->oldestDate) $this->oldestDate = @filemtime($this->wire('config')->paths->assets . 'installed.php'); 
  }

  /**
   * Add a 'recentTimeStr' property to all Page objects while this Process is active
   * 
   * @param HookEvent $event
   * 
   */
  public function hookPageRecentTimeStr(HookEvent $event) { 
    $page = $event->object; 
    $time = $this->getSort() == '-created' ? $page->created : $page->modified; 
    $event->return = wireRelativeTimeStr($time, true, false); 
  }

  /**
   * Provides output for recent edited/added nav options ajax calls
   * 
   * @param array $options
   * @return string
   * 
   */
  public function ___executeNavJSON(array $options = array()) {

    $options['add'] = '';
    $options['iconKey'] = 'template.icon';
    $options['itemLabel'] = 'title|name';
    $options['itemLabel2'] = 'recentTimeStr';
    $options['sort'] = false; 

    $selector = str_replace(',,', ',', $this->getSelectorString());
    
    $items = $this->wire('pages')->find("$selector, limit=$this->itemLimit"); 
    if(!$this->wire('user')->isSuperuser()) foreach($items as $item) {
      if(!$item->editable()) $items->remove($item); 
    }
    $options['items'] = $items; 

    return parent::___executeNavJSON($options); 
  }

  /**
   * Update selector string to exclude things we won't want showing in any of our results
   * 
   * @param string $selector
   * 
   */
  protected function updateSelectorString(&$selector) {
    
    $adminID = $this->wire('config')->adminRootPageID; 
    $selector .= ", has_parent!=$adminID";
    
    if($this->wire('modules')->isInstalled('FieldtypePageTable')) {
      $this->wire('modules')->get('FieldtypePageTable');
      $skipTemplates = array();
      $skipParents = array();
      foreach($this->wire('fields') as $field) {
        if(!$field->type instanceof FieldtypePageTable) continue;
        if(!empty($field->parent_id)) $skipParents[] = $field->parent_id;
        if(!empty($field->template_id)) {
          $value = $field->template_id; 
          if(is_array($value)) $skipTemplates = array_merge($skipTemplates, $value);
            else $skipTemplates[] = $value;
        }
      }
      if(count($skipTemplates)) $selector .= ", template!=" . implode('|', $skipTemplates); 
      if(count($skipParents)) $selector .= ", parent_id!=" . implode('|', $skipParents); 
    }
  }
  
  /**
   * Get selector string to find pages for navJSON (not used by 'add another')
   *
   * @return string
   *
   */
  protected function getSelectorString() {

    $sort = $this->getSort();
    $selector = "include=unpublished, sort=$sort";
    
    $this->updateSelectorString($selector); 

    if($this->input->get('me')) {
      if($sort == '-modified') {
        $selector .= ",,modified_users_id=$this->user, modified>=$this->oldestDate";
      } else if($sort == '-created') {
        $selector .= ",,created_users_id=$this->user, created>=$this->oldestDate";
      }
    }

    return $selector;
  }

  /**
   * Get the current sort for navJSON (not used by 'add another')
   *
   * @return string
   *
   */
  protected function getSort() {
    if($this->input->get('added')) {
      return "-created";
    } else {
      return "-modified";
    }
  }

  /**
   * Provide the interactive view for when user clicks on Added/Edited menu items rather than pages
   *
   * This basically redirects to a Lister that shows the pages.
   *
   */
  public function ___execute() {

    $selector = $this->getSelectorString();
    $useLister = false;
    $out = '';

    if($useLister) {
      if(strpos($selector, ',,') !== false) {
        list($initSelector, $defaultSelector) = explode(',,', $selector);
      } else {
        $initSelector = $selector;
        $defaultSelector = '';
      }
      $a = array(
        'initSelector' => $initSelector,
        'defaultSelector' => $defaultSelector,
        'defaultSort' => $this->getSort(),
      );
      $url = ProcessPageLister::addSessionBookmark('recent-pages', $a);
      if($url) {
        $this->session->redirect($url);
      } else {
        $this->error($this->_('This feature requires page-lister permission'));
      }
    } else {
      $table = $this->wire('modules')->get('MarkupAdminDataTable');
      $table->setEncodeEntities(false);
      $table->headerRow(array(
        $this->_x('Page', 'th'),
        $this->_x('Parent', 'th'),
        $this->_x('Template', 'th'),
        $this->_x('Created', 'th'),
        $this->_x('Modified', 'th')
      ));
      $items = $this->wire('pages')->find("$selector, limit=$this->itemLimit"); 
      $sanitizer = $this->wire('sanitizer');
      $title = $this->_('Recent pages');
      foreach($items as $item) {
        if(!$item->editable()) continue;  
        $created = $item->created;
        $modified = $item->modified;
        if($created < $this->oldestDate) $created = $this->oldestDate; 
        if($modified < $this->oldestDate) $modified = $this->oldestDate; 
        $table->row(array(
          "<!--$item->id-->" . $sanitizer->entities1($item->get('title|name')) => $item->editURL, 
          $sanitizer->entities1($item->parent->get('title|name')), 
          $sanitizer->entities1($item->template->getLabel()), 
          "<span class='sort-date'>$created</span>" . wireRelativeTimeStr($created), 
          "<span class='sort-date'>$modified</span>" . wireRelativeTimeStr($modified)
        ));
        $me = $this->input->get('me'); 
        if($this->getSort() == '-created') {
          if($me) $title = sprintf($this->_('Pages recently created by %s'), $this->wire('user')->name); 
            else $title = $this->_('Recently created pages');
        } else {
          if($me) $title = sprintf($this->_('Pages recently edited by %s'), $this->wire('user')->name); 
            else $title = $this->_('Recently edited pages'); 
        }
      }
      $out .= "<h2>$title</h2>";
      if(count($items)) { 
        $out .= $table->render();
      } else {
        $out .= "<p>" . $this->labels['nothing'] . "</p>";
      } 
    }
    return $out; 
  }

  /**
   * The /edit/ option redirects to the page editor for the given page ID
   *
   * @throws WireException On invalid page ID
   *
   */
  public function ___executeEdit() {
    $id = (int) $this->input->get('id');
    if(!$id) throw new WireException("No page ID");
    $this->session->redirect("../edit/?id=$id");
  }


  /**
   * Returns array of 'add another' pages
   * 
   * There is only one of each parent/template combination here. 
   * Each returned page has two additional properties populated:
   *  - _addAnotherURL: URL to add another of the same type
   *  - _addAnotherLabel: Recommended label for links
   * 
   * @param int $limit Max items to include
   * @param string|int $oldest Timestamp or strtotime() compatible oldest date to retrieve from
   * @param int $userID When omitted, current user assumed
   * @return array of Page objects
   * 
   */
  public function getAddAnotherNavItems($limit = 10, $oldest = 0, $userID = 0) {
    
    $items = array();
    if(empty($oldest)) $oldest = $this->oldestDate; 
      else if(!ctype_digit($oldest)) $oldest = strtotime($oldest);
    if(!$userID) $userID = $this->wire('user')->id;
    $n = 0;
    $_selector = "include=unpublished, created_users_id=$userID, created>=$oldest, sort=-created, limit=50";
    $this->updateSelectorString($_selector); 

    do {
      $selector = $_selector; 
      if(count($items)) {
        $selector .= ", id!=";
        foreach($items as $item) $selector .= $item->id . '|';
        $selector = rtrim($selector, '|');
      }
      
      $matches = $this->wire('pages')->find($selector);
      foreach($matches as $item) {
        
        if(!$item->editable() || !$item->parent->addable()) continue;
        if($item->template->noParents) continue; 
        if($item->parent->template->noChildren) continue; 
        
        $childTemplates = $item->parent->template->childTemplates; 
        if(count($childTemplates) && !in_array($item->template->id, $childTemplates)) continue; 
        
        $parentTemplates = $item->template->parentTemplates; 
        if(count($parentTemplates) && !in_array($item->parent->template->id, $parentTemplates)) continue;
        
        $key = $item->parent->id . "-" . $item->template->id; // limit to 1 parent-template match
        if(isset($items[$key])) continue;
        
        $url = $this->wire('config')->urls->admin . "page/add/?parent_id=$item->parent_id&template_id={$item->template->id}"; 
        $label = $this->sanitizer->entities($item->template->getLabel());
        $label .= ' <small>' . sprintf($this->_('in %s'), $item->parent->get('title|name')) . '</small>';
        $item->set('_addAnotherURL', $url);
        $item->set('_addAnotherLabel', $label); 
        
        $items[$key] = $item;
      }
    } while(count($items) < $limit && (++$n < 10));
    
    return $items;
  }

  /**
   * Provides the navJSON data for the 'add another' menu item
   * 
   * @return string
   * 
   */
  public function ___executeAnotherNavJSON() {
    
    $data = array(
      'url' => '',
      'label' => $this->_((string) $this->page->get('title|name')),
      'icon' => '',
      'list' => array(),
    );
  
    $items = $this->getAddAnotherNavItems();
    foreach($items as $item) {
      $data['list'][] = array(
        'url' => $item->_addAnotherURL, 
        'label' => $item->_addAnotherLabel,
        'icon' => $item->template->getIcon(), 
      );
    }
    if(!count($items)) {
      $data['list'][] = array(
        'url' => $this->wire('config')->urls->admin, 
        'label' => $this->labels['nothing'], 
      );
    }
    
    if($this->wire('config')->ajax) header("Content-Type: application/json");
    return json_encode($data);
  }

  /**
   * Outputs a table of 'add another' pages, for when user clicks on the 'Add another' text rather than a page item
   * 
   * @return string
   * 
   */
  public function ___executeAnother() {
    $items = $this->getAddAnotherNavItems();
    if(!count($items)) return "<h2>" . $this->labels['nothing'] . "</h2>";
    $this->headline($this->_('Add another'));
    $table = $this->wire('modules')->get('MarkupAdminDataTable'); 
    $table->headerRow(array(
      $this->_x('Template', 'th'), 
      $this->_x('Parent', 'th'),
      $this->_x('Last Created', 'th')
    ));
    foreach($items as $item) {
      $table->row(array(
        $item->template->getLabel() => $item->_addAnotherURL,
        $item->parent->get('title|name'),
        wireRelativeTimeStr($item->created)
      ));
    }
    return "<h2>" . $this->_('Click any item to add another of the same type') . "</h2>" . $table->render();
  }

}