mirror of
https://we.phorge.it/source/phorge.git
synced 2025-02-20 10:48:40 +01:00
Project list and profile view modifications
Summary: Added some change on the project's list view, to show information about active tasks, population, etc. Also modified the "profile view", and added a class "PhabricatorProfileView" to render the profile, both on projects and users. Test Plan: play around the project directory :) Reviewers: epriestley ericfrenkiel CC: Differential Revision: 477
This commit is contained in:
parent
565cc43f27
commit
7851b6573f
27 changed files with 664 additions and 263 deletions
2
resources/sql/patches/047.projectstatus.sql
Normal file
2
resources/sql/patches/047.projectstatus.sql
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE phabricator_project.project
|
||||||
|
ADD status varchar(32) not null;
|
|
@ -63,7 +63,7 @@ celerity_register_resource_map(array(
|
||||||
),
|
),
|
||||||
'aphront-headsup-action-list-view-css' =>
|
'aphront-headsup-action-list-view-css' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/c0ef93b6/rsrc/css/aphront/headsup-action-list-view.css',
|
'uri' => '/res/af3dff49/rsrc/css/aphront/headsup-action-list-view.css',
|
||||||
'type' => 'css',
|
'type' => 'css',
|
||||||
'requires' =>
|
'requires' =>
|
||||||
array(
|
array(
|
||||||
|
@ -154,7 +154,7 @@ celerity_register_resource_map(array(
|
||||||
),
|
),
|
||||||
'differential-core-view-css' =>
|
'differential-core-view-css' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/dd6b4ca9/rsrc/css/application/differential/core.css',
|
'uri' => '/res/438fe316/rsrc/css/application/differential/core.css',
|
||||||
'type' => 'css',
|
'type' => 'css',
|
||||||
'requires' =>
|
'requires' =>
|
||||||
array(
|
array(
|
||||||
|
@ -1048,12 +1048,12 @@ celerity_register_resource_map(array(
|
||||||
),
|
),
|
||||||
'phabricator-profile-css' =>
|
'phabricator-profile-css' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/adcdb5f3/rsrc/css/application/people/profile.css',
|
'uri' => '/res/4cb0251e/rsrc/css/application/profile/profile-view.css',
|
||||||
'type' => 'css',
|
'type' => 'css',
|
||||||
'requires' =>
|
'requires' =>
|
||||||
array(
|
array(
|
||||||
),
|
),
|
||||||
'disk' => '/rsrc/css/application/people/profile.css',
|
'disk' => '/rsrc/css/application/profile/profile-view.css',
|
||||||
),
|
),
|
||||||
'phabricator-remarkup-css' =>
|
'phabricator-remarkup-css' =>
|
||||||
array(
|
array(
|
||||||
|
@ -1156,23 +1156,6 @@ celerity_register_resource_map(array(
|
||||||
'uri' => '/res/pkg/a452c449/core.pkg.css',
|
'uri' => '/res/pkg/a452c449/core.pkg.css',
|
||||||
'type' => 'css',
|
'type' => 'css',
|
||||||
),
|
),
|
||||||
'c9226a80' =>
|
|
||||||
array (
|
|
||||||
'name' => 'differential.pkg.css',
|
|
||||||
'symbols' =>
|
|
||||||
array (
|
|
||||||
0 => 'differential-core-view-css',
|
|
||||||
1 => 'differential-changeset-view-css',
|
|
||||||
2 => 'differential-revision-detail-css',
|
|
||||||
3 => 'differential-revision-history-css',
|
|
||||||
4 => 'differential-table-of-contents-css',
|
|
||||||
5 => 'differential-revision-comment-css',
|
|
||||||
6 => 'differential-revision-add-comment-css',
|
|
||||||
7 => 'differential-revision-comment-list-css',
|
|
||||||
),
|
|
||||||
'uri' => '/res/pkg/c9226a80/differential.pkg.css',
|
|
||||||
'type' => 'css',
|
|
||||||
),
|
|
||||||
'da416e1c' =>
|
'da416e1c' =>
|
||||||
array (
|
array (
|
||||||
'name' => 'differential.pkg.js',
|
'name' => 'differential.pkg.js',
|
||||||
|
@ -1222,6 +1205,23 @@ celerity_register_resource_map(array(
|
||||||
'uri' => '/res/pkg/df91920b/workflow.pkg.js',
|
'uri' => '/res/pkg/df91920b/workflow.pkg.js',
|
||||||
'type' => 'js',
|
'type' => 'js',
|
||||||
),
|
),
|
||||||
|
55967526 =>
|
||||||
|
array (
|
||||||
|
'name' => 'differential.pkg.css',
|
||||||
|
'symbols' =>
|
||||||
|
array (
|
||||||
|
0 => 'differential-core-view-css',
|
||||||
|
1 => 'differential-changeset-view-css',
|
||||||
|
2 => 'differential-revision-detail-css',
|
||||||
|
3 => 'differential-revision-history-css',
|
||||||
|
4 => 'differential-table-of-contents-css',
|
||||||
|
5 => 'differential-revision-comment-css',
|
||||||
|
6 => 'differential-revision-add-comment-css',
|
||||||
|
7 => 'differential-revision-comment-list-css',
|
||||||
|
),
|
||||||
|
'uri' => '/res/pkg/55967526/differential.pkg.css',
|
||||||
|
'type' => 'css',
|
||||||
|
),
|
||||||
),
|
),
|
||||||
'reverse' =>
|
'reverse' =>
|
||||||
array (
|
array (
|
||||||
|
@ -1234,14 +1234,14 @@ celerity_register_resource_map(array(
|
||||||
'aphront-table-view-css' => 'a452c449',
|
'aphront-table-view-css' => 'a452c449',
|
||||||
'aphront-tokenizer-control-css' => 'a452c449',
|
'aphront-tokenizer-control-css' => 'a452c449',
|
||||||
'aphront-typeahead-control-css' => 'a452c449',
|
'aphront-typeahead-control-css' => 'a452c449',
|
||||||
'differential-changeset-view-css' => 'c9226a80',
|
'differential-changeset-view-css' => '55967526',
|
||||||
'differential-core-view-css' => 'c9226a80',
|
'differential-core-view-css' => '55967526',
|
||||||
'differential-revision-add-comment-css' => 'c9226a80',
|
'differential-revision-add-comment-css' => '55967526',
|
||||||
'differential-revision-comment-css' => 'c9226a80',
|
'differential-revision-comment-css' => '55967526',
|
||||||
'differential-revision-comment-list-css' => 'c9226a80',
|
'differential-revision-comment-list-css' => '55967526',
|
||||||
'differential-revision-detail-css' => 'c9226a80',
|
'differential-revision-detail-css' => '55967526',
|
||||||
'differential-revision-history-css' => 'c9226a80',
|
'differential-revision-history-css' => '55967526',
|
||||||
'differential-table-of-contents-css' => 'c9226a80',
|
'differential-table-of-contents-css' => '55967526',
|
||||||
'diffusion-commit-view-css' => '03ef179e',
|
'diffusion-commit-view-css' => '03ef179e',
|
||||||
'javelin-behavior' => 'db95a6d0',
|
'javelin-behavior' => 'db95a6d0',
|
||||||
'javelin-behavior-aphront-basic-tokenizer' => '2892314d',
|
'javelin-behavior-aphront-basic-tokenizer' => '2892314d',
|
||||||
|
|
|
@ -419,16 +419,19 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorPeopleProfileController' => 'applications/people/controller/profile',
|
'PhabricatorPeopleProfileController' => 'applications/people/controller/profile',
|
||||||
'PhabricatorPeopleProfileEditController' => 'applications/people/controller/profileedit',
|
'PhabricatorPeopleProfileEditController' => 'applications/people/controller/profileedit',
|
||||||
'PhabricatorPreferencesController' => 'applications/preferences/controller/base',
|
'PhabricatorPreferencesController' => 'applications/preferences/controller/base',
|
||||||
|
'PhabricatorProfileView' => 'view/layout/profile',
|
||||||
'PhabricatorProject' => 'applications/project/storage/project',
|
'PhabricatorProject' => 'applications/project/storage/project',
|
||||||
'PhabricatorProjectAffiliation' => 'applications/project/storage/affiliation',
|
'PhabricatorProjectAffiliation' => 'applications/project/storage/affiliation',
|
||||||
'PhabricatorProjectAffiliationEditController' => 'applications/project/controller/editaffiliation',
|
'PhabricatorProjectAffiliationEditController' => 'applications/project/controller/editaffiliation',
|
||||||
'PhabricatorProjectController' => 'applications/project/controller/base',
|
'PhabricatorProjectController' => 'applications/project/controller/base',
|
||||||
'PhabricatorProjectDAO' => 'applications/project/storage/base',
|
'PhabricatorProjectDAO' => 'applications/project/storage/base',
|
||||||
'PhabricatorProjectEditController' => 'applications/project/controller/edit',
|
|
||||||
'PhabricatorProjectListController' => 'applications/project/controller/list',
|
'PhabricatorProjectListController' => 'applications/project/controller/list',
|
||||||
'PhabricatorProjectProfile' => 'applications/project/storage/profile',
|
'PhabricatorProjectProfile' => 'applications/project/storage/profile',
|
||||||
'PhabricatorProjectProfileController' => 'applications/project/controller/profile',
|
'PhabricatorProjectProfileController' => 'applications/project/controller/profile',
|
||||||
|
'PhabricatorProjectProfileEditController' => 'applications/project/controller/profileedit',
|
||||||
'PhabricatorProjectQuickCreateController' => 'applications/project/controller/quickcreate',
|
'PhabricatorProjectQuickCreateController' => 'applications/project/controller/quickcreate',
|
||||||
|
'PhabricatorProjectStatus' => 'applications/project/constants/status',
|
||||||
|
'PhabricatorProjectTransactionSearch' => 'applications/project/transactions/search',
|
||||||
'PhabricatorRedirectController' => 'applications/base/controller/redirect',
|
'PhabricatorRedirectController' => 'applications/base/controller/redirect',
|
||||||
'PhabricatorRemarkupRuleDifferential' => 'infrastructure/markup/remarkup/markuprule/differential',
|
'PhabricatorRemarkupRuleDifferential' => 'infrastructure/markup/remarkup/markuprule/differential',
|
||||||
'PhabricatorRemarkupRuleDiffusion' => 'infrastructure/markup/remarkup/markuprule/diffusion',
|
'PhabricatorRemarkupRuleDiffusion' => 'infrastructure/markup/remarkup/markuprule/diffusion',
|
||||||
|
@ -877,15 +880,16 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorPeopleProfileController' => 'PhabricatorPeopleController',
|
'PhabricatorPeopleProfileController' => 'PhabricatorPeopleController',
|
||||||
'PhabricatorPeopleProfileEditController' => 'PhabricatorPeopleController',
|
'PhabricatorPeopleProfileEditController' => 'PhabricatorPeopleController',
|
||||||
'PhabricatorPreferencesController' => 'PhabricatorController',
|
'PhabricatorPreferencesController' => 'PhabricatorController',
|
||||||
|
'PhabricatorProfileView' => 'AphrontView',
|
||||||
'PhabricatorProject' => 'PhabricatorProjectDAO',
|
'PhabricatorProject' => 'PhabricatorProjectDAO',
|
||||||
'PhabricatorProjectAffiliation' => 'PhabricatorProjectDAO',
|
'PhabricatorProjectAffiliation' => 'PhabricatorProjectDAO',
|
||||||
'PhabricatorProjectAffiliationEditController' => 'PhabricatorProjectController',
|
'PhabricatorProjectAffiliationEditController' => 'PhabricatorProjectController',
|
||||||
'PhabricatorProjectController' => 'PhabricatorController',
|
'PhabricatorProjectController' => 'PhabricatorController',
|
||||||
'PhabricatorProjectDAO' => 'PhabricatorLiskDAO',
|
'PhabricatorProjectDAO' => 'PhabricatorLiskDAO',
|
||||||
'PhabricatorProjectEditController' => 'PhabricatorProjectController',
|
|
||||||
'PhabricatorProjectListController' => 'PhabricatorProjectController',
|
'PhabricatorProjectListController' => 'PhabricatorProjectController',
|
||||||
'PhabricatorProjectProfile' => 'PhabricatorProjectDAO',
|
'PhabricatorProjectProfile' => 'PhabricatorProjectDAO',
|
||||||
'PhabricatorProjectProfileController' => 'PhabricatorProjectController',
|
'PhabricatorProjectProfileController' => 'PhabricatorProjectController',
|
||||||
|
'PhabricatorProjectProfileEditController' => 'PhabricatorProjectController',
|
||||||
'PhabricatorProjectQuickCreateController' => 'PhabricatorProjectController',
|
'PhabricatorProjectQuickCreateController' => 'PhabricatorProjectController',
|
||||||
'PhabricatorRedirectController' => 'PhabricatorController',
|
'PhabricatorRedirectController' => 'PhabricatorController',
|
||||||
'PhabricatorRemarkupRuleDifferential' => 'PhabricatorRemarkupRuleObjectName',
|
'PhabricatorRemarkupRuleDifferential' => 'PhabricatorRemarkupRuleObjectName',
|
||||||
|
|
|
@ -76,7 +76,8 @@ class AphrontDefaultApplicationConfiguration
|
||||||
'edit/(?:(?P<id>\d+)/(?:(?P<view>\w+)/)?)?$'
|
'edit/(?:(?P<id>\d+)/(?:(?P<view>\w+)/)?)?$'
|
||||||
=> 'PhabricatorPeopleEditController',
|
=> 'PhabricatorPeopleEditController',
|
||||||
),
|
),
|
||||||
'/p/(?P<username>\w+)/$' => 'PhabricatorPeopleProfileController',
|
'/p/(?P<username>\w+)/(?:(?P<page>\w+)/)?$'
|
||||||
|
=> 'PhabricatorPeopleProfileController',
|
||||||
'/profile/' => array(
|
'/profile/' => array(
|
||||||
'edit/$' => 'PhabricatorPeopleProfileEditController',
|
'edit/$' => 'PhabricatorPeopleProfileEditController',
|
||||||
),
|
),
|
||||||
|
@ -196,8 +197,9 @@ class AphrontDefaultApplicationConfiguration
|
||||||
|
|
||||||
'/project/' => array(
|
'/project/' => array(
|
||||||
'$' => 'PhabricatorProjectListController',
|
'$' => 'PhabricatorProjectListController',
|
||||||
'edit/(?:(?P<id>\d+)/)?$' => 'PhabricatorProjectEditController',
|
'edit/(?:(?P<id>\d+)/)?$' => 'PhabricatorProjectProfileEditController',
|
||||||
'view/(?P<id>\d+)/$' => 'PhabricatorProjectProfileController',
|
'view/(?P<id>\d+)/(?:(?P<page>\w+)/)?$'
|
||||||
|
=> 'PhabricatorProjectProfileController',
|
||||||
'affiliation/(?P<id>\d+)/$'
|
'affiliation/(?P<id>\d+)/$'
|
||||||
=> 'PhabricatorProjectAffiliationEditController',
|
=> 'PhabricatorProjectAffiliationEditController',
|
||||||
'quickcreate/$' => 'PhabricatorProjectQuickCreateController',
|
'quickcreate/$' => 'PhabricatorProjectQuickCreateController',
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
phutil_require_module('phabricator', 'aphront/response/webpage');
|
phutil_require_module('phabricator', 'aphront/response/webpage');
|
||||||
phutil_require_module('phabricator', 'applications/base/controller/base');
|
phutil_require_module('phabricator', 'applications/base/controller/base');
|
||||||
|
phutil_require_module('phabricator', 'infrastructure/env');
|
||||||
|
|
||||||
phutil_require_module('phutil', 'utils');
|
phutil_require_module('phutil', 'utils');
|
||||||
|
|
||||||
|
|
|
@ -19,9 +19,11 @@
|
||||||
class PhabricatorPeopleProfileController extends PhabricatorPeopleController {
|
class PhabricatorPeopleProfileController extends PhabricatorPeopleController {
|
||||||
|
|
||||||
private $username;
|
private $username;
|
||||||
|
private $page;
|
||||||
|
|
||||||
public function willProcessRequest(array $data) {
|
public function willProcessRequest(array $data) {
|
||||||
$this->username = $data['username'];
|
$this->username = idx($data, 'username');
|
||||||
|
$this->page = idx($data, 'page');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function processRequest() {
|
public function processRequest() {
|
||||||
|
@ -83,44 +85,56 @@ class PhabricatorPeopleProfileController extends PhabricatorPeopleController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($links as $k => $link) {
|
// TODO: perhaps, if someone wants to add to the profile of the user the
|
||||||
$links[$k] = '<li>'.$link.'</li>';
|
// ability to show the task/revisions where he is working/commenting
|
||||||
|
// on, this has to be changed to something like
|
||||||
|
// |$this->page = key($pages)|, since the "page" regexp was added to
|
||||||
|
// the aphrontconfiguration.
|
||||||
|
if (empty($links[$this->page])) {
|
||||||
|
$this->page = 'action';
|
||||||
}
|
}
|
||||||
$links =
|
|
||||||
'<ul class="profile-nav-links">'.
|
|
||||||
implode("\n", $links).
|
|
||||||
'</ul>';
|
|
||||||
|
|
||||||
$title = nonempty($profile->getTitle(), 'Untitled Document');
|
switch ($this->page) {
|
||||||
|
default:
|
||||||
$username_tag =
|
$content = $this->renderBasicInformation($user, $profile);
|
||||||
'<h1 class="profile-username">'.
|
break;
|
||||||
phutil_escape_html($user->getUserName()).
|
}
|
||||||
'</h1>';
|
|
||||||
$realname_tag =
|
|
||||||
'<h2 class="profile-realname">'.
|
|
||||||
'('.phutil_escape_html($user->getRealName()).')'.
|
|
||||||
'</h2>';
|
|
||||||
$title_tag =
|
|
||||||
'<h2 class="profile-usertitle">'.
|
|
||||||
phutil_escape_html($title).
|
|
||||||
'</h2>';
|
|
||||||
|
|
||||||
$src_phid = $profile->getProfileImagePHID();
|
$src_phid = $profile->getProfileImagePHID();
|
||||||
if (!$src_phid) {
|
if (!$src_phid) {
|
||||||
$src_phid = $user->getProfileImagePHID();
|
$src_phid = $user->getProfileImagePHID();
|
||||||
}
|
}
|
||||||
$src = PhabricatorFileURI::getViewURIForPHID($src_phid);
|
$picture = PhabricatorFileURI::getViewURIForPHID($src_phid);
|
||||||
|
$title = nonempty($profile->getTitle(), 'Untitled Document');
|
||||||
|
$realname = '('.$user->getRealName().')';
|
||||||
|
|
||||||
$picture = phutil_render_tag(
|
$profile = new PhabricatorProfileView();
|
||||||
'img',
|
$profile->setProfilePicture($picture);
|
||||||
|
$profile->setProfileNames(
|
||||||
|
$user->getUserName(),
|
||||||
|
$realname,
|
||||||
|
$title);
|
||||||
|
foreach ($links as $page => $name) {
|
||||||
|
if (is_integer($page)) {
|
||||||
|
$profile->addProfileItem(
|
||||||
|
phutil_render_tag(
|
||||||
|
'span',
|
||||||
|
array(),
|
||||||
|
$name));
|
||||||
|
} else {
|
||||||
|
$profile->addProfileItem($page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$profile->appendChild($content);
|
||||||
|
return $this->buildStandardPageResponse(
|
||||||
|
$profile,
|
||||||
array(
|
array(
|
||||||
'class' => 'profile-image',
|
'title' => $user->getUsername(),
|
||||||
'src' => $src,
|
|
||||||
));
|
));
|
||||||
|
}
|
||||||
|
|
||||||
require_celerity_resource('phabricator-profile-css');
|
private function renderBasicInformation($user, $profile) {
|
||||||
|
|
||||||
$blurb = nonempty(
|
$blurb = nonempty(
|
||||||
$profile->getBlurb(),
|
$profile->getBlurb(),
|
||||||
'//Nothing is known about this rare specimen.//');
|
'//Nothing is known about this rare specimen.//');
|
||||||
|
@ -158,30 +172,6 @@ class PhabricatorPeopleProfileController extends PhabricatorPeopleController {
|
||||||
</div>
|
</div>
|
||||||
</div>';
|
</div>';
|
||||||
|
|
||||||
$profile =
|
return $content;
|
||||||
'<table class="phabricator-profile-master-layout">
|
|
||||||
<tr>
|
|
||||||
<td class="phabricator-profile-navigation">'.
|
|
||||||
$username_tag.
|
|
||||||
$realname_tag.
|
|
||||||
$title_tag.
|
|
||||||
'<hr />'.
|
|
||||||
$picture.
|
|
||||||
'<hr />'.
|
|
||||||
$links.
|
|
||||||
'<hr />'.
|
|
||||||
'</td>
|
|
||||||
<td class="phabricator-profile-content">'.
|
|
||||||
$content.
|
|
||||||
'</td>
|
|
||||||
</tr>
|
|
||||||
</table>';
|
|
||||||
|
|
||||||
return $this->buildStandardPageResponse(
|
|
||||||
$profile,
|
|
||||||
array(
|
|
||||||
'title' => $user->getUsername(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ phutil_require_module('phabricator', 'applications/people/controller/base');
|
||||||
phutil_require_module('phabricator', 'applications/people/storage/profile');
|
phutil_require_module('phabricator', 'applications/people/storage/profile');
|
||||||
phutil_require_module('phabricator', 'applications/people/storage/user');
|
phutil_require_module('phabricator', 'applications/people/storage/user');
|
||||||
phutil_require_module('phabricator', 'applications/people/storage/useroauthinfo');
|
phutil_require_module('phabricator', 'applications/people/storage/useroauthinfo');
|
||||||
phutil_require_module('phabricator', 'infrastructure/celerity/api');
|
phutil_require_module('phabricator', 'view/layout/profile');
|
||||||
phutil_require_module('phabricator', 'view/utils');
|
phutil_require_module('phabricator', 'view/utils');
|
||||||
|
|
||||||
phutil_require_module('phutil', 'markup');
|
phutil_require_module('phutil', 'markup');
|
||||||
|
|
|
@ -60,6 +60,7 @@ class PhabricatorPeopleProfileEditController
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$error_view = null;
|
||||||
if ($errors) {
|
if ($errors) {
|
||||||
$error_view = new AphrontErrorView();
|
$error_view = new AphrontErrorView();
|
||||||
$error_view->setTitle('Form Errors');
|
$error_view->setTitle('Form Errors');
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
<?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 PhabricatorProjectStatus {
|
||||||
|
|
||||||
|
const UNKNOWN = 0;
|
||||||
|
const NOT_STARTED = 1;
|
||||||
|
const IN_PROGRESS = 2;
|
||||||
|
const REVIEW_PROCESS = 3;
|
||||||
|
const RELEASED = 4;
|
||||||
|
const COMPLETED = 5;
|
||||||
|
const DEFERRED = 6;
|
||||||
|
const ONGOING = 7;
|
||||||
|
|
||||||
|
|
||||||
|
public static function getNameForStatus($status) {
|
||||||
|
static $map = array(
|
||||||
|
self::UNKNOWN => 'Who knows?',
|
||||||
|
self::NOT_STARTED => 'Not started',
|
||||||
|
self::IN_PROGRESS => 'In progress',
|
||||||
|
self::ONGOING => 'Ongoing',
|
||||||
|
self::REVIEW_PROCESS => 'Review process',
|
||||||
|
self::RELEASED => 'Released',
|
||||||
|
self::COMPLETED => 'Completed',
|
||||||
|
self::DEFERRED => 'Deferred',
|
||||||
|
);
|
||||||
|
|
||||||
|
return idx($map, coalesce($status, '?'), $map[self::UNKNOWN]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getStatusMap() {
|
||||||
|
return array(
|
||||||
|
self::UNKNOWN => 'Who knows?',
|
||||||
|
self::NOT_STARTED => 'Not started',
|
||||||
|
self::IN_PROGRESS => 'In progress',
|
||||||
|
self::ONGOING => 'Ongoing',
|
||||||
|
self::REVIEW_PROCESS => 'Review process',
|
||||||
|
self::RELEASED => 'Released',
|
||||||
|
self::COMPLETED => 'Completed',
|
||||||
|
self::DEFERRED => 'Deferred',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
12
src/applications/project/constants/status/__init__.php
Normal file
12
src/applications/project/constants/status/__init__.php
Normal 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('PhabricatorProjectStatus.php');
|
|
@ -24,10 +24,50 @@ class PhabricatorProjectListController
|
||||||
$projects = id(new PhabricatorProject())->loadAllWhere(
|
$projects = id(new PhabricatorProject())->loadAllWhere(
|
||||||
'1 = 1 ORDER BY id DESC limit 100');
|
'1 = 1 ORDER BY id DESC limit 100');
|
||||||
|
|
||||||
|
$author_phids = mpull($projects, 'getAuthorPHID');
|
||||||
|
$handles = id(new PhabricatorObjectHandleData($author_phids))
|
||||||
|
->loadHandles();
|
||||||
|
|
||||||
$rows = array();
|
$rows = array();
|
||||||
foreach ($projects as $project) {
|
foreach ($projects as $project) {
|
||||||
|
$documents = new PhabricatorProjectTransactionSearch($project->getPHID());
|
||||||
|
// search all open documents by default
|
||||||
|
$documents->setSearchOptions();
|
||||||
|
$documents = $documents->executeSearch();
|
||||||
|
|
||||||
|
$documents_types = igroup($documents,'documentType');
|
||||||
|
$tasks = idx(
|
||||||
|
$documents_types,
|
||||||
|
PhabricatorPHIDConstants::PHID_TYPE_TASK);
|
||||||
|
$tasks_amount = count($tasks);
|
||||||
|
|
||||||
|
// TODO: set up a relationship between the project and the arcanist's
|
||||||
|
// project, to be able get the revisions.
|
||||||
|
$revisions = idx(
|
||||||
|
$documents_types,
|
||||||
|
PhabricatorPHIDConstants::PHID_TYPE_DREV);
|
||||||
|
$revisions_amount = count($revisions);
|
||||||
|
|
||||||
|
$profile = $project->getProfile();
|
||||||
|
$affiliations = $project->loadAffiliations();
|
||||||
|
$population = count($affiliations);
|
||||||
|
|
||||||
|
$status = PhabricatorProjectStatus::getNameForStatus(
|
||||||
|
$project->getStatus());
|
||||||
|
|
||||||
|
$blurb = nonempty(
|
||||||
|
$profile->getBlurb(),
|
||||||
|
'Oops!, nothing is known about this elusive project.');
|
||||||
|
$blurb = $this->textWrap($blurb, $columns = 100);
|
||||||
|
|
||||||
$rows[] = array(
|
$rows[] = array(
|
||||||
phutil_escape_html($project->getName()),
|
phutil_escape_html($project->getName()),
|
||||||
|
phutil_escape_html($blurb),
|
||||||
|
$handles[$project->getAuthorPHID()]->renderLink(),
|
||||||
|
phutil_escape_html($population),
|
||||||
|
phutil_escape_html($status),
|
||||||
|
phutil_escape_html($tasks_amount),
|
||||||
|
// phutil_escape_html($revisions_amount),
|
||||||
phutil_render_tag(
|
phutil_render_tag(
|
||||||
'a',
|
'a',
|
||||||
array(
|
array(
|
||||||
|
@ -42,11 +82,23 @@ class PhabricatorProjectListController
|
||||||
$table->setHeaders(
|
$table->setHeaders(
|
||||||
array(
|
array(
|
||||||
'Project',
|
'Project',
|
||||||
|
'Blurb',
|
||||||
|
'Mastermind',
|
||||||
|
'Population',
|
||||||
|
'Status',
|
||||||
|
'Open Tasks',
|
||||||
|
// 'Open Revisions',
|
||||||
'',
|
'',
|
||||||
));
|
));
|
||||||
$table->setColumnClasses(
|
$table->setColumnClasses(
|
||||||
array(
|
array(
|
||||||
|
'pri',
|
||||||
'wide',
|
'wide',
|
||||||
|
'',
|
||||||
|
'right',
|
||||||
|
'pri',
|
||||||
|
'right',
|
||||||
|
// 'right',
|
||||||
'action',
|
'action',
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -62,4 +114,17 @@ class PhabricatorProjectListController
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function textWrap($text, $length) {
|
||||||
|
if (strlen($text) <= $length) {
|
||||||
|
return $text;
|
||||||
|
} else {
|
||||||
|
// TODO: perhaps this could be improved, adding the ability to get the
|
||||||
|
// last letter and suppress it, if it is one of [(,:; ,etc.
|
||||||
|
// making "blurb" looks a little bit better. :)
|
||||||
|
$wrapped = wordwrap($text, $length, '__#END#__');
|
||||||
|
$end_position = strpos($wrapped, '__#END#__');
|
||||||
|
$wrapped = substr($text, 0, $end_position).'...';
|
||||||
|
return $wrapped;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,12 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
phutil_require_module('phabricator', 'applications/phid/constants');
|
||||||
|
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/controller/base');
|
||||||
phutil_require_module('phabricator', 'applications/project/storage/project');
|
phutil_require_module('phabricator', 'applications/project/storage/project');
|
||||||
|
phutil_require_module('phabricator', 'applications/project/transactions/search');
|
||||||
phutil_require_module('phabricator', 'view/control/table');
|
phutil_require_module('phabricator', 'view/control/table');
|
||||||
phutil_require_module('phabricator', 'view/layout/panel');
|
phutil_require_module('phabricator', 'view/layout/panel');
|
||||||
|
|
||||||
|
|
|
@ -20,139 +20,168 @@ class PhabricatorProjectProfileController
|
||||||
extends PhabricatorProjectController {
|
extends PhabricatorProjectController {
|
||||||
|
|
||||||
private $id;
|
private $id;
|
||||||
|
private $page;
|
||||||
|
|
||||||
public function willProcessRequest(array $data) {
|
public function willProcessRequest(array $data) {
|
||||||
$this->id = $data['id'];
|
$this->id = idx($data, 'id');
|
||||||
|
$this->page = idx($data, 'page');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function processRequest() {
|
public function processRequest() {
|
||||||
|
$request = $this->getRequest();
|
||||||
|
$user = $request->getUser();
|
||||||
|
$uri = $request->getRequestURI();
|
||||||
|
|
||||||
$project = id(new PhabricatorProject())->load($this->id);
|
$project = id(new PhabricatorProject())->load($this->id);
|
||||||
if (!$project) {
|
if (!$project) {
|
||||||
return new Aphront404Response();
|
return new Aphront404Response();
|
||||||
}
|
}
|
||||||
$profile = id(new PhabricatorProjectProfile())->loadOneWhere(
|
$profile = $project->getProfile();
|
||||||
'projectPHID = %s',
|
|
||||||
$project->getPHID());
|
|
||||||
if (!$profile) {
|
if (!$profile) {
|
||||||
$profile = new PhabricatorProjectProfile();
|
$profile = new PhabricatorProjectProfile();
|
||||||
}
|
}
|
||||||
|
|
||||||
require_celerity_resource('phabricator-profile-css');
|
|
||||||
|
|
||||||
$src_phid = $profile->getProfileImagePHID();
|
$src_phid = $profile->getProfileImagePHID();
|
||||||
$src = PhabricatorFileURI::getViewURIForPHID($src_phid);
|
if (!$src_phid) {
|
||||||
|
$src_phid = $user->getProfileImagePHID();
|
||||||
|
}
|
||||||
|
$picture = PhabricatorFileURI::getViewURIForPHID($src_phid);
|
||||||
|
|
||||||
$picture = phutil_render_tag(
|
$pages = array(
|
||||||
'img',
|
/*
|
||||||
array(
|
'<h2>Active Documents</h2>',
|
||||||
'class' => 'profile-image',
|
'tasks' => 'Maniphest Tasks',
|
||||||
'src' => $src,
|
'revisions' => 'Differential Revisions',
|
||||||
));
|
'<hr />',
|
||||||
|
'<h2>Workflow</h2>',
|
||||||
|
'goals' => 'Goals',
|
||||||
|
'statistics' => 'Statistics',
|
||||||
|
'<hr />', */
|
||||||
|
'<h2>Information</h2>',
|
||||||
|
'edit' => 'Edit Profile',
|
||||||
|
'affiliation' => 'Edit Affiliation',
|
||||||
|
);
|
||||||
|
|
||||||
$links =
|
if (empty($pages[$this->page])) {
|
||||||
'<ul class="profile-nav-links">'.
|
$this->page = 'action'; // key($pages);
|
||||||
'<li><a href="/project/edit/'.$project->getID().'/">'.
|
}
|
||||||
'Edit Project</a></li>'.
|
|
||||||
'<li><a href="/project/affiliation/'.$project->getID().'/">'.
|
|
||||||
'Edit Affiliation</a></li>'.
|
|
||||||
'</ul>';
|
|
||||||
|
|
||||||
$blurb = nonempty(
|
switch ($this->page) {
|
||||||
$profile->getBlurb(),
|
default:
|
||||||
'//Nothing is known about this elusive project.//');
|
$content = $this->renderBasicInformation($project, $profile);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
$factory = new DifferentialMarkupEngineFactory();
|
$profile = new PhabricatorProfileView();
|
||||||
$engine = $factory->newDifferentialCommentMarkupEngine();
|
$profile->setProfilePicture($picture);
|
||||||
$blurb = $engine->markupText($blurb);
|
$profile->setProfileNames($project->getName());
|
||||||
|
foreach ($pages as $page => $name) {
|
||||||
|
if (is_integer($page)) {
|
||||||
$affiliations = id(new PhabricatorProjectAffiliation())->loadAllWhere(
|
$profile->addProfileItem(
|
||||||
'projectPHID = %s ORDER BY IF(status = "former", 1, 0), dateCreated',
|
phutil_render_tag(
|
||||||
$project->getPHID());
|
'span',
|
||||||
|
array(),
|
||||||
$phids = array_merge(
|
$name));
|
||||||
array($project->getAuthorPHID()),
|
} else {
|
||||||
mpull($affiliations, 'getUserPHID'));
|
$uri->setPath('/project/'.$page.'/'.$project->getID().'/');
|
||||||
$handles = id(new PhabricatorObjectHandleData($phids))
|
$profile->addProfileItem(
|
||||||
->loadHandles();
|
phutil_render_tag(
|
||||||
|
'a',
|
||||||
$affiliated = array();
|
array(
|
||||||
foreach ($affiliations as $affiliation) {
|
'href' => $uri,
|
||||||
$user = $handles[$affiliation->getUserPHID()]->renderLink();
|
'class' => ($this->page == $page)
|
||||||
$role = phutil_escape_html($affiliation->getRole());
|
? 'phabricator-profile-item-selected'
|
||||||
|
: null,
|
||||||
$status = null;
|
),
|
||||||
if ($affiliation->getStatus() == 'former') {
|
phutil_escape_html($name)));
|
||||||
$role = '<em>Former '.$role.'</em>';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$affiliated[] = '<li>'.$user.' — '.$role.$status.'</li>';
|
|
||||||
}
|
|
||||||
if ($affiliated) {
|
|
||||||
$affiliated = '<ul>'.implode("\n", $affiliated).'</ul>';
|
|
||||||
} else {
|
|
||||||
$affiliated = '<p><em>No one is affiliated with this project.</em></p>';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$timestamp = phabricator_format_timestamp($project->getDateCreated());
|
$profile->appendChild($content);
|
||||||
|
|
||||||
$content =
|
|
||||||
'<div class="phabricator-profile-info-group">
|
|
||||||
<h1 class="phabricator-profile-info-header">Basic Information</h1>
|
|
||||||
<div class="phabricator-profile-info-pane">
|
|
||||||
<table class="phabricator-profile-info-table">
|
|
||||||
<tr>
|
|
||||||
<th>Creator</th>
|
|
||||||
<td>'.$handles[$project->getAuthorPHID()]->renderLink().'</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>Created</th>
|
|
||||||
<td>'.$timestamp.'</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>PHID</th>
|
|
||||||
<td>'.phutil_escape_html($project->getPHID()).'</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>Blurb</th>
|
|
||||||
<td>'.$blurb.'</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>';
|
|
||||||
|
|
||||||
$content .=
|
|
||||||
'<div class="phabricator-profile-info-group">
|
|
||||||
<h1 class="phabricator-profile-info-header">Resources</h1>
|
|
||||||
<div class="phabricator-profile-info-pane">'.
|
|
||||||
$affiliated.
|
|
||||||
'</div>
|
|
||||||
</div>';
|
|
||||||
|
|
||||||
|
|
||||||
$profile_markup =
|
|
||||||
'<table class="phabricator-profile-master-layout">
|
|
||||||
<tr>
|
|
||||||
<td class="phabricator-profile-navigation">'.
|
|
||||||
'<h1>'.phutil_escape_html($project->getName()).'</h1>'.
|
|
||||||
'<hr />'.
|
|
||||||
$picture.
|
|
||||||
'<hr />'.
|
|
||||||
$links.
|
|
||||||
'<hr />'.
|
|
||||||
'</td>
|
|
||||||
<td class="phabricator-profile-content">'.
|
|
||||||
$content.
|
|
||||||
'</td>
|
|
||||||
</tr>
|
|
||||||
</table>';
|
|
||||||
|
|
||||||
return $this->buildStandardPageResponse(
|
return $this->buildStandardPageResponse(
|
||||||
$profile_markup,
|
$profile,
|
||||||
array(
|
array(
|
||||||
'title' => $project->getName(),
|
'title' => $project->getName(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function renderBasicInformation($project, $profile) {
|
||||||
|
$blurb = nonempty(
|
||||||
|
$profile->getBlurb(),
|
||||||
|
'//Nothing is known about this elusive project.//');
|
||||||
|
|
||||||
|
$factory = new DifferentialMarkupEngineFactory();
|
||||||
|
$engine = $factory->newDifferentialCommentMarkupEngine();
|
||||||
|
$blurb = $engine->markupText($blurb);
|
||||||
|
|
||||||
|
$affiliations = $project->loadAffiliations();
|
||||||
|
|
||||||
|
$phids = array_merge(
|
||||||
|
array($project->getAuthorPHID()),
|
||||||
|
mpull($affiliations, 'getUserPHID'));
|
||||||
|
$handles = id(new PhabricatorObjectHandleData($phids))
|
||||||
|
->loadHandles();
|
||||||
|
|
||||||
|
$affiliated = array();
|
||||||
|
foreach ($affiliations as $affiliation) {
|
||||||
|
$user = $handles[$affiliation->getUserPHID()]->renderLink();
|
||||||
|
$role = phutil_escape_html($affiliation->getRole());
|
||||||
|
|
||||||
|
$status = null;
|
||||||
|
if ($affiliation->getStatus() == 'former') {
|
||||||
|
$role = '<em>Former '.$role.'</em>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$affiliated[] = '<li>'.$user.' — '.$role.$status.'</li>';
|
||||||
|
}
|
||||||
|
if ($affiliated) {
|
||||||
|
$affiliated = '<ul>'.implode("\n", $affiliated).'</ul>';
|
||||||
|
} else {
|
||||||
|
$affiliated = '<p><em>No one is affiliated with this project.</em></p>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$timestamp = phabricator_format_timestamp($project->getDateCreated());
|
||||||
|
$status = PhabricatorProjectStatus::getNameForStatus(
|
||||||
|
$project->getStatus());
|
||||||
|
|
||||||
|
$content =
|
||||||
|
'<div class="phabricator-profile-info-group">
|
||||||
|
<h1 class="phabricator-profile-info-header">Basic Information</h1>
|
||||||
|
<div class="phabricator-profile-info-pane">
|
||||||
|
<table class="phabricator-profile-info-table">
|
||||||
|
<tr>
|
||||||
|
<th>Creator</th>
|
||||||
|
<td>'.$handles[$project->getAuthorPHID()]->renderLink().'</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Status</th>
|
||||||
|
<td><strong>'.phutil_escape_html($status).'</strong></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Created</th>
|
||||||
|
<td>'.$timestamp.'</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>PHID</th>
|
||||||
|
<td>'.phutil_escape_html($project->getPHID()).'</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Blurb</th>
|
||||||
|
<td>'.$blurb.'</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>';
|
||||||
|
|
||||||
|
$content .=
|
||||||
|
'<div class="phabricator-profile-info-group">
|
||||||
|
<h1 class="phabricator-profile-info-header">Resources</h1>
|
||||||
|
<div class="phabricator-profile-info-pane">'.
|
||||||
|
$affiliated.
|
||||||
|
'</div>
|
||||||
|
</div>';
|
||||||
|
|
||||||
|
return $content;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,11 +10,11 @@ phutil_require_module('phabricator', 'aphront/response/404');
|
||||||
phutil_require_module('phabricator', 'applications/differential/parser/markup');
|
phutil_require_module('phabricator', 'applications/differential/parser/markup');
|
||||||
phutil_require_module('phabricator', 'applications/files/uri');
|
phutil_require_module('phabricator', 'applications/files/uri');
|
||||||
phutil_require_module('phabricator', 'applications/phid/handle/data');
|
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/controller/base');
|
||||||
phutil_require_module('phabricator', 'applications/project/storage/affiliation');
|
|
||||||
phutil_require_module('phabricator', 'applications/project/storage/profile');
|
phutil_require_module('phabricator', 'applications/project/storage/profile');
|
||||||
phutil_require_module('phabricator', 'applications/project/storage/project');
|
phutil_require_module('phabricator', 'applications/project/storage/project');
|
||||||
phutil_require_module('phabricator', 'infrastructure/celerity/api');
|
phutil_require_module('phabricator', 'view/layout/profile');
|
||||||
phutil_require_module('phabricator', 'view/utils');
|
phutil_require_module('phabricator', 'view/utils');
|
||||||
|
|
||||||
phutil_require_module('phutil', 'markup');
|
phutil_require_module('phutil', 'markup');
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class PhabricatorProjectEditController
|
class PhabricatorProjectProfileEditController
|
||||||
extends PhabricatorProjectController {
|
extends PhabricatorProjectController {
|
||||||
|
|
||||||
private $id;
|
private $id;
|
||||||
|
@ -35,9 +35,7 @@ class PhabricatorProjectEditController
|
||||||
if (!$project) {
|
if (!$project) {
|
||||||
return new Aphront404Response();
|
return new Aphront404Response();
|
||||||
}
|
}
|
||||||
$profile = id(new PhabricatorProjectProfile())->loadOneWhere(
|
$profile = $project->getProfile();
|
||||||
'projectPHID = %s',
|
|
||||||
$project->getPHID());
|
|
||||||
} else {
|
} else {
|
||||||
$project = new PhabricatorProject();
|
$project = new PhabricatorProject();
|
||||||
$project->setAuthorPHID($user->getPHID());
|
$project->setAuthorPHID($user->getPHID());
|
||||||
|
@ -47,12 +45,13 @@ class PhabricatorProjectEditController
|
||||||
$profile = new PhabricatorProjectProfile();
|
$profile = new PhabricatorProjectProfile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$options = PhabricatorProjectStatus::getStatusMap();
|
||||||
|
|
||||||
$e_name = true;
|
$e_name = true;
|
||||||
$errors = array();
|
$errors = array();
|
||||||
|
|
||||||
if ($request->isFormPost()) {
|
if ($request->isFormPost()) {
|
||||||
|
|
||||||
$project->setName($request->getStr('name'));
|
$project->setName($request->getStr('name'));
|
||||||
|
$project->setStatus($request->getStr('status'));
|
||||||
$profile->setBlurb($request->getStr('blurb'));
|
$profile->setBlurb($request->getStr('blurb'));
|
||||||
|
|
||||||
if (!strlen($project->getName())) {
|
if (!strlen($project->getName())) {
|
||||||
|
@ -62,6 +61,21 @@ class PhabricatorProjectEditController
|
||||||
$e_name = null;
|
$e_name = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!empty($_FILES['image'])) {
|
||||||
|
$err = idx($_FILES['image'], 'error');
|
||||||
|
if ($err != UPLOAD_ERR_NO_FILE) {
|
||||||
|
$file = PhabricatorFile::newFromPHPUpload($_FILES['image']);
|
||||||
|
$okay = $file->isTransformableImage();
|
||||||
|
if ($okay) {
|
||||||
|
$profile->setProfileImagePHID($file->getPHID());
|
||||||
|
} else {
|
||||||
|
$errors[] =
|
||||||
|
'Only valid image files (jpg, jpeg, png or gif) '.
|
||||||
|
'will be accepted.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!$errors) {
|
if (!$errors) {
|
||||||
$project->save();
|
$project->save();
|
||||||
$profile->setProjectPHID($project->getPHID());
|
$profile->setProjectPHID($project->getPHID());
|
||||||
|
@ -92,20 +106,32 @@ class PhabricatorProjectEditController
|
||||||
$form
|
$form
|
||||||
->setUser($user)
|
->setUser($user)
|
||||||
->setAction($action)
|
->setAction($action)
|
||||||
|
->setEncType('multipart/form-data')
|
||||||
->appendChild(
|
->appendChild(
|
||||||
id(new AphrontFormTextControl())
|
id(new AphrontFormTextControl())
|
||||||
->setLabel('Name')
|
->setLabel('Name')
|
||||||
->setName('name')
|
->setName('name')
|
||||||
->setValue($project->getName())
|
->setValue($project->getName())
|
||||||
->setError($e_name))
|
->setError($e_name))
|
||||||
|
->appendChild(
|
||||||
|
id(new AphrontFormSelectControl())
|
||||||
|
->setLabel('Project Status')
|
||||||
|
->setName('status')
|
||||||
|
->setOptions($options)
|
||||||
|
->setValue($project->getStatus()))
|
||||||
->appendChild(
|
->appendChild(
|
||||||
id(new AphrontFormTextAreaControl())
|
id(new AphrontFormTextAreaControl())
|
||||||
->setLabel('Blurb')
|
->setLabel('Blurb')
|
||||||
->setName('blurb')
|
->setName('blurb')
|
||||||
->setValue($profile->getBlurb()))
|
->setValue($profile->getBlurb()))
|
||||||
|
->appendChild(
|
||||||
|
id(new AphrontFormFileControl())
|
||||||
|
->setLabel('Change Image')
|
||||||
|
->setName('image')
|
||||||
|
->setCaption('Upload a 280px-wide image.'))
|
||||||
->appendChild(
|
->appendChild(
|
||||||
id(new AphrontFormSubmitControl())
|
id(new AphrontFormSubmitControl())
|
||||||
->addCancelButton('/project/')
|
->addCancelButton('/project/view/'.$project->getID().'/')
|
||||||
->setValue('Save'));
|
->setValue('Save'));
|
||||||
|
|
||||||
$panel = new AphrontPanelView();
|
$panel = new AphrontPanelView();
|
||||||
|
@ -122,5 +148,4 @@ class PhabricatorProjectEditController
|
||||||
'title' => $title,
|
'title' => $title,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -8,10 +8,14 @@
|
||||||
|
|
||||||
phutil_require_module('phabricator', 'aphront/response/404');
|
phutil_require_module('phabricator', 'aphront/response/404');
|
||||||
phutil_require_module('phabricator', 'aphront/response/redirect');
|
phutil_require_module('phabricator', 'aphront/response/redirect');
|
||||||
|
phutil_require_module('phabricator', 'applications/files/storage/file');
|
||||||
|
phutil_require_module('phabricator', 'applications/project/constants/status');
|
||||||
phutil_require_module('phabricator', 'applications/project/controller/base');
|
phutil_require_module('phabricator', 'applications/project/controller/base');
|
||||||
phutil_require_module('phabricator', 'applications/project/storage/profile');
|
phutil_require_module('phabricator', 'applications/project/storage/profile');
|
||||||
phutil_require_module('phabricator', 'applications/project/storage/project');
|
phutil_require_module('phabricator', 'applications/project/storage/project');
|
||||||
phutil_require_module('phabricator', 'view/form/base');
|
phutil_require_module('phabricator', 'view/form/base');
|
||||||
|
phutil_require_module('phabricator', 'view/form/control/file');
|
||||||
|
phutil_require_module('phabricator', 'view/form/control/select');
|
||||||
phutil_require_module('phabricator', 'view/form/control/submit');
|
phutil_require_module('phabricator', 'view/form/control/submit');
|
||||||
phutil_require_module('phabricator', 'view/form/control/text');
|
phutil_require_module('phabricator', 'view/form/control/text');
|
||||||
phutil_require_module('phabricator', 'view/form/control/textarea');
|
phutil_require_module('phabricator', 'view/form/control/textarea');
|
||||||
|
@ -21,4 +25,4 @@ phutil_require_module('phabricator', 'view/layout/panel');
|
||||||
phutil_require_module('phutil', 'utils');
|
phutil_require_module('phutil', 'utils');
|
||||||
|
|
||||||
|
|
||||||
phutil_require_source('PhabricatorProjectEditController.php');
|
phutil_require_source('PhabricatorProjectProfileEditController.php');
|
|
@ -28,10 +28,10 @@ class PhabricatorProjectQuickCreateController
|
||||||
$project = new PhabricatorProject();
|
$project = new PhabricatorProject();
|
||||||
$project->setAuthorPHID($user->getPHID());
|
$project->setAuthorPHID($user->getPHID());
|
||||||
$profile = new PhabricatorProjectProfile();
|
$profile = new PhabricatorProjectProfile();
|
||||||
|
$options = PhabricatorProjectStatus::getStatusMap();
|
||||||
|
|
||||||
$e_name = true;
|
$e_name = true;
|
||||||
$errors = array();
|
$errors = array();
|
||||||
|
|
||||||
if ($request->isFormPost()) {
|
if ($request->isFormPost()) {
|
||||||
|
|
||||||
$project->setName($request->getStr('name'));
|
$project->setName($request->getStr('name'));
|
||||||
|
@ -46,7 +46,6 @@ class PhabricatorProjectQuickCreateController
|
||||||
|
|
||||||
if (!$errors) {
|
if (!$errors) {
|
||||||
$project->save();
|
$project->save();
|
||||||
|
|
||||||
$profile->setProjectPHID($project->getPHID());
|
$profile->setProjectPHID($project->getPHID());
|
||||||
$profile->save();
|
$profile->save();
|
||||||
|
|
||||||
|
@ -91,5 +90,4 @@ class PhabricatorProjectQuickCreateController
|
||||||
|
|
||||||
return id(new AphrontDialogResponse())->setDialog($dialog);
|
return id(new AphrontDialogResponse())->setDialog($dialog);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
phutil_require_module('phabricator', 'aphront/response/ajax');
|
phutil_require_module('phabricator', 'aphront/response/ajax');
|
||||||
phutil_require_module('phabricator', 'aphront/response/dialog');
|
phutil_require_module('phabricator', 'aphront/response/dialog');
|
||||||
|
phutil_require_module('phabricator', 'applications/project/constants/status');
|
||||||
phutil_require_module('phabricator', 'applications/project/controller/base');
|
phutil_require_module('phabricator', 'applications/project/controller/base');
|
||||||
phutil_require_module('phabricator', 'applications/project/storage/profile');
|
phutil_require_module('phabricator', 'applications/project/storage/profile');
|
||||||
phutil_require_module('phabricator', 'applications/project/storage/project');
|
phutil_require_module('phabricator', 'applications/project/storage/project');
|
||||||
|
|
|
@ -23,12 +23,8 @@ class PhabricatorProjectProfile extends PhabricatorProjectDAO {
|
||||||
protected $profileImagePHID;
|
protected $profileImagePHID;
|
||||||
|
|
||||||
public function getProfileImagePHID() {
|
public function getProfileImagePHID() {
|
||||||
if ($this->profileImagePHID) {
|
return nonempty(
|
||||||
return $this->profileImagePHID;
|
$this->profileImagePHID,
|
||||||
}
|
PhabricatorEnv::getEnvConfig('user.default-profile-image-phid'));
|
||||||
// TODO: Make a separate one of these for projects.
|
|
||||||
return PhabricatorEnv::getEnvConfig('user.default-profile-image-phid');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,5 +9,7 @@
|
||||||
phutil_require_module('phabricator', 'applications/project/storage/base');
|
phutil_require_module('phabricator', 'applications/project/storage/base');
|
||||||
phutil_require_module('phabricator', 'infrastructure/env');
|
phutil_require_module('phabricator', 'infrastructure/env');
|
||||||
|
|
||||||
|
phutil_require_module('phutil', 'utils');
|
||||||
|
|
||||||
|
|
||||||
phutil_require_source('PhabricatorProjectProfile.php');
|
phutil_require_source('PhabricatorProjectProfile.php');
|
||||||
|
|
|
@ -20,6 +20,7 @@ class PhabricatorProject extends PhabricatorProjectDAO {
|
||||||
|
|
||||||
protected $name;
|
protected $name;
|
||||||
protected $phid;
|
protected $phid;
|
||||||
|
protected $status = PhabricatorProjectStatus::UNKNOWN;
|
||||||
protected $authorPHID;
|
protected $authorPHID;
|
||||||
|
|
||||||
public function getConfiguration() {
|
public function getConfiguration() {
|
||||||
|
@ -33,4 +34,17 @@ class PhabricatorProject extends PhabricatorProjectDAO {
|
||||||
PhabricatorPHIDConstants::PHID_TYPE_PROJ);
|
PhabricatorPHIDConstants::PHID_TYPE_PROJ);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getProfile() {
|
||||||
|
$profile = id(new PhabricatorProjectProfile())->loadOneWhere(
|
||||||
|
'projectPHID = %s',
|
||||||
|
$this->getPHID());
|
||||||
|
return $profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadAffiliations() {
|
||||||
|
$affiliations = id(new PhabricatorProjectAffiliation())->loadAllWhere(
|
||||||
|
'projectPHID = %s ORDER BY IF(status = "former", 1, 0), dateCreated',
|
||||||
|
$this->getPHID());
|
||||||
|
return $affiliations;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,12 @@
|
||||||
|
|
||||||
phutil_require_module('phabricator', 'applications/phid/constants');
|
phutil_require_module('phabricator', 'applications/phid/constants');
|
||||||
phutil_require_module('phabricator', 'applications/phid/storage/phid');
|
phutil_require_module('phabricator', 'applications/phid/storage/phid');
|
||||||
|
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/base');
|
||||||
|
phutil_require_module('phabricator', 'applications/project/storage/profile');
|
||||||
|
|
||||||
|
phutil_require_module('phutil', 'utils');
|
||||||
|
|
||||||
|
|
||||||
phutil_require_source('PhabricatorProject.php');
|
phutil_require_source('PhabricatorProject.php');
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
<?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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class PhabricatorProjectTransactionSearch {
|
||||||
|
private $projectPhids;
|
||||||
|
private $documents;
|
||||||
|
private $status;
|
||||||
|
|
||||||
|
public function __construct($project_phids) {
|
||||||
|
if (is_array($project_phids)) {
|
||||||
|
$this->projectPhids = $project_phids;
|
||||||
|
} else {
|
||||||
|
$this->projectPhids = array($project_phids);
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// search all open documents by default
|
||||||
|
public function setSearchOptions($documents = '', $status = true) {
|
||||||
|
$this->documents = $documents;
|
||||||
|
$this->status = $status;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function executeSearch() {
|
||||||
|
$projects = $this->projectPhids;
|
||||||
|
$on_documents = $this->documents;
|
||||||
|
$with_status = $this->status;
|
||||||
|
|
||||||
|
$query = new PhabricatorSearchQuery();
|
||||||
|
$query->setQuery('');
|
||||||
|
$query->setParameter('project', $projects);
|
||||||
|
$query->setParameter('type', $on_documents);
|
||||||
|
$query->setParameter('open', $with_status);
|
||||||
|
|
||||||
|
$executor = new PhabricatorSearchMySQLExecutor();
|
||||||
|
$results = $executor->executeSearch($query);
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
}
|
13
src/applications/project/transactions/search/__init__.php
Normal file
13
src/applications/project/transactions/search/__init__.php
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* This file is automatically generated. Lint this module to rebuild it.
|
||||||
|
* @generated
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
phutil_require_module('phabricator', 'applications/search/execute/mysql');
|
||||||
|
phutil_require_module('phabricator', 'applications/search/storage/query');
|
||||||
|
|
||||||
|
|
||||||
|
phutil_require_source('PhabricatorProjectTransactionSearch.php');
|
93
src/view/layout/profile/PhabricatorProfileView.php
Normal file
93
src/view/layout/profile/PhabricatorProfileView.php
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
<?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 PhabricatorProfileView extends AphrontView {
|
||||||
|
|
||||||
|
protected $items = array();
|
||||||
|
protected $profilePicture;
|
||||||
|
protected $profileName;
|
||||||
|
protected $profileRealname;
|
||||||
|
protected $profileTitle;
|
||||||
|
|
||||||
|
public function addProfileItem($item) {
|
||||||
|
$this->items[] = $item;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setProfilePicture($picture) {
|
||||||
|
$this->profilePicture = $picture;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setProfileNames($name, $realname = null, $title = null) {
|
||||||
|
$this->profileName = $name;
|
||||||
|
$this->profileRealname = $realname;
|
||||||
|
$this->profileTitle = $title;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render() {
|
||||||
|
$view = new AphrontNullView();
|
||||||
|
$view->appendChild($this->items);
|
||||||
|
|
||||||
|
$side_links = null;
|
||||||
|
$realname = null;
|
||||||
|
$title = null;
|
||||||
|
if (!empty($this->profileRealname)) {
|
||||||
|
$realname =
|
||||||
|
'<h2 class="phabricator-profile-realname">'.
|
||||||
|
phutil_escape_html($this->profileRealname).
|
||||||
|
'</h2>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($this->profileTitle)) {
|
||||||
|
$title =
|
||||||
|
'<h2>'.
|
||||||
|
phutil_escape_html($this->profileTitle).
|
||||||
|
'</h2>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($this->items)) {
|
||||||
|
$side_links =
|
||||||
|
$view->render().
|
||||||
|
'<hr />';
|
||||||
|
}
|
||||||
|
|
||||||
|
require_celerity_resource('phabricator-profile-css');
|
||||||
|
|
||||||
|
return
|
||||||
|
'<table class="phabricator-profile-master-layout">'.
|
||||||
|
'<tr>'.
|
||||||
|
'<td class="phabricator-profile-navigation">'.
|
||||||
|
'<h1>'.phutil_escape_html($this->profileName).'</h1>'.
|
||||||
|
$realname.
|
||||||
|
$title.
|
||||||
|
'<hr />'.
|
||||||
|
'<img class="phabricator-profile-image" src="'.
|
||||||
|
$this->profilePicture.
|
||||||
|
'"/>'.
|
||||||
|
'<hr />'.
|
||||||
|
$side_links.
|
||||||
|
'</td>'.
|
||||||
|
'<td class="phabricator-profile-content">'.
|
||||||
|
$this->renderChildren().
|
||||||
|
'</td>'.
|
||||||
|
'</tr>'.
|
||||||
|
'</table>';
|
||||||
|
}
|
||||||
|
}
|
16
src/view/layout/profile/__init__.php
Normal file
16
src/view/layout/profile/__init__.php
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* This file is automatically generated. Lint this module to rebuild it.
|
||||||
|
* @generated
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
phutil_require_module('phabricator', 'infrastructure/celerity/api');
|
||||||
|
phutil_require_module('phabricator', 'view/base');
|
||||||
|
phutil_require_module('phabricator', 'view/null');
|
||||||
|
|
||||||
|
phutil_require_module('phutil', 'markup');
|
||||||
|
|
||||||
|
|
||||||
|
phutil_require_source('PhabricatorProfileView.php');
|
|
@ -2,20 +2,72 @@
|
||||||
* @provides phabricator-profile-css
|
* @provides phabricator-profile-css
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.phabricator-profile-master-layout {
|
table.phabricator-profile-master-layout {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.phabricator-profile-navigation {
|
td.phabricator-profile-navigation {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
background: #efefef;
|
background: #efefef;
|
||||||
border-right: 1px solid #cccccc;
|
border-right: 1px solid #cccccc;
|
||||||
|
padding-top: 8px;
|
||||||
|
padding-bottom: 8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.phabricator-profile-content {
|
td.phabricator-profile-navigation a,
|
||||||
|
td.phabricator-profile-navigation span {
|
||||||
|
display: block;
|
||||||
|
margin: 0 0 2px;
|
||||||
|
min-width: 150px;
|
||||||
|
font-weight: bold;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.phabricator-profile-navigation a {
|
||||||
|
padding: 4px 8px 4px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.phabricator-profile-navigation a:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
background: #cccccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.phabricator-profile-navigation a.phabricator-profile-item-selected,
|
||||||
|
td.phabricator-profile-navigation a.phabricator-profile-item-selected :hover {
|
||||||
|
background: #cccccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.phabricator-profile-navigation hr {
|
||||||
|
border: none;
|
||||||
|
background: #cccccc;
|
||||||
|
padding: 0;
|
||||||
|
margin: 10px 0;
|
||||||
|
height: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.phabricator-profile-navigation h1,
|
||||||
|
td.phabricator-profile-navigation h2 {
|
||||||
|
padding: 2px 0px 0px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.phabricator-profile-content {
|
||||||
padding: 2em 2%;
|
padding: 2em 2%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.phabricator-profile-info-table th {
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: right;
|
||||||
|
color: #666666;
|
||||||
|
width: 10%;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phabricator-profile-info-table td {
|
||||||
|
width: 100%;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.phabricator-profile-info-group {
|
.phabricator-profile-info-group {
|
||||||
margin-bottom: 2em;
|
margin-bottom: 2em;
|
||||||
background: #efefef;
|
background: #efefef;
|
||||||
|
@ -35,52 +87,11 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.phabricator-profile-info-table th {
|
h2.phabricator-profile-realname {
|
||||||
font-weight: bold;
|
|
||||||
text-align: right;
|
|
||||||
color: #666666;
|
|
||||||
width: 10%;
|
|
||||||
padding: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.phabricator-profile-info-table td {
|
|
||||||
width: 100%;
|
|
||||||
padding: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.phabricator-profile-navigation hr {
|
|
||||||
border: none;
|
|
||||||
background: #cccccc;
|
|
||||||
padding: 0;
|
|
||||||
margin: 10px 0;
|
|
||||||
height: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.phabricator-profile-navigation {
|
|
||||||
padding-top: 8px;
|
|
||||||
padding-bottom: 8em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.phabricator-profile-navigation h1,
|
|
||||||
.phabricator-profile-navigation h2 {
|
|
||||||
padding: 2px 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2.profile-realname {
|
|
||||||
color: #666666;
|
color: #666666;
|
||||||
}
|
}
|
||||||
|
|
||||||
img.profile-image {
|
img.phabricator-profile-image {
|
||||||
width: 280px;
|
width: 280px;
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.profile-nav-links li a {
|
|
||||||
display: block;
|
|
||||||
padding: 4px 8px 4px 12px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.profile-nav-links li a:hover {
|
|
||||||
background: #cccccc;
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue