Commit 1175784d authored by Bob Trahan's avatar Bob Trahan
Browse files

PhabricatorSlug

Summary:
This is to be used in Phame so the logic is shared where possible. The change has three main things going on

- broke out functionality from PhrictionDocument that isn't Phriction specific.
- swept up code base to use new PhabricatorSlug class.
- altered the regex ever so slightly per discussion and http://stackoverflow.com/questions/2028022/javascript-how-to-convert-unicode-string-to-ascii

I think maybe we should punt on unicode here for quite a bit -- http://www.456bereastreet.com/archive/201006/be_careful_with_non-ascii_characters_in_urls/ -- but we'll be well-positioned to add it with the code here.

Test Plan: used phriction to create, edit, view documents. used a tool (codemod) for the codebase sweeping

Reviewers: epriestley

Reviewed By: epriestley

CC: aran

Differential Revision: https://secure.phabricator.com/D2195
parent 01907bcc
......@@ -862,6 +862,8 @@ phutil_register_library_map(array(
'PhabricatorSlowvoteOption' => 'applications/slowvote/storage/option',
'PhabricatorSlowvotePoll' => 'applications/slowvote/storage/poll',
'PhabricatorSlowvotePollController' => 'applications/slowvote/controller/poll',
'PhabricatorSlug' => 'infrastructure/util/slug',
'PhabricatorSlugTestCase' => 'infrastructure/util/slug/__tests__',
'PhabricatorSortTableExample' => 'applications/uiexample/examples/sorttable',
'PhabricatorStandardPageView' => 'view/page/standard',
'PhabricatorStatusController' => 'applications/status/base',
......@@ -1679,6 +1681,7 @@ phutil_register_library_map(array(
'PhabricatorSlowvoteOption' => 'PhabricatorSlowvoteDAO',
'PhabricatorSlowvotePoll' => 'PhabricatorSlowvoteDAO',
'PhabricatorSlowvotePollController' => 'PhabricatorSlowvoteController',
'PhabricatorSlugTestCase' => 'PhabricatorTestCase',
'PhabricatorSortTableExample' => 'PhabricatorUIExample',
'PhabricatorStandardPageView' => 'AphrontPageView',
'PhabricatorStatusController' => 'PhabricatorController',
......
......@@ -46,7 +46,7 @@ final class ConduitAPI_phriction_history_Method
$slug = $request->getValue('slug');
$doc = id(new PhrictionDocument())->loadOneWhere(
'slug = %s',
PhrictionDocument::normalizeSlug($slug));
PhabricatorSlug::normalize($slug));
if (!$doc) {
throw new ConduitException('ERR-BAD-DOCUMENT');
}
......
......@@ -10,6 +10,7 @@ phutil_require_module('phabricator', 'applications/conduit/method/phriction/base
phutil_require_module('phabricator', 'applications/conduit/protocol/exception');
phutil_require_module('phabricator', 'applications/phriction/storage/content');
phutil_require_module('phabricator', 'applications/phriction/storage/document');
phutil_require_module('phabricator', 'infrastructure/util/slug');
phutil_require_module('phutil', 'utils');
......
......@@ -47,7 +47,7 @@ final class ConduitAPI_phriction_info_Method
$doc = id(new PhrictionDocument())->loadOneWhere(
'slug = %s',
PhrictionDocument::normalizeSlug($slug));
PhabricatorSlug::normalize($slug));
if (!$doc) {
throw new ConduitException('ERR-BAD-DOCUMENT');
......
......@@ -10,6 +10,7 @@ phutil_require_module('phabricator', 'applications/conduit/method/phriction/base
phutil_require_module('phabricator', 'applications/conduit/protocol/exception');
phutil_require_module('phabricator', 'applications/phriction/storage/content');
phutil_require_module('phabricator', 'applications/phriction/storage/document');
phutil_require_module('phabricator', 'infrastructure/util/slug');
phutil_require_module('phutil', 'utils');
......
......@@ -33,7 +33,7 @@ final class PhrictionDocumentController
$request = $this->getRequest();
$user = $request->getUser();
$slug = PhrictionDocument::normalizeSlug($this->slug);
$slug = PhabricatorSlug::normalize($this->slug);
if ($slug != $this->slug) {
$uri = PhrictionDocument::getSlugURI($slug);
// Canonicalize pages to their one true URI.
......@@ -208,7 +208,7 @@ final class PhrictionDocumentController
private function renderBreadcrumbs($slug) {
$ancestor_handles = array();
$ancestral_slugs = PhrictionDocument::getSlugAncestry($slug);
$ancestral_slugs = PhabricatorSlug::getAncestry($slug);
$ancestral_slugs[] = $slug;
if ($ancestral_slugs) {
$empty_slugs = array_fill_keys($ancestral_slugs, null);
......@@ -230,7 +230,7 @@ final class PhrictionDocumentController
$ancestor_handles[] = $handles[$ancestors[$slug]->getPHID()];
} else {
$handle = new PhabricatorObjectHandle();
$handle->setName(PhrictionDocument::getDefaultSlugTitle($slug));
$handle->setName(PhabricatorSlug::getDefaultTitle($slug));
$handle->setURI(PhrictionDocument::getSlugURI($slug));
$ancestor_handles[] = $handle;
}
......@@ -264,8 +264,8 @@ final class PhrictionDocumentController
$conn = $document_dao->establishConnection('r');
$limit = 50;
$d_child = PhrictionDocument::getSlugDepth($slug) + 1;
$d_grandchild = PhrictionDocument::getSlugDepth($slug) + 2;
$d_child = PhabricatorSlug::getDepth($slug) + 1;
$d_grandchild = PhabricatorSlug::getDepth($slug) + 2;
// Select children and grandchildren.
$children = queryfx_all(
......@@ -320,7 +320,7 @@ final class PhrictionDocumentController
} else {
unset($children[$key]);
if ($show_grandchildren) {
$ancestors = PhrictionDocument::getSlugAncestry($child['slug']);
$ancestors = PhabricatorSlug::getAncestry($child['slug']);
$grandchildren[end($ancestors)][] = $child;
}
}
......@@ -333,7 +333,7 @@ final class PhrictionDocumentController
$children[] = array(
'slug' => $slug,
'depth' => $d_child,
'title' => PhrictionDocument::getDefaultSlugTitle($slug),
'title' => PhabricatorSlug::getDefaultTitle($slug),
'empty' => true,
);
}
......
......@@ -16,6 +16,7 @@ phutil_require_module('phabricator', 'applications/phriction/storage/content');
phutil_require_module('phabricator', 'applications/phriction/storage/document');
phutil_require_module('phabricator', 'applications/project/storage/project');
phutil_require_module('phabricator', 'infrastructure/celerity/api');
phutil_require_module('phabricator', 'infrastructure/util/slug');
phutil_require_module('phabricator', 'storage/queryfx');
phutil_require_module('phabricator', 'view/form/error');
phutil_require_module('phabricator', 'view/utils');
......
......@@ -54,7 +54,7 @@ final class PhrictionEditController
} else {
$slug = $request->getStr('slug');
$slug = PhrictionDocument::normalizeSlug($slug);
$slug = PhabricatorSlug::normalize($slug);
if (!$slug) {
return new Aphront404Response();
}
......@@ -72,7 +72,7 @@ final class PhrictionEditController
$content = new PhrictionContent();
$content->setSlug($slug);
$default_title = PhrictionDocument::getDefaultSlugTitle($slug);
$default_title = PhabricatorSlug::getDefaultTitle($slug);
$content->setTitle($default_title);
}
}
......
......@@ -16,6 +16,7 @@ phutil_require_module('phabricator', 'applications/phriction/storage/document');
phutil_require_module('phabricator', 'infrastructure/celerity/api');
phutil_require_module('phabricator', 'infrastructure/env');
phutil_require_module('phabricator', 'infrastructure/javelin/api');
phutil_require_module('phabricator', 'infrastructure/util/slug');
phutil_require_module('phabricator', 'view/form/base');
phutil_require_module('phabricator', 'view/form/control/static');
phutil_require_module('phabricator', 'view/form/control/submit');
......
......@@ -35,7 +35,7 @@ final class PhrictionHistoryController
$document = id(new PhrictionDocument())->loadOneWhere(
'slug = %s',
PhrictionDocument::normalizeSlug($this->slug));
PhabricatorSlug::normalize($this->slug));
if (!$document) {
return new Aphront404Response();
......
......@@ -12,6 +12,7 @@ phutil_require_module('phabricator', 'applications/phriction/constants/changetyp
phutil_require_module('phabricator', 'applications/phriction/controller/base');
phutil_require_module('phabricator', 'applications/phriction/storage/content');
phutil_require_module('phabricator', 'applications/phriction/storage/document');
phutil_require_module('phabricator', 'infrastructure/util/slug');
phutil_require_module('phabricator', 'view/control/pager');
phutil_require_module('phabricator', 'view/control/table');
phutil_require_module('phabricator', 'view/layout/crumbs');
......
<?php
/*
* Copyright 2011 Facebook, Inc.
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -37,7 +37,7 @@ final class PhrictionDocumentEditor {
}
public static function newForSlug($slug) {
$slug = PhrictionDocument::normalizeSlug($slug);
$slug = PhabricatorSlug::normalize($slug);
$document = id(new PhrictionDocument())->loadOneWhere(
'slug = %s',
$slug);
......@@ -51,7 +51,7 @@ final class PhrictionDocumentEditor {
}
if (!$content) {
$default_title = PhrictionDocument::getDefaultSlugTitle($slug);
$default_title = PhabricatorSlug::getDefaultTitle($slug);
$content = new PhrictionContent();
$content->setSlug($slug);
$content->setTitle($default_title);
......
......@@ -15,6 +15,7 @@ phutil_require_module('phabricator', 'applications/phriction/storage/content');
phutil_require_module('phabricator', 'applications/phriction/storage/document');
phutil_require_module('phabricator', 'applications/project/storage/project');
phutil_require_module('phabricator', 'applications/search/index/indexer/phriction');
phutil_require_module('phabricator', 'infrastructure/util/slug');
phutil_require_module('phutil', 'utils');
......
......@@ -61,65 +61,9 @@ final class PhrictionDocument extends PhrictionDAO {
}
}
public static function normalizeSlug($slug) {
// TODO: We need to deal with unicode at some point, this is just a very
// basic proof-of-concept implementation.
$slug = strtolower($slug);
$slug = preg_replace('@/+@', '/', $slug);
$slug = trim($slug, '/');
$slug = preg_replace('@[^a-z0-9/]+@', '_', $slug);
$slug = trim($slug, '_');
return $slug.'/';
}
public static function getDefaultSlugTitle($slug) {
$parts = explode('/', trim($slug, '/'));
$default_title = end($parts);
$default_title = str_replace('_', ' ', $default_title);
$default_title = ucwords($default_title);
$default_title = nonempty($default_title, 'Untitled Document');
return $default_title;
}
public static function getSlugAncestry($slug) {
$slug = self::normalizeSlug($slug);
if ($slug == '/') {
return array();
}
$ancestors = array(
'/',
);
$slug = explode('/', $slug);
array_pop($slug);
array_pop($slug);
$accumulate = '';
foreach ($slug as $part) {
$accumulate .= $part.'/';
$ancestors[] = $accumulate;
}
return $ancestors;
}
public static function getSlugDepth($slug) {
$slug = self::normalizeSlug($slug);
if ($slug == '/') {
return 0;
} else {
return substr_count($slug, '/');
}
}
public function setSlug($slug) {
$this->slug = self::normalizeSlug($slug);
$this->depth = self::getSlugDepth($slug);
$this->slug = PhabricatorSlug::normalize($slug);
$this->depth = PhabricatorSlug::getDepth($slug);
return $this;
}
......@@ -136,7 +80,7 @@ final class PhrictionDocument extends PhrictionDAO {
}
public static function isProjectSlug($slug) {
$slug = self::normalizeSlug($slug);
$slug = PhabricatorSlug::normalize($slug);
$prefix = 'projects/';
if ($slug == $prefix) {
// The 'projects/' document is not itself a project slug.
......@@ -150,7 +94,7 @@ final class PhrictionDocument extends PhrictionDAO {
throw new Exception("Slug '{$slug}' is not a project slug!");
}
$slug = self::normalizeSlug($slug);
$slug = PhabricatorSlug::normalize($slug);
$parts = explode('/', $slug);
return $parts[1].'/';
}
......
......@@ -9,8 +9,7 @@
phutil_require_module('phabricator', 'applications/phid/constants');
phutil_require_module('phabricator', 'applications/phid/storage/phid');
phutil_require_module('phabricator', 'applications/phriction/storage/base');
phutil_require_module('phutil', 'utils');
phutil_require_module('phabricator', 'infrastructure/util/slug');
phutil_require_source('PhrictionDocument.php');
......@@ -21,58 +21,6 @@
*/
final class PhrictionDocumentTestCase extends PhabricatorTestCase {
public function testSlugNormalization() {
$slugs = array(
'' => '/',
'/' => '/',
'//' => '/',
'&&&' => '/',
'/derp/' => 'derp/',
'derp' => 'derp/',
'derp//derp' => 'derp/derp/',
'DERP//DERP' => 'derp/derp/',
'a B c' => 'a_b_c/',
);
foreach ($slugs as $slug => $normal) {
$this->assertEqual(
$normal,
PhrictionDocument::normalizeSlug($slug),
"Normalization of '{$slug}'");
}
}
public function testSlugAncestry() {
$slugs = array(
'/' => array(),
'pokemon/' => array('/'),
'pokemon/squirtle/' => array('/', 'pokemon/'),
);
foreach ($slugs as $slug => $ancestry) {
$this->assertEqual(
$ancestry,
PhrictionDocument::getSlugAncestry($slug),
"Ancestry of '{$slug}'");
}
}
public function testSlugDepth() {
$slugs = array(
'/' => 0,
'a/' => 1,
'a/b/' => 2,
'a////b/' => 2,
);
foreach ($slugs as $slug => $depth) {
$this->assertEqual(
$depth,
PhrictionDocument::getSlugDepth($slug),
"Depth of '{$slug}'");
}
}
public function testProjectSlugs() {
$slugs = array(
'/' => false,
......
......@@ -60,7 +60,7 @@ final class PhabricatorProjectProfileController
$external_arrow = "\xE2\x86\x97";
$tasks_uri = '/maniphest/view/all/?projects='.$project->getPHID();
$slug = PhrictionDocument::normalizeSlug($project->getName());
$slug = PhabricatorSlug::normalize($project->getName());
$phriction_uri = '/w/projects/'.$slug;
$edit_uri = '/project/edit/'.$project->getID().'/';
......
......@@ -13,12 +13,12 @@ phutil_require_module('phabricator', 'applications/files/storage/file');
phutil_require_module('phabricator', 'applications/maniphest/query');
phutil_require_module('phabricator', 'applications/maniphest/view/tasksummary');
phutil_require_module('phabricator', 'applications/phid/handle/data');
phutil_require_module('phabricator', 'applications/phriction/storage/document');
phutil_require_module('phabricator', 'applications/project/controller/base');
phutil_require_module('phabricator', 'applications/project/storage/profile');
phutil_require_module('phabricator', 'applications/project/storage/project');
phutil_require_module('phabricator', 'infrastructure/celerity/api');
phutil_require_module('phabricator', 'infrastructure/javelin/markup');
phutil_require_module('phabricator', 'infrastructure/util/slug');
phutil_require_module('phabricator', 'view/control/table');
phutil_require_module('phabricator', 'view/layout/profileheader');
phutil_require_module('phabricator', 'view/layout/sidenavfilter');
......
......@@ -82,7 +82,7 @@ final class PhabricatorProject extends PhabricatorProjectDAO {
// 'hack_slash' instead of 'hack/slash').
$slug = str_replace('/', ' ', $slug);
$slug = PhrictionDocument::normalizeSlug($slug);
$slug = PhabricatorSlug::normalize($slug);
$this->phrictionSlug = $slug;
return $this;
}
......
......@@ -8,12 +8,12 @@
phutil_require_module('phabricator', 'applications/phid/constants');
phutil_require_module('phabricator', 'applications/phid/storage/phid');
phutil_require_module('phabricator', 'applications/phriction/storage/document');
phutil_require_module('phabricator', 'applications/project/constants/status');
phutil_require_module('phabricator', 'applications/project/storage/affiliation');
phutil_require_module('phabricator', 'applications/project/storage/base');
phutil_require_module('phabricator', 'applications/project/storage/profile');
phutil_require_module('phabricator', 'applications/project/storage/subproject');
phutil_require_module('phabricator', 'infrastructure/util/slug');
phutil_require_module('phutil', 'utils');
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment