[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/applications/phriction/controller/ -> PhrictionDocumentController.php (source)

   1  <?php
   2  
   3  final class PhrictionDocumentController
   4    extends PhrictionController {
   5  
   6    private $slug;
   7  
   8    public function shouldAllowPublic() {
   9      return true;
  10    }
  11  
  12    public function willProcessRequest(array $data) {
  13      $this->slug = $data['slug'];
  14    }
  15  
  16    public function processRequest() {
  17      $request = $this->getRequest();
  18      $user = $request->getUser();
  19  
  20      $slug = PhabricatorSlug::normalize($this->slug);
  21      if ($slug != $this->slug) {
  22        $uri = PhrictionDocument::getSlugURI($slug);
  23        // Canonicalize pages to their one true URI.
  24        return id(new AphrontRedirectResponse())->setURI($uri);
  25      }
  26  
  27      require_celerity_resource('phriction-document-css');
  28  
  29      $document = id(new PhrictionDocumentQuery())
  30        ->setViewer($user)
  31        ->withSlugs(array($slug))
  32        ->executeOne();
  33  
  34      $version_note = null;
  35      $core_content = '';
  36      $move_notice = '';
  37      $properties = null;
  38  
  39      if (!$document) {
  40  
  41        $document = PhrictionDocument::initializeNewDocument($user, $slug);
  42  
  43        $create_uri = '/phriction/edit/?slug='.$slug;
  44  
  45        $notice = new AphrontErrorView();
  46        $notice->setSeverity(AphrontErrorView::SEVERITY_NODATA);
  47        $notice->setTitle(pht('No content here!'));
  48        $notice->appendChild(
  49          pht(
  50            'No document found at %s. You can <strong>'.
  51              '<a href="%s">create a new document here</a></strong>.',
  52            phutil_tag('tt', array(), $slug),
  53            $create_uri));
  54        $core_content = $notice;
  55  
  56        $page_title = pht('Page Not Found');
  57      } else {
  58        $version = $request->getInt('v');
  59        if ($version) {
  60          $content = id(new PhrictionContent())->loadOneWhere(
  61            'documentID = %d AND version = %d',
  62            $document->getID(),
  63            $version);
  64          if (!$content) {
  65            return new Aphront404Response();
  66          }
  67  
  68          if ($content->getID() != $document->getContentID()) {
  69            $vdate = phabricator_datetime($content->getDateCreated(), $user);
  70            $version_note = new AphrontErrorView();
  71            $version_note->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
  72            $version_note->appendChild(
  73              pht('You are viewing an older version of this document, as it '.
  74              'appeared on %s.', $vdate));
  75          }
  76        } else {
  77          $content = id(new PhrictionContent())->load($document->getContentID());
  78        }
  79        $page_title = $content->getTitle();
  80  
  81        $properties = $this
  82          ->buildPropertyListView($document, $content, $slug);
  83  
  84        $doc_status = $document->getStatus();
  85        $current_status = $content->getChangeType();
  86        if ($current_status == PhrictionChangeType::CHANGE_EDIT ||
  87          $current_status == PhrictionChangeType::CHANGE_MOVE_HERE) {
  88  
  89          $core_content = $content->renderContent($user);
  90        } else if ($current_status == PhrictionChangeType::CHANGE_DELETE) {
  91          $notice = new AphrontErrorView();
  92          $notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
  93          $notice->setTitle(pht('Document Deleted'));
  94          $notice->appendChild(
  95            pht('This document has been deleted. You can edit it to put new '.
  96            'content here, or use history to revert to an earlier version.'));
  97          $core_content = $notice->render();
  98        } else if ($current_status == PhrictionChangeType::CHANGE_STUB) {
  99          $notice = new AphrontErrorView();
 100          $notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
 101          $notice->setTitle(pht('Empty Document'));
 102          $notice->appendChild(
 103            pht('This document is empty. You can edit it to put some proper '.
 104            'content here.'));
 105          $core_content = $notice->render();
 106        } else if ($current_status == PhrictionChangeType::CHANGE_MOVE_AWAY) {
 107          $new_doc_id = $content->getChangeRef();
 108  
 109          $slug_uri = null;
 110  
 111          // If the new document exists and the viewer can see it, provide a link
 112          // to it. Otherwise, render a generic message.
 113          $new_docs = id(new PhrictionDocumentQuery())
 114            ->setViewer($user)
 115            ->withIDs(array($new_doc_id))
 116            ->execute();
 117          if ($new_docs) {
 118            $new_doc = head($new_docs);
 119            $slug_uri = PhrictionDocument::getSlugURI($new_doc->getSlug());
 120          }
 121  
 122          $notice = id(new AphrontErrorView())
 123            ->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
 124  
 125          if ($slug_uri) {
 126            $notice->appendChild(
 127              phutil_tag(
 128                'p',
 129                array(),
 130                pht(
 131                  'This document has been moved to %s. You can edit it to put '.
 132                  'new content here, or use history to revert to an earlier '.
 133                  'version.',
 134                  phutil_tag('a', array('href' => $slug_uri), $slug_uri))));
 135          } else {
 136            $notice->appendChild(
 137              phutil_tag(
 138                'p',
 139                array(),
 140                pht(
 141                  'This document has been moved. You can edit it to put new '.
 142                  'contne here, or use history to revert to an earlier '.
 143                  'version.')));
 144          }
 145  
 146          $core_content = $notice->render();
 147        } else {
 148          throw new Exception("Unknown document status '{$doc_status}'!");
 149        }
 150  
 151        $move_notice = null;
 152        if ($current_status == PhrictionChangeType::CHANGE_MOVE_HERE) {
 153          $from_doc_id = $content->getChangeRef();
 154  
 155          $slug_uri = null;
 156  
 157          // If the old document exists and is visible, provide a link to it.
 158          $from_docs = id(new PhrictionDocumentQuery())
 159            ->setViewer($user)
 160            ->withIDs(array($from_doc_id))
 161            ->execute();
 162          if ($from_docs) {
 163            $from_doc = head($from_docs);
 164            $slug_uri = PhrictionDocument::getSlugURI($from_doc->getSlug());
 165          }
 166  
 167          $move_notice = id(new AphrontErrorView())
 168            ->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
 169  
 170          if ($slug_uri) {
 171            $move_notice->appendChild(
 172              pht(
 173                'This document was moved from %s.',
 174                phutil_tag('a', array('href' => $slug_uri), $slug_uri)));
 175          } else {
 176            // Render this for consistency, even though it's a bit silly.
 177            $move_notice->appendChild(
 178              pht('This document was moved from elsewhere.'));
 179          }
 180        }
 181      }
 182  
 183      $children = $this->renderDocumentChildren($slug);
 184  
 185      $actions = $this->buildActionView($user, $document);
 186  
 187      $crumbs = $this->buildApplicationCrumbs();
 188      $crumbs->setActionList($actions);
 189      $crumb_views = $this->renderBreadcrumbs($slug);
 190      foreach ($crumb_views as $view) {
 191        $crumbs->addCrumb($view);
 192      }
 193  
 194      $header = id(new PHUIHeaderView())
 195        ->setUser($user)
 196        ->setPolicyObject($document)
 197        ->setHeader($page_title);
 198  
 199      $prop_list = null;
 200      if ($properties) {
 201        $prop_list = new PHUIPropertyGroupView();
 202        $prop_list->addPropertyList($properties);
 203      }
 204  
 205      $page_content = id(new PHUIDocumentView())
 206        ->setOffset(true)
 207        ->setFontKit(PHUIDocumentView::FONT_SOURCE_SANS)
 208        ->setHeader($header)
 209        ->appendChild(
 210          array(
 211            $actions,
 212            $prop_list,
 213            $version_note,
 214            $move_notice,
 215            $core_content,
 216          ));
 217  
 218      $core_page = phutil_tag(
 219        'div',
 220          array(
 221            'class' => 'phriction-offset',
 222          ),
 223          array(
 224            $page_content,
 225            $children,
 226          ));
 227  
 228      return $this->buildApplicationPage(
 229        array(
 230          $crumbs->render(),
 231          $core_page,
 232        ),
 233        array(
 234          'pageObjects' => array($document->getPHID()),
 235          'title'   => $page_title,
 236        ));
 237  
 238    }
 239  
 240    private function buildPropertyListView(
 241      PhrictionDocument $document,
 242      PhrictionContent $content,
 243      $slug) {
 244  
 245      $viewer = $this->getRequest()->getUser();
 246      $view = id(new PHUIPropertyListView())
 247        ->setUser($viewer)
 248        ->setObject($document);
 249  
 250      $phids = array($content->getAuthorPHID());
 251  
 252      $this->loadHandles($phids);
 253  
 254      $view->addProperty(
 255        pht('Last Author'),
 256        $this->getHandle($content->getAuthorPHID())->renderLink());
 257  
 258      $age = time() - $content->getDateCreated();
 259      $age = floor($age / (60 * 60 * 24));
 260      if ($age < 1) {
 261        $when = pht('Today');
 262      } else if ($age == 1) {
 263        $when = pht('Yesterday');
 264      } else {
 265        $when = pht('%d Days Ago', $age);
 266      }
 267      $view->addProperty(pht('Last Updated'), $when);
 268  
 269      return $view;
 270    }
 271  
 272    private function buildActionView(
 273      PhabricatorUser $user,
 274      PhrictionDocument $document) {
 275      $can_edit = PhabricatorPolicyFilter::hasCapability(
 276        $user,
 277        $document,
 278        PhabricatorPolicyCapability::CAN_EDIT);
 279  
 280      $slug = PhabricatorSlug::normalize($this->slug);
 281  
 282      $action_view = id(new PhabricatorActionListView())
 283        ->setUser($user)
 284        ->setObjectURI($this->getRequest()->getRequestURI())
 285        ->setObject($document);
 286  
 287      if (!$document->getID()) {
 288        return $action_view->addAction(
 289          id(new PhabricatorActionView())
 290            ->setName(pht('Create This Document'))
 291            ->setIcon('fa-plus-square')
 292            ->setHref('/phriction/edit/?slug='.$slug));
 293      }
 294  
 295      $action_view->addAction(
 296        id(new PhabricatorActionView())
 297          ->setName(pht('Edit Document'))
 298          ->setIcon('fa-pencil')
 299          ->setHref('/phriction/edit/'.$document->getID().'/'));
 300  
 301      if ($document->getStatus() == PhrictionDocumentStatus::STATUS_EXISTS) {
 302        $action_view->addAction(
 303          id(new PhabricatorActionView())
 304            ->setName(pht('Move Document'))
 305            ->setIcon('fa-arrows')
 306            ->setHref('/phriction/move/'.$document->getID().'/')
 307            ->setWorkflow(true));
 308  
 309        $action_view->addAction(
 310          id(new PhabricatorActionView())
 311            ->setName(pht('Delete Document'))
 312            ->setIcon('fa-times')
 313            ->setHref('/phriction/delete/'.$document->getID().'/')
 314            ->setWorkflow(true));
 315      }
 316  
 317      return
 318        $action_view->addAction(
 319          id(new PhabricatorActionView())
 320          ->setName(pht('View History'))
 321          ->setIcon('fa-list')
 322          ->setHref(PhrictionDocument::getSlugURI($slug, 'history')));
 323    }
 324  
 325    private function renderDocumentChildren($slug) {
 326  
 327      $d_child = PhabricatorSlug::getDepth($slug) + 1;
 328      $d_grandchild = PhabricatorSlug::getDepth($slug) + 2;
 329      $limit = 250;
 330  
 331      $query = id(new PhrictionDocumentQuery())
 332        ->setViewer($this->getRequest()->getUser())
 333        ->withDepths(array($d_child, $d_grandchild))
 334        ->withSlugPrefix($slug == '/' ? '' : $slug)
 335        ->withStatuses(array(
 336          PhrictionDocumentStatus::STATUS_EXISTS,
 337          PhrictionDocumentStatus::STATUS_STUB,
 338        ))
 339        ->setLimit($limit)
 340        ->setOrder(PhrictionDocumentQuery::ORDER_HIERARCHY)
 341        ->needContent(true);
 342  
 343      $children = $query->execute();
 344      if (!$children) {
 345        return;
 346      }
 347  
 348      // We're going to render in one of three modes to try to accommodate
 349      // different information scales:
 350      //
 351      //  - If we found fewer than $limit rows, we know we have all the children
 352      //    and grandchildren and there aren't all that many. We can just render
 353      //    everything.
 354      //  - If we found $limit rows but the results included some grandchildren,
 355      //    we just throw them out and render only the children, as we know we
 356      //    have them all.
 357      //  - If we found $limit rows and the results have no grandchildren, we
 358      //    have a ton of children. Render them and then let the user know that
 359      //    this is not an exhaustive list.
 360  
 361      if (count($children) == $limit) {
 362        $more_children = true;
 363        foreach ($children as $child) {
 364          if ($child->getDepth() == $d_grandchild) {
 365            $more_children = false;
 366          }
 367        }
 368        $show_grandchildren = false;
 369      } else {
 370        $show_grandchildren = true;
 371        $more_children = false;
 372      }
 373  
 374      $children_dicts = array();
 375      $grandchildren_dicts = array();
 376      foreach ($children as $key => $child) {
 377        $child_dict = array(
 378          'slug' => $child->getSlug(),
 379          'depth' => $child->getDepth(),
 380          'title' => $child->getContent()->getTitle(),);
 381        if ($child->getDepth() == $d_child) {
 382          $children_dicts[] = $child_dict;
 383          continue;
 384        } else {
 385          unset($children[$key]);
 386          if ($show_grandchildren) {
 387            $ancestors = PhabricatorSlug::getAncestry($child->getSlug());
 388            $grandchildren_dicts[end($ancestors)][] = $child_dict;
 389          }
 390        }
 391      }
 392  
 393      // Fill in any missing children.
 394      $known_slugs = mpull($children, null, 'getSlug');
 395      foreach ($grandchildren_dicts as $slug => $ignored) {
 396        if (empty($known_slugs[$slug])) {
 397          $children_dicts[] = array(
 398            'slug'    => $slug,
 399            'depth'   => $d_child,
 400            'title'   => PhabricatorSlug::getDefaultTitle($slug),
 401            'empty'   => true,
 402          );
 403        }
 404      }
 405  
 406      $children_dicts = isort($children_dicts, 'title');
 407  
 408      $list = array();
 409      foreach ($children_dicts as $child) {
 410        $list[] = hsprintf('<li>');
 411        $list[] = $this->renderChildDocumentLink($child);
 412        $grand = idx($grandchildren_dicts, $child['slug'], array());
 413        if ($grand) {
 414          $list[] = hsprintf('<ul>');
 415          foreach ($grand as $grandchild) {
 416            $list[] = hsprintf('<li>');
 417            $list[] = $this->renderChildDocumentLink($grandchild);
 418            $list[] = hsprintf('</li>');
 419          }
 420          $list[] = hsprintf('</ul>');
 421        }
 422        $list[] = hsprintf('</li>');
 423      }
 424      if ($more_children) {
 425        $list[] = phutil_tag('li', array(), pht('More...'));
 426      }
 427  
 428      $content = array(
 429        phutil_tag(
 430          'div',
 431          array(
 432            'class' => 'phriction-children-header '.
 433              'sprite-gradient gradient-lightblue-header',
 434          ),
 435          pht('Document Hierarchy')),
 436        phutil_tag(
 437          'div',
 438          array(
 439            'class' => 'phriction-children',
 440          ),
 441          phutil_tag('ul', array(), $list)),
 442      );
 443  
 444      return id(new PHUIDocumentView())
 445        ->setOffset(true)
 446        ->appendChild($content);
 447    }
 448  
 449    private function renderChildDocumentLink(array $info) {
 450      $title = nonempty($info['title'], pht('(Untitled Document)'));
 451      $item = phutil_tag(
 452        'a',
 453        array(
 454          'href' => PhrictionDocument::getSlugURI($info['slug']),
 455        ),
 456        $title);
 457  
 458      if (isset($info['empty'])) {
 459        $item = phutil_tag('em', array(), $item);
 460      }
 461  
 462      return $item;
 463    }
 464  
 465    protected function getDocumentSlug() {
 466      return $this->slug;
 467    }
 468  
 469  }


Generated: Sun Nov 30 09:20:46 2014 Cross-referenced by PHPXref 0.7.1