[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
1 <?php 2 3 final class ManiphestTaskEditController extends ManiphestController { 4 5 private $id; 6 7 public function willProcessRequest(array $data) { 8 $this->id = idx($data, 'id'); 9 } 10 11 public function processRequest() { 12 $request = $this->getRequest(); 13 $user = $request->getUser(); 14 15 $response_type = $request->getStr('responseType', 'task'); 16 $order = $request->getStr('order', PhabricatorProjectColumn::DEFAULT_ORDER); 17 18 $can_edit_assign = $this->hasApplicationCapability( 19 ManiphestEditAssignCapability::CAPABILITY); 20 $can_edit_policies = $this->hasApplicationCapability( 21 ManiphestEditPoliciesCapability::CAPABILITY); 22 $can_edit_priority = $this->hasApplicationCapability( 23 ManiphestEditPriorityCapability::CAPABILITY); 24 $can_edit_projects = $this->hasApplicationCapability( 25 ManiphestEditProjectsCapability::CAPABILITY); 26 $can_edit_status = $this->hasApplicationCapability( 27 ManiphestEditStatusCapability::CAPABILITY); 28 29 $parent_task = null; 30 $template_id = null; 31 32 if ($this->id) { 33 $task = id(new ManiphestTaskQuery()) 34 ->setViewer($user) 35 ->requireCapabilities( 36 array( 37 PhabricatorPolicyCapability::CAN_VIEW, 38 PhabricatorPolicyCapability::CAN_EDIT, 39 )) 40 ->withIDs(array($this->id)) 41 ->executeOne(); 42 if (!$task) { 43 return new Aphront404Response(); 44 } 45 } else { 46 $task = ManiphestTask::initializeNewTask($user); 47 48 // We currently do not allow you to set the task status when creating 49 // a new task, although now that statuses are custom it might make 50 // sense. 51 $can_edit_status = false; 52 53 // These allow task creation with defaults. 54 if (!$request->isFormPost()) { 55 $task->setTitle($request->getStr('title')); 56 57 if ($can_edit_projects) { 58 $projects = $request->getStr('projects'); 59 if ($projects) { 60 $tokens = $request->getStrList('projects'); 61 62 $type_project = PhabricatorProjectProjectPHIDType::TYPECONST; 63 foreach ($tokens as $key => $token) { 64 if (phid_get_type($token) == $type_project) { 65 // If this is formatted like a PHID, leave it as-is. 66 continue; 67 } 68 69 if (preg_match('/^#/', $token)) { 70 // If this already has a "#", leave it as-is. 71 continue; 72 } 73 74 // Add a "#" prefix. 75 $tokens[$key] = '#'.$token; 76 } 77 78 $default_projects = id(new PhabricatorObjectQuery()) 79 ->setViewer($user) 80 ->withNames($tokens) 81 ->execute(); 82 $default_projects = mpull($default_projects, 'getPHID'); 83 84 if ($default_projects) { 85 $task->attachProjectPHIDs($default_projects); 86 } 87 } 88 } 89 90 if ($can_edit_priority) { 91 $priority = $request->getInt('priority'); 92 if ($priority !== null) { 93 $priority_map = ManiphestTaskPriority::getTaskPriorityMap(); 94 if (isset($priority_map[$priority])) { 95 $task->setPriority($priority); 96 } 97 } 98 } 99 100 $task->setDescription($request->getStr('description')); 101 102 if ($can_edit_assign) { 103 $assign = $request->getStr('assign'); 104 if (strlen($assign)) { 105 $assign_user = id(new PhabricatorPeopleQuery()) 106 ->setViewer($user) 107 ->withUsernames(array($assign)) 108 ->executeOne(); 109 if (!$assign_user) { 110 $assign_user = id(new PhabricatorPeopleQuery()) 111 ->setViewer($user) 112 ->withPHIDs(array($assign)) 113 ->executeOne(); 114 } 115 116 if ($assign_user) { 117 $task->setOwnerPHID($assign_user->getPHID()); 118 } 119 } 120 } 121 } 122 123 $template_id = $request->getInt('template'); 124 125 // You can only have a parent task if you're creating a new task. 126 $parent_id = $request->getInt('parent'); 127 if ($parent_id) { 128 $parent_task = id(new ManiphestTaskQuery()) 129 ->setViewer($user) 130 ->withIDs(array($parent_id)) 131 ->executeOne(); 132 if (!$template_id) { 133 $template_id = $parent_id; 134 } 135 } 136 } 137 138 $errors = array(); 139 $e_title = true; 140 141 $field_list = PhabricatorCustomField::getObjectFields( 142 $task, 143 PhabricatorCustomField::ROLE_EDIT); 144 $field_list->setViewer($user); 145 $field_list->readFieldsFromStorage($task); 146 147 $aux_fields = $field_list->getFields(); 148 149 if ($request->isFormPost()) { 150 $changes = array(); 151 152 $new_title = $request->getStr('title'); 153 $new_desc = $request->getStr('description'); 154 $new_status = $request->getStr('status'); 155 156 if (!$task->getID()) { 157 $workflow = 'create'; 158 } else { 159 $workflow = ''; 160 } 161 162 $changes[ManiphestTransaction::TYPE_TITLE] = $new_title; 163 $changes[ManiphestTransaction::TYPE_DESCRIPTION] = $new_desc; 164 165 if ($can_edit_status) { 166 $changes[ManiphestTransaction::TYPE_STATUS] = $new_status; 167 } else if (!$task->getID()) { 168 // Create an initial status transaction for the burndown chart. 169 // TODO: We can probably remove this once Facts comes online. 170 $changes[ManiphestTransaction::TYPE_STATUS] = $task->getStatus(); 171 } 172 173 $owner_tokenizer = $request->getArr('assigned_to'); 174 $owner_phid = reset($owner_tokenizer); 175 176 if (!strlen($new_title)) { 177 $e_title = pht('Required'); 178 $errors[] = pht('Title is required.'); 179 } 180 181 $old_values = array(); 182 foreach ($aux_fields as $aux_arr_key => $aux_field) { 183 // TODO: This should be buildFieldTransactionsFromRequest() once we 184 // switch to ApplicationTransactions properly. 185 186 $aux_old_value = $aux_field->getOldValueForApplicationTransactions(); 187 $aux_field->readValueFromRequest($request); 188 $aux_new_value = $aux_field->getNewValueForApplicationTransactions(); 189 190 // TODO: We're faking a call to the ApplicaitonTransaction validation 191 // logic here. We need valid objects to pass, but they aren't used 192 // in a meaningful way. For now, build User objects. Once the Maniphest 193 // objects exist, this will switch over automatically. This is a big 194 // hack but shouldn't be long for this world. 195 $placeholder_editor = new PhabricatorUserProfileEditor(); 196 197 $field_errors = $aux_field->validateApplicationTransactions( 198 $placeholder_editor, 199 PhabricatorTransactions::TYPE_CUSTOMFIELD, 200 array( 201 id(new ManiphestTransaction()) 202 ->setOldValue($aux_old_value) 203 ->setNewValue($aux_new_value), 204 )); 205 206 foreach ($field_errors as $error) { 207 $errors[] = $error->getMessage(); 208 } 209 210 $old_values[$aux_field->getFieldKey()] = $aux_old_value; 211 } 212 213 if ($errors) { 214 $task->setTitle($new_title); 215 $task->setDescription($new_desc); 216 $task->setPriority($request->getInt('priority')); 217 $task->setOwnerPHID($owner_phid); 218 $task->setCCPHIDs($request->getArr('cc')); 219 $task->attachProjectPHIDs($request->getArr('projects')); 220 } else { 221 222 if ($can_edit_priority) { 223 $changes[ManiphestTransaction::TYPE_PRIORITY] = 224 $request->getInt('priority'); 225 } 226 if ($can_edit_assign) { 227 $changes[ManiphestTransaction::TYPE_OWNER] = $owner_phid; 228 } 229 230 $changes[ManiphestTransaction::TYPE_CCS] = $request->getArr('cc'); 231 232 if ($can_edit_projects) { 233 $projects = $request->getArr('projects'); 234 $changes[ManiphestTransaction::TYPE_PROJECTS] = 235 $projects; 236 $column_phid = $request->getStr('columnPHID'); 237 // allow for putting a task in a project column at creation -only- 238 if (!$task->getID() && $column_phid && $projects) { 239 $column = id(new PhabricatorProjectColumnQuery()) 240 ->setViewer($user) 241 ->withProjectPHIDs($projects) 242 ->withPHIDs(array($column_phid)) 243 ->executeOne(); 244 if ($column) { 245 $changes[ManiphestTransaction::TYPE_PROJECT_COLUMN] = 246 array( 247 'new' => array( 248 'projectPHID' => $column->getProjectPHID(), 249 'columnPHIDs' => array($column_phid), 250 ), 251 'old' => array( 252 'projectPHID' => $column->getProjectPHID(), 253 'columnPHIDs' => array(), 254 ), 255 ); 256 } 257 } 258 } 259 260 if ($can_edit_policies) { 261 $changes[PhabricatorTransactions::TYPE_VIEW_POLICY] = 262 $request->getStr('viewPolicy'); 263 $changes[PhabricatorTransactions::TYPE_EDIT_POLICY] = 264 $request->getStr('editPolicy'); 265 } 266 267 $template = new ManiphestTransaction(); 268 $transactions = array(); 269 270 foreach ($changes as $type => $value) { 271 $transaction = clone $template; 272 $transaction->setTransactionType($type); 273 if ($type == ManiphestTransaction::TYPE_PROJECT_COLUMN) { 274 $transaction->setNewValue($value['new']); 275 $transaction->setOldValue($value['old']); 276 } else if ($type == ManiphestTransaction::TYPE_PROJECTS) { 277 // TODO: Gross. 278 $project_type = 279 PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; 280 $transaction 281 ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) 282 ->setMetadataValue('edge:type', $project_type) 283 ->setNewValue( 284 array( 285 '=' => array_fuse($value), 286 )); 287 } else { 288 $transaction->setNewValue($value); 289 } 290 $transactions[] = $transaction; 291 } 292 293 if ($aux_fields) { 294 foreach ($aux_fields as $aux_field) { 295 $transaction = clone $template; 296 $transaction->setTransactionType( 297 PhabricatorTransactions::TYPE_CUSTOMFIELD); 298 $aux_key = $aux_field->getFieldKey(); 299 $transaction->setMetadataValue('customfield:key', $aux_key); 300 $old = idx($old_values, $aux_key); 301 $new = $aux_field->getNewValueForApplicationTransactions(); 302 303 $transaction->setOldValue($old); 304 $transaction->setNewValue($new); 305 306 $transactions[] = $transaction; 307 } 308 } 309 310 if ($transactions) { 311 $is_new = !$task->getID(); 312 313 $event = new PhabricatorEvent( 314 PhabricatorEventType::TYPE_MANIPHEST_WILLEDITTASK, 315 array( 316 'task' => $task, 317 'new' => $is_new, 318 'transactions' => $transactions, 319 )); 320 $event->setUser($user); 321 $event->setAphrontRequest($request); 322 PhutilEventEngine::dispatchEvent($event); 323 324 $task = $event->getValue('task'); 325 $transactions = $event->getValue('transactions'); 326 327 $editor = id(new ManiphestTransactionEditor()) 328 ->setActor($user) 329 ->setContentSourceFromRequest($request) 330 ->setContinueOnNoEffect(true) 331 ->applyTransactions($task, $transactions); 332 333 $event = new PhabricatorEvent( 334 PhabricatorEventType::TYPE_MANIPHEST_DIDEDITTASK, 335 array( 336 'task' => $task, 337 'new' => $is_new, 338 'transactions' => $transactions, 339 )); 340 $event->setUser($user); 341 $event->setAphrontRequest($request); 342 PhutilEventEngine::dispatchEvent($event); 343 } 344 345 346 if ($parent_task) { 347 // TODO: This should be transactional now. 348 id(new PhabricatorEdgeEditor()) 349 ->addEdge( 350 $parent_task->getPHID(), 351 PhabricatorEdgeConfig::TYPE_TASK_DEPENDS_ON_TASK, 352 $task->getPHID()) 353 ->save(); 354 $workflow = $parent_task->getID(); 355 } 356 357 if ($request->isAjax()) { 358 switch ($response_type) { 359 case 'card': 360 $owner = null; 361 if ($task->getOwnerPHID()) { 362 $owner = id(new PhabricatorHandleQuery()) 363 ->setViewer($user) 364 ->withPHIDs(array($task->getOwnerPHID())) 365 ->executeOne(); 366 } 367 $tasks = id(new ProjectBoardTaskCard()) 368 ->setViewer($user) 369 ->setTask($task) 370 ->setOwner($owner) 371 ->setCanEdit(true) 372 ->getItem(); 373 374 $column = id(new PhabricatorProjectColumnQuery()) 375 ->setViewer($user) 376 ->withPHIDs(array($request->getStr('columnPHID'))) 377 ->executeOne(); 378 if (!$column) { 379 return new Aphront404Response(); 380 } 381 382 $positions = id(new PhabricatorProjectColumnPositionQuery()) 383 ->setViewer($user) 384 ->withColumns(array($column)) 385 ->execute(); 386 $task_phids = mpull($positions, 'getObjectPHID'); 387 388 $column_tasks = id(new ManiphestTaskQuery()) 389 ->setViewer($user) 390 ->withPHIDs($task_phids) 391 ->execute(); 392 393 if ($order == PhabricatorProjectColumn::ORDER_NATURAL) { 394 // TODO: This is a little bit awkward, because PHP and JS use 395 // slightly different sort order parameters to achieve the same 396 // effect. It would be unify this a bit at some point. 397 $sort_map = array(); 398 foreach ($positions as $position) { 399 $sort_map[$position->getObjectPHID()] = array( 400 -$position->getSequence(), 401 $position->getID(), 402 ); 403 } 404 } else { 405 $sort_map = mpull( 406 $column_tasks, 407 'getPrioritySortVector', 408 'getPHID'); 409 } 410 411 $data = array( 412 'sortMap' => $sort_map, 413 ); 414 break; 415 case 'task': 416 default: 417 $tasks = $this->renderSingleTask($task); 418 $data = array(); 419 break; 420 } 421 return id(new AphrontAjaxResponse())->setContent( 422 array( 423 'tasks' => $tasks, 424 'data' => $data, 425 )); 426 } 427 428 $redirect_uri = '/T'.$task->getID(); 429 430 if ($workflow) { 431 $redirect_uri .= '?workflow='.$workflow; 432 } 433 434 return id(new AphrontRedirectResponse()) 435 ->setURI($redirect_uri); 436 } 437 } else { 438 if (!$task->getID()) { 439 $task->setCCPHIDs(array( 440 $user->getPHID(), 441 )); 442 if ($template_id) { 443 $template_task = id(new ManiphestTaskQuery()) 444 ->setViewer($user) 445 ->withIDs(array($template_id)) 446 ->executeOne(); 447 if ($template_task) { 448 $cc_phids = array_unique(array_merge( 449 $template_task->getCCPHIDs(), 450 array($user->getPHID()))); 451 $task->setCCPHIDs($cc_phids); 452 $task->attachProjectPHIDs($template_task->getProjectPHIDs()); 453 $task->setOwnerPHID($template_task->getOwnerPHID()); 454 $task->setPriority($template_task->getPriority()); 455 $task->setViewPolicy($template_task->getViewPolicy()); 456 $task->setEditPolicy($template_task->getEditPolicy()); 457 458 $template_fields = PhabricatorCustomField::getObjectFields( 459 $template_task, 460 PhabricatorCustomField::ROLE_EDIT); 461 462 $fields = $template_fields->getFields(); 463 foreach ($fields as $key => $field) { 464 if (!$field->shouldCopyWhenCreatingSimilarTask()) { 465 unset($fields[$key]); 466 } 467 if (empty($aux_fields[$key])) { 468 unset($fields[$key]); 469 } 470 } 471 472 if ($fields) { 473 id(new PhabricatorCustomFieldList($fields)) 474 ->setViewer($user) 475 ->readFieldsFromStorage($template_task); 476 477 foreach ($fields as $key => $field) { 478 $aux_fields[$key]->setValueFromStorage( 479 $field->getValueForStorage()); 480 } 481 } 482 } 483 } 484 } 485 } 486 487 $phids = array_merge( 488 array($task->getOwnerPHID()), 489 $task->getCCPHIDs(), 490 $task->getProjectPHIDs()); 491 492 if ($parent_task) { 493 $phids[] = $parent_task->getPHID(); 494 } 495 496 $phids = array_filter($phids); 497 $phids = array_unique($phids); 498 499 $handles = $this->loadViewerHandles($phids); 500 501 $error_view = null; 502 if ($errors) { 503 $error_view = new AphrontErrorView(); 504 $error_view->setErrors($errors); 505 } 506 507 $priority_map = ManiphestTaskPriority::getTaskPriorityMap(); 508 509 if ($task->getOwnerPHID()) { 510 $assigned_value = array($handles[$task->getOwnerPHID()]); 511 } else { 512 $assigned_value = array(); 513 } 514 515 if ($task->getCCPHIDs()) { 516 $cc_value = array_select_keys($handles, $task->getCCPHIDs()); 517 } else { 518 $cc_value = array(); 519 } 520 521 if ($task->getProjectPHIDs()) { 522 $projects_value = array_select_keys($handles, $task->getProjectPHIDs()); 523 } else { 524 $projects_value = array(); 525 } 526 527 $cancel_id = nonempty($task->getID(), $template_id); 528 if ($cancel_id) { 529 $cancel_uri = '/T'.$cancel_id; 530 } else { 531 $cancel_uri = '/maniphest/'; 532 } 533 534 if ($task->getID()) { 535 $button_name = pht('Save Task'); 536 $header_name = pht('Edit Task'); 537 } else if ($parent_task) { 538 $cancel_uri = '/T'.$parent_task->getID(); 539 $button_name = pht('Create Task'); 540 $header_name = pht('Create New Subtask'); 541 } else { 542 $button_name = pht('Create Task'); 543 $header_name = pht('Create New Task'); 544 } 545 546 require_celerity_resource('maniphest-task-edit-css'); 547 548 $project_tokenizer_id = celerity_generate_unique_node_id(); 549 550 $form = new AphrontFormView(); 551 $form 552 ->setUser($user) 553 ->addHiddenInput('template', $template_id) 554 ->addHiddenInput('responseType', $response_type) 555 ->addHiddenInput('order', $order) 556 ->addHiddenInput('ungrippable', $request->getStr('ungrippable')) 557 ->addHiddenInput('columnPHID', $request->getStr('columnPHID')); 558 559 if ($parent_task) { 560 $form 561 ->appendChild( 562 id(new AphrontFormStaticControl()) 563 ->setLabel(pht('Parent Task')) 564 ->setValue($handles[$parent_task->getPHID()]->getFullName())) 565 ->addHiddenInput('parent', $parent_task->getID()); 566 } 567 568 $form 569 ->appendChild( 570 id(new AphrontFormTextAreaControl()) 571 ->setLabel(pht('Title')) 572 ->setName('title') 573 ->setError($e_title) 574 ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT) 575 ->setValue($task->getTitle())); 576 577 if ($can_edit_status) { 578 // See T4819. 579 $status_map = ManiphestTaskStatus::getTaskStatusMap(); 580 $dup_status = ManiphestTaskStatus::getDuplicateStatus(); 581 582 if ($task->getStatus() != $dup_status) { 583 unset($status_map[$dup_status]); 584 } 585 586 $form 587 ->appendChild( 588 id(new AphrontFormSelectControl()) 589 ->setLabel(pht('Status')) 590 ->setName('status') 591 ->setValue($task->getStatus()) 592 ->setOptions($status_map)); 593 } 594 595 $policies = id(new PhabricatorPolicyQuery()) 596 ->setViewer($user) 597 ->setObject($task) 598 ->execute(); 599 600 if ($can_edit_assign) { 601 $form->appendChild( 602 id(new AphrontFormTokenizerControl()) 603 ->setLabel(pht('Assigned To')) 604 ->setName('assigned_to') 605 ->setValue($assigned_value) 606 ->setUser($user) 607 ->setDatasource(new PhabricatorPeopleDatasource()) 608 ->setLimit(1)); 609 } 610 611 $form 612 ->appendChild( 613 id(new AphrontFormTokenizerControl()) 614 ->setLabel(pht('CC')) 615 ->setName('cc') 616 ->setValue($cc_value) 617 ->setUser($user) 618 ->setDatasource(new PhabricatorMetaMTAMailableDatasource())); 619 620 if ($can_edit_priority) { 621 $form 622 ->appendChild( 623 id(new AphrontFormSelectControl()) 624 ->setLabel(pht('Priority')) 625 ->setName('priority') 626 ->setOptions($priority_map) 627 ->setValue($task->getPriority())); 628 } 629 630 if ($can_edit_policies) { 631 $form 632 ->appendChild( 633 id(new AphrontFormPolicyControl()) 634 ->setUser($user) 635 ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) 636 ->setPolicyObject($task) 637 ->setPolicies($policies) 638 ->setName('viewPolicy')) 639 ->appendChild( 640 id(new AphrontFormPolicyControl()) 641 ->setUser($user) 642 ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) 643 ->setPolicyObject($task) 644 ->setPolicies($policies) 645 ->setName('editPolicy')); 646 } 647 648 if ($can_edit_projects) { 649 $form 650 ->appendChild( 651 id(new AphrontFormTokenizerControl()) 652 ->setLabel(pht('Projects')) 653 ->setName('projects') 654 ->setValue($projects_value) 655 ->setID($project_tokenizer_id) 656 ->setCaption( 657 javelin_tag( 658 'a', 659 array( 660 'href' => '/project/create/', 661 'mustcapture' => true, 662 'sigil' => 'project-create', 663 ), 664 pht('Create New Project'))) 665 ->setDatasource(new PhabricatorProjectDatasource())); 666 } 667 668 $field_list->appendFieldsToForm($form); 669 670 require_celerity_resource('aphront-error-view-css'); 671 672 Javelin::initBehavior('project-create', array( 673 'tokenizerID' => $project_tokenizer_id, 674 )); 675 676 $description_control = new PhabricatorRemarkupControl(); 677 // "Upsell" creating tasks via email in create flows if the instance is 678 // configured for this awesomeness. 679 $email_create = PhabricatorEnv::getEnvConfig( 680 'metamta.maniphest.public-create-email'); 681 if (!$task->getID() && $email_create) { 682 $email_hint = pht( 683 'You can also create tasks by sending an email to: %s', 684 phutil_tag('tt', array(), $email_create)); 685 $description_control->setCaption($email_hint); 686 } 687 688 $description_control 689 ->setLabel(pht('Description')) 690 ->setName('description') 691 ->setID('description-textarea') 692 ->setValue($task->getDescription()) 693 ->setUser($user); 694 695 $form 696 ->appendChild($description_control); 697 698 699 if ($request->isAjax()) { 700 $dialog = id(new AphrontDialogView()) 701 ->setUser($user) 702 ->setWidth(AphrontDialogView::WIDTH_FULL) 703 ->setTitle($header_name) 704 ->appendChild( 705 array( 706 $error_view, 707 $form->buildLayoutView(), 708 )) 709 ->addCancelButton($cancel_uri) 710 ->addSubmitButton($button_name); 711 return id(new AphrontDialogResponse())->setDialog($dialog); 712 } 713 714 $form 715 ->appendChild( 716 id(new AphrontFormSubmitControl()) 717 ->addCancelButton($cancel_uri) 718 ->setValue($button_name)); 719 720 $form_box = id(new PHUIObjectBoxView()) 721 ->setHeaderText($header_name) 722 ->setFormErrors($errors) 723 ->setForm($form); 724 725 $preview = id(new PHUIRemarkupPreviewPanel()) 726 ->setHeader(pht('Description Preview')) 727 ->setControlID('description-textarea') 728 ->setPreviewURI($this->getApplicationURI('task/descriptionpreview/')); 729 730 if ($task->getID()) { 731 $page_objects = array($task->getPHID()); 732 } else { 733 $page_objects = array(); 734 } 735 736 $crumbs = $this->buildApplicationCrumbs(); 737 738 if ($task->getID()) { 739 $crumbs->addTextCrumb('T'.$task->getID(), '/T'.$task->getID()); 740 } 741 742 $crumbs->addTextCrumb($header_name); 743 744 return $this->buildApplicationPage( 745 array( 746 $crumbs, 747 $form_box, 748 $preview, 749 ), 750 array( 751 'title' => $header_name, 752 'pageObjects' => $page_objects, 753 )); 754 } 755 756 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |