mirror of
https://we.phorge.it/source/phorge.git
synced 2025-01-15 17:21:10 +01:00
Provide wiki pages for projects
Summary: Provide tighter integration between Projects and Phriction. Partly, I have most of a rewrite for the Projects homepage ready but it's not currently possible to publish feed stories about a project so all the feeds are empty/boring. This partly makes them more useful and partly just provides a tool integration point. - When you create a project, all the wiki pages in projects/<project_name>/* are associated with it. - Publish updates to those pages as being related to the project so they'll show up in project feeds. - Show a project link on those pages. This is very "convention over configuration" but I think it's the right approach. We could provide some sort of, like, "@project=derp" tag to let you associated arbitrary pages to projects later, but just letting you move pages is probably far better. Test Plan: - Ran upgrade scripts against stupidly named projects ("der", " der", " der ", "der (2)", " der (2) (2)", etc). Ended up with uniquely named projects. - Ran unit tests. - Created /projects/ wiki documents and made sure they displayed correctly. - Verified feed stories publish as project-related. - Edited projects, including perfomring a name-colliding edit. - Created projects, including performing a name-colliding create. Reviewers: btrahan, jungejason Reviewed By: btrahan CC: aran, epriestley, btrahan Maniphest Tasks: T681 Differential Revision: 1231
This commit is contained in:
parent
c80d1480d5
commit
21ba07d5bd
20 changed files with 427 additions and 15 deletions
2
resources/sql/patches/089.projectwiki.sql
Normal file
2
resources/sql/patches/089.projectwiki.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE phabricator_project.project
|
||||
ADD phrictionSlug varchar(512);
|
117
resources/sql/patches/090.forceuniqueprojectnames.php
Normal file
117
resources/sql/patches/090.forceuniqueprojectnames.php
Normal file
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2011 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.
|
||||
*/
|
||||
|
||||
echo "Ensuring project names are unique enough...\n";
|
||||
$projects = id(new PhabricatorProject())->loadAll();
|
||||
|
||||
$slug_map = array();
|
||||
|
||||
foreach ($projects as $project) {
|
||||
$project->setPhrictionSlug($project->getName());
|
||||
$slug = $project->getPhrictionSlug();
|
||||
if ($slug == '/') {
|
||||
$project_id = $project->getID();
|
||||
echo "Project #{$project_id} doesn't have a meaningful name...\n";
|
||||
$project->setName(trim('Unnamed Project '.$project->getName()));
|
||||
}
|
||||
$slug_map[$slug][] = $project->getID();
|
||||
}
|
||||
|
||||
|
||||
foreach ($slug_map as $slug => $similar) {
|
||||
if (count($similar) <= 1) {
|
||||
continue;
|
||||
}
|
||||
echo "Too many projects are similar to '{$slug}'...\n";
|
||||
|
||||
foreach (array_slice($similar, 1, null, true) as $key => $project_id) {
|
||||
$project = $projects[$project_id];
|
||||
$old_name = $project->getName();
|
||||
$new_name = rename_project($project, $projects);
|
||||
|
||||
echo "Renaming project #{$project_id} ".
|
||||
"from '{$old_name}' to '{$new_name}'.\n";
|
||||
$project->setName($new_name);
|
||||
}
|
||||
}
|
||||
|
||||
$update = $projects;
|
||||
while ($update) {
|
||||
$size = count($update);
|
||||
foreach ($update as $key => $project) {
|
||||
$id = $project->getID();
|
||||
$name = $project->getName();
|
||||
$project->setPhrictionSlug($name);
|
||||
$slug = $project->getPhrictionSlug();
|
||||
|
||||
echo "Updating project #{$id} '{$name}' ({$slug})...";
|
||||
try {
|
||||
queryfx(
|
||||
$project->establishConnection('w'),
|
||||
'UPDATE %T SET name = %s, phrictionSlug = %s WHERE id = %d',
|
||||
$project->getTableName(),
|
||||
$name,
|
||||
$slug,
|
||||
$project->getID());
|
||||
unset($update[$key]);
|
||||
echo "okay.\n";
|
||||
} catch (AphrontQueryDuplicateKeyException $ex) {
|
||||
echo "failed, will retry.\n";
|
||||
}
|
||||
}
|
||||
if (count($update) == $size) {
|
||||
throw new Exception(
|
||||
"Failed to make any progress while updating projects. Schema upgrade ".
|
||||
"has failed. Go manually fix your project names to be unique (they are ".
|
||||
"probably ridiculous?) and then try again.");
|
||||
}
|
||||
}
|
||||
|
||||
echo "Done.\n";
|
||||
|
||||
|
||||
/**
|
||||
* Rename the project so that it has a unique slug, by appending (2), (3), etc.
|
||||
* to its name.
|
||||
*/
|
||||
function rename_project($project, $projects) {
|
||||
$suffix = 2;
|
||||
while (true) {
|
||||
$new_name = $project->getName().' ('.$suffix.')';
|
||||
$project->setPhrictionSlug($new_name);
|
||||
$new_slug = $project->getPhrictionSlug();
|
||||
|
||||
$okay = true;
|
||||
foreach ($projects as $other) {
|
||||
if ($other->getID() == $project->getID()) {
|
||||
continue;
|
||||
}
|
||||
if ($other->getPhrictionSlug() == $new_slug) {
|
||||
$okay = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($okay) {
|
||||
break;
|
||||
} else {
|
||||
$suffix++;
|
||||
}
|
||||
}
|
||||
|
||||
return $new_name;
|
||||
}
|
2
resources/sql/patches/091.uniqueslugkey.sql
Normal file
2
resources/sql/patches/091.uniqueslugkey.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE phabricator_project.project
|
||||
ADD UNIQUE KEY (phrictionSlug);
|
|
@ -575,7 +575,9 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProjectController' => 'applications/project/controller/base',
|
||||
'PhabricatorProjectCreateController' => 'applications/project/controller/create',
|
||||
'PhabricatorProjectDAO' => 'applications/project/storage/base',
|
||||
'PhabricatorProjectEditor' => 'applications/project/editor/project',
|
||||
'PhabricatorProjectListController' => 'applications/project/controller/list',
|
||||
'PhabricatorProjectNameCollisionException' => 'applications/project/exception/namecollison',
|
||||
'PhabricatorProjectProfile' => 'applications/project/storage/profile',
|
||||
'PhabricatorProjectProfileController' => 'applications/project/controller/profile',
|
||||
'PhabricatorProjectProfileEditController' => 'applications/project/controller/profileedit',
|
||||
|
|
|
@ -98,7 +98,19 @@ class PhrictionDocumentController
|
|||
}
|
||||
$page_title = $content->getTitle();
|
||||
|
||||
$phids = array($content->getAuthorPHID());
|
||||
$project_phid = null;
|
||||
if (PhrictionDocument::isProjectSlug($slug)) {
|
||||
$project = id(new PhabricatorProject())->loadOneWhere(
|
||||
'phrictionSlug = %s',
|
||||
PhrictionDocument::getProjectSlugIdentifier($slug));
|
||||
$project_phid = $project->getPHID();
|
||||
}
|
||||
|
||||
$phids = array_filter(
|
||||
array(
|
||||
$content->getAuthorPHID(),
|
||||
$project_phid,
|
||||
));
|
||||
$handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
|
||||
|
||||
$age = time() - $content->getDateCreated();
|
||||
|
@ -112,10 +124,21 @@ class PhrictionDocumentController
|
|||
$when = "{$age} days ago";
|
||||
}
|
||||
|
||||
|
||||
$project_info = null;
|
||||
if ($project_phid) {
|
||||
$project_info =
|
||||
'<br />This document is about the project '.
|
||||
$handles[$project_phid]->renderLink().'.';
|
||||
}
|
||||
|
||||
|
||||
|
||||
$byline =
|
||||
'<div class="phriction-byline">'.
|
||||
"Last updated {$when} by ".
|
||||
$handles[$content->getAuthorPHID()]->renderLink().'.'.
|
||||
$project_info.
|
||||
'</div>';
|
||||
|
||||
$engine = PhabricatorMarkupEngine::newPhrictionMarkupEngine();
|
||||
|
|
|
@ -15,6 +15,7 @@ phutil_require_module('phabricator', 'applications/phriction/constants/documents
|
|||
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', 'applications/project/storage/project');
|
||||
phutil_require_module('phabricator', 'infrastructure/celerity/api');
|
||||
phutil_require_module('phabricator', 'storage/queryfx');
|
||||
phutil_require_module('phabricator', 'view/form/error');
|
||||
|
|
|
@ -201,12 +201,28 @@ final class PhrictionDocumentEditor {
|
|||
$document->attachContent($new_content);
|
||||
PhabricatorSearchPhrictionIndexer::indexDocument($document);
|
||||
|
||||
$project_phid = null;
|
||||
$slug = $document->getSlug();
|
||||
if (PhrictionDocument::isProjectSlug($slug)) {
|
||||
$project = id(new PhabricatorProject())->loadOneWhere(
|
||||
'phrictionSlug = %s',
|
||||
PhrictionDocument::getProjectSlugIdentifier($slug));
|
||||
if ($project) {
|
||||
$project_phid = $project->getPHID();
|
||||
}
|
||||
}
|
||||
|
||||
$related_phids = array(
|
||||
$document->getPHID(),
|
||||
$this->user->getPHID(),
|
||||
);
|
||||
|
||||
if ($project_phid) {
|
||||
$related_phids[] = $project_phid;
|
||||
}
|
||||
|
||||
id(new PhabricatorFeedStoryPublisher())
|
||||
->setRelatedPHIDs(
|
||||
array(
|
||||
$document->getPHID(),
|
||||
$this->user->getPHID(),
|
||||
))
|
||||
->setRelatedPHIDs($related_phids)
|
||||
->setStoryAuthorPHID($this->user->getPHID())
|
||||
->setStoryTime(time())
|
||||
->setStoryType(PhabricatorFeedStoryTypeConstants::STORY_PHRICTION)
|
||||
|
@ -215,6 +231,7 @@ final class PhrictionDocumentEditor {
|
|||
'phid' => $document->getPHID(),
|
||||
'action' => $feed_action,
|
||||
'content' => phutil_utf8_shorten($new_content->getContent(), 140),
|
||||
'project' => $project_phid,
|
||||
))
|
||||
->publish();
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ phutil_require_module('phabricator', 'applications/phriction/constants/changetyp
|
|||
phutil_require_module('phabricator', 'applications/phriction/constants/documentstatus');
|
||||
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('phutil', 'utils');
|
||||
|
|
|
@ -135,4 +135,25 @@ class PhrictionDocument extends PhrictionDAO {
|
|||
return $this->contentObject;
|
||||
}
|
||||
|
||||
public static function isProjectSlug($slug) {
|
||||
$slug = self::normalizeSlug($slug);
|
||||
$prefix = 'projects/';
|
||||
if ($slug == $prefix) {
|
||||
// The 'projects/' document is not itself a project slug.
|
||||
return false;
|
||||
}
|
||||
return !strncmp($slug, $prefix, strlen($prefix));
|
||||
}
|
||||
|
||||
public static function getProjectSlugIdentifier($slug) {
|
||||
if (!self::isProjectSlug($slug)) {
|
||||
throw new Exception("Slug '{$slug}' is not a project slug!");
|
||||
}
|
||||
|
||||
$slug = self::normalizeSlug($slug);
|
||||
$parts = explode('/', $slug);
|
||||
return $parts[1].'/';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ class PhrictionDocumentTestCase extends PhabricatorTestCase {
|
|||
'' => '/',
|
||||
'/' => '/',
|
||||
'//' => '/',
|
||||
'&&&' => '/',
|
||||
'/derp/' => 'derp/',
|
||||
'derp' => 'derp/',
|
||||
'derp//derp' => 'derp/derp/',
|
||||
|
@ -72,4 +73,47 @@ class PhrictionDocumentTestCase extends PhabricatorTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
public function testProjectSlugs() {
|
||||
$slugs = array(
|
||||
'/' => false,
|
||||
'zebra/' => false,
|
||||
'projects/' => false,
|
||||
'projects/a/' => true,
|
||||
'projects/a/b/' => true,
|
||||
'stuff/projects/a/' => false,
|
||||
);
|
||||
|
||||
foreach ($slugs as $slug => $expect) {
|
||||
$this->assertEqual(
|
||||
$expect,
|
||||
PhrictionDocument::isProjectSlug($slug),
|
||||
"Is '{$slug}' a project slug?");
|
||||
}
|
||||
}
|
||||
|
||||
public function testProjectSlugIdentifiers() {
|
||||
$slugs = array(
|
||||
'projects/' => null,
|
||||
'derp/' => null,
|
||||
'projects/a/' => 'a/',
|
||||
'projects/a/b/' => 'a/',
|
||||
);
|
||||
|
||||
foreach ($slugs as $slug => $expect) {
|
||||
$ex = null;
|
||||
$result = null;
|
||||
try {
|
||||
$result = PhrictionDocument::getProjectSlugIdentifier($slug);
|
||||
} catch (Exception $e) {
|
||||
$ex = $e;
|
||||
}
|
||||
|
||||
if ($expect === null) {
|
||||
$this->assertEqual(true, (bool)$ex, "Slug '{$slug}' is invalid.");
|
||||
} else {
|
||||
$this->assertEqual($expect, $result, "Slug '{$slug}' identifier.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -33,18 +33,20 @@ class PhabricatorProjectCreateController
|
|||
$errors = array();
|
||||
if ($request->isFormPost()) {
|
||||
|
||||
$project->setName($request->getStr('name'));
|
||||
try {
|
||||
$editor = new PhabricatorProjectEditor($project);
|
||||
$editor->setUser($user);
|
||||
$editor->setName($request->getStr('name'));
|
||||
$editor->save();
|
||||
} catch (PhabricatorProjectNameCollisionException $ex) {
|
||||
$e_name = 'Not Unique';
|
||||
$errors[] = $ex->getMessage();
|
||||
}
|
||||
|
||||
$project->setStatus(PhabricatorProjectStatus::ONGOING);
|
||||
|
||||
$profile->setBlurb($request->getStr('blurb'));
|
||||
|
||||
if (!strlen($project->getName())) {
|
||||
$e_name = 'Required';
|
||||
$errors[] = 'Project name is required.';
|
||||
} else {
|
||||
$e_name = null;
|
||||
}
|
||||
|
||||
if (!$errors) {
|
||||
$project->save();
|
||||
$profile->setProjectPHID($project->getPHID());
|
||||
|
|
|
@ -11,6 +11,7 @@ phutil_require_module('phabricator', 'aphront/response/dialog');
|
|||
phutil_require_module('phabricator', 'aphront/response/redirect');
|
||||
phutil_require_module('phabricator', 'applications/project/constants/status');
|
||||
phutil_require_module('phabricator', 'applications/project/controller/base');
|
||||
phutil_require_module('phabricator', 'applications/project/editor/project');
|
||||
phutil_require_module('phabricator', 'applications/project/storage/affiliation');
|
||||
phutil_require_module('phabricator', 'applications/project/storage/profile');
|
||||
phutil_require_module('phabricator', 'applications/project/storage/project');
|
||||
|
|
|
@ -56,7 +56,17 @@ class PhabricatorProjectProfileEditController
|
|||
$errors = array();
|
||||
$state = null;
|
||||
if ($request->isFormPost()) {
|
||||
$project->setName($request->getStr('name'));
|
||||
|
||||
try {
|
||||
$editor = new PhabricatorProjectEditor($project);
|
||||
$editor->setUser($user);
|
||||
$editor->setName($request->getStr('name'));
|
||||
$editor->save();
|
||||
} catch (PhabricatorProjectNameCollisionException $ex) {
|
||||
$e_name = 'Not Unique';
|
||||
$errors[] = $ex->getMessage();
|
||||
}
|
||||
|
||||
$project->setStatus($request->getStr('status'));
|
||||
$project->setSubprojectPHIDs($request->getArr('set_subprojects'));
|
||||
$profile->setBlurb($request->getStr('blurb'));
|
||||
|
|
|
@ -13,6 +13,7 @@ phutil_require_module('phabricator', 'applications/files/transform');
|
|||
phutil_require_module('phabricator', 'applications/phid/handle/data');
|
||||
phutil_require_module('phabricator', 'applications/project/constants/status');
|
||||
phutil_require_module('phabricator', 'applications/project/controller/base');
|
||||
phutil_require_module('phabricator', 'applications/project/editor/project');
|
||||
phutil_require_module('phabricator', 'applications/project/storage/affiliation');
|
||||
phutil_require_module('phabricator', 'applications/project/storage/profile');
|
||||
phutil_require_module('phabricator', 'applications/project/storage/project');
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2011 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 PhabricatorProjectEditor {
|
||||
|
||||
private $project;
|
||||
private $user;
|
||||
|
||||
private $projectName;
|
||||
|
||||
public function __construct(PhabricatorProject $project) {
|
||||
$this->project = $project;
|
||||
}
|
||||
|
||||
public function setName($name) {
|
||||
$this->projectName = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setUser(PhabricatorUser $user) {
|
||||
$this->user = $user;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function save() {
|
||||
if (!$this->user) {
|
||||
throw new Exception('Call setUser() before save()!');
|
||||
}
|
||||
|
||||
$project = $this->project;
|
||||
|
||||
$is_new = !$project->getID();
|
||||
|
||||
if ($is_new) {
|
||||
$project->setAuthorPHID($this->user->getPHID());
|
||||
}
|
||||
|
||||
if (($this->projectName !== null) &&
|
||||
($this->projectName !== $project->getName())) {
|
||||
$project->setName($this->projectName);
|
||||
$project->setPhrictionSlug($this->projectName);
|
||||
$this->validateName($project);
|
||||
}
|
||||
|
||||
try {
|
||||
$project->save();
|
||||
} catch (AphrontQueryDuplicateKeyException $ex) {
|
||||
// We already validated the slug, but might race. Try again to see if
|
||||
// that's the issue. If it is, we'll throw a more specific exception. If
|
||||
// not, throw the original exception.
|
||||
$this->validateName($project);
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
// TODO: If we rename a project, we should move its Phriction page. Do
|
||||
// that once Phriction supports document moves.
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function validateName(PhabricatorProject $project) {
|
||||
$slug = $project->getPhrictionSlug();
|
||||
$name = $project->getName();
|
||||
|
||||
if ($slug == '/') {
|
||||
throw new PhabricatorProjectNameCollisionException(
|
||||
"Project names must be unique and contain some letters or numbers.");
|
||||
}
|
||||
|
||||
$id = $project->getID();
|
||||
$collision = id(new PhabricatorProject())->loadOneWhere(
|
||||
'(name = %s OR phrictionSlug = %s) AND id %Q %nd',
|
||||
$name,
|
||||
$slug,
|
||||
$id ? '!=' : 'IS NOT',
|
||||
$id ? $id : null);
|
||||
|
||||
if ($collision) {
|
||||
$other_name = $collision->getName();
|
||||
$other_id = $collision->getID();
|
||||
throw new PhabricatorProjectNameCollisionException(
|
||||
"Project names must be unique. The name '{$name}' is too similar to ".
|
||||
"the name of another project, '{$other_name}' (Project ID: ".
|
||||
"{$other_id}). Choose a unique name.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
15
src/applications/project/editor/project/__init__.php
Normal file
15
src/applications/project/editor/project/__init__.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('phabricator', 'applications/project/exception/namecollison');
|
||||
phutil_require_module('phabricator', 'applications/project/storage/project');
|
||||
|
||||
phutil_require_module('phutil', 'utils');
|
||||
|
||||
|
||||
phutil_require_source('PhabricatorProjectEditor.php');
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2011 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Thrown when you try to save a project with a name too similar to another
|
||||
* project.
|
||||
*/
|
||||
final class PhabricatorProjectNameCollisionException extends Exception {
|
||||
|
||||
}
|
10
src/applications/project/exception/namecollison/__init__.php
Normal file
10
src/applications/project/exception/namecollison/__init__.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
phutil_require_source('PhabricatorProjectNameCollisionException.php');
|
|
@ -23,6 +23,7 @@ class PhabricatorProject extends PhabricatorProjectDAO {
|
|||
protected $status = PhabricatorProjectStatus::UNKNOWN;
|
||||
protected $authorPHID;
|
||||
protected $subprojectPHIDs = array();
|
||||
protected $phrictionSlug;
|
||||
|
||||
private $subprojectsNeedUpdate;
|
||||
|
||||
|
@ -59,6 +60,19 @@ class PhabricatorProject extends PhabricatorProjectDAO {
|
|||
return $affils[$this->getPHID()];
|
||||
}
|
||||
|
||||
public function setPhrictionSlug($slug) {
|
||||
|
||||
// NOTE: We're doing a little magic here and stripping out '/' so that
|
||||
// project pages always appear at top level under projects/ even if the
|
||||
// display name is "Hack / Slash" or similar (it will become
|
||||
// 'hack_slash' instead of 'hack/slash').
|
||||
|
||||
$slug = str_replace('/', ' ', $slug);
|
||||
$slug = PhrictionDocument::normalizeSlug($slug);
|
||||
$this->phrictionSlug = $slug;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function save() {
|
||||
$result = parent::save();
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
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');
|
||||
|
|
Loading…
Reference in a new issue