1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-23 14:00:56 +01:00

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
This commit is contained in:
Bob Trahan 2012-04-10 14:18:20 -07:00
parent 01907bcccc
commit 1175784d5d
26 changed files with 211 additions and 134 deletions

View file

@ -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',

View file

@ -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');
}

View file

@ -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');

View file

@ -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');

View file

@ -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');

View file

@ -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,
);
}

View file

@ -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');

View file

@ -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);
}
}

View file

@ -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');

View file

@ -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();

View file

@ -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');

View file

@ -1,7 +1,7 @@
<?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);

View file

@ -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');

View file

@ -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].'/';
}

View file

@ -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');

View file

@ -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,

View file

@ -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().'/';

View file

@ -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');

View file

@ -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;
}

View file

@ -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');

View file

@ -36,7 +36,7 @@ final class PhabricatorRemarkupRulePhriction
$name = explode('/', trim($name, '/'));
$name = end($name);
$slug = PhrictionDocument::normalizeSlug($slug);
$slug = PhabricatorSlug::normalize($slug);
$uri = PhrictionDocument::getSlugURI($slug);
return $this->getEngine()->storeText(

View file

@ -7,6 +7,7 @@
phutil_require_module('phabricator', 'applications/phriction/storage/document');
phutil_require_module('phabricator', 'infrastructure/util/slug');
phutil_require_module('phutil', 'markup');
phutil_require_module('phutil', 'markup/engine/remarkup/markuprule/base');

View file

@ -0,0 +1,77 @@
<?php
/*
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class PhabricatorSlug {
public static function normalize($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 getDefaultTitle($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 getAncestry($slug) {
$slug = self::normalize($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 getDepth($slug) {
$slug = self::normalize($slug);
if ($slug == '/') {
return 0;
} else {
return substr_count($slug, '/');
}
}
}

View file

@ -0,0 +1,12 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phutil', 'utils');
phutil_require_source('PhabricatorSlug.php');

View file

@ -0,0 +1,74 @@
<?php
/*
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class PhabricatorSlugTestCase 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/',
'-1~2.3abcd' => '1_2_3abcd/',
"T\x95O\x95D\x95O" => 't_o_d_o/',
);
foreach ($slugs as $slug => $normal) {
$this->assertEqual(
$normal,
PhabricatorSlug::normalize($slug),
"Normalization of '{$slug}'");
}
}
public function testSlugAncestry() {
$slugs = array(
'/' => array(),
'pokemon/' => array('/'),
'pokemon/squirtle/' => array('/', 'pokemon/'),
);
foreach ($slugs as $slug => $ancestry) {
$this->assertEqual(
$ancestry,
PhabricatorSlug::getAncestry($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,
PhabricatorSlug::getDepth($slug),
"Depth of '{$slug}'");
}
}
}

View file

@ -0,0 +1,13 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'infrastructure/testing/testcase');
phutil_require_module('phabricator', 'infrastructure/util/slug');
phutil_require_source('PhabricatorSlugTestCase.php');