@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Build an early multi-step repository create form

Summary: Ref T2231. Ref T2232. This form doesn't do anything yet and there are no link sto it, but it lets you enter all the data to create a repository in a relatively simple, straightforward way.

Test Plan:
{F49740}
{F49741}
{F49742}
{F49743}

Reviewers: chad, btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T2231, T2232

Differential Revision: https://secure.phabricator.com/D6430

+698 -34
+2
src/__phutil_library_map__.php
··· 490 490 'DiffusionRemarkupRule' => 'applications/diffusion/remarkup/DiffusionRemarkupRule.php', 491 491 'DiffusionRenameHistoryQuery' => 'applications/diffusion/query/DiffusionRenameHistoryQuery.php', 492 492 'DiffusionRepositoryController' => 'applications/diffusion/controller/DiffusionRepositoryController.php', 493 + 'DiffusionRepositoryCreateController' => 'applications/diffusion/controller/DiffusionRepositoryCreateController.php', 493 494 'DiffusionRepositoryEditBasicController' => 'applications/diffusion/controller/DiffusionRepositoryEditBasicController.php', 494 495 'DiffusionRepositoryEditController' => 'applications/diffusion/controller/DiffusionRepositoryEditController.php', 495 496 'DiffusionRepositoryEditEncodingController' => 'applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php', ··· 2423 2424 'DiffusionRawDiffQuery' => 'DiffusionQuery', 2424 2425 'DiffusionRemarkupRule' => 'PhabricatorRemarkupRuleObject', 2425 2426 'DiffusionRepositoryController' => 'DiffusionController', 2427 + 'DiffusionRepositoryCreateController' => 'DiffusionController', 2426 2428 'DiffusionRepositoryEditBasicController' => 'DiffusionController', 2427 2429 'DiffusionRepositoryEditController' => 'DiffusionController', 2428 2430 'DiffusionRepositoryEditEncodingController' => 'DiffusionController',
+1
src/applications/diffusion/application/PhabricatorApplicationDiffusion.php
··· 42 42 => 'DiffusionCommitController', 43 43 '/diffusion/' => array( 44 44 '' => 'DiffusionHomeController', 45 + 'create/' => 'DiffusionRepositoryCreateController', 45 46 '(?P<callsign>[A-Z]+)/' => array( 46 47 '' => 'DiffusionRepositoryController', 47 48
+545
src/applications/diffusion/controller/DiffusionRepositoryCreateController.php
··· 1 + <?php 2 + 3 + final class DiffusionRepositoryCreateController extends DiffusionController { 4 + 5 + public function processRequest() { 6 + $request = $this->getRequest(); 7 + $viewer = $request->getUser(); 8 + 9 + $form = id(new PHUIPagedFormView()) 10 + ->setUser($viewer) 11 + ->addPage('vcs', $this->buildVCSPage()) 12 + ->addPage('name', $this->buildNamePage()) 13 + ->addPage('auth', $this->buildAuthPage()) 14 + ->addPage('done', $this->buildDonePage()); 15 + 16 + if ($request->isFormPost()) { 17 + $form->readFromRequest($request); 18 + if ($form->isComplete()) { 19 + // TODO: This exception is heartwarming but should probably take more 20 + // substantive actions. 21 + throw new Exception("GOOD JOB AT FORM"); 22 + } 23 + } else { 24 + $form->readFromObject(null); 25 + } 26 + 27 + $title = pht('Import Repository'); 28 + 29 + $crumbs = $this->buildCrumbs(); 30 + $crumbs->addCrumb( 31 + id(new PhabricatorCrumbView()) 32 + ->setName($title)); 33 + 34 + return $this->buildApplicationPage( 35 + array( 36 + $crumbs, 37 + $form, 38 + ), 39 + array( 40 + 'title' => $title, 41 + 'device' => true, 42 + 'dust' => true, 43 + )); 44 + } 45 + 46 + 47 + /* -( Page: VCS Type )----------------------------------------------------- */ 48 + 49 + 50 + private function buildVCSPage() { 51 + return id(new PHUIFormPageView()) 52 + ->setPageName(pht('Repository Type')) 53 + ->setUser($this->getRequest()->getUser()) 54 + ->setValidateFormPageCallback(array($this, 'validateVCSPage')) 55 + ->addControl( 56 + id(new AphrontFormRadioButtonControl()) 57 + ->setName('vcs') 58 + ->setLabel(pht('Type')) 59 + ->addButton( 60 + PhabricatorRepositoryType::REPOSITORY_TYPE_GIT, 61 + pht('Git'), 62 + pht( 63 + 'Import a Git repository (for example, a repository hosted '. 64 + 'on GitHub).')) 65 + ->addButton( 66 + PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL, 67 + pht('Mercurial'), 68 + pht( 69 + 'Import a Mercurial repository (for example, a repository '. 70 + 'hosted on Bitbucket).')) 71 + ->addButton( 72 + PhabricatorRepositoryType::REPOSITORY_TYPE_SVN, 73 + pht('Subversion'), 74 + pht('Import a Subversion repository.')) 75 + ->addButton( 76 + PhabricatorRepositoryType::REPOSITORY_TYPE_PERFORCE, 77 + pht('Perforce'), 78 + pht( 79 + 'Perforce is not directly supported, but you can import '. 80 + 'a Perforce repository as a Git repository using %s.', 81 + phutil_tag( 82 + 'a', 83 + array( 84 + 'href' => 85 + 'http://www.perforce.com/product/components/git-fusion', 86 + 'target' => '_blank', 87 + ), 88 + pht('Perforce Git Fusion'))), 89 + 'disabled', 90 + $disabled = true)); 91 + } 92 + 93 + public function validateVCSPage(PHUIFormPageView $page) { 94 + $valid = array( 95 + PhabricatorRepositoryType::REPOSITORY_TYPE_GIT => true, 96 + PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL => true, 97 + PhabricatorRepositoryType::REPOSITORY_TYPE_SVN => true, 98 + ); 99 + 100 + $c_vcs = $page->getControl('vcs'); 101 + $v_vcs = $c_vcs->getValue(); 102 + if (!$v_vcs) { 103 + $c_vcs->setError(pht('Required')); 104 + $page->addPageError( 105 + pht('You must select a version control system.')); 106 + } else if (empty($valid[$v_vcs])) { 107 + $c_vcs->setError(pht('Invalid')); 108 + $page->addPageError( 109 + pht('You must select a valid version control system.')); 110 + } 111 + 112 + return $c_vcs->isValid(); 113 + } 114 + 115 + 116 + /* -( Page: Name and Callsign )-------------------------------------------- */ 117 + 118 + 119 + private function buildNamePage() { 120 + return id(new PHUIFormPageView()) 121 + ->setUser($this->getRequest()->getUser()) 122 + ->setPageName(pht('Repository Name and Location')) 123 + ->setValidateFormPageCallback(array($this, 'validateNamePage')) 124 + ->setAdjustFormPageCallback(array($this, 'adjustNamePage')) 125 + ->addRemarkupInstructions( 126 + pht( 127 + '**Choose a human-readable name for this repository**, like '. 128 + '"CompanyName Mobile App" or "CompanyName Backend Server". You '. 129 + 'can change this later.')) 130 + ->addControl( 131 + id(new AphrontFormTextControl()) 132 + ->setName('name') 133 + ->setLabel(pht('Name')) 134 + ->setCaption(pht('Human-readable repository name.'))) 135 + ->addRemarkupInstructions( 136 + pht( 137 + '**Choose a "Callsign" for the repository.** This is a short, '. 138 + 'unique string which identifies commits elsewhere in Phabricator. '. 139 + 'For example, you might use `M` for your mobile app repository '. 140 + 'and `B` for your backend repository.'. 141 + "\n\n". 142 + '**Callsigns must be UPPERCASE**, and can not be edited after the '. 143 + 'repository is created. Generally, you should choose short '. 144 + 'callsigns.')) 145 + ->addControl( 146 + id(new AphrontFormTextControl()) 147 + ->setName('callsign') 148 + ->setLabel(pht('Callsign')) 149 + ->setCaption(pht('Short UPPERCASE identifier.'))) 150 + ->addControl( 151 + id(new AphrontFormTextControl()) 152 + ->setName('remoteURI')); 153 + } 154 + 155 + public function adjustNamePage(PHUIFormPageView $page) { 156 + $form = $page->getForm(); 157 + 158 + $is_git = false; 159 + $is_svn = false; 160 + $is_mercurial = false; 161 + 162 + switch ($form->getPage('vcs')->getControl('vcs')->getValue()) { 163 + case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: 164 + $is_git = true; 165 + break; 166 + case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: 167 + $is_svn = true; 168 + break; 169 + case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: 170 + $is_mercurial = true; 171 + break; 172 + default: 173 + throw new Exception("Unsupported VCS!"); 174 + } 175 + 176 + $has_local = ($is_git || $is_mercurial); 177 + if ($is_git) { 178 + $uri_label = pht('Remote URI'); 179 + $instructions = pht( 180 + 'Enter the URI to clone this Git repository from. It should usually '. 181 + 'look like one of these examples:'. 182 + "\n\n". 183 + "| Example Git Remote URIs |\n". 184 + "| ----------------------- |\n". 185 + "| `git@github.com:example/example.git` |\n". 186 + "| `ssh://user@host.com/git/example.git` |\n". 187 + "| `file:///local/path/to/repo` |\n". 188 + "| `https://example.com/repository.git` |\n"); 189 + } else if ($is_mercurial) { 190 + $uri_label = pht('Remote URI'); 191 + $instructions = pht( 192 + 'Enter the URI to clone this Mercurial repository from. It should '. 193 + 'usually look like one of these examples:'. 194 + "\n\n". 195 + "| Example Mercurial Remote URIs |\n". 196 + "| ----------------------- |\n". 197 + "| `ssh://hg@bitbucket.org/example/repository` |\n". 198 + "| `file:///local/path/to/repo` |\n"); 199 + } else if ($is_svn) { 200 + $uri_label = pht('Repository Root'); 201 + $instructions = pht( 202 + 'Enter the **Repository Root** for this Subversion repository. '. 203 + 'You can figure this out by running `svn info` in a working copy '. 204 + 'and looking at the value in the `Repository Root` field. It '. 205 + 'should be a URI and will usually look like these:'. 206 + "\n\n". 207 + "| Example Subversion Repository Root URIs |\n". 208 + "| ------------------------------ |\n". 209 + "| `http://svn.example.org/svnroot/` |\n". 210 + "| `svn+ssh://svn.example.com/svnroot/` |\n". 211 + "| `svn://svn.example.net/svnroot/` |\n". 212 + "| `file:///local/path/to/svnroot/` |\n". 213 + "\n\n". 214 + "Make sure you specify the root of the repository, not a ". 215 + "subdirectory."); 216 + } else { 217 + throw new Exception("Unsupported VCS!"); 218 + } 219 + 220 + $page->addRemarkupInstructions($instructions, 'remoteURI'); 221 + $page->getControl('remoteURI')->setLabel($uri_label); 222 + } 223 + 224 + public function validateNamePage(PHUIFormPageView $page) { 225 + $c_name = $page->getControl('name'); 226 + $v_name = $c_name->getValue(); 227 + if (!strlen($v_name)) { 228 + $c_name->setError(pht('Required')); 229 + $page->addPageError( 230 + pht('You must choose a name for this repository.')); 231 + } 232 + 233 + $c_call = $page->getControl('callsign'); 234 + $v_call = $c_call->getValue(); 235 + if (!strlen($v_call)) { 236 + $c_call->setError(pht('Required')); 237 + $page->addPageError( 238 + pht('You must choose a callsign for this repository.')); 239 + } else if (!preg_match('/^[A-Z]+$/', $v_call)) { 240 + $c_call->setError(pht('Invalid')); 241 + $page->addPageError( 242 + pht('The callsign must contain only UPPERCASE letters.')); 243 + } else { 244 + $exists = false; 245 + try { 246 + $repo = id(new PhabricatorRepositoryQuery()) 247 + ->setViewer($this->getRequest()->getUser()) 248 + ->withCallsigns(array($v_call)) 249 + ->executeOne(); 250 + $exists = (bool)$repo; 251 + } catch (PhabricatorPolicyException $ex) { 252 + $exists = true; 253 + } 254 + if ($exists) { 255 + $c_call->setError(pht('Not Unique')); 256 + $page->addPageError( 257 + pht( 258 + 'Another repository already uses that callsign. You must choose '. 259 + 'a unique callsign.')); 260 + } 261 + } 262 + 263 + $c_remote = $page->getControl('remoteURI'); 264 + $v_remote = $c_remote->getValue(); 265 + 266 + if (!strlen($v_remote)) { 267 + $c_remote->setError(pht('Required')); 268 + $page->addPageError( 269 + pht("You must specify a URI.")); 270 + } else { 271 + $proto = $this->getRemoteURIProtocol($v_remote); 272 + 273 + if ($proto === 'file') { 274 + if (!preg_match('@^file:///@', $v_remote)) { 275 + $c_remote->setError(pht('Invalid')); 276 + $page->addPageError( 277 + pht( 278 + "URIs using the 'file://' protocol should have three slashes ". 279 + "(e.g., 'file:///absolute/path/to/file'). You only have two. ". 280 + "Add another one.")); 281 + } 282 + } 283 + 284 + switch ($proto) { 285 + case 'ssh': 286 + case 'http': 287 + case 'https': 288 + case 'file': 289 + case 'git': 290 + case 'svn': 291 + case 'svn+ssh': 292 + break; 293 + default: 294 + $c_remote->setError(pht('Invalid')); 295 + $page->addPageError( 296 + pht( 297 + "The URI protocol is unrecognized. It should begin ". 298 + "'ssh://', 'http://', 'https://', 'file://', 'git://', ". 299 + "'svn://', 'svn+ssh://', or be in the form ". 300 + "'git@domain.com:path'.")); 301 + break; 302 + } 303 + } 304 + 305 + return $c_name->isValid() && 306 + $c_call->isValid() && 307 + $c_remote->isValid(); 308 + } 309 + 310 + 311 + /* -( Page: Authentication )----------------------------------------------- */ 312 + 313 + 314 + public function buildAuthPage() { 315 + return id(new PHUIFormPageView()) 316 + ->setPageName(pht('Authentication')) 317 + ->setUser($this->getRequest()->getUser()) 318 + ->setAdjustFormPageCallback(array($this, 'adjustAuthPage')) 319 + ->addControl( 320 + id(new AphrontFormTextControl()) 321 + ->setName('ssh-login') 322 + ->setLabel('SSH User')) 323 + ->addControl( 324 + id(new AphrontFormTextAreaControl()) 325 + ->setName('ssh-key') 326 + ->setLabel('SSH Private Key') 327 + ->setHeight(AphrontFormTextAreaControl::HEIGHT_SHORT) 328 + ->setCaption( 329 + hsprintf('Specify the entire private key, <em>or</em>...'))) 330 + ->addControl( 331 + id(new AphrontFormTextControl()) 332 + ->setName('ssh-keyfile') 333 + ->setLabel('SSH Private Key Path') 334 + ->setCaption( 335 + '...specify a path on disk where the daemon should '. 336 + 'look for a private key.')) 337 + ->addControl( 338 + id(new AphrontFormTextControl()) 339 + ->setName('http-login') 340 + ->setLabel('Username')) 341 + ->addControl( 342 + id(new AphrontFormPasswordControl()) 343 + ->setName('http-pass') 344 + ->setLabel('Password')); 345 + } 346 + 347 + 348 + public function adjustAuthPage($page) { 349 + $form = $page->getForm(); 350 + $remote_uri = $form->getPage('name')->getControl('remoteURI')->getValue(); 351 + $vcs = $form->getPage('vcs')->getControl('vcs')->getValue(); 352 + $proto = $this->getRemoteURIProtocol($remote_uri); 353 + $remote_user = $this->getRemoteURIUser($remote_uri); 354 + 355 + $page->getControl('ssh-login')->setHidden(true); 356 + $page->getControl('ssh-key')->setHidden(true); 357 + $page->getControl('ssh-keyfile')->setHidden(true); 358 + $page->getControl('http-login')->setHidden(true); 359 + $page->getControl('http-pass')->setHidden(true); 360 + 361 + if ($this->isSSHProtocol($proto)) { 362 + $page->getControl('ssh-login')->setHidden(false); 363 + $page->getControl('ssh-key')->setHidden(false); 364 + $page->getControl('ssh-keyfile')->setHidden(false); 365 + 366 + $c_login = $page->getControl('ssh-login'); 367 + if (!strlen($c_login->getValue())) { 368 + $c_login->setValue($remote_user); 369 + } 370 + 371 + $page->addRemarkupInstructions( 372 + pht( 373 + 'Enter the username and private key to use to connect to the '. 374 + 'the repository hosted at:'. 375 + "\n\n". 376 + " lang=text\n". 377 + " %s". 378 + "\n\n". 379 + 'You can either copy/paste the entire private key, or put it '. 380 + 'somewhere on disk and provide the path to it.', 381 + $remote_uri), 382 + 'ssh-login'); 383 + 384 + } else if ($this->isUsernamePasswordProtocol($proto)) { 385 + $page->getControl('http-login')->setHidden(false); 386 + $page->getControl('http-pass')->setHidden(false); 387 + 388 + $page->addRemarkupInstructions( 389 + pht( 390 + 'Enter the a username and pasword used to connect to the '. 391 + 'repository hosted at:'. 392 + "\n\n". 393 + " lang=text\n". 394 + " %s". 395 + "\n\n". 396 + "If this repository does not require a username or password, ". 397 + "you can leave these fields blank.", 398 + $remote_uri), 399 + 'http-login'); 400 + } else if ($proto == 'file') { 401 + $page->addRemarkupInstructions( 402 + pht( 403 + 'You do not need to configure any authentication options for '. 404 + 'repositories accessed over the `file://` protocol. Continue '. 405 + 'to the next step.'), 406 + 'ssh-login'); 407 + } else { 408 + throw new Exception("Unknown URI protocol!"); 409 + } 410 + } 411 + 412 + public function validateAuthPage(PHUIFormPageView $page) { 413 + $form = $page->getForm(); 414 + $remote_uri = $form->getPage('remote')->getControl('remoteURI')->getValue(); 415 + $proto = $this->getRemoteURIProtocol($remote_uri); 416 + 417 + if ($this->isSSHProtocol($proto)) { 418 + $c_user = $page->getControl('ssh-login'); 419 + $c_key = $page->getControl('ssh-key'); 420 + $c_file = $page->getControl('ssh-keyfile'); 421 + 422 + $v_user = $c_user->getValue(); 423 + $v_key = $c_key->getValue(); 424 + $v_file = $c_file->getValue(); 425 + 426 + if (!strlen($v_user)) { 427 + $c_user->setError(pht('Required')); 428 + $page->addPageError( 429 + pht('You must provide an SSH login username to connect over SSH.')); 430 + } 431 + 432 + if (!strlen($v_key) && !strlen($v_file)) { 433 + $c_key->setError(pht('No Key')); 434 + $c_file->setError(pht('No Key')); 435 + $page->addPageError( 436 + pht( 437 + 'You must provide either a private key or the path to a private '. 438 + 'key to connect over SSH.')); 439 + } else if (strlen($v_key) && strlen($v_file)) { 440 + $c_key->setError(pht('Ambiguous')); 441 + $c_file->setError(pht('Ambiguous')); 442 + $page->addPageError( 443 + pht( 444 + 'Provide either a private key or the path to a private key, not '. 445 + 'both.')); 446 + } else if (!preg_match('/PRIVATE KEY/', $v_key)) { 447 + $c_key->setError(pht('Invalid')); 448 + $page->addPageError( 449 + pht( 450 + 'The private key you provided is missing the PRIVATE KEY header. '. 451 + 'You should include the header and footer. (Did you provide a '. 452 + 'public key by mistake?)')); 453 + } 454 + 455 + return $c_user->isValid() && 456 + $c_key->isValid() && 457 + $c_file->isValid(); 458 + } else if ($this->isUsernamePasswordProtocol($proto)) { 459 + return true; 460 + } else { 461 + return true; 462 + } 463 + } 464 + 465 + /* -( Page: Done )--------------------------------------------------------- */ 466 + 467 + 468 + private function buildDonePage() { 469 + return id(new PHUIFormPageView()) 470 + ->setPageName(pht('Repository Ready!')) 471 + ->setValidateFormPageCallback(array($this, 'validateDonePage')) 472 + ->setUser($this->getRequest()->getUser()) 473 + ->addControl( 474 + id(new AphrontFormRadioButtonControl()) 475 + ->setName('activate') 476 + ->setLabel(pht('Start Now')) 477 + ->addButton( 478 + 'start', 479 + pht('Start Import Now'), 480 + pht( 481 + 'Start importing the repository right away. This will import '. 482 + 'the entire repository using default settings.')) 483 + ->addButton( 484 + 'wait', 485 + pht('Configure More Options First'), 486 + pht( 487 + 'Configure more options before beginning the repository '. 488 + 'import. This will let you fine-tune settings.. You can '. 489 + 'start the import whenever you are ready.'))); 490 + } 491 + 492 + public function validateDonePage(PHUIFormPageView $page) { 493 + $c_activate = $page->getControl('activate'); 494 + $v_activate = $c_activate->getValue(); 495 + 496 + if ($v_activate != 'start' && $v_activate != 'wait') { 497 + $c_activate->setError(pht('Required')); 498 + $page->addPageError( 499 + pht('Make a choice about repository activation.')); 500 + } 501 + 502 + return $c_activate->isValid(); 503 + } 504 + 505 + 506 + /* -( Internal )----------------------------------------------------------- */ 507 + 508 + 509 + private function getRemoteURIProtocol($raw_uri) { 510 + $uri = new PhutilURI($raw_uri); 511 + if ($uri->getProtocol()) { 512 + return strtolower($uri->getProtocol()); 513 + } 514 + 515 + $git_uri = new PhutilGitURI($raw_uri); 516 + if (strlen($git_uri->getDomain()) && strlen($git_uri->getPath())) { 517 + return 'ssh'; 518 + } 519 + 520 + return null; 521 + } 522 + 523 + private function getRemoteURIUser($raw_uri) { 524 + $uri = new PhutilURI($raw_uri); 525 + if ($uri->getUser()) { 526 + return $uri->getUser(); 527 + } 528 + 529 + $git_uri = new PhutilGitURI($raw_uri); 530 + if (strlen($git_uri->getDomain()) && strlen($git_uri->getPath())) { 531 + return $git_uri->getUser(); 532 + } 533 + 534 + return null; 535 + } 536 + 537 + private function isSSHProtocol($proto) { 538 + return ($proto == 'git' || $proto == 'ssh' || $proto == 'svn+ssh'); 539 + } 540 + 541 + private function isUsernamePasswordProtocol($proto) { 542 + return ($proto == 'http' || $proto == 'https' || $proto == 'svn'); 543 + } 544 + 545 + }
+1
src/applications/repository/constants/PhabricatorRepositoryType.php
··· 5 5 const REPOSITORY_TYPE_GIT = 'git'; 6 6 const REPOSITORY_TYPE_SVN = 'svn'; 7 7 const REPOSITORY_TYPE_MERCURIAL = 'hg'; 8 + const REPOSITORY_TYPE_PERFORCE = 'p4'; 8 9 9 10 public static function getAllRepositoryTypes() { 10 11 static $map = array(
+87 -2
src/view/form/PHUIFormPageView.php
··· 8 8 private $key; 9 9 private $form; 10 10 private $controls = array(); 11 + private $content = array(); 11 12 private $values = array(); 12 13 private $isValid; 14 + private $validateFormPageCallback; 15 + private $adjustFormPageCallback; 16 + private $pageErrors; 17 + private $pageName; 18 + 19 + 20 + public function setPageName($page_name) { 21 + $this->pageName = $page_name; 22 + return $this; 23 + } 24 + 25 + public function getPageName() { 26 + return $this->pageName; 27 + } 28 + 29 + public function addPageError($page_error) { 30 + $this->pageErrors[] = $page_error; 31 + return $this; 32 + } 33 + 34 + public function getPageErrors() { 35 + return $this->pageErrors; 36 + } 37 + 38 + public function setAdjustFormPageCallback($adjust_form_page_callback) { 39 + $this->adjustFormPageCallback = $adjust_form_page_callback; 40 + return $this; 41 + } 42 + 43 + public function setValidateFormPageCallback($validate_form_page_callback) { 44 + $this->validateFormPageCallback = $validate_form_page_callback; 45 + return $this; 46 + } 47 + 48 + public function addInstructions($text, $before = null) { 49 + $tag = phutil_tag( 50 + 'div', 51 + array( 52 + 'class' => 'aphront-form-instructions', 53 + ), 54 + $text); 55 + 56 + $append = true; 57 + if ($before !== null) { 58 + for ($ii = 0; $ii < count($this->content); $ii++) { 59 + if ($this->content[$ii] instanceof AphrontFormControl) { 60 + if ($this->content[$ii]->getName() == $before) { 61 + array_splice($this->content, $ii, 0, array($tag)); 62 + $append = false; 63 + break; 64 + } 65 + } 66 + } 67 + } 68 + 69 + if ($append) { 70 + $this->content[] = $tag; 71 + } 72 + 73 + return $this; 74 + } 75 + 76 + public function addRemarkupInstructions($remarkup, $before = null) { 77 + return $this->addInstructions( 78 + PhabricatorMarkupEngine::renderOneObject( 79 + id(new PhabricatorMarkupOneOff())->setContent($remarkup), 80 + 'default', 81 + $this->getUser()), $before); 82 + } 13 83 14 84 public function addControl(AphrontFormControl $control) { 15 85 $name = $control->getName(); ··· 24 94 } 25 95 26 96 $this->controls[$name] = $control; 97 + $this->content[] = $control; 27 98 $control->setFormPage($this); 28 99 29 100 return $this; ··· 53 124 return $this; 54 125 } 55 126 127 + public function adjustFormPage() { 128 + if ($this->adjustFormPageCallback) { 129 + call_user_func($this->adjustFormPageCallback, $this); 130 + } 131 + return $this; 132 + } 133 + 134 + protected function validateFormPage() { 135 + if ($this->validateFormPageCallback) { 136 + return call_user_func($this->validateFormPageCallback, $this); 137 + } 138 + return true; 139 + } 140 + 56 141 public function getKey() { 57 142 return $this->key; 58 143 } 59 144 60 145 public function render() { 61 - return $this->getControls(); 146 + return $this->content; 62 147 } 63 148 64 149 public function getForm() { ··· 91 176 92 177 public function isValid() { 93 178 if ($this->isValid === null) { 94 - $this->isValid = $this->validateControls(); 179 + $this->isValid = $this->validateControls() && $this->validateFormPage(); 95 180 } 96 181 return $this->isValid; 97 182 }
+50 -31
src/view/form/PHUIPagedFormView.php
··· 31 31 } 32 32 $this->pages[$key] = $page; 33 33 $page->setPagedFormView($this, $key); 34 + 35 + $this->selectedPage = null; 36 + $this->complete = null; 37 + 34 38 return $this; 35 39 } 36 40 ··· 104 108 } 105 109 106 110 public function readFromObject($object) { 107 - foreach ($this->pages as $page) { 108 - $page->validateObjectType($object); 109 - $page->readFromObject($object); 110 - } 111 - 112 - return $this->processForm(); 111 + return $this->processForm($is_request = false, $object); 113 112 } 114 113 115 114 public function writeToResponse($response) { ··· 122 121 } 123 122 124 123 public function readFromRequest(AphrontRequest $request) { 125 - $active_page = $request->getStr($this->getRequestKey('page')); 126 - 127 - foreach ($this->pages as $key => $page) { 128 - if ($key == $active_page) { 129 - $page->readFromRequest($request); 130 - } else { 131 - $page->readSerializedValues($request); 132 - } 133 - } 134 - 135 - $this->choosePage = $active_page; 124 + $this->choosePage = $request->getStr($this->getRequestKey('page')); 136 125 $this->nextPage = $request->getStr('__submit__'); 137 126 $this->prevPage = $request->getStr('__back__'); 138 127 139 - return $this->processForm(); 128 + return $this->processForm($is_request = true, $request); 140 129 } 141 130 142 131 public function setName($name) { ··· 154 143 return $this; 155 144 } 156 145 157 - public function processForm() { 158 - foreach ($this->pages as $key => $page) { 159 - if (!$page->isValid()) { 160 - break; 161 - } 162 - } 163 - 146 + private function processForm($is_request, $source) { 164 147 if ($this->pageExists($this->choosePage)) { 165 148 $selected = $this->getPage($this->choosePage); 166 149 } else { ··· 182 165 } 183 166 184 167 $validation_error = false; 168 + $found_selected = false; 185 169 foreach ($this->pages as $key => $page) { 186 - if (!$page->isValid()) { 187 - $validation_error = true; 188 - break; 170 + if ($is_request) { 171 + if ($key === $this->choosePage) { 172 + $page->readFromRequest($source); 173 + } else { 174 + $page->readSerializedValues($source); 175 + } 176 + } else { 177 + $page->readFromObject($source); 189 178 } 179 + 180 + if (!$found_selected) { 181 + $page->adjustFormPage(); 182 + } 183 + 190 184 if ($page === $selected) { 191 - break; 185 + $found_selected = true; 186 + } 187 + 188 + if (!$found_selected || $is_attempt_complete) { 189 + if (!$page->isValid()) { 190 + $selected = $page; 191 + $validation_error = true; 192 + break; 193 + } 192 194 } 193 195 } 194 196 195 197 if ($is_attempt_complete && !$validation_error) { 196 198 $this->complete = true; 197 199 } else { 198 - $this->selectedPage = $page; 200 + $this->selectedPage = $selected; 199 201 } 200 202 201 203 return $this; ··· 221 223 $form->addHiddenInput( 222 224 $this->getRequestKey('page'), 223 225 $selected_page->getKey()); 226 + 227 + $errors = array(); 224 228 225 229 foreach ($this->pages as $page) { 226 230 if ($page == $selected_page) { 231 + $errors = $page->getPageErrors(); 227 232 continue; 228 233 } 229 234 foreach ($page->getSerializedValues() as $key => $value) { ··· 246 251 $form->appendChild($selected_page); 247 252 $form->appendChild($submit); 248 253 249 - return $form; 254 + if ($errors) { 255 + $errors = id(new AphrontErrorView())->setErrors($errors); 256 + } 257 + 258 + $header = null; 259 + if ($selected_page->getPageName()) { 260 + $header = id(new PhabricatorHeaderView()) 261 + ->setHeader($selected_page->getPageName()); 262 + } 263 + 264 + return array( 265 + $header, 266 + $errors, 267 + $form, 268 + ); 250 269 } 251 270 252 271 }
+12 -1
src/view/form/control/AphrontFormControl.php
··· 13 13 private $controlStyle; 14 14 private $formPage; 15 15 private $required; 16 + private $hidden; 17 + 18 + public function setHidden($hidden) { 19 + $this->hidden = $hidden; 20 + return $this; 21 + } 16 22 17 23 public function setID($id) { 18 24 $this->id = $id; ··· 203 209 $classes[] = 'aphront-form-control'; 204 210 $classes[] = $custom_class; 205 211 212 + $style = $this->controlStyle; 213 + if ($this->hidden) { 214 + $style = 'display: none; '.$style; 215 + } 216 + 206 217 return phutil_tag( 207 218 'div', 208 219 array( 209 220 'class' => implode(' ', $classes), 210 221 'id' => $this->controlID, 211 - 'style' => $this->controlStyle, 222 + 'style' => $style, 212 223 ), 213 224 array( 214 225 $label,