Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F470830
PhabricatorProfileMenuEngine.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
37 KB
Referenced Files
None
Subscribers
None
PhabricatorProfileMenuEngine.php
View Options
<?php
abstract
class
PhabricatorProfileMenuEngine
extends
Phobject
{
private
$viewer
;
private
$profileObject
;
private
$customPHID
;
private
$items
;
private
$controller
;
private
$navigation
;
private
$editMode
;
private
$pageClasses
=
array
();
private
$showContentCrumbs
=
true
;
const
ITEM_CUSTOM_DIVIDER
=
'engine.divider'
;
const
ITEM_MANAGE
=
'item.configure'
;
const
MODE_COMBINED
=
'combined'
;
const
MODE_GLOBAL
=
'global'
;
const
MODE_CUSTOM
=
'custom'
;
public
function
setViewer
(
PhabricatorUser
$viewer
)
{
$this
->
viewer
=
$viewer
;
return
$this
;
}
public
function
getViewer
()
{
return
$this
->
viewer
;
}
public
function
setProfileObject
(
$profile_object
)
{
$this
->
profileObject
=
$profile_object
;
return
$this
;
}
public
function
getProfileObject
()
{
return
$this
->
profileObject
;
}
public
function
setCustomPHID
(
$custom_phid
)
{
$this
->
customPHID
=
$custom_phid
;
return
$this
;
}
public
function
getCustomPHID
()
{
return
$this
->
customPHID
;
}
private
function
getEditModeCustomPHID
()
{
$mode
=
$this
->
getEditMode
();
switch
(
$mode
)
{
case
self
::
MODE_CUSTOM
:
$custom_phid
=
$this
->
getCustomPHID
();
break
;
case
self
::
MODE_GLOBAL
:
default
:
$custom_phid
=
null
;
break
;
}
return
$custom_phid
;
}
public
function
setController
(
PhabricatorController
$controller
)
{
$this
->
controller
=
$controller
;
return
$this
;
}
public
function
getController
()
{
return
$this
->
controller
;
}
public
function
addContentPageClass
(
$class
)
{
$this
->
pageClasses
[]
=
$class
;
return
$this
;
}
public
function
setShowContentCrumbs
(
$show_content_crumbs
)
{
$this
->
showContentCrumbs
=
$show_content_crumbs
;
return
$this
;
}
public
function
getShowContentCrumbs
()
{
return
$this
->
showContentCrumbs
;
}
abstract
public
function
getItemURI
(
$path
);
abstract
protected
function
isMenuEngineConfigurable
();
/**
* @return array of PhabricatorProfileMenuItemConfiguration objects
*/
abstract
protected
function
getBuiltinProfileItems
(
$object
);
protected
function
getBuiltinCustomProfileItems
(
$object
,
$custom_phid
)
{
return
array
();
}
protected
function
getEditMode
()
{
return
$this
->
editMode
;
}
public
function
buildResponse
()
{
$controller
=
$this
->
getController
();
$viewer
=
$controller
->
getViewer
();
$this
->
setViewer
(
$viewer
);
$request
=
$controller
->
getRequest
();
$item_action
=
$request
->
getURIData
(
'itemAction'
);
if
(!
$item_action
)
{
$item_action
=
'view'
;
}
$is_view
=
(
$item_action
==
'view'
);
// If the engine is not configurable, don't respond to any of the editing
// or configuration routes.
if
(!
$this
->
isMenuEngineConfigurable
())
{
if
(!
$is_view
)
{
return
new
Aphront404Response
();
}
}
$item_id
=
$request
->
getURIData
(
'itemID'
);
// If we miss on the MenuEngine route, try the EditEngine route. This will
// be populated while editing items.
if
(!
$item_id
)
{
$item_id
=
$request
->
getURIData
(
'id'
);
}
$view_list
=
$this
->
newProfileMenuItemViewList
();
if
(
$is_view
)
{
$selected_item
=
$this
->
selectViewItem
(
$view_list
,
$item_id
);
}
else
{
if
(!
phutil_nonempty_scalar
(
$item_id
))
{
$item_id
=
self
::
ITEM_MANAGE
;
}
$selected_item
=
$this
->
selectEditItem
(
$view_list
,
$item_id
);
}
switch
(
$item_action
)
{
case
'view'
:
// If we were not able to select an item, we're still going to render
// a page state. For example, this happens when you create a new
// portal for the first time.
break
;
case
'info'
:
case
'hide'
:
case
'default'
:
case
'builtin'
:
if
(!
$selected_item
)
{
return
new
Aphront404Response
();
}
break
;
case
'edit'
:
if
(!
$request
->
getURIData
(
'id'
))
{
// If we continue along the "edit" pathway without an ID, we hit an
// unrelated exception because we can not build a new menu item out
// of thin air. For menus, new items are created via the "new"
// action. Just catch this case and 404 early since there's currently
// no clean way to make EditEngine aware of this.
return
new
Aphront404Response
();
}
break
;
}
$navigation
=
$view_list
->
newNavigationView
();
$crumbs
=
$controller
->
buildApplicationCrumbsForEditEngine
();
if
(!
$is_view
)
{
$edit_mode
=
null
;
if
(
$selected_item
)
{
if
(
$selected_item
->
getBuiltinKey
()
!==
self
::
ITEM_MANAGE
)
{
if
(
$selected_item
->
getCustomPHID
())
{
$edit_mode
=
'custom'
;
}
else
{
$edit_mode
=
'global'
;
}
}
}
if
(
$edit_mode
===
null
)
{
$edit_mode
=
$request
->
getURIData
(
'itemEditMode'
);
}
$available_modes
=
$this
->
getViewerEditModes
();
if
(
$available_modes
)
{
$available_modes
=
array_fuse
(
$available_modes
);
if
(
isset
(
$available_modes
[
$edit_mode
]))
{
$this
->
editMode
=
$edit_mode
;
}
else
{
if
(
$item_action
!=
'configure'
)
{
return
new
Aphront404Response
();
}
}
}
$page_title
=
pht
(
'Configure Menu'
);
}
else
{
if
(
$selected_item
)
{
$page_title
=
$selected_item
->
getDisplayName
();
}
else
{
$page_title
=
pht
(
'Empty'
);
}
}
switch
(
$item_action
)
{
case
'view'
:
if
(
$selected_item
)
{
try
{
$content
=
$this
->
buildItemViewContent
(
$selected_item
);
}
catch
(
Exception
$ex
)
{
$content
=
id
(
new
PHUIInfoView
())
->
setTitle
(
pht
(
'Unable to Render Dashboard'
))
->
setErrors
(
array
(
$ex
->
getMessage
()));
}
$crumbs
->
addTextCrumb
(
$selected_item
->
getDisplayName
());
}
else
{
$content
=
$this
->
newNoContentView
(
$this
->
getItems
());
}
if
(!
$content
)
{
$content
=
$this
->
newEmptyView
(
pht
(
'Empty'
),
pht
(
'There is nothing here.'
));
}
break
;
case
'configure'
:
$mode
=
$this
->
getEditMode
();
if
(!
$mode
)
{
$crumbs
->
addTextCrumb
(
pht
(
'Configure Menu'
));
$content
=
$this
->
buildMenuEditModeContent
();
}
else
{
if
(
count
(
$available_modes
)
>
1
)
{
$crumbs
->
addTextCrumb
(
pht
(
'Configure Menu'
),
$this
->
getItemURI
(
'configure/'
));
switch
(
$mode
)
{
case
self
::
MODE_CUSTOM
:
$crumbs
->
addTextCrumb
(
pht
(
'Personal'
));
break
;
case
self
::
MODE_GLOBAL
:
$crumbs
->
addTextCrumb
(
pht
(
'Global'
));
break
;
}
}
else
{
$crumbs
->
addTextCrumb
(
pht
(
'Configure Menu'
));
}
$edit_list
=
$this
->
loadItems
(
$mode
);
$content
=
$this
->
buildItemConfigureContent
(
$edit_list
);
}
break
;
case
'reorder'
:
$mode
=
$this
->
getEditMode
();
$edit_list
=
$this
->
loadItems
(
$mode
);
$content
=
$this
->
buildItemReorderContent
(
$edit_list
);
break
;
case
'new'
:
$item_key
=
$request
->
getURIData
(
'itemKey'
);
$mode
=
$this
->
getEditMode
();
$content
=
$this
->
buildItemNewContent
(
$item_key
,
$mode
);
break
;
case
'builtin'
:
$content
=
$this
->
buildItemBuiltinContent
(
$selected_item
);
break
;
case
'hide'
:
$content
=
$this
->
buildItemHideContent
(
$selected_item
);
break
;
case
'default'
:
if
(!
$this
->
isMenuEnginePinnable
())
{
return
new
Aphront404Response
();
}
$content
=
$this
->
buildItemDefaultContent
(
$selected_item
);
break
;
case
'edit'
:
$content
=
$this
->
buildItemEditContent
();
break
;
default
:
throw
new
Exception
(
pht
(
'Unsupported item action "%s".'
,
$item_action
));
}
if
(
$content
instanceof
AphrontResponse
)
{
return
$content
;
}
if
(
$content
instanceof
AphrontResponseProducerInterface
)
{
return
$content
;
}
$crumbs
->
setBorder
(
true
);
$page
=
$controller
->
newPage
()
->
setTitle
(
$page_title
)
->
appendChild
(
$content
);
if
(!
$is_view
||
$this
->
getShowContentCrumbs
())
{
$page
->
setCrumbs
(
$crumbs
);
}
$page
->
setNavigation
(
$navigation
);
if
(
$is_view
)
{
foreach
(
$this
->
pageClasses
as
$class
)
{
$page
->
addClass
(
$class
);
}
}
return
$page
;
}
private
function
getItems
()
{
if
(
$this
->
items
===
null
)
{
$this
->
items
=
$this
->
loadItems
(
self
::
MODE_COMBINED
);
}
return
$this
->
items
;
}
private
function
loadItems
(
$mode
)
{
$viewer
=
$this
->
getViewer
();
$object
=
$this
->
getProfileObject
();
$items
=
$this
->
loadBuiltinProfileItems
(
$mode
);
$query
=
id
(
new
PhabricatorProfileMenuItemConfigurationQuery
())
->
setViewer
(
$viewer
)
->
withProfilePHIDs
(
array
(
$object
->
getPHID
()));
switch
(
$mode
)
{
case
self
::
MODE_GLOBAL
:
$query
->
withCustomPHIDs
(
array
(),
true
);
break
;
case
self
::
MODE_CUSTOM
:
$query
->
withCustomPHIDs
(
array
(
$this
->
getCustomPHID
()),
false
);
break
;
case
self
::
MODE_COMBINED
:
$query
->
withCustomPHIDs
(
array
(
$this
->
getCustomPHID
()),
true
);
break
;
}
$stored_items
=
$query
->
execute
();
foreach
(
$stored_items
as
$stored_item
)
{
$impl
=
$stored_item
->
getMenuItem
();
$impl
->
setViewer
(
$viewer
);
$impl
->
setEngine
(
$this
);
}
// Merge the stored items into the builtin items. If a builtin item has
// a stored version, replace the defaults with the stored changes.
foreach
(
$stored_items
as
$stored_item
)
{
if
(!
$stored_item
->
shouldEnableForObject
(
$object
))
{
continue
;
}
$builtin_key
=
$stored_item
->
getBuiltinKey
();
if
(
$builtin_key
!==
null
)
{
// If this builtin actually exists, replace the builtin with the
// stored configuration. Otherwise, we're just going to drop the
// stored config: it corresponds to an out-of-date or uninstalled
// item.
if
(
isset
(
$items
[
$builtin_key
]))
{
$builtin_item
=
$items
[
$builtin_key
];
// Copy runtime properties from the builtin item to the stored item.
$stored_item
->
setIsHeadItem
(
$builtin_item
->
getIsHeadItem
());
$stored_item
->
setIsTailItem
(
$builtin_item
->
getIsTailItem
());
$items
[
$builtin_key
]
=
$stored_item
;
}
else
{
continue
;
}
}
else
{
$items
[]
=
$stored_item
;
}
}
return
$this
->
arrangeItems
(
$items
,
$mode
);
}
private
function
loadBuiltinProfileItems
(
$mode
)
{
$object
=
$this
->
getProfileObject
();
switch
(
$mode
)
{
case
self
::
MODE_GLOBAL
:
$builtins
=
$this
->
getBuiltinProfileItems
(
$object
);
break
;
case
self
::
MODE_CUSTOM
:
$builtins
=
$this
->
getBuiltinCustomProfileItems
(
$object
,
$this
->
getCustomPHID
());
break
;
case
self
::
MODE_COMBINED
:
$builtins
=
array
();
$builtins
[]
=
$this
->
getBuiltinCustomProfileItems
(
$object
,
$this
->
getCustomPHID
());
$builtins
[]
=
$this
->
getBuiltinProfileItems
(
$object
);
$builtins
=
array_mergev
(
$builtins
);
break
;
}
$items
=
PhabricatorProfileMenuItem
::
getAllMenuItems
();
$viewer
=
$this
->
getViewer
();
$order
=
1
;
$map
=
array
();
foreach
(
$builtins
as
$builtin
)
{
$builtin_key
=
$builtin
->
getBuiltinKey
();
if
(!
$builtin_key
)
{
throw
new
Exception
(
pht
(
'Object produced a builtin item with no builtin item key! '
.
'Builtin items must have a unique key.'
));
}
if
(
isset
(
$map
[
$builtin_key
]))
{
throw
new
Exception
(
pht
(
'Object produced two items with the same builtin key ("%s"). '
.
'Each item must have a unique builtin key.'
,
$builtin_key
));
}
$item_key
=
$builtin
->
getMenuItemKey
();
$item
=
idx
(
$items
,
$item_key
);
if
(!
$item
)
{
throw
new
Exception
(
pht
(
'Builtin item ("%s") specifies a bad item key ("%s"); there '
.
'is no corresponding item implementation available.'
,
$builtin_key
,
$item_key
));
}
$item
=
clone
$item
;
$item
->
setViewer
(
$viewer
);
$item
->
setEngine
(
$this
);
$builtin
->
setProfilePHID
(
$object
->
getPHID
())
->
attachMenuItem
(
$item
)
->
attachProfileObject
(
$object
)
->
setMenuItemOrder
(
$order
);
if
(!
$builtin
->
shouldEnableForObject
(
$object
))
{
continue
;
}
$map
[
$builtin_key
]
=
$builtin
;
$order
++;
}
return
$map
;
}
public
function
getConfigureURI
()
{
$mode
=
$this
->
getEditMode
();
switch
(
$mode
)
{
case
self
::
MODE_CUSTOM
:
return
$this
->
getItemURI
(
'configure/custom/'
);
case
self
::
MODE_GLOBAL
:
return
$this
->
getItemURI
(
'configure/global/'
);
}
return
$this
->
getItemURI
(
'configure/'
);
}
private
function
buildItemReorderContent
(
array
$items
)
{
$viewer
=
$this
->
getViewer
();
$object
=
$this
->
getProfileObject
();
// If you're reordering global items, you need to be able to edit the
// object the menu appears on. If you're reordering custom items, you only
// need to be able to edit the custom object. Currently, the custom object
// is always the viewing user's own user object.
$custom_phid
=
$this
->
getEditModeCustomPHID
();
if
(!
$custom_phid
)
{
PhabricatorPolicyFilter
::
requireCapability
(
$viewer
,
$object
,
PhabricatorPolicyCapability
::
CAN_EDIT
);
}
else
{
$policy_object
=
id
(
new
PhabricatorObjectQuery
())
->
setViewer
(
$viewer
)
->
withPHIDs
(
array
(
$custom_phid
))
->
executeOne
();
if
(!
$policy_object
)
{
throw
new
Exception
(
pht
(
'Failed to load custom PHID "%s"!'
,
$custom_phid
));
}
PhabricatorPolicyFilter
::
requireCapability
(
$viewer
,
$policy_object
,
PhabricatorPolicyCapability
::
CAN_EDIT
);
}
$controller
=
$this
->
getController
();
$request
=
$controller
->
getRequest
();
$request
->
validateCSRF
();
$order
=
$request
->
getStrList
(
'order'
);
$by_builtin
=
array
();
$by_id
=
array
();
foreach
(
$items
as
$key
=>
$item
)
{
$id
=
$item
->
getID
();
if
(
$id
)
{
$by_id
[
$id
]
=
$key
;
continue
;
}
$builtin_key
=
$item
->
getBuiltinKey
();
if
(
$builtin_key
)
{
$by_builtin
[
$builtin_key
]
=
$key
;
continue
;
}
}
$key_order
=
array
();
foreach
(
$order
as
$order_item
)
{
if
(
isset
(
$by_id
[
$order_item
]))
{
$key_order
[]
=
$by_id
[
$order_item
];
continue
;
}
if
(
isset
(
$by_builtin
[
$order_item
]))
{
$key_order
[]
=
$by_builtin
[
$order_item
];
continue
;
}
}
$items
=
array_select_keys
(
$items
,
$key_order
)
+
$items
;
$type_order
=
PhabricatorProfileMenuItemConfigurationTransaction
::
TYPE_ORDER
;
$order
=
1
;
foreach
(
$items
as
$item
)
{
$xactions
=
array
();
$xactions
[]
=
id
(
new
PhabricatorProfileMenuItemConfigurationTransaction
())
->
setTransactionType
(
$type_order
)
->
setNewValue
(
$order
);
$editor
=
id
(
new
PhabricatorProfileMenuEditor
())
->
setContentSourceFromRequest
(
$request
)
->
setActor
(
$viewer
)
->
setContinueOnMissingFields
(
true
)
->
setContinueOnNoEffect
(
true
)
->
applyTransactions
(
$item
,
$xactions
);
$order
++;
}
return
id
(
new
AphrontRedirectResponse
())
->
setURI
(
$this
->
getConfigureURI
());
}
protected
function
buildItemViewContent
(
PhabricatorProfileMenuItemConfiguration
$item
)
{
return
$item
->
newPageContent
();
}
private
function
getViewerEditModes
()
{
$modes
=
array
();
$viewer
=
$this
->
getViewer
();
if
(
$viewer
->
isLoggedIn
()
&&
$this
->
isMenuEnginePersonalizable
())
{
$modes
[]
=
self
::
MODE_CUSTOM
;
}
$object
=
$this
->
getProfileObject
();
$can_edit
=
PhabricatorPolicyFilter
::
hasCapability
(
$viewer
,
$object
,
PhabricatorPolicyCapability
::
CAN_EDIT
);
if
(
$can_edit
)
{
$modes
[]
=
self
::
MODE_GLOBAL
;
}
return
$modes
;
}
protected
function
isMenuEnginePersonalizable
()
{
return
true
;
}
/**
* Does this engine support pinning items?
*
* Personalizable menus disable pinning by default since it creates a number
* of weird edge cases without providing many benefits for current menus.
*
* @return bool True if items may be pinned as default items.
*/
public
function
isMenuEnginePinnable
()
{
return
!
$this
->
isMenuEnginePersonalizable
();
}
private
function
buildMenuEditModeContent
()
{
$viewer
=
$this
->
getViewer
();
$modes
=
$this
->
getViewerEditModes
();
if
(!
$modes
)
{
return
new
Aphront404Response
();
}
if
(
count
(
$modes
)
==
1
)
{
$mode
=
head
(
$modes
);
return
id
(
new
AphrontRedirectResponse
())
->
setURI
(
$this
->
getItemURI
(
"configure/{$mode}/"
));
}
$menu
=
id
(
new
PHUIObjectItemListView
())
->
setUser
(
$viewer
);
$modes
=
array_fuse
(
$modes
);
if
(
isset
(
$modes
[
'custom'
]))
{
$menu
->
addItem
(
id
(
new
PHUIObjectItemView
())
->
setHeader
(
pht
(
'Personal Menu Items'
))
->
setHref
(
$this
->
getItemURI
(
'configure/custom/'
))
->
setImageURI
(
$viewer
->
getProfileImageURI
())
->
addAttribute
(
pht
(
'Edit the menu for your personal account.'
)));
}
if
(
isset
(
$modes
[
'global'
]))
{
$icon
=
id
(
new
PHUIIconView
())
->
setIcon
(
'fa-globe'
)
->
setBackground
(
'bg-blue'
);
$menu
->
addItem
(
id
(
new
PHUIObjectItemView
())
->
setHeader
(
pht
(
'Global Menu Items'
))
->
setHref
(
$this
->
getItemURI
(
'configure/global/'
))
->
setImageIcon
(
$icon
)
->
addAttribute
(
pht
(
'Edit the global default menu for all users.'
)));
}
$box
=
id
(
new
PHUIObjectBoxView
())
->
setObjectList
(
$menu
);
$header
=
id
(
new
PHUIHeaderView
())
->
setHeader
(
pht
(
'Manage Menu'
))
->
setHeaderIcon
(
'fa-list'
);
return
id
(
new
PHUITwoColumnView
())
->
setHeader
(
$header
)
->
setFooter
(
$box
);
}
private
function
buildItemConfigureContent
(
array
$items
)
{
$viewer
=
$this
->
getViewer
();
$object
=
$this
->
getProfileObject
();
$filtered_groups
=
mgroup
(
$items
,
'getMenuItemKey'
);
foreach
(
$filtered_groups
as
$group
)
{
$first_item
=
head
(
$group
);
$first_item
->
willGetMenuItemViewList
(
$group
);
}
// Users only need to be able to edit the object which this menu appears
// on if they're editing global menu items. For example, users do not need
// to be able to edit the Favorites application to add new items to the
// Favorites menu.
if
(!
$this
->
getCustomPHID
())
{
PhabricatorPolicyFilter
::
requireCapability
(
$viewer
,
$object
,
PhabricatorPolicyCapability
::
CAN_EDIT
);
}
$list_id
=
celerity_generate_unique_node_id
();
$mode
=
$this
->
getEditMode
();
Javelin
::
initBehavior
(
'reorder-profile-menu-items'
,
array
(
'listID'
=>
$list_id
,
'orderURI'
=>
$this
->
getItemURI
(
"reorder/{$mode}/"
),
));
$list
=
id
(
new
PHUIObjectItemListView
())
->
setID
(
$list_id
)
->
setNoDataString
(
pht
(
'This menu currently has no items.'
));
$any_draggable
=
false
;
foreach
(
$items
as
$item
)
{
$id
=
$item
->
getID
();
$builtin_key
=
$item
->
getBuiltinKey
();
$can_edit
=
PhabricatorPolicyFilter
::
hasCapability
(
$viewer
,
$item
,
PhabricatorPolicyCapability
::
CAN_EDIT
);
$view
=
id
(
new
PHUIObjectItemView
());
$name
=
$item
->
getDisplayName
();
$type
=
$item
->
getMenuItemTypeName
();
if
(!
strlen
(
trim
(
$name
)))
{
$name
=
pht
(
'Untitled "%s" Item'
,
$type
);
}
$view
->
setHeader
(
$name
);
$view
->
addAttribute
(
$type
);
$icon
=
$item
->
getMenuItem
()->
getMenuItemTypeIcon
();
if
(
$icon
!==
null
)
{
$view
->
setStatusIcon
(
$icon
);
}
if
(
$can_edit
)
{
$can_move
=
(!
$item
->
getIsHeadItem
()
&&
!
$item
->
getIsTailItem
());
if
(
$can_move
)
{
$view
->
setGrippable
(
true
)
->
addSigil
(
'profile-menu-item'
)
->
setMetadata
(
array
(
'key'
=>
nonempty
(
$id
,
$builtin_key
),
));
$any_draggable
=
true
;
}
else
{
$view
->
setGrippable
(
false
);
}
if
(
$id
)
{
$default_uri
=
$this
->
getItemURI
(
"default/{$id}/"
);
}
else
{
$default_uri
=
$this
->
getItemURI
(
"default/{$builtin_key}/"
);
}
$default_text
=
null
;
if
(
$this
->
isMenuEnginePinnable
())
{
if
(
$item
->
isDefault
())
{
$default_icon
=
'fa-thumb-tack green'
;
$default_text
=
pht
(
'Current Default'
);
}
else
if
(
$item
->
canMakeDefault
())
{
$default_icon
=
'fa-thumb-tack'
;
$default_text
=
pht
(
'Make Default'
);
}
}
if
(
$default_text
!==
null
)
{
$view
->
addAction
(
id
(
new
PHUIListItemView
())
->
setHref
(
$default_uri
)
->
setWorkflow
(
true
)
->
setName
(
$default_text
)
->
setIcon
(
$default_icon
));
}
if
(
$id
)
{
$view
->
setHref
(
$this
->
getItemURI
(
"edit/{$id}/"
));
$hide_uri
=
$this
->
getItemURI
(
"hide/{$id}/"
);
}
else
{
$view
->
setHref
(
$this
->
getItemURI
(
"builtin/{$builtin_key}/"
));
$hide_uri
=
$this
->
getItemURI
(
"hide/{$builtin_key}/"
);
}
if
(
$item
->
isDisabled
())
{
$hide_icon
=
'fa-plus'
;
$hide_text
=
pht
(
'Enable'
);
}
else
if
(
$item
->
getBuiltinKey
()
!==
null
)
{
$hide_icon
=
'fa-times'
;
$hide_text
=
pht
(
'Disable'
);
}
else
{
$hide_icon
=
'fa-times'
;
$hide_text
=
pht
(
'Delete'
);
}
$can_disable
=
$item
->
canHideMenuItem
();
$view
->
addAction
(
id
(
new
PHUIListItemView
())
->
setHref
(
$hide_uri
)
->
setWorkflow
(
true
)
->
setDisabled
(!
$can_disable
)
->
setName
(
$hide_text
)
->
setIcon
(
$hide_icon
));
}
if
(
$item
->
isDisabled
())
{
$view
->
setDisabled
(
true
);
}
$list
->
addItem
(
$view
);
}
$item_types
=
PhabricatorProfileMenuItem
::
getAllMenuItems
();
$object
=
$this
->
getProfileObject
();
$action_list
=
id
(
new
PhabricatorActionListView
())
->
setViewer
(
$viewer
);
// See T12167. This makes the "Actions" dropdown button show up in the
// page header.
$action_list
->
setID
(
celerity_generate_unique_node_id
());
$action_list
->
addAction
(
id
(
new
PhabricatorActionView
())
->
setLabel
(
true
)
->
setName
(
pht
(
'Add New Menu Item...'
)));
foreach
(
$item_types
as
$item_type
)
{
if
(!
$item_type
->
canAddToObject
(
$object
))
{
continue
;
}
$item_key
=
$item_type
->
getMenuItemKey
();
$edit_mode
=
$this
->
getEditMode
();
$action_list
->
addAction
(
id
(
new
PhabricatorActionView
())
->
setIcon
(
$item_type
->
getMenuItemTypeIcon
())
->
setName
(
$item_type
->
getMenuItemTypeName
())
->
setHref
(
$this
->
getItemURI
(
"new/{$edit_mode}/{$item_key}/"
))
->
setWorkflow
(
true
));
}
$action_list
->
addAction
(
id
(
new
PhabricatorActionView
())
->
setLabel
(
true
)
->
setName
(
pht
(
'Documentation'
)));
$doc_link
=
PhabricatorEnv
::
getDoclink
(
'Profile Menu User Guide'
);
$doc_name
=
pht
(
'Profile Menu User Guide'
);
$action_list
->
addAction
(
id
(
new
PhabricatorActionView
())
->
setIcon
(
'fa-book'
)
->
setHref
(
$doc_link
)
->
setName
(
$doc_name
));
$header
=
id
(
new
PHUIHeaderView
())
->
setHeader
(
pht
(
'Menu Items'
))
->
setHeaderIcon
(
'fa-list'
);
$list_header
=
id
(
new
PHUIHeaderView
())
->
setHeader
(
pht
(
'Current Menu Items'
));
if
(
$any_draggable
)
{
$list_header
->
setSubheader
(
pht
(
'Drag items in this list to reorder them.'
));
}
$box
=
id
(
new
PHUIObjectBoxView
())
->
setHeader
(
$list_header
)
->
setBackground
(
PHUIObjectBoxView
::
BLUE_PROPERTY
)
->
setObjectList
(
$list
);
$curtain
=
id
(
new
PHUICurtainView
())
->
setViewer
(
$viewer
)
->
setActionList
(
$action_list
);
$view
=
id
(
new
PHUITwoColumnView
())
->
setHeader
(
$header
)
->
setCurtain
(
$curtain
)
->
setMainColumn
(
array
(
$box
,
));
return
$view
;
}
private
function
buildItemNewContent
(
$item_key
,
$mode
)
{
$item_types
=
PhabricatorProfileMenuItem
::
getAllMenuItems
();
$item_type
=
idx
(
$item_types
,
$item_key
);
if
(!
$item_type
)
{
return
new
Aphront404Response
();
}
$object
=
$this
->
getProfileObject
();
if
(!
$item_type
->
canAddToObject
(
$object
))
{
return
new
Aphront404Response
();
}
$custom_phid
=
$this
->
getEditModeCustomPHID
();
$configuration
=
PhabricatorProfileMenuItemConfiguration
::
initializeNewItem
(
$object
,
$item_type
,
$custom_phid
);
$viewer
=
$this
->
getViewer
();
PhabricatorPolicyFilter
::
requireCapability
(
$viewer
,
$configuration
,
PhabricatorPolicyCapability
::
CAN_EDIT
);
$controller
=
$this
->
getController
();
return
id
(
new
PhabricatorProfileMenuEditEngine
())
->
setMenuEngine
(
$this
)
->
setProfileObject
(
$object
)
->
setNewMenuItemConfiguration
(
$configuration
)
->
setCustomPHID
(
$custom_phid
)
->
setController
(
$controller
)
->
buildResponse
();
}
private
function
buildItemEditContent
()
{
$viewer
=
$this
->
getViewer
();
$object
=
$this
->
getProfileObject
();
$controller
=
$this
->
getController
();
$custom_phid
=
$this
->
getEditModeCustomPHID
();
return
id
(
new
PhabricatorProfileMenuEditEngine
())
->
setMenuEngine
(
$this
)
->
setProfileObject
(
$object
)
->
setController
(
$controller
)
->
setCustomPHID
(
$custom_phid
)
->
buildResponse
();
}
private
function
buildItemBuiltinContent
(
PhabricatorProfileMenuItemConfiguration
$configuration
)
{
// If this builtin item has already been persisted, redirect to the
// edit page.
$id
=
$configuration
->
getID
();
if
(
$id
)
{
return
id
(
new
AphrontRedirectResponse
())
->
setURI
(
$this
->
getItemURI
(
"edit/{$id}/"
));
}
// Otherwise, act like we're creating a new item, we're just starting
// with the builtin template.
$viewer
=
$this
->
getViewer
();
PhabricatorPolicyFilter
::
requireCapability
(
$viewer
,
$configuration
,
PhabricatorPolicyCapability
::
CAN_EDIT
);
$object
=
$this
->
getProfileObject
();
$controller
=
$this
->
getController
();
$custom_phid
=
$this
->
getEditModeCustomPHID
();
return
id
(
new
PhabricatorProfileMenuEditEngine
())
->
setIsBuiltin
(
true
)
->
setMenuEngine
(
$this
)
->
setProfileObject
(
$object
)
->
setNewMenuItemConfiguration
(
$configuration
)
->
setController
(
$controller
)
->
setCustomPHID
(
$custom_phid
)
->
buildResponse
();
}
private
function
buildItemHideContent
(
PhabricatorProfileMenuItemConfiguration
$configuration
)
{
$controller
=
$this
->
getController
();
$request
=
$controller
->
getRequest
();
$viewer
=
$this
->
getViewer
();
PhabricatorPolicyFilter
::
requireCapability
(
$viewer
,
$configuration
,
PhabricatorPolicyCapability
::
CAN_EDIT
);
if
(!
$configuration
->
canHideMenuItem
())
{
return
$controller
->
newDialog
()
->
setTitle
(
pht
(
'Mandatory Item'
))
->
appendParagraph
(
pht
(
'This menu item is very important, and can not be disabled.'
))
->
addCancelButton
(
$this
->
getConfigureURI
());
}
if
(
$configuration
->
getBuiltinKey
()
===
null
)
{
$new_value
=
null
;
$title
=
pht
(
'Delete Menu Item'
);
$body
=
pht
(
'Delete this menu item?'
);
$button
=
pht
(
'Delete Menu Item'
);
}
else
if
(
$configuration
->
isDisabled
())
{
$new_value
=
PhabricatorProfileMenuItemConfiguration
::
VISIBILITY_VISIBLE
;
$title
=
pht
(
'Enable Menu Item'
);
$body
=
pht
(
'Enable this menu item? It will appear in the menu again.'
);
$button
=
pht
(
'Enable Menu Item'
);
}
else
{
$new_value
=
PhabricatorProfileMenuItemConfiguration
::
VISIBILITY_DISABLED
;
$title
=
pht
(
'Disable Menu Item'
);
$body
=
pht
(
'Disable this menu item? It will no longer appear in the menu, but '
.
'you can re-enable it later.'
);
$button
=
pht
(
'Disable Menu Item'
);
}
$v_visibility
=
$configuration
->
getVisibility
();
if
(
$request
->
isFormPost
())
{
if
(
$new_value
===
null
)
{
$configuration
->
delete
();
}
else
{
$type_visibility
=
PhabricatorProfileMenuItemConfigurationTransaction
::
TYPE_VISIBILITY
;
$xactions
=
array
();
$xactions
[]
=
id
(
new
PhabricatorProfileMenuItemConfigurationTransaction
())
->
setTransactionType
(
$type_visibility
)
->
setNewValue
(
$new_value
);
$editor
=
id
(
new
PhabricatorProfileMenuEditor
())
->
setContentSourceFromRequest
(
$request
)
->
setActor
(
$viewer
)
->
setContinueOnMissingFields
(
true
)
->
setContinueOnNoEffect
(
true
)
->
applyTransactions
(
$configuration
,
$xactions
);
}
return
id
(
new
AphrontRedirectResponse
())
->
setURI
(
$this
->
getConfigureURI
());
}
return
$controller
->
newDialog
()
->
setTitle
(
$title
)
->
appendParagraph
(
$body
)
->
addCancelButton
(
$this
->
getConfigureURI
())
->
addSubmitButton
(
$button
);
}
private
function
buildItemDefaultContent
(
PhabricatorProfileMenuItemConfiguration
$configuration
)
{
$controller
=
$this
->
getController
();
$request
=
$controller
->
getRequest
();
$viewer
=
$this
->
getViewer
();
PhabricatorPolicyFilter
::
requireCapability
(
$viewer
,
$configuration
,
PhabricatorPolicyCapability
::
CAN_EDIT
);
$done_uri
=
$this
->
getConfigureURI
();
if
(!
$configuration
->
canMakeDefault
())
{
return
$controller
->
newDialog
()
->
setTitle
(
pht
(
'Not Defaultable'
))
->
appendParagraph
(
pht
(
'This item can not be set as the default item. This is usually '
.
'because the item has no page of its own, or links to an '
.
'external page.'
))
->
addCancelButton
(
$done_uri
);
}
if
(
$configuration
->
isDefault
())
{
return
$controller
->
newDialog
()
->
setTitle
(
pht
(
'Already Default'
))
->
appendParagraph
(
pht
(
'This item is already set as the default item for this menu.'
))
->
addCancelButton
(
$done_uri
);
}
if
(
$request
->
isFormPost
())
{
$key
=
$configuration
->
getID
();
if
(!
$key
)
{
$key
=
$configuration
->
getBuiltinKey
();
}
$this
->
adjustDefault
(
$key
);
return
id
(
new
AphrontRedirectResponse
())
->
setURI
(
$done_uri
);
}
return
$controller
->
newDialog
()
->
setTitle
(
pht
(
'Make Default'
))
->
appendParagraph
(
pht
(
'Set this item as the default for this menu? Users arriving on '
.
'this page will be shown the content of this item by default.'
))
->
addCancelButton
(
$done_uri
)
->
addSubmitButton
(
pht
(
'Make Default'
));
}
protected
function
newItem
()
{
return
PhabricatorProfileMenuItemConfiguration
::
initializeNewBuiltin
();
}
protected
function
newManageItem
()
{
return
$this
->
newItem
()
->
setBuiltinKey
(
self
::
ITEM_MANAGE
)
->
setMenuItemKey
(
PhabricatorManageProfileMenuItem
::
MENUITEMKEY
)
->
setIsTailItem
(
true
);
}
protected
function
newDividerItem
(
$key
)
{
return
$this
->
newItem
()
->
setBuiltinKey
(
$key
)
->
setMenuItemKey
(
PhabricatorDividerProfileMenuItem
::
MENUITEMKEY
)
->
setIsTailItem
(
true
);
}
public
function
getDefaultMenuItemConfiguration
()
{
$configs
=
$this
->
getItems
();
foreach
(
$configs
as
$config
)
{
if
(
$config
->
isDefault
())
{
return
$config
;
}
}
return
null
;
}
public
function
adjustDefault
(
$key
)
{
$controller
=
$this
->
getController
();
$request
=
$controller
->
getRequest
();
$viewer
=
$request
->
getViewer
();
$items
=
$this
->
loadItems
(
self
::
MODE_COMBINED
);
// To adjust the default item, we first change any existing items that
// are marked as defaults to "visible", then make the new default item
// the default.
$default
=
array
();
$visible
=
array
();
foreach
(
$items
as
$item
)
{
$builtin_key
=
$item
->
getBuiltinKey
();
$id
=
$item
->
getID
();
$is_target
=
((
$builtin_key
!==
null
)
&&
(
$builtin_key
===
$key
))
||
((
$id
!==
null
)
&&
((
int
)
$id
===
(
int
)
$key
));
if
(
$is_target
)
{
if
(!
$item
->
isDefault
())
{
$default
[]
=
$item
;
}
}
else
{
if
(
$item
->
isDefault
())
{
$visible
[]
=
$item
;
}
}
}
$type_visibility
=
PhabricatorProfileMenuItemConfigurationTransaction
::
TYPE_VISIBILITY
;
$v_visible
=
PhabricatorProfileMenuItemConfiguration
::
VISIBILITY_VISIBLE
;
$v_default
=
PhabricatorProfileMenuItemConfiguration
::
VISIBILITY_DEFAULT
;
$apply
=
array
(
array
(
$v_visible
,
$visible
),
array
(
$v_default
,
$default
),
);
foreach
(
$apply
as
$group
)
{
list
(
$value
,
$items
)
=
$group
;
foreach
(
$items
as
$item
)
{
$xactions
=
array
();
$xactions
[]
=
id
(
new
PhabricatorProfileMenuItemConfigurationTransaction
())
->
setTransactionType
(
$type_visibility
)
->
setNewValue
(
$value
);
$editor
=
id
(
new
PhabricatorProfileMenuEditor
())
->
setContentSourceFromRequest
(
$request
)
->
setActor
(
$viewer
)
->
setContinueOnMissingFields
(
true
)
->
setContinueOnNoEffect
(
true
)
->
applyTransactions
(
$item
,
$xactions
);
}
}
return
$this
;
}
private
function
arrangeItems
(
array
$items
,
$mode
)
{
// Sort the items.
$items
=
msortv
(
$items
,
'getSortVector'
);
$object
=
$this
->
getProfileObject
();
// If we have some global items and some custom items and are in "combined"
// mode, put a hard-coded divider item between them.
if
(
$mode
==
self
::
MODE_COMBINED
)
{
$list
=
array
();
$seen_custom
=
false
;
$seen_global
=
false
;
foreach
(
$items
as
$item
)
{
if
(
$item
->
getCustomPHID
())
{
$seen_custom
=
true
;
}
else
{
if
(
$seen_custom
&&
!
$seen_global
)
{
$list
[]
=
$this
->
newItem
()
->
setBuiltinKey
(
self
::
ITEM_CUSTOM_DIVIDER
)
->
setMenuItemKey
(
PhabricatorDividerProfileMenuItem
::
MENUITEMKEY
)
->
attachProfileObject
(
$object
)
->
attachMenuItem
(
new
PhabricatorDividerProfileMenuItem
());
}
$seen_global
=
true
;
}
$list
[]
=
$item
;
}
$items
=
$list
;
}
// Normalize keys since callers shouldn't rely on this array being
// partially keyed.
$items
=
array_values
(
$items
);
return
$items
;
}
final
protected
function
newEmptyView
(
$title
,
$message
)
{
return
id
(
new
PHUIInfoView
())
->
setTitle
(
$title
)
->
setSeverity
(
PHUIInfoView
::
SEVERITY_NODATA
)
->
setErrors
(
array
(
$message
,
));
}
protected
function
newNoContentView
(
array
$items
)
{
return
$this
->
newEmptyView
(
pht
(
'No Content'
),
pht
(
'No visible menu items can render content.'
));
}
final
public
function
newProfileMenuItemViewList
()
{
$items
=
$this
->
getItems
();
// Throw away disabled items: they are not allowed to build any views for
// the menu.
foreach
(
$items
as
$key
=>
$item
)
{
if
(
$item
->
isDisabled
())
{
unset
(
$items
[
$key
]);
continue
;
}
}
// Give each item group a callback so it can load data it needs to render
// views.
$groups
=
mgroup
(
$items
,
'getMenuItemKey'
);
foreach
(
$groups
as
$group
)
{
$item
=
head
(
$group
);
$item
->
willGetMenuItemViewList
(
$group
);
}
$view_list
=
id
(
new
PhabricatorProfileMenuItemViewList
())
->
setProfileMenuEngine
(
$this
);
foreach
(
$items
as
$item
)
{
$views
=
$item
->
getMenuItemViewList
();
foreach
(
$views
as
$view
)
{
$view_list
->
addItemView
(
$view
);
}
}
return
$view_list
;
}
private
function
selectViewItem
(
PhabricatorProfileMenuItemViewList
$view_list
,
$item_id
)
{
// Figure out which view's content we're going to render. In most cases,
// the URI tells us. If we don't have an identifier in the URI, we'll
// render the default view instead.
$selected_view
=
null
;
if
(
phutil_nonempty_string
(
$item_id
))
{
$item_views
=
$view_list
->
getViewsWithItemIdentifier
(
$item_id
);
if
(
$item_views
)
{
$selected_view
=
head
(
$item_views
);
}
}
else
{
$default_views
=
$view_list
->
getDefaultViews
();
if
(
$default_views
)
{
$selected_view
=
head
(
$default_views
);
}
}
if
(
$selected_view
)
{
$view_list
->
setSelectedView
(
$selected_view
);
$selected_item
=
$selected_view
->
getMenuItemConfiguration
();
}
else
{
$selected_item
=
null
;
}
return
$selected_item
;
}
private
function
selectEditItem
(
PhabricatorProfileMenuItemViewList
$view_list
,
$item_id
)
{
// First, try to select a visible item using the normal view selection
// pathway. If this works, it also highlights the menu properly.
if
(
$item_id
)
{
$selected_item
=
$this
->
selectViewItem
(
$view_list
,
$item_id
);
if
(
$selected_item
)
{
return
$selected_item
;
}
}
// If we didn't find an item in the view list, we may be enabling an item
// which is currently disabled or editing an item which is not generating
// any actual items in the menu.
foreach
(
$this
->
getItems
()
as
$item
)
{
if
(
$item
->
matchesIdentifier
(
$item_id
))
{
return
$item
;
}
}
return
null
;
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Tue, May 6, 12:18 PM (1 d, 23 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
71945
Default Alt Text
PhabricatorProfileMenuEngine.php (37 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment