mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-25 13:24:12 +01:00
Compare commits
95 Commits
documentat
...
2.7.9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5475b9fbbe | ||
|
|
6f8e7c7002 | ||
|
|
bc7c1b4744 | ||
|
|
4d8246c4d8 | ||
|
|
5c61d725e1 | ||
|
|
2c4cad4dac | ||
|
|
da45651121 | ||
|
|
d388ce9a06 | ||
|
|
47e71d8838 | ||
|
|
2b5973ec67 | ||
|
|
78396d8e4a | ||
|
|
9afc22bd8f | ||
|
|
264a8cd70a | ||
|
|
aa1834170b | ||
|
|
f94d67ab35 | ||
|
|
3048c8c41f | ||
|
|
246e4a9f50 | ||
|
|
6d58adb6dd | ||
|
|
5a0b5364d6 | ||
|
|
76eed2eba0 | ||
|
|
1ec671ef61 | ||
|
|
72716b7ec8 | ||
|
|
4f999de844 | ||
|
|
ea49c0a87c | ||
|
|
6cc971849b | ||
|
|
2405810864 | ||
|
|
fff46d99fc | ||
|
|
3a891f707c | ||
|
|
8b6ea43ebe | ||
|
|
90cf7502e8 | ||
|
|
c596fa2967 | ||
|
|
a45177410e | ||
|
|
9e96ea2873 | ||
|
|
1172159745 | ||
|
|
fa038ded3d | ||
|
|
e7ea1b831c | ||
|
|
4aff65f98b | ||
|
|
3c94974d9d | ||
|
|
fbd72b2783 | ||
|
|
4e95ca3c7b | ||
|
|
1114ed9562 | ||
|
|
34368fe795 | ||
|
|
0f016d7511 | ||
|
|
5ee6223434 | ||
|
|
d908827787 | ||
|
|
4cea418517 | ||
|
|
97965277c7 | ||
|
|
18ed5ed526 | ||
|
|
94c4f8c929 | ||
|
|
822922df5c | ||
|
|
cac7e94a67 | ||
|
|
6d019615d0 | ||
|
|
dbd58cfeb6 | ||
|
|
f65e14397c | ||
|
|
c696a81c3a | ||
|
|
845adf43c6 | ||
|
|
5916e4ea39 | ||
|
|
fbc0a898ae | ||
|
|
36f8e58e25 | ||
|
|
6a7dbb06b0 | ||
|
|
5721a324c1 | ||
|
|
7de6c72154 | ||
|
|
c0cee02351 | ||
|
|
bb674fb873 | ||
|
|
6136eadd31 | ||
|
|
87cb73c038 | ||
|
|
11d8547cef | ||
|
|
0998c73a1a | ||
|
|
471f66649a | ||
|
|
e8bf9cf688 | ||
|
|
4f88a0e7d2 | ||
|
|
c6b0e273e6 | ||
|
|
d9539f9d01 | ||
|
|
a3e309acb5 | ||
|
|
c06cbfd4a9 | ||
|
|
1d7e4e1a42 | ||
|
|
92a36dcfdd | ||
|
|
b37e74b407 | ||
|
|
0d49c605e2 | ||
|
|
7c2f8f4d93 | ||
|
|
1f76ff940d | ||
|
|
bb26e48d38 | ||
|
|
cf433f2f80 | ||
|
|
ae94e58a43 | ||
|
|
cda017fa4f | ||
|
|
dad22f6f83 | ||
|
|
9077f7ba37 | ||
|
|
957ff40f30 | ||
|
|
aff9c7748b | ||
|
|
e518d34bc9 | ||
|
|
f0141530b9 | ||
|
|
ce5096a896 | ||
|
|
23e0ed5e56 | ||
|
|
d412a52fcc | ||
|
|
3e18ad590f |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -8,7 +8,7 @@
|
||||
|
||||
# composer reserver directory, from sources, populate/update using "composer install"
|
||||
vendor/*
|
||||
test/vendor/*
|
||||
tests/*/vendor/*
|
||||
|
||||
# all conf but listing prevention
|
||||
/conf/**
|
||||
@@ -33,7 +33,7 @@ test/vendor/*
|
||||
!/log/web.config
|
||||
|
||||
# PHPUnit cache file
|
||||
/test/.phpunit.result.cache
|
||||
/tests/php-unit-tests/.phpunit.result.cache
|
||||
|
||||
|
||||
# Jetbrains
|
||||
|
||||
@@ -19,17 +19,24 @@
|
||||
* The target license file path is in `$xmlFilePath`
|
||||
*/
|
||||
|
||||
$iTopFolder = __DIR__ . "/../../" ;
|
||||
$xmlFilePath = $iTopFolder . "setup/licenses/community-licenses.xml";
|
||||
$iTopFolder = __DIR__."/../../";
|
||||
$xmlFilePath = $iTopFolder."setup/licenses/community-licenses.xml";
|
||||
|
||||
function get_scope($product_node)
|
||||
{
|
||||
$jqExec = shell_exec("jq -V"); // a param is mandatory otherwise the script will freeze
|
||||
if ((null === $jqExec) || (false === $jqExec)) {
|
||||
echo "/!\ JQ is required but cannot be launched :( \n";
|
||||
echo "Check this script PHPDoc block for instructions\n";
|
||||
die(-1);
|
||||
}
|
||||
|
||||
|
||||
function get_scope($product_node) {
|
||||
$scope = $product_node->getAttribute("scope");
|
||||
|
||||
if ($scope === "")
|
||||
{ //put iTop first
|
||||
if ($scope === "") { //put iTop first
|
||||
return "aaaaaaaaa";
|
||||
}
|
||||
|
||||
return $scope;
|
||||
}
|
||||
|
||||
|
||||
8
Jenkinsfile
vendored
8
Jenkinsfile
vendored
@@ -1,6 +1,14 @@
|
||||
def infra
|
||||
|
||||
node(){
|
||||
properties([
|
||||
buildDiscarder(
|
||||
logRotator(
|
||||
daysToKeepStr: "28",
|
||||
numToKeepStr: "500")
|
||||
)
|
||||
])
|
||||
|
||||
checkout scm
|
||||
|
||||
infra = load '/var/lib/jenkins/workspace/itop-test-infra_master/src/Infra.groovy'
|
||||
|
||||
@@ -29,9 +29,9 @@ require_once(APPROOT.'application/newsroomprovider.class.inc.php');
|
||||
* You may implement such interfaces in a module file (e.g. main.mymodule.php)
|
||||
*
|
||||
* @api
|
||||
* @package LoginExtensibilityAPI
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
* @package Extensibility
|
||||
* @since 2.7.0
|
||||
*/
|
||||
interface iLoginExtension
|
||||
@@ -39,12 +39,16 @@ interface iLoginExtension
|
||||
/**
|
||||
* Return the list of supported login modes for this plugin
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @return array of supported login modes
|
||||
*/
|
||||
public function ListSupportedLoginModes();
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
* @package LoginExtensibilityAPI
|
||||
* @since 2.7.0
|
||||
*/
|
||||
interface iLoginFSMExtension extends iLoginExtension
|
||||
@@ -56,6 +60,7 @@ interface iLoginFSMExtension extends iLoginExtension
|
||||
* if LoginWebPage::LOGIN_FSM_RETURN_OK is returned then the login is OK and terminated
|
||||
* if LoginWebPage::LOGIN_FSM_RETURN_IGNORE is returned then the FSM will proceed to next plugin or state
|
||||
*
|
||||
* @api
|
||||
* @param string $sLoginState (see LoginWebPage::LOGIN_STATE_...)
|
||||
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
|
||||
*
|
||||
@@ -65,6 +70,8 @@ interface iLoginFSMExtension extends iLoginExtension
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
* @package LoginExtensibilityAPI
|
||||
* @since 2.7.0
|
||||
*/
|
||||
abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
|
||||
@@ -111,6 +118,7 @@ abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
|
||||
/**
|
||||
* Initialization
|
||||
*
|
||||
* @api
|
||||
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
|
||||
*
|
||||
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_RETURN_IGNORE
|
||||
@@ -124,6 +132,7 @@ abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
|
||||
* Detect login mode explicitly without respecting configured order (legacy mode)
|
||||
* In most case do nothing here
|
||||
*
|
||||
* @api
|
||||
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
|
||||
*
|
||||
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_RETURN_IGNORE
|
||||
@@ -140,6 +149,7 @@ abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
|
||||
* 1 - display login form
|
||||
* 2 - read the values posted by the user
|
||||
*
|
||||
* @api
|
||||
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
|
||||
*
|
||||
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_RETURN_IGNORE
|
||||
@@ -153,6 +163,7 @@ abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
|
||||
* Control the validity of the data provided by the user
|
||||
* Automatic user provisioning can be done here
|
||||
*
|
||||
* @api
|
||||
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
|
||||
*
|
||||
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_RETURN_IGNORE
|
||||
@@ -163,6 +174,7 @@ abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
|
||||
*
|
||||
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_RETURN_IGNORE
|
||||
@@ -173,6 +185,7 @@ abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
|
||||
*
|
||||
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_RETURN_IGNORE
|
||||
@@ -183,6 +196,7 @@ abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
|
||||
*
|
||||
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_RETURN_IGNORE
|
||||
@@ -193,6 +207,7 @@ abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
|
||||
*
|
||||
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_RETURN_IGNORE
|
||||
@@ -204,22 +219,28 @@ abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
* @package LoginExtensibilityAPI
|
||||
* @since 2.7.0
|
||||
*/
|
||||
interface iLogoutExtension extends iLoginExtension
|
||||
{
|
||||
/**
|
||||
* Execute all actions to log out properly
|
||||
* @api
|
||||
*/
|
||||
public function LogoutAction();
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
* @package UIExtensibilityAPI
|
||||
* @since 2.7.0
|
||||
*/
|
||||
interface iLoginUIExtension extends iLoginExtension
|
||||
{
|
||||
/**
|
||||
* @api
|
||||
* @return LoginTwigContext
|
||||
*/
|
||||
public function GetTwigContext();
|
||||
@@ -227,18 +248,20 @@ interface iLoginUIExtension extends iLoginExtension
|
||||
|
||||
/**
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package PreferencesExtensibilityAPI
|
||||
* @since 2.7.0
|
||||
*/
|
||||
interface iPreferencesExtension
|
||||
{
|
||||
/**
|
||||
* @api
|
||||
* @param \WebPage $oPage
|
||||
*
|
||||
*/
|
||||
public function DisplayPreferences(WebPage $oPage);
|
||||
|
||||
/**
|
||||
* @api
|
||||
* @param \WebPage $oPage
|
||||
* @param string $sOperation
|
||||
*
|
||||
@@ -251,7 +274,7 @@ interface iPreferencesExtension
|
||||
* Extend this class instead of implementing iPreferencesExtension if you don't need to overload all methods
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package PreferencesExtensibilityAPI
|
||||
* @since 2.7.0
|
||||
*/
|
||||
abstract class AbstractPreferencesExtension implements iPreferencesExtension
|
||||
@@ -297,7 +320,7 @@ abstract class AbstractPreferencesExtension implements iPreferencesExtension
|
||||
* A recommended pattern is to cache data by the mean of static members.
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package UIExtensibilityAPI
|
||||
*/
|
||||
interface iApplicationUIExtension
|
||||
{
|
||||
@@ -319,6 +342,7 @@ interface iApplicationUIExtension
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
* @api
|
||||
* @param DBObject $oObject The object being displayed
|
||||
* @param WebPage $oPage The output context
|
||||
* @param boolean $bEditMode True if the edition form is being displayed
|
||||
@@ -332,6 +356,7 @@ interface iApplicationUIExtension
|
||||
*
|
||||
* The method is called rigth after all the tabs have been displayed
|
||||
*
|
||||
* @api
|
||||
* @param DBObject $oObject The object being displayed
|
||||
* @param WebPage $oPage The output context
|
||||
* @param boolean $bEditMode True if the edition form is being displayed
|
||||
@@ -346,6 +371,7 @@ interface iApplicationUIExtension
|
||||
* The method is called after the changes from the standard form have been
|
||||
* taken into account, and before saving the changes into the database.
|
||||
*
|
||||
* @api
|
||||
* @param DBObject $oObject The object being edited
|
||||
* @param string $sFormPrefix Prefix given to the HTML form inputs
|
||||
*
|
||||
@@ -360,6 +386,7 @@ interface iApplicationUIExtension
|
||||
* javascript into the edition form, and if that code requires to store temporary data
|
||||
* (this is the case when a file must be uploaded).
|
||||
*
|
||||
* @api
|
||||
* @param string $sTempId Unique temporary identifier made of session_id and transaction_id. It identifies the object in a unique way.
|
||||
*
|
||||
* @return void
|
||||
@@ -371,6 +398,7 @@ interface iApplicationUIExtension
|
||||
*
|
||||
* Sorry, the verb has been reserved. You must implement it, but it is not called as of now.
|
||||
*
|
||||
* @api
|
||||
* @param DBObject $oObject The object being displayed
|
||||
*
|
||||
* @return string[] desc
|
||||
@@ -382,6 +410,7 @@ interface iApplicationUIExtension
|
||||
*
|
||||
* Sorry, the verb has been reserved. You must implement it, but it is not called as of now.
|
||||
*
|
||||
* @api
|
||||
* @param DBObject $oObject The object being displayed
|
||||
*
|
||||
* @return string Path of the icon, relative to the modules directory.
|
||||
@@ -401,6 +430,7 @@ interface iApplicationUIExtension
|
||||
* * HILIGHT_CLASS_OK
|
||||
* * HILIGHT_CLASS_NONE
|
||||
*
|
||||
* @api
|
||||
* @param DBObject $oObject The object being displayed
|
||||
*
|
||||
* @return integer The value representing the mood of the object
|
||||
@@ -427,6 +457,7 @@ interface iApplicationUIExtension
|
||||
*
|
||||
* See also iPopupMenuExtension for greater flexibility
|
||||
*
|
||||
* @api
|
||||
* @param DBObjectSet $oSet A set of persistent objects (DBObject)
|
||||
*
|
||||
* @return string[string]
|
||||
@@ -438,7 +469,7 @@ interface iApplicationUIExtension
|
||||
* Extend this class instead of implementing iApplicationUIExtension if you don't need to overload
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package UIExtensibilityAPI
|
||||
* @since 2.7.0
|
||||
*/
|
||||
abstract class AbstractApplicationUIExtension implements iApplicationUIExtension
|
||||
@@ -512,7 +543,7 @@ abstract class AbstractApplicationUIExtension implements iApplicationUIExtension
|
||||
* or through the GUI.
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package ORMExtensibilityAPI
|
||||
*/
|
||||
interface iApplicationObjectExtension
|
||||
{
|
||||
@@ -525,6 +556,7 @@ interface iApplicationObjectExtension
|
||||
* If the extension returns false, then the framework will perform the usual evaluation.
|
||||
* Otherwise, the answer is definitively "yes, the object has changed".
|
||||
*
|
||||
* @api
|
||||
* @param \cmdbAbstractObject $oObject The target object
|
||||
*
|
||||
* @return boolean True if something has changed for the target object
|
||||
@@ -537,6 +569,7 @@ interface iApplicationObjectExtension
|
||||
* The GUI calls this verb and reports any issue.
|
||||
* Anyhow, this API can be called in other contexts such as the CSV import tool.
|
||||
*
|
||||
* @api
|
||||
* @param \cmdbAbstractObject $oObject The target object
|
||||
*
|
||||
* @return string[] A list of errors message. An error message is made of one line and it can be displayed to the end-user.
|
||||
@@ -550,6 +583,7 @@ interface iApplicationObjectExtension
|
||||
*
|
||||
* Please not that it is not possible to cascade deletion by this mean: only stopper issues can be handled.
|
||||
*
|
||||
* @api
|
||||
* @param \cmdbAbstractObject $oObject The target object
|
||||
*
|
||||
* @return string[] A list of errors message. An error message is made of one line and it can be displayed to the end-user.
|
||||
@@ -565,6 +599,7 @@ interface iApplicationObjectExtension
|
||||
* * {@see DBObject::ListPreviousValuesForUpdatedAttributes()} : list of changed attributes and their values before the change
|
||||
* * {@see DBObject::Get()} : for a given attribute the new value that was persisted
|
||||
*
|
||||
* @api
|
||||
* @param \cmdbAbstractObject $oObject The target object
|
||||
* @param CMDBChange|null $oChange A change context. Since 2.0 it is fine to ignore it, as the framework does maintain this information
|
||||
* once for all the changes made within the current page
|
||||
@@ -580,6 +615,7 @@ interface iApplicationObjectExtension
|
||||
*
|
||||
* The method is called right <b>after</b> the object has been written to the database.
|
||||
*
|
||||
* @api
|
||||
* @param \cmdbAbstractObject $oObject The target object
|
||||
* @param CMDBChange|null $oChange A change context. Since 2.0 it is fine to ignore it, as the framework does maintain this information
|
||||
* once for all the changes made within the current page
|
||||
@@ -593,6 +629,7 @@ interface iApplicationObjectExtension
|
||||
*
|
||||
* The method is called right <b>before</b> the object will be deleted from the database.
|
||||
*
|
||||
* @api
|
||||
* @param \cmdbAbstractObject $oObject The target object
|
||||
* @param CMDBChange|null $oChange A change context. Since 2.0 it is fine to ignore it, as the framework does maintain this information
|
||||
* once for all the changes made within the current page
|
||||
@@ -606,7 +643,7 @@ interface iApplicationObjectExtension
|
||||
* Extend this class instead of iApplicationObjectExtension if you don't need to overload all methods
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package ORMExtensibilityAPI
|
||||
* @since 2.7.0
|
||||
*/
|
||||
abstract class AbstractApplicationObjectExtension implements iApplicationObjectExtension
|
||||
@@ -666,7 +703,7 @@ abstract class AbstractApplicationObjectExtension implements iApplicationObjectE
|
||||
* by the application, as long as the class definition is included somewhere in the code
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package UIExtensibilityAPI
|
||||
* @since 2.0
|
||||
*/
|
||||
interface iPopupMenuExtension
|
||||
@@ -675,18 +712,21 @@ interface iPopupMenuExtension
|
||||
* Insert an item into the Actions menu of a list
|
||||
*
|
||||
* $param is a DBObjectSet containing the list of objects
|
||||
* @api
|
||||
*/
|
||||
const MENU_OBJLIST_ACTIONS = 1;
|
||||
/**
|
||||
* Insert an item into the Toolkit menu of a list
|
||||
*
|
||||
* $param is a DBObjectSet containing the list of objects
|
||||
* @api
|
||||
*/
|
||||
const MENU_OBJLIST_TOOLKIT = 2;
|
||||
/**
|
||||
* Insert an item into the Actions menu on an object details page
|
||||
*
|
||||
* $param is a DBObject instance: the object currently displayed
|
||||
* @api
|
||||
*/
|
||||
const MENU_OBJDETAILS_ACTIONS = 3;
|
||||
/**
|
||||
@@ -696,12 +736,14 @@ interface iPopupMenuExtension
|
||||
* is being displayed.
|
||||
*
|
||||
* $param is a Dashboard instance: the dashboard currently displayed
|
||||
* @api
|
||||
*/
|
||||
const MENU_DASHBOARD_ACTIONS = 4;
|
||||
/**
|
||||
* Insert an item into the User menu (upper right corner)
|
||||
*
|
||||
* $param is null
|
||||
* @api
|
||||
*/
|
||||
const MENU_USER_ACTIONS = 5;
|
||||
/**
|
||||
@@ -709,6 +751,7 @@ interface iPopupMenuExtension
|
||||
*
|
||||
* $param is an array('portal_id' => $sPortalId, 'object' => $oObject) containing the portal id and a DBObject instance (the object on
|
||||
* the current line)
|
||||
* @api
|
||||
*/
|
||||
const PORTAL_OBJLISTITEM_ACTIONS = 7;
|
||||
/**
|
||||
@@ -716,6 +759,7 @@ interface iPopupMenuExtension
|
||||
*
|
||||
* $param is an array('portal_id' => $sPortalId, 'object' => $oObject) containing the portal id and a DBObject instance (the object
|
||||
* currently displayed)
|
||||
* @api
|
||||
*/
|
||||
const PORTAL_OBJDETAILS_ACTIONS = 8;
|
||||
|
||||
@@ -753,6 +797,7 @@ interface iPopupMenuExtension
|
||||
* This method is called by the framework for each menu.
|
||||
* The items will be inserted in the menu in the order of the returned array.
|
||||
*
|
||||
* @api
|
||||
* @param int $iMenuId The identifier of the type of menu, as listed by the constants MENU_xxx
|
||||
* @param mixed $param Depends on $iMenuId, see the constants defined above
|
||||
*
|
||||
@@ -765,7 +810,7 @@ interface iPopupMenuExtension
|
||||
* Base class for the various types of custom menus
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package UIExtensibilityAPI
|
||||
* @since 2.0
|
||||
*/
|
||||
abstract class ApplicationPopupMenuItem
|
||||
@@ -826,6 +871,7 @@ abstract class ApplicationPopupMenuItem
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
* @param $aCssClasses
|
||||
*/
|
||||
public function SetCssClasses($aCssClasses)
|
||||
@@ -836,6 +882,7 @@ abstract class ApplicationPopupMenuItem
|
||||
/**
|
||||
* Adds a CSS class to the CSS classes that will be put on the menu item
|
||||
*
|
||||
* @api
|
||||
* @param $sCssClass
|
||||
*/
|
||||
public function AddCssClass($sCssClass)
|
||||
@@ -862,7 +909,7 @@ abstract class ApplicationPopupMenuItem
|
||||
* Class for adding an item into a popup menu that browses to the given URL
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package UIExtensibilityAPI
|
||||
* @since 2.0
|
||||
*/
|
||||
class URLPopupMenuItem extends ApplicationPopupMenuItem
|
||||
@@ -875,6 +922,7 @@ class URLPopupMenuItem extends ApplicationPopupMenuItem
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @api
|
||||
* @param string $sUID The unique identifier of this menu in iTop... make sure you pass something unique enough
|
||||
* @param string $sLabel The display label of the menu (must be localized)
|
||||
* @param string $sURL If the menu is an hyperlink, provide the absolute hyperlink here
|
||||
@@ -898,7 +946,7 @@ class URLPopupMenuItem extends ApplicationPopupMenuItem
|
||||
* Class for adding an item into a popup menu that triggers some Javascript code
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package UIExtensibilityAPI
|
||||
* @since 2.0
|
||||
*/
|
||||
class JSPopupMenuItem extends ApplicationPopupMenuItem
|
||||
@@ -950,7 +998,7 @@ class JSPopupMenuItem extends ApplicationPopupMenuItem
|
||||
* will automatically reduce several consecutive separators to just one
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package UIExtensibilityAPI
|
||||
* @since 2.0
|
||||
*/
|
||||
class SeparatorPopupMenuItem extends ApplicationPopupMenuItem
|
||||
@@ -959,6 +1007,7 @@ class SeparatorPopupMenuItem extends ApplicationPopupMenuItem
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @api
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
@@ -976,7 +1025,7 @@ class SeparatorPopupMenuItem extends ApplicationPopupMenuItem
|
||||
* Class for adding an item as a button that browses to the given URL
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package UIExtensibilityAPI
|
||||
* @since 2.0
|
||||
*/
|
||||
class URLButtonItem extends URLPopupMenuItem
|
||||
@@ -988,7 +1037,7 @@ class URLButtonItem extends URLPopupMenuItem
|
||||
* Class for adding an item as a button that runs some JS code
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package UIExtensibilityAPI
|
||||
* @since 2.0
|
||||
*/
|
||||
class JSButtonItem extends JSPopupMenuItem
|
||||
@@ -1012,7 +1061,7 @@ class JSButtonItem extends JSPopupMenuItem
|
||||
* the specified place and can use the passed iTopWebPage object to add javascript or CSS definitions
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package UIExtensibilityAPI
|
||||
* @since 2.0
|
||||
*/
|
||||
interface iPageUIExtension
|
||||
@@ -1020,6 +1069,7 @@ interface iPageUIExtension
|
||||
/**
|
||||
* Add content to the North pane
|
||||
*
|
||||
* @api
|
||||
* @param iTopWebPage $oPage The page to insert stuff into.
|
||||
*
|
||||
* @return string The HTML content to add into the page
|
||||
@@ -1029,6 +1079,7 @@ interface iPageUIExtension
|
||||
/**
|
||||
* Add content to the South pane
|
||||
*
|
||||
* @api
|
||||
* @param iTopWebPage $oPage The page to insert stuff into.
|
||||
*
|
||||
* @return string The HTML content to add into the page
|
||||
@@ -1038,6 +1089,7 @@ interface iPageUIExtension
|
||||
/**
|
||||
* Add content to the "admin banner"
|
||||
*
|
||||
* @api
|
||||
* @param iTopWebPage $oPage The page to insert stuff into.
|
||||
*
|
||||
* @return string The HTML content to add into the page
|
||||
@@ -1049,7 +1101,7 @@ interface iPageUIExtension
|
||||
* Extend this class instead of iPageUIExtension if you don't need to overload all methods
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package UIExtensibilityAPI
|
||||
* @since 2.7.0
|
||||
*/
|
||||
abstract class AbstractPageUIExtension implements iPageUIExtension
|
||||
@@ -1084,7 +1136,7 @@ abstract class AbstractPageUIExtension implements iPageUIExtension
|
||||
* Implement this interface to add content to any enhanced portal page
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package PortalExtensibilityAPI
|
||||
*
|
||||
* @since 2.4.0 interface creation
|
||||
* @since 2.7.0 change method signatures due to Silex to Symfony migration
|
||||
@@ -1098,6 +1150,7 @@ interface iPortalUIExtension
|
||||
/**
|
||||
* Returns an array of CSS file urls
|
||||
*
|
||||
* @api
|
||||
* @param \Symfony\Component\DependencyInjection\Container $oContainer
|
||||
*
|
||||
* @return array
|
||||
@@ -1107,6 +1160,7 @@ interface iPortalUIExtension
|
||||
/**
|
||||
* Returns inline (raw) CSS
|
||||
*
|
||||
* @api
|
||||
* @param \Symfony\Component\DependencyInjection\Container $oContainer
|
||||
*
|
||||
* @return string
|
||||
@@ -1116,6 +1170,7 @@ interface iPortalUIExtension
|
||||
/**
|
||||
* Returns an array of JS file urls
|
||||
*
|
||||
* @api
|
||||
* @param \Symfony\Component\DependencyInjection\Container $oContainer
|
||||
*
|
||||
* @return array
|
||||
@@ -1125,6 +1180,7 @@ interface iPortalUIExtension
|
||||
/**
|
||||
* Returns raw JS code
|
||||
*
|
||||
* @api
|
||||
* @param \Symfony\Component\DependencyInjection\Container $oContainer
|
||||
*
|
||||
* @return string
|
||||
@@ -1134,6 +1190,7 @@ interface iPortalUIExtension
|
||||
/**
|
||||
* Returns raw HTML code to put at the end of the <body> tag
|
||||
*
|
||||
* @api
|
||||
* @param \Symfony\Component\DependencyInjection\Container $oContainer
|
||||
*
|
||||
* @return string
|
||||
@@ -1143,6 +1200,7 @@ interface iPortalUIExtension
|
||||
/**
|
||||
* Returns raw HTML code to put at the end of the #main-wrapper element
|
||||
*
|
||||
* @api
|
||||
* @param \Symfony\Component\DependencyInjection\Container $oContainer
|
||||
*
|
||||
* @return string
|
||||
@@ -1152,6 +1210,7 @@ interface iPortalUIExtension
|
||||
/**
|
||||
* Returns raw HTML code to put at the end of the #topbar and #sidebar elements
|
||||
*
|
||||
* @api
|
||||
* @param \Symfony\Component\DependencyInjection\Container $oContainer
|
||||
*
|
||||
* @return string
|
||||
@@ -1163,7 +1222,7 @@ interface iPortalUIExtension
|
||||
* Extend this class instead of iPortalUIExtension if you don't need to overload all methods
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package PortalExtensibilityAPI
|
||||
* @since 2.4.0
|
||||
*/
|
||||
abstract class AbstractPortalUIExtension implements iPortalUIExtension
|
||||
@@ -1229,7 +1288,7 @@ abstract class AbstractPortalUIExtension implements iPortalUIExtension
|
||||
* Implement this interface to add new operations to the REST/JSON web service
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package RESTExtensibilityAPI
|
||||
* @since 2.0.1
|
||||
*/
|
||||
interface iRestServiceProvider
|
||||
@@ -1237,6 +1296,7 @@ interface iRestServiceProvider
|
||||
/**
|
||||
* Enumerate services delivered by this class
|
||||
*
|
||||
* @api
|
||||
* @param string $sVersion The version (e.g. 1.0) supported by the services
|
||||
*
|
||||
* @return array An array of hash 'verb' => verb, 'description' => description
|
||||
@@ -1246,6 +1306,7 @@ interface iRestServiceProvider
|
||||
/**
|
||||
* Enumerate services delivered by this class
|
||||
*
|
||||
* @api
|
||||
* @param string $sVersion The version (e.g. 1.0) supported by the services
|
||||
* @param string $sVerb
|
||||
* @param array $aParams
|
||||
@@ -1259,69 +1320,90 @@ interface iRestServiceProvider
|
||||
* Minimal REST response structure. Derive this structure to add response data and error codes.
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package RESTExtensibilityAPI
|
||||
* @since 2.0.1
|
||||
*/
|
||||
class RestResult
|
||||
{
|
||||
/**
|
||||
* Result: no issue has been encountered
|
||||
* @api
|
||||
*/
|
||||
const OK = 0;
|
||||
/**
|
||||
* Result: missing/wrong credentials or the user does not have enough rights to perform the requested operation
|
||||
* @api
|
||||
*/
|
||||
const UNAUTHORIZED = 1;
|
||||
/**
|
||||
* Result: the parameter 'version' is missing
|
||||
* @api
|
||||
*/
|
||||
const MISSING_VERSION = 2;
|
||||
/**
|
||||
* Result: the parameter 'json_data' is missing
|
||||
* @api
|
||||
*/
|
||||
const MISSING_JSON = 3;
|
||||
/**
|
||||
* Result: the input structure is not a valid JSON string
|
||||
* @api
|
||||
*/
|
||||
const INVALID_JSON = 4;
|
||||
/**
|
||||
* Result: the parameter 'auth_user' is missing, authentication aborted
|
||||
* @api
|
||||
*/
|
||||
const MISSING_AUTH_USER = 5;
|
||||
/**
|
||||
* Result: the parameter 'auth_pwd' is missing, authentication aborted
|
||||
* @api
|
||||
*/
|
||||
const MISSING_AUTH_PWD = 6;
|
||||
/**
|
||||
* Result: no operation is available for the specified version
|
||||
* @api
|
||||
*/
|
||||
const UNSUPPORTED_VERSION = 10;
|
||||
/**
|
||||
* Result: the requested operation is not valid for the specified version
|
||||
* @api
|
||||
*/
|
||||
const UNKNOWN_OPERATION = 11;
|
||||
/**
|
||||
* Result: the requested operation cannot be performed because it can cause data (integrity) loss
|
||||
* @api
|
||||
*/
|
||||
const UNSAFE = 12;
|
||||
/**
|
||||
* Result: the request page number is not valid. It must be an integer greater than 0
|
||||
* @api
|
||||
*/
|
||||
const INVALID_PAGE = 13;
|
||||
/**
|
||||
* Result: the operation could not be performed, see the message for troubleshooting
|
||||
* @api
|
||||
*/
|
||||
const INTERNAL_ERROR = 100;
|
||||
|
||||
/**
|
||||
* Default constructor - ok!
|
||||
* @api
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->code = RestResult::OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var int
|
||||
* @api
|
||||
*/
|
||||
public $code;
|
||||
/**
|
||||
* @var string
|
||||
* @api
|
||||
*/
|
||||
public $message;
|
||||
}
|
||||
|
||||
@@ -1329,7 +1411,7 @@ class RestResult
|
||||
* Helpers for implementing REST services
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package RESTExtensibilityAPI
|
||||
*/
|
||||
class RestUtils
|
||||
{
|
||||
@@ -1478,6 +1560,7 @@ class RestUtils
|
||||
/**
|
||||
* Read and interpret object search criteria from a Rest/Json structure
|
||||
*
|
||||
* @api
|
||||
* @param string $sClass Name of the class
|
||||
* @param StdClass $oCriteria Hash of attribute code => value (can be a substructure or a scalar, depending on the nature of the
|
||||
* attriute)
|
||||
@@ -1587,6 +1670,7 @@ class RestUtils
|
||||
/**
|
||||
* Search objects from a polymorph search specification (Rest/Json)
|
||||
*
|
||||
* @api
|
||||
* @param string $sClass Name of the class
|
||||
* @param mixed $key Either search criteria (substructure), or an object or an OQL string.
|
||||
* @param int $iLimit The limit of results to return
|
||||
@@ -1773,4 +1857,28 @@ class RestUtils
|
||||
interface iModuleExtension
|
||||
{
|
||||
public function __construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* KPI logging extensibility point
|
||||
*
|
||||
* KPI Logger extension
|
||||
*/
|
||||
interface iKPILoggerExtension
|
||||
{
|
||||
/**
|
||||
* Init the statistics collected
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function InitStats();
|
||||
|
||||
/**
|
||||
* Add a new KPI to the stats
|
||||
*
|
||||
* @param \Combodo\iTop\Core\Kpi\KpiLogData $oKPILogData
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function LogOperation($oKPILogData);
|
||||
}
|
||||
@@ -4003,7 +4003,9 @@ EOF
|
||||
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
||||
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||
{
|
||||
$oExtensionInstance->OnDBInsert($this, self::GetCurrentChange());
|
||||
$oKPI = new ExecutionKPI();
|
||||
$oExtensionInstance->OnDBInsert($this, self::GetCurrentChange());
|
||||
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBInsert');
|
||||
}
|
||||
|
||||
return $res;
|
||||
@@ -4020,13 +4022,16 @@ EOF
|
||||
|
||||
protected function DBCloneTracked_Internal($newKey = null)
|
||||
{
|
||||
$oNewObj = parent::DBCloneTracked_Internal($newKey);
|
||||
/** @var cmdbAbstractObject $oNewObj */
|
||||
$oNewObj = MetaModel::GetObject(get_class($this), parent::DBCloneTracked_Internal($newKey));
|
||||
|
||||
// Invoke extensions after insertion (the object must exist, have an id, etc.)
|
||||
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
||||
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
$oExtensionInstance->OnDBInsert($oNewObj, self::GetCurrentChange());
|
||||
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBInsert');
|
||||
}
|
||||
|
||||
return $oNewObj;
|
||||
@@ -4054,7 +4059,9 @@ EOF
|
||||
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
||||
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
$oExtensionInstance->OnDBUpdate($this, self::GetCurrentChange());
|
||||
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBUpdate');
|
||||
}
|
||||
}
|
||||
catch (Exception $e)
|
||||
@@ -4100,7 +4107,9 @@ EOF
|
||||
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
||||
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
$oExtensionInstance->OnDBDelete($this, self::GetCurrentChange());
|
||||
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBDelete');
|
||||
}
|
||||
|
||||
return parent::DBDeleteTracked_Internal($oDeletionPlan);
|
||||
@@ -4118,7 +4127,10 @@ EOF
|
||||
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
||||
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||
{
|
||||
if ($oExtensionInstance->OnIsModified($this))
|
||||
$oKPI = new ExecutionKPI();
|
||||
$bIsModified = $oExtensionInstance->OnIsModified($this);
|
||||
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnIsModified');
|
||||
if ($bIsModified)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -4162,7 +4174,9 @@ EOF
|
||||
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
||||
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
$aNewIssues = $oExtensionInstance->OnCheckToWrite($this);
|
||||
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnCheckToWrite');
|
||||
if (is_array($aNewIssues) && (count($aNewIssues) > 0)) // Some extensions return null instead of an empty array
|
||||
{
|
||||
$this->m_aCheckIssues = array_merge($this->m_aCheckIssues, $aNewIssues);
|
||||
@@ -4210,7 +4224,9 @@ EOF
|
||||
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
||||
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
$aNewIssues = $oExtensionInstance->OnCheckToDelete($this);
|
||||
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnCheckToDelete');
|
||||
if (is_array($aNewIssues) && count($aNewIssues) > 0)
|
||||
{
|
||||
$this->m_aDeleteIssues = array_merge($this->m_aDeleteIssues, $aNewIssues);
|
||||
@@ -4722,7 +4738,7 @@ EOF
|
||||
$bResult = (count($aErrors) == 0);
|
||||
if ($bResult)
|
||||
{
|
||||
list($bResult, $aErrors) = $oObj->CheckToWrite();
|
||||
[$bResult, $aErrors] = $oObj->CheckToWrite();
|
||||
}
|
||||
if ($bPreview)
|
||||
{
|
||||
|
||||
@@ -1478,6 +1478,29 @@ JS
|
||||
return $this->sDefinitionFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sDashboardFileRelative can also be an absolute path (compatibility with old URL)
|
||||
*
|
||||
* @return string full path to the Dashboard file
|
||||
* @throws \SecurityException if path isn't under approot
|
||||
* @uses utils::RealPath()
|
||||
* @since 2.7.8 3.0.3 3.1.0 N°4449 remove FPD
|
||||
*/
|
||||
public static function GetDashboardFileFromRelativePath($sDashboardFileRelative)
|
||||
{
|
||||
if (utils::RealPath($sDashboardFileRelative, APPROOT)) {
|
||||
// compatibility with old URL containing absolute path !
|
||||
return $sDashboardFileRelative;
|
||||
}
|
||||
|
||||
$sDashboardFile = APPROOT.$sDashboardFileRelative;
|
||||
if (false === utils::RealPath($sDashboardFile, APPROOT)) {
|
||||
throw new SecurityException('Invalid dashboard file !');
|
||||
}
|
||||
|
||||
return $sDashboardFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sDefinitionFile
|
||||
*/
|
||||
|
||||
@@ -372,7 +372,7 @@ EOF;
|
||||
if (!$oPage->IsPrintableVersion())
|
||||
{
|
||||
$sMenuTitle = Dict::S('UI:ConfigureThisList');
|
||||
$sHtml = '<div class="itop_popup toolkit_menu" id="tk_'.$this->iListId.'"><ul><li><i class="fas fa-tools"></i><i class="fas fa-caret-down"></i><ul>';
|
||||
$sHtml = '<div class="itop_popup toolkit_menu" id="tk_'.$this->iListId.'"><ul><li aria-label="'.Dict::S('UI:Menu:Toolkit').'"><i class="fas fa-tools"></i><i class="fas fa-caret-down"></i><ul>';
|
||||
|
||||
$oMenuItem1 = new JSPopupMenuItem('iTop::ConfigureList', $sMenuTitle, "$('#datatable_dlg_".$this->iListId."').dialog('open');");
|
||||
$aActions = array(
|
||||
|
||||
@@ -1009,6 +1009,7 @@ EOF
|
||||
$iTotalCount = 0;
|
||||
$aValues = array();
|
||||
$aURLs = array();
|
||||
|
||||
foreach ($aRes as $iRow => $aRow)
|
||||
{
|
||||
$sValue = $aRow['grouped_by_1'];
|
||||
@@ -1016,7 +1017,8 @@ EOF
|
||||
$aGroupBy[(int)$iRow] = (int) $aRow[$sFctVar];
|
||||
$iTotalCount += $aRow['_itop_count_'];
|
||||
$aValues[] = array('label' => html_entity_decode(strip_tags($sHtmlValue), ENT_QUOTES, 'UTF-8'), 'label_html' => $sHtmlValue, 'value' => (int) $aRow[$sFctVar]);
|
||||
|
||||
|
||||
|
||||
// Build the search for this subset
|
||||
$oSubsetSearch = $this->m_oFilter->DeepClone();
|
||||
$oCondition = new BinaryExpression($oGroupByExp, '=', new ScalarExpression($sValue));
|
||||
@@ -1030,16 +1032,23 @@ EOF
|
||||
{
|
||||
case 'bars':
|
||||
$aNames = array();
|
||||
$iMaxNbCharsInLabel = 0;
|
||||
foreach($aValues as $idx => $aValue)
|
||||
{
|
||||
$aNames[$idx] = $aValue['label'];
|
||||
if ($iMaxNbCharsInLabel < mb_strlen($aValue['label'])) {
|
||||
$iMaxNbCharsInLabel = mb_strlen($aValue['label']);
|
||||
}
|
||||
}
|
||||
$sJSNames = json_encode($aNames);
|
||||
|
||||
$sJson = json_encode($aValues);
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
|
||||
var iChartDefaultHeight = 200,
|
||||
iChartLegendHeight = 6 * $iMaxNbCharsInLabel,
|
||||
iChartTotalHeight = iChartDefaultHeight + iChartLegendHeight;
|
||||
$('#my_chart_$sId').height(iChartTotalHeight+ 'px');
|
||||
var chart = c3.generate({
|
||||
bindto: d3.select('#my_chart_$sId'),
|
||||
data: {
|
||||
@@ -1107,8 +1116,19 @@ EOF
|
||||
}
|
||||
$sJSColumns = json_encode($aColumns);
|
||||
$sJSNames = json_encode($aNames);
|
||||
$iNbLinesToAddForName = 0;
|
||||
if (count($aNames) > 50) {
|
||||
// Calculation of the number of legends line add to the height of the graph to have a maximum of 5 legend columns
|
||||
$iNbLinesIncludedInChartHeight = 10;
|
||||
$iNbLinesToAddForName = ceil(count($aNames) / 5) - $iNbLinesIncludedInChartHeight;
|
||||
}
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
// Calculate height of graph : 200px (minimum height for the chart) + 20*iNbLinesToAddForName for the legend
|
||||
var iChartDefaultHeight = 200,
|
||||
iChartLegendHeight = 20 * $iNbLinesToAddForName,
|
||||
iChartTotalHeight = (iChartDefaultHeight + iChartLegendHeight);
|
||||
$('#my_chart_$sId').height(iChartTotalHeight + 'px');
|
||||
var chart = c3.generate({
|
||||
bindto: d3.select('#my_chart_$sId'),
|
||||
data: {
|
||||
@@ -1915,11 +1935,13 @@ class MenuBlock extends DisplayBlock
|
||||
{
|
||||
if (count($aFavoriteActions) > 0)
|
||||
{
|
||||
$sHtml .= "<div class=\"itop_popup actions_menu\"><ul>\n<li>".Dict::S('UI:Menu:OtherActions')."<i class=\"fas fa-caret-down\"></i>"."\n<ul>\n";
|
||||
$sActionsMenuLabel = Dict::S('UI:Menu:OtherActions');
|
||||
$sHtml .= "<div class=\"itop_popup actions_menu\"><ul>\n<li aria-label=\"{$sActionsMenuLabel}\">{$sActionsMenuLabel}<i class=\"fas fa-caret-down\"></i>"."\n<ul>\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
$sHtml .= "<div class=\"itop_popup actions_menu\"><ul>\n<li>".Dict::S('UI:Menu:Actions')."<i class=\"fas fa-caret-down\"></i>"."\n<ul>\n";
|
||||
$sActionsMenuLabel = Dict::S('UI:Menu:Actions');
|
||||
$sHtml .= "<div class=\"itop_popup actions_menu\"><ul>\n<li aria-label=\"{$sActionsMenuLabel}\">{$sActionsMenuLabel}<i class=\"fas fa-caret-down\"></i>"."\n<ul>\n";
|
||||
}
|
||||
|
||||
$sHtml .= $oPage->RenderPopupMenuItems($aActions, $aFavoriteActions);
|
||||
|
||||
@@ -1219,7 +1219,7 @@ EOF;
|
||||
{
|
||||
$sLogonMessage = Dict::Format('UI:LoggedAsMessage', $sUserName);
|
||||
}
|
||||
$sLogOffMenu = "<span id=\"logOffBtn\"><ul><li><i class=\"top-right-icon icon-additional-arrow fas fa-power-off\"></i><ul>";
|
||||
$sLogOffMenu = "<span id=\"logOffBtn\"><ul><li aria-label=\"" . Dict::S("UI:PowerMenu") . "\"><i class=\"top-right-icon icon-additional-arrow fas fa-power-off\"></i><ul>";
|
||||
$sLogOffMenu .= "<li><span>$sLogonMessage</span></li>\n";
|
||||
$aActions = array();
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ class LoginBasic extends AbstractLoginFSMExtension
|
||||
|
||||
protected function OnCheckCredentials(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'basic')
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'basic')
|
||||
{
|
||||
list($sAuthUser, $sAuthPwd) = $this->GetAuthUserAndPassword();
|
||||
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, $_SESSION['login_mode'], 'internal'))
|
||||
@@ -67,7 +67,7 @@ class LoginBasic extends AbstractLoginFSMExtension
|
||||
|
||||
protected function OnCredentialsOK(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'basic')
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'basic')
|
||||
{
|
||||
$sAuthUser = $_SESSION['auth_user'];
|
||||
LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', $_SESSION['login_mode']);
|
||||
@@ -77,8 +77,13 @@ class LoginBasic extends AbstractLoginFSMExtension
|
||||
|
||||
protected function OnError(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'basic')
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'basic')
|
||||
{
|
||||
$iOnExit = LoginWebPage::getIOnExit();
|
||||
if ($iOnExit === LoginWebPage::EXIT_RETURN)
|
||||
{
|
||||
return LoginWebPage::LOGIN_FSM_RETURN; // Error, exit FSM
|
||||
}
|
||||
LoginWebPage::HTTP401Error();
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
@@ -86,7 +91,7 @@ class LoginBasic extends AbstractLoginFSMExtension
|
||||
|
||||
protected function OnConnected(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'basic')
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'basic')
|
||||
{
|
||||
$_SESSION['can_logoff'] = true;
|
||||
return LoginWebPage::CheckLoggedUser($iErrorCode);
|
||||
|
||||
@@ -77,7 +77,7 @@ class LoginDefaultAfter extends AbstractLoginFSMExtension implements iLogoutExte
|
||||
{
|
||||
self::ResetLoginSession();
|
||||
$iOnExit = LoginWebPage::getIOnExit();
|
||||
if ($iOnExit == LoginWebPage::EXIT_RETURN)
|
||||
if ($iOnExit === LoginWebPage::EXIT_RETURN)
|
||||
{
|
||||
return LoginWebPage::LOGIN_FSM_RETURN; // Error, exit FSM
|
||||
}
|
||||
@@ -93,6 +93,12 @@ class LoginDefaultAfter extends AbstractLoginFSMExtension implements iLogoutExte
|
||||
{
|
||||
if (!isset($_SESSION['login_mode']))
|
||||
{
|
||||
// N°6358 - if EXIT_RETURN was asked, send an error
|
||||
if (LoginWebPage::getIOnExit() === LoginWebPage::EXIT_RETURN) {
|
||||
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
|
||||
return LoginWebPage::LOGIN_FSM_ERROR;
|
||||
}
|
||||
|
||||
// If no plugin validated the user, exit
|
||||
self::ResetLoginSession();
|
||||
exit();
|
||||
|
||||
@@ -35,7 +35,7 @@ class LoginExternal extends AbstractLoginFSMExtension
|
||||
|
||||
protected function OnCheckCredentials(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'external')
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'external')
|
||||
{
|
||||
$sAuthUser = $this->GetAuthUser();
|
||||
if (!UserRights::CheckCredentials($sAuthUser, '', $_SESSION['login_mode'], 'external'))
|
||||
@@ -51,7 +51,7 @@ class LoginExternal extends AbstractLoginFSMExtension
|
||||
|
||||
protected function OnCredentialsOK(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'external')
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'external')
|
||||
{
|
||||
$sAuthUser = $_SESSION['auth_user'];
|
||||
LoginWebPage::OnLoginSuccess($sAuthUser, 'external', $_SESSION['login_mode']);
|
||||
@@ -61,7 +61,7 @@ class LoginExternal extends AbstractLoginFSMExtension
|
||||
|
||||
protected function OnConnected(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'external')
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'external')
|
||||
{
|
||||
$_SESSION['can_logoff'] = false;
|
||||
return LoginWebPage::CheckLoggedUser($iErrorCode);
|
||||
@@ -71,8 +71,13 @@ class LoginExternal extends AbstractLoginFSMExtension
|
||||
|
||||
protected function OnError(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'external')
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'external')
|
||||
{
|
||||
$iOnExit = LoginWebPage::getIOnExit();
|
||||
if ($iOnExit === LoginWebPage::EXIT_RETURN)
|
||||
{
|
||||
return LoginWebPage::LOGIN_FSM_RETURN; // Error, exit FSM
|
||||
}
|
||||
LoginWebPage::HTTP401Error();
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
|
||||
@@ -43,6 +43,10 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
|
||||
exit;
|
||||
}
|
||||
|
||||
if (LoginWebPage::getIOnExit() === LoginWebPage::EXIT_RETURN) {
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
|
||||
// No credentials yet, display the form
|
||||
$oPage = LoginWebPage::NewLoginWebPage();
|
||||
$oPage->DisplayLoginForm($this->bForceFormOnError);
|
||||
@@ -62,7 +66,7 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
|
||||
*/
|
||||
protected function OnCheckCredentials(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'form')
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'form')
|
||||
{
|
||||
$sAuthUser = utils::ReadPostedParam('auth_user', '', 'raw_data');
|
||||
$sAuthPwd = utils::ReadPostedParam('auth_pwd', null, 'raw_data');
|
||||
@@ -82,7 +86,7 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
|
||||
*/
|
||||
protected function OnCredentialsOK(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'form')
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'form')
|
||||
{
|
||||
$sAuthUser = $_SESSION['auth_user'];
|
||||
// Store 'auth_user' in session for further use
|
||||
@@ -96,7 +100,7 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
|
||||
*/
|
||||
protected function OnError(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'form')
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'form')
|
||||
{
|
||||
$this->bForceFormOnError = true;
|
||||
}
|
||||
@@ -108,7 +112,7 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
|
||||
*/
|
||||
protected function OnConnected(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'form')
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'form')
|
||||
{
|
||||
$_SESSION['can_logoff'] = true;
|
||||
return LoginWebPage::CheckLoggedUser($iErrorCode);
|
||||
|
||||
@@ -40,7 +40,7 @@ class LoginURL extends AbstractLoginFSMExtension
|
||||
|
||||
protected function OnReadCredentials(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'url')
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'url')
|
||||
{
|
||||
$_SESSION['login_temp_auth_user'] = utils::ReadParam('auth_user', '', false, 'raw_data');
|
||||
}
|
||||
@@ -49,7 +49,7 @@ class LoginURL extends AbstractLoginFSMExtension
|
||||
|
||||
protected function OnCheckCredentials(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'url')
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'url')
|
||||
{
|
||||
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
|
||||
$sAuthPwd = utils::ReadParam('auth_pwd', null, false, 'raw_data');
|
||||
@@ -66,7 +66,7 @@ class LoginURL extends AbstractLoginFSMExtension
|
||||
|
||||
protected function OnCredentialsOK(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'url')
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'url')
|
||||
{
|
||||
$sAuthUser = $_SESSION['auth_user'];
|
||||
LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', $_SESSION['login_mode']);
|
||||
@@ -76,7 +76,7 @@ class LoginURL extends AbstractLoginFSMExtension
|
||||
|
||||
protected function OnError(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'url')
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'url')
|
||||
{
|
||||
$this->bErrorOccurred = true;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ class LoginWebPage extends NiceWebPage
|
||||
{
|
||||
const EXIT_PROMPT = 0;
|
||||
const EXIT_HTTP_401 = 1;
|
||||
const EXIT_RETURN = 2;
|
||||
const EXIT_RETURN = 2; // Non interactive mode (ajax, rest, ...)
|
||||
|
||||
const EXIT_CODE_OK = 0;
|
||||
const EXIT_CODE_MISSINGLOGIN = 1;
|
||||
@@ -352,14 +352,20 @@ class LoginWebPage extends NiceWebPage
|
||||
$this->output();
|
||||
}
|
||||
|
||||
public static function ResetSession()
|
||||
public static function ResetSession($bFullCleanup = false)
|
||||
{
|
||||
// Unset all of the session variables.
|
||||
unset($_SESSION['auth_user']);
|
||||
unset($_SESSION['login_state']);
|
||||
unset($_SESSION['can_logoff']);
|
||||
unset($_SESSION['archive_mode']);
|
||||
unset($_SESSION['impersonate_user']);
|
||||
if ($bFullCleanup) {
|
||||
// Unset all of the session variables.
|
||||
foreach (array_keys($_SESSION) as $sKey) {
|
||||
unset($_SESSION[$sKey]);
|
||||
}
|
||||
} else {
|
||||
unset($_SESSION['auth_user']);
|
||||
unset($_SESSION['login_state']);
|
||||
unset($_SESSION['can_logoff']);
|
||||
unset($_SESSION['archive_mode']);
|
||||
unset($_SESSION['impersonate_user']);
|
||||
}
|
||||
UserRights::_ResetSessionCache();
|
||||
// If it's desired to kill the session, also delete the session cookie.
|
||||
// Note: This will destroy the session, and not just the session data!
|
||||
@@ -931,7 +937,7 @@ class LoginWebPage extends NiceWebPage
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($iOnExit == self::EXIT_RETURN)
|
||||
if ($iOnExit === self::EXIT_RETURN)
|
||||
{
|
||||
return self::EXIT_CODE_PORTALUSERNOTAUTHORIZED;
|
||||
}
|
||||
@@ -987,7 +993,7 @@ class LoginWebPage extends NiceWebPage
|
||||
{
|
||||
if ($bMustBeAdmin && !UserRights::IsAdministrator())
|
||||
{
|
||||
if ($iOnExit == self::EXIT_RETURN)
|
||||
if ($iOnExit === self::EXIT_RETURN)
|
||||
{
|
||||
return self::EXIT_CODE_MUSTBEADMIN;
|
||||
}
|
||||
@@ -1003,7 +1009,7 @@ class LoginWebPage extends NiceWebPage
|
||||
}
|
||||
$iRet = call_user_func(array(self::$sHandlerClass, 'ChangeLocation'), $sRequestedPortalId, $iOnExit);
|
||||
}
|
||||
if ($iOnExit == self::EXIT_RETURN)
|
||||
if ($iOnExit === self::EXIT_RETURN)
|
||||
{
|
||||
return $iRet;
|
||||
}
|
||||
|
||||
@@ -91,4 +91,10 @@ else
|
||||
$_SESSION['itop_env'] = ITOP_DEFAULT_ENV;
|
||||
}
|
||||
$sConfigFile = APPCONF.$sEnv.'/'.ITOP_CONFIG_FILE;
|
||||
MetaModel::Startup($sConfigFile, false /* $bModelOnly */, $bAllowCache, false /* $bTraceSourceFiles */, $sEnv);
|
||||
try {
|
||||
MetaModel::Startup($sConfigFile, false /* $bModelOnly */, $bAllowCache, false /* $bTraceSourceFiles */, $sEnv);
|
||||
}
|
||||
catch (MySQLException $e) {
|
||||
IssueLog::Debug($e->getMessage());
|
||||
throw new MySQLException('Could not connect to the DB server', []);
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Service\Module\ModuleService;
|
||||
use ScssPhp\ScssPhp\Compiler;
|
||||
|
||||
|
||||
@@ -361,7 +362,8 @@ class utils
|
||||
|
||||
// For URL
|
||||
case 'url':
|
||||
$retValue = filter_var($value, FILTER_SANITIZE_URL);
|
||||
// N°6350 - returns only valid URLs
|
||||
$retValue = filter_var($value, FILTER_VALIDATE_URL);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -1329,19 +1331,19 @@ class utils
|
||||
$oDashboard = $param;
|
||||
$sDashboardId = $oDashboard->GetId();
|
||||
$sDashboardFile = $oDashboard->GetDefinitionFile();
|
||||
$sDashboardFileRelative = utils::LocalPath($sDashboardFile);
|
||||
$sDlgTitle = addslashes(Dict::S('UI:ImportDashboardTitle'));
|
||||
$sDlgText = addslashes(Dict::S('UI:ImportDashboardText'));
|
||||
$sCloseBtn = addslashes(Dict::S('UI:Button:Cancel'));
|
||||
$sDashboardFileJS = addslashes($sDashboardFile);
|
||||
$sDashboardFileURL = urlencode($sDashboardFile);
|
||||
$sDashboardFileJS = addslashes($sDashboardFileRelative);
|
||||
$sDashboardFileURL = urlencode($sDashboardFileRelative);
|
||||
$sUploadDashboardTransactId = utils::GetNewTransactionId();
|
||||
$aResult = array(
|
||||
new SeparatorPopupMenuItem(),
|
||||
new URLPopupMenuItem('UI:ExportDashboard', Dict::S('UI:ExportDashBoard'), utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=export_dashboard&id='.$sDashboardId.'&file='.$sDashboardFileURL),
|
||||
new JSPopupMenuItem('UI:ImportDashboard', Dict::S('UI:ImportDashBoard'), "UploadDashboard({dashboard_id: '$sDashboardId', file: '$sDashboardFileJS', title: '$sDlgTitle', text: '$sDlgText', close_btn: '$sCloseBtn', transaction: '$sUploadDashboardTransactId' })"),
|
||||
);
|
||||
if ($oDashboard->GetReloadURL())
|
||||
{
|
||||
if ($oDashboard->GetReloadURL()) {
|
||||
$aResult[] = new SeparatorPopupMenuItem();
|
||||
$aResult[] = new URLPopupMenuItem('UI:Menu:PrintableVersion', Dict::S('UI:Menu:PrintableVersion'), $oDashboard->GetReloadURL().'&printable=1', '_blank');
|
||||
}
|
||||
@@ -1945,24 +1947,7 @@ class utils
|
||||
*/
|
||||
public static function GetCurrentModuleName($iCallDepth = 0)
|
||||
{
|
||||
$sCurrentModuleName = '';
|
||||
$aCallStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||
$sCallerFile = realpath($aCallStack[$iCallDepth]['file']);
|
||||
|
||||
foreach(GetModulesInfo() as $sModuleName => $aInfo)
|
||||
{
|
||||
if ($aInfo['root_dir'] !== '')
|
||||
{
|
||||
$sRootDir = realpath(APPROOT.$aInfo['root_dir']);
|
||||
|
||||
if(substr($sCallerFile, 0, strlen($sRootDir)) === $sRootDir)
|
||||
{
|
||||
$sCurrentModuleName = $sModuleName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $sCurrentModuleName;
|
||||
return ModuleService::GetInstance()->GetCurrentModuleName($iCallDepth + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1978,24 +1963,7 @@ class utils
|
||||
*/
|
||||
public static function GetCurrentModuleDir($iCallDepth)
|
||||
{
|
||||
$sCurrentModuleDir = '';
|
||||
$aCallStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||
$sCallerFile = realpath($aCallStack[$iCallDepth]['file']);
|
||||
|
||||
foreach(GetModulesInfo() as $sModuleName => $aInfo)
|
||||
{
|
||||
if ($aInfo['root_dir'] !== '')
|
||||
{
|
||||
$sRootDir = realpath(APPROOT.$aInfo['root_dir']);
|
||||
|
||||
if(substr($sCallerFile, 0, strlen($sRootDir)) === $sRootDir)
|
||||
{
|
||||
$sCurrentModuleDir = basename($sRootDir);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $sCurrentModuleDir;
|
||||
return ModuleService::GetInstance()->GetCurrentModuleDir($iCallDepth);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2010,12 +1978,7 @@ class utils
|
||||
*/
|
||||
public static function GetCurrentModuleUrl()
|
||||
{
|
||||
$sDir = static::GetCurrentModuleDir(1);
|
||||
if ( $sDir !== '')
|
||||
{
|
||||
return static::GetAbsoluteUrlModulesRoot().'/'.$sDir;
|
||||
}
|
||||
return '';
|
||||
return ModuleService::GetInstance()->GetCurrentModuleUrl(1);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2025,8 +1988,7 @@ class utils
|
||||
*/
|
||||
public static function GetCurrentModuleSetting($sProperty, $defaultvalue = null)
|
||||
{
|
||||
$sModuleName = static::GetCurrentModuleName(1);
|
||||
return MetaModel::GetModuleSetting($sModuleName, $sProperty, $defaultvalue);
|
||||
return ModuleService::GetInstance()->GetCurrentModuleSetting($sProperty, $defaultvalue);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2035,12 +1997,7 @@ class utils
|
||||
*/
|
||||
public static function GetCompiledModuleVersion($sModuleName)
|
||||
{
|
||||
$aModulesInfo = GetModulesInfo();
|
||||
if (array_key_exists($sModuleName, $aModulesInfo))
|
||||
{
|
||||
return $aModulesInfo[$sModuleName]['version'];
|
||||
}
|
||||
return null;
|
||||
return ModuleService::GetInstance()->GetCompiledModuleVersion($sModuleName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2504,4 +2461,5 @@ class utils
|
||||
return (substr(PHP_OS,0,3) === 'WIN');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ define('APPCONF', APPROOT.'conf/');
|
||||
* @used-by utils::GetItopVersionWikiSyntax()
|
||||
* @used-by iTopModulesPhpVersionIntegrationTest
|
||||
*/
|
||||
define('ITOP_CORE_VERSION', '2.7.8');
|
||||
define('ITOP_CORE_VERSION', '2.7.9');
|
||||
|
||||
|
||||
require_once APPROOT.'bootstrap.inc.php';
|
||||
|
||||
@@ -22,14 +22,8 @@ define('ITOP_DEFAULT_ENV', 'production');
|
||||
define('MAINTENANCE_MODE_FILE', APPROOT.'data/.maintenance');
|
||||
define('READONLY_MODE_FILE', APPROOT.'data/.readonly');
|
||||
|
||||
if (function_exists('microtime'))
|
||||
{
|
||||
$fItopStarted = microtime(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
$fItopStarted = 1000 * time();
|
||||
}
|
||||
$fItopStarted = microtime(true);
|
||||
$iItopInitialMemory = memory_get_usage(true);
|
||||
|
||||
if (! isset($GLOBALS['bBypassAutoload']) || $GLOBALS['bBypassAutoload'] == false)
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"type": "project",
|
||||
"license": "AGPL-3.0-only",
|
||||
"require": {
|
||||
"php": ">=7.0.8",
|
||||
"php": ">=7.1.3",
|
||||
"ext-ctype": "*",
|
||||
"ext-dom": "*",
|
||||
"ext-gd": "*",
|
||||
@@ -13,7 +13,9 @@
|
||||
"ext-mysqli": "*",
|
||||
"ext-soap": "*",
|
||||
"combodo/tcpdf": "~6.4.4",
|
||||
"firebase/php-jwt": "~6.4.0",
|
||||
"guzzlehttp/guzzle": "^6.5.8",
|
||||
"guzzlehttp/psr7": "~1.9.1",
|
||||
"laminas/laminas-mail": "^2.11",
|
||||
"laminas/laminas-servicemanager": "^3.5",
|
||||
"league/oauth2-google": "^3.0",
|
||||
@@ -44,7 +46,7 @@
|
||||
},
|
||||
"config": {
|
||||
"platform": {
|
||||
"php": "7.0.8"
|
||||
"php": "7.1.3"
|
||||
},
|
||||
"vendor-dir": "lib",
|
||||
"preferred-install": {
|
||||
@@ -60,6 +62,7 @@
|
||||
"sources/application",
|
||||
"sources/Composer",
|
||||
"sources/Controller",
|
||||
"sources/Service",
|
||||
"sources/Core"
|
||||
],
|
||||
"exclude-from-classmap": [
|
||||
|
||||
63
composer.lock
generated
63
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "8415e71a4288813b5a5d82ec4a00216b",
|
||||
"content-hash": "abee0f7bd244530a88b08e1e338b0ec5",
|
||||
"packages": [
|
||||
{
|
||||
"name": "combodo/tcpdf",
|
||||
@@ -254,25 +254,31 @@
|
||||
},
|
||||
{
|
||||
"name": "firebase/php-jwt",
|
||||
"version": "v5.5.1",
|
||||
"version": "v6.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/firebase/php-jwt.git",
|
||||
"reference": "83b609028194aa042ea33b5af2d41a7427de80e6"
|
||||
"reference": "4dd1e007f22a927ac77da5a3fbb067b42d3bc224"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/firebase/php-jwt/zipball/83b609028194aa042ea33b5af2d41a7427de80e6",
|
||||
"reference": "83b609028194aa042ea33b5af2d41a7427de80e6",
|
||||
"url": "https://api.github.com/repos/firebase/php-jwt/zipball/4dd1e007f22a927ac77da5a3fbb067b42d3bc224",
|
||||
"reference": "4dd1e007f22a927ac77da5a3fbb067b42d3bc224",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.0"
|
||||
"php": "^7.1||^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": ">=4.8 <=9"
|
||||
"guzzlehttp/guzzle": "^6.5||^7.4",
|
||||
"phpspec/prophecy-phpunit": "^1.1",
|
||||
"phpunit/phpunit": "^7.5||^9.5",
|
||||
"psr/cache": "^1.0||^2.0",
|
||||
"psr/http-client": "^1.0",
|
||||
"psr/http-factory": "^1.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-sodium": "Support EdDSA (Ed25519) signatures",
|
||||
"paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present"
|
||||
},
|
||||
"type": "library",
|
||||
@@ -305,9 +311,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/firebase/php-jwt/issues",
|
||||
"source": "https://github.com/firebase/php-jwt/tree/v5.5.1"
|
||||
"source": "https://github.com/firebase/php-jwt/tree/v6.4.0"
|
||||
},
|
||||
"time": "2021-11-08T20:18:51+00:00"
|
||||
"time": "2023-02-09T21:01:23+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/guzzle",
|
||||
@@ -510,16 +516,16 @@
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/psr7",
|
||||
"version": "1.9.0",
|
||||
"version": "1.9.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/psr7.git",
|
||||
"reference": "e98e3e6d4f86621a9b75f623996e6bbdeb4b9318"
|
||||
"reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/psr7/zipball/e98e3e6d4f86621a9b75f623996e6bbdeb4b9318",
|
||||
"reference": "e98e3e6d4f86621a9b75f623996e6bbdeb4b9318",
|
||||
"url": "https://api.github.com/repos/guzzle/psr7/zipball/e4490cabc77465aaee90b20cfc9a770f8c04be6b",
|
||||
"reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -538,11 +544,6 @@
|
||||
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.9-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/functions_include.php"
|
||||
@@ -600,7 +601,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/guzzle/psr7/issues",
|
||||
"source": "https://github.com/guzzle/psr7/tree/1.9.0"
|
||||
"source": "https://github.com/guzzle/psr7/tree/1.9.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -616,7 +617,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-06-20T21:43:03+00:00"
|
||||
"time": "2023-04-17T16:00:37+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laminas/laminas-loader",
|
||||
@@ -4333,22 +4334,24 @@
|
||||
},
|
||||
{
|
||||
"name": "thenetworg/oauth2-azure",
|
||||
"version": "v2.0.1",
|
||||
"version": "v2.1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/TheNetworg/oauth2-azure.git",
|
||||
"reference": "2649422a0dc74af32d21d9d738d37abcd5b03998"
|
||||
"reference": "06fb2d620fb6e6c934f632c7ec7c5ea2e978a844"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/TheNetworg/oauth2-azure/zipball/2649422a0dc74af32d21d9d738d37abcd5b03998",
|
||||
"reference": "2649422a0dc74af32d21d9d738d37abcd5b03998",
|
||||
"url": "https://api.github.com/repos/TheNetworg/oauth2-azure/zipball/06fb2d620fb6e6c934f632c7ec7c5ea2e978a844",
|
||||
"reference": "06fb2d620fb6e6c934f632c7ec7c5ea2e978a844",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"firebase/php-jwt": "~3.0||~4.0||~5.0",
|
||||
"ext-json": "*",
|
||||
"ext-openssl": "*",
|
||||
"firebase/php-jwt": "~3.0||~4.0||~5.0||~6.0",
|
||||
"league/oauth2-client": "~2.0",
|
||||
"php": "^5.6|^7.0|^8.0"
|
||||
"php": "^7.1|^8.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -4382,9 +4385,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/TheNetworg/oauth2-azure/issues",
|
||||
"source": "https://github.com/TheNetworg/oauth2-azure/tree/v2.0.1"
|
||||
"source": "https://github.com/TheNetworg/oauth2-azure/tree/v2.1.1"
|
||||
},
|
||||
"time": "2021-01-11T12:20:12+00:00"
|
||||
"time": "2022-06-23T10:35:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "true/punycode",
|
||||
@@ -4736,7 +4739,7 @@
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"php": ">=7.0.8",
|
||||
"php": ">=7.1.3",
|
||||
"ext-ctype": "*",
|
||||
"ext-dom": "*",
|
||||
"ext-gd": "*",
|
||||
@@ -4747,7 +4750,7 @@
|
||||
},
|
||||
"platform-dev": [],
|
||||
"platform-overrides": {
|
||||
"php": "7.0.8"
|
||||
"php": "7.1.3"
|
||||
},
|
||||
"plugin-api-version": "2.3.0"
|
||||
}
|
||||
|
||||
@@ -67,8 +67,7 @@ class MyHelpers
|
||||
// format sss.mmmuuupppnnn
|
||||
public static function getmicrotime()
|
||||
{
|
||||
list($usec, $sec) = explode(" ",microtime());
|
||||
return ((float)$usec + (float)$sec);
|
||||
return microtime(true);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -420,6 +419,7 @@ class MyHelpers
|
||||
//}
|
||||
return $sOutput;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -524,5 +524,3 @@ class Str
|
||||
return (strtolower($sString) == $sString);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
@@ -731,8 +731,9 @@ class CMDBSource
|
||||
{
|
||||
self::LogDeadLock($e);
|
||||
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql, $e));
|
||||
}
|
||||
$oKPI->ComputeStats('Query exec (mySQL)', $sSql);
|
||||
} finally {
|
||||
$oKPI->ComputeStats('Query exec (mySQL)', $sSql);
|
||||
}
|
||||
if ($oResult === false)
|
||||
{
|
||||
$aContext = array('query' => $sSql);
|
||||
|
||||
@@ -555,6 +555,22 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'email_transport_smtp.allow_self_signed' => array(
|
||||
'type' => 'bool',
|
||||
'description' => 'Allow self signed peer certificates',
|
||||
'default' => false,
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'email_transport_smtp.verify_peer' => array(
|
||||
'type' => 'bool',
|
||||
'description' => 'Verify peer certificate',
|
||||
'default' => true,
|
||||
'value' => true,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'email_css' => array(
|
||||
'type' => 'string',
|
||||
'description' => 'CSS that will override the standard stylesheet used for the notifications',
|
||||
@@ -947,6 +963,14 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'log_kpi_generate_legacy_report' => array(
|
||||
'type' => 'bool',
|
||||
'description' => 'Generate the legacy KPI report (kpi.html)',
|
||||
'default' => true,
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'max_linkset_output' => array(
|
||||
'type' => 'integer',
|
||||
'description' => 'Maximum number of items shown when getting a list of related items in an email, using the form $this->some_list$. 0 means no limit.',
|
||||
|
||||
@@ -188,8 +188,8 @@ final class ItopCounter
|
||||
|
||||
if (!$hDBLink)
|
||||
{
|
||||
throw new Exception("Could not connect to the DB server (host=$sDBHost, user=$sDBUser): ".mysqli_connect_error().' (mysql errno: '.mysqli_connect_errno().')');
|
||||
}
|
||||
throw new MySQLException('Could not connect to the DB server '.mysqli_connect_error().' (mysql errno: '.mysqli_connect_errno(), array('host' => $sDBHost, 'user' => $sDBUser));
|
||||
}
|
||||
|
||||
return $hDBLink;
|
||||
}
|
||||
|
||||
@@ -2225,7 +2225,7 @@ abstract class DBObject implements iDisplay
|
||||
|
||||
$oKPI = new ExecutionKPI();
|
||||
$this->DoCheckToWrite();
|
||||
$oKPI->ComputeStats('CheckToWrite', get_class($this));
|
||||
$oKPI->ComputeStatsForExtension($this, 'DoCheckToWrite');
|
||||
if (count($this->m_aCheckIssues) == 0)
|
||||
{
|
||||
$this->m_bCheckStatus = true;
|
||||
@@ -2693,8 +2693,12 @@ abstract class DBObject implements iDisplay
|
||||
$sRootClass = MetaModel::GetRootClass($sClass);
|
||||
|
||||
// Ensure the update of the values (we are accessing the data directly)
|
||||
$oKPI = new ExecutionKPI();
|
||||
$this->DoComputeValues();
|
||||
$oKPI->ComputeStatsForExtension($this, 'DoComputeValues');
|
||||
$oKPI = new ExecutionKPI();
|
||||
$this->OnInsert();
|
||||
$oKPI->ComputeStatsForExtension($this, 'OnInsert');
|
||||
|
||||
if ($this->m_iKey < 0)
|
||||
{
|
||||
@@ -2712,7 +2716,7 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
|
||||
// Ultimate check - ensure DB integrity
|
||||
list($bRes, $aIssues) = $this->CheckToWrite();
|
||||
[$bRes, $aIssues] = $this->CheckToWrite();
|
||||
if (!$bRes)
|
||||
{
|
||||
throw new CoreCannotSaveObjectException(array('issues' => $aIssues, 'class' => get_class($this), 'id' => $this->GetKey()));
|
||||
@@ -2818,7 +2822,9 @@ abstract class DBObject implements iDisplay
|
||||
$this->m_aOrigValues[$sAttCode] = $value;
|
||||
}
|
||||
|
||||
$oKPI = new ExecutionKPI();
|
||||
$this->AfterInsert();
|
||||
$oKPI->ComputeStatsForExtension($this, 'AfterInsert');
|
||||
|
||||
// Activate any existing trigger
|
||||
$sClass = get_class($this);
|
||||
@@ -2826,13 +2832,14 @@ abstract class DBObject implements iDisplay
|
||||
$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnObjectCreate AS t WHERE t.target_class IN (:class_list)"), array(), $aParams);
|
||||
while ($oTrigger = $oSet->Fetch())
|
||||
{
|
||||
/** @var \Trigger $oTrigger */
|
||||
/** @var \TriggerOnObjectCreate $oTrigger */
|
||||
try
|
||||
{
|
||||
$oTrigger->DoActivate($this->ToArgs('this'));
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
$oTrigger->LogException($e, $this);
|
||||
utils::EnrichRaisedException($oTrigger, $e);
|
||||
}
|
||||
}
|
||||
@@ -3093,8 +3100,11 @@ abstract class DBObject implements iDisplay
|
||||
|
||||
try
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
$this->DoComputeValues();
|
||||
// Stop watches
|
||||
$oKPI->ComputeStatsForExtension($this, 'DoComputeValues');
|
||||
|
||||
// Stop watches
|
||||
$sState = $this->GetState();
|
||||
if ($sState != '')
|
||||
{
|
||||
@@ -3113,7 +3123,9 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->OnUpdate();
|
||||
$oKPI = new ExecutionKPI();
|
||||
$this->OnUpdate();
|
||||
$oKPI->ComputeStatsForExtension($this, 'OnUpdate');
|
||||
|
||||
$aChanges = $this->ListChanges();
|
||||
if (count($aChanges) == 0)
|
||||
@@ -3125,7 +3137,7 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
|
||||
// Ultimate check - ensure DB integrity
|
||||
list($bRes, $aIssues) = $this->CheckToWrite();
|
||||
[$bRes, $aIssues] = $this->CheckToWrite();
|
||||
if (!$bRes)
|
||||
{
|
||||
throw new CoreCannotSaveObjectException(array(
|
||||
@@ -3150,6 +3162,7 @@ abstract class DBObject implements iDisplay
|
||||
$oTrigger->DoActivate($this->ToArgs('this'));
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$oTrigger->LogException($e, $this);
|
||||
utils::EnrichRaisedException($oTrigger, $e);
|
||||
}
|
||||
}
|
||||
@@ -3324,7 +3337,9 @@ abstract class DBObject implements iDisplay
|
||||
|
||||
try
|
||||
{
|
||||
$this->AfterUpdate();
|
||||
$oKPI = new ExecutionKPI();
|
||||
$this->AfterUpdate();
|
||||
$oKPI->ComputeStatsForExtension($this, 'AfterUpdate');
|
||||
|
||||
// Reload to get the external attributes
|
||||
if ($bHasANewExternalKeyValue)
|
||||
@@ -3470,13 +3485,13 @@ abstract class DBObject implements iDisplay
|
||||
$aParams);
|
||||
while ($oTrigger = $oSet->Fetch())
|
||||
{
|
||||
/** @var \Trigger $oTrigger */
|
||||
/** @var \TriggerOnObjectDelete $oTrigger */
|
||||
try
|
||||
{
|
||||
$oTrigger->DoActivate($this->ToArgs('this'));
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
catch(Exception $e) {
|
||||
$oTrigger->LogException($e, $this);
|
||||
utils::EnrichRaisedException($oTrigger, $e);
|
||||
}
|
||||
}
|
||||
@@ -3891,6 +3906,7 @@ abstract class DBObject implements iDisplay
|
||||
$oTrigger->DoActivate($this->ToArgs('this'));
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$oTrigger->LogException($e, $this);
|
||||
utils::EnrichRaisedException($oTrigger, $e);
|
||||
}
|
||||
}
|
||||
@@ -3902,6 +3918,7 @@ abstract class DBObject implements iDisplay
|
||||
$oTrigger->DoActivate($this->ToArgs('this'));
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$oTrigger->LogException($e, $this);
|
||||
utils::EnrichRaisedException($oTrigger, $e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -763,7 +763,10 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
|
||||
try
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
$this->m_oSQLResult = CMDBSource::Query($sSQL);
|
||||
$sOQL = $this->GetPseudoOQL($this->m_oFilter, $this->GetRealSortOrder(), $this->m_iLimitCount, $this->m_iLimitStart, false);
|
||||
$oKPI->ComputeStats('OQL Query Exec', $sOQL);
|
||||
} catch (MySQLException $e)
|
||||
{
|
||||
// 1116 = ER_TOO_MANY_TABLES
|
||||
@@ -843,8 +846,11 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
{
|
||||
if (is_null($this->m_iNumTotalDBRows))
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
$sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, 0, 0, true);
|
||||
$resQuery = CMDBSource::Query($sSQL);
|
||||
$sOQL = $this->GetPseudoOQL($this->m_oFilter, array(), 0, 0, true);
|
||||
$oKPI->ComputeStats('OQL Query Exec', $sOQL);
|
||||
if (!$resQuery) return 0;
|
||||
|
||||
$aRow = CMDBSource::FetchArray($resQuery);
|
||||
@@ -855,6 +861,42 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
return $this->m_iNumTotalDBRows + count($this->m_aAddedObjects); // Does it fix Trac #887 ??
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DBSearch $oFilter
|
||||
* @param array $aOrder
|
||||
* @param int $iLimitCount
|
||||
* @param int $iLimitStart
|
||||
* @param bool $bCount
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function GetPseudoOQL($oFilter, $aOrder, $iLimitCount, $iLimitStart, $bCount)
|
||||
{
|
||||
$sOQL = '';
|
||||
if ($bCount) {
|
||||
$sOQL .= 'COUNT ';
|
||||
}
|
||||
$sOQL .= $oFilter->ToOQL();
|
||||
|
||||
if ($iLimitCount > 0) {
|
||||
$sOQL .= ' LIMIT ';
|
||||
if ($iLimitStart > 0) {
|
||||
$sOQL .= "$iLimitStart, ";
|
||||
}
|
||||
$sOQL .= "$iLimitCount";
|
||||
}
|
||||
|
||||
if (count($aOrder) > 0) {
|
||||
$sOQL .= ' ORDER BY ';
|
||||
$aOrderBy = [];
|
||||
foreach ($aOrder as $sAttCode => $bAsc) {
|
||||
$aOrderBy[] = $sAttCode.' '.($bAsc ? 'ASC' : 'DESC');
|
||||
}
|
||||
$sOQL .= implode(', ', $aOrderBy);
|
||||
}
|
||||
return $sOQL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the count exceeds a given limit
|
||||
*
|
||||
@@ -871,8 +913,11 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
{
|
||||
if (is_null($this->m_iNumTotalDBRows))
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
$sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, $iLimit + 2, 0, true);
|
||||
$resQuery = CMDBSource::Query($sSQL);
|
||||
$sOQL = $this->GetPseudoOQL($this->m_oFilter, array(), $iLimit + 2, 0, true);
|
||||
$oKPI->ComputeStats('OQL Query Exec', $sOQL);
|
||||
if ($resQuery)
|
||||
{
|
||||
$aRow = CMDBSource::FetchArray($resQuery);
|
||||
@@ -883,7 +928,7 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
{
|
||||
$iCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$iCount = $this->m_iNumTotalDBRows;
|
||||
@@ -908,8 +953,11 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
{
|
||||
if (is_null($this->m_iNumTotalDBRows))
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
$sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, $iLimit + 2, 0, true);
|
||||
$resQuery = CMDBSource::Query($sSQL);
|
||||
$sOQL = $this->GetPseudoOQL($this->m_oFilter, array(), $iLimit + 2, 0, true);
|
||||
$oKPI->ComputeStats('OQL Query Exec', $sOQL);
|
||||
if ($resQuery)
|
||||
{
|
||||
$aRow = CMDBSource::FetchArray($resQuery);
|
||||
@@ -920,7 +968,7 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
{
|
||||
$iCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$iCount = $this->m_iNumTotalDBRows;
|
||||
|
||||
@@ -754,14 +754,14 @@ abstract class DBSearch
|
||||
* @see DBSearch::ToOQL()
|
||||
*
|
||||
* @param string $sQuery The OQL to convert to a DBSearch
|
||||
* @param mixed[string] $aParams array of <mixed> params index by <string> name
|
||||
* @param array $aParams array of <mixed> params index by <string> name
|
||||
* @param ModelReflection|null $oMetaModel The MetaModel to use when checking the consistency of the OQL
|
||||
*
|
||||
* @return DBObjectSearch|DBUnionSearch
|
||||
*
|
||||
* @throws OQLException
|
||||
*/
|
||||
static public function FromOQL($sQuery, $aParams = null, ModelReflection $oMetaModel=null)
|
||||
public static function FromOQL($sQuery, $aParams = null, ModelReflection $oMetaModel=null)
|
||||
{
|
||||
if (empty($sQuery))
|
||||
{
|
||||
|
||||
@@ -49,10 +49,9 @@ abstract class HTMLSanitizer
|
||||
$sSanitizerClass = 'HTMLDOMSanitizer';
|
||||
} else if (false === is_subclass_of($sSanitizerClass, HTMLSanitizer::class)) {
|
||||
if ($sConfigKey === 'html_sanitizer') {
|
||||
IssueLog::Warning('The configured "'.$sConfigKey.'" class "'.$sSanitizerClass.'" is not a subclass of HTMLSanitizer. Will use HTMLDOMSanitizer as the default sanitizer.');
|
||||
IssueLog::Warning('The configured "'.$sConfigKey.'" class "'.$sSanitizerClass.'" is not a subclass of '.HTMLSanitizer::class.'. Will use HTMLDOMSanitizer as the default sanitizer.');
|
||||
$sSanitizerClass = 'HTMLDOMSanitizer';
|
||||
}
|
||||
if ($sConfigKey === 'svg_sanitizer') {
|
||||
} else {
|
||||
IssueLog::Error('The configured "'.$sConfigKey.'" class "'.$sSanitizerClass.'" is not a subclass of '.HTMLSanitizer::class.' ! Won\'t sanitize string.');
|
||||
|
||||
return $sHTML;
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
use Combodo\iTop\Core\Kpi\KpiLogData;
|
||||
use Combodo\iTop\Service\Module\ModuleService;
|
||||
|
||||
|
||||
/**
|
||||
@@ -30,6 +32,8 @@ class ExecutionKPI
|
||||
static protected $m_bEnabled_Memory = false;
|
||||
static protected $m_bBlameCaller = false;
|
||||
static protected $m_sAllowedUser = '*';
|
||||
static protected $m_bGenerateLegacyReport = true;
|
||||
static protected $m_fSlowQueries = 0;
|
||||
|
||||
static protected $m_aStats = array(); // Recurrent operations
|
||||
static protected $m_aExecData = array(); // One shot operations
|
||||
@@ -77,14 +81,39 @@ class ExecutionKPI
|
||||
return false;
|
||||
}
|
||||
|
||||
static public function SetGenerateLegacyReport($bReportExtensionsOnly)
|
||||
{
|
||||
self::$m_bGenerateLegacyReport = $bReportExtensionsOnly;
|
||||
}
|
||||
|
||||
static public function SetSlowQueries($fSlowQueries)
|
||||
{
|
||||
self::$m_fSlowQueries = $fSlowQueries;
|
||||
}
|
||||
|
||||
static public function GetDescription()
|
||||
{
|
||||
$aFeatures = array();
|
||||
if (self::$m_bEnabled_Duration) $aFeatures[] = 'Duration';
|
||||
if (self::$m_bEnabled_Memory) $aFeatures[] = 'Memory usage';
|
||||
$sFeatures = implode(', ', $aFeatures);
|
||||
$sFeatures = 'Measures: '.implode(', ', $aFeatures);
|
||||
$sFor = self::$m_sAllowedUser == '*' ? 'EVERYBODY' : "'".trim(self::$m_sAllowedUser)."'";
|
||||
return "KPI logging is active for $sFor. Measures: $sFeatures";
|
||||
$sSlowQueries = '';
|
||||
if (self::$m_fSlowQueries > 0) {
|
||||
$sSlowQueries = ". Slow Queries: ".self::$m_fSlowQueries."s";
|
||||
}
|
||||
|
||||
$aExtensions = [];
|
||||
/** @var \iKPILoggerExtension $oExtensionInstance */
|
||||
foreach (MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance) {
|
||||
$aExtensions[] = ModuleService::GetInstance()->GetModuleNameFromObject($oExtensionInstance);
|
||||
}
|
||||
$sExtensions = '';
|
||||
if (count($aExtensions) > 0) {
|
||||
$sExtensions = '. KPI Extensions: ['.implode(', ', $aExtensions).']';
|
||||
}
|
||||
|
||||
return "KPI logging is active for $sFor. $sFeatures$sSlowQueries$sExtensions";
|
||||
}
|
||||
|
||||
static public function ReportStats()
|
||||
@@ -92,7 +121,28 @@ class ExecutionKPI
|
||||
if (!self::IsEnabled()) return;
|
||||
|
||||
global $fItopStarted;
|
||||
global $iItopInitialMemory;
|
||||
$sExecId = microtime(); // id to differentiate the hrefs!
|
||||
$sRequest = $_SERVER['REQUEST_URI'].' ('.$_SERVER['REQUEST_METHOD'].')';
|
||||
if (isset($_POST['operation'])) {
|
||||
$sRequest .= ' operation: '.$_POST['operation'];
|
||||
}
|
||||
|
||||
$fStop = MyHelpers::getmicrotime();
|
||||
if (($fStop - $fItopStarted) > self::$m_fSlowQueries) {
|
||||
// Invoke extensions to log the KPI operation
|
||||
/** @var \iKPILoggerExtension $oExtensionInstance */
|
||||
$iCurrentMemory = self::memory_get_usage();
|
||||
$iPeakMemory = self::memory_get_peak_usage();
|
||||
foreach (MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance) {
|
||||
$oKPILogData = new KpiLogData(KpiLogData::TYPE_REQUEST, 'Page', $sRequest, $fItopStarted, $fStop, '', $iItopInitialMemory, $iCurrentMemory, $iPeakMemory);
|
||||
$oExtensionInstance->LogOperation($oKPILogData);
|
||||
}
|
||||
}
|
||||
|
||||
if (!self::$m_bGenerateLegacyReport) {
|
||||
return;
|
||||
}
|
||||
|
||||
$aBeginTimes = array();
|
||||
foreach (self::$m_aExecData as $aOpStats)
|
||||
@@ -105,7 +155,7 @@ class ExecutionKPI
|
||||
|
||||
self::Report("<hr/>");
|
||||
self::Report("<div style=\"background-color: grey; padding: 10px;\">");
|
||||
self::Report("<h3><a name=\"".md5($sExecId)."\">KPIs</a> - ".$_SERVER['REQUEST_URI']." (".$_SERVER['REQUEST_METHOD'].")</h3>");
|
||||
self::Report("<h3><a name=\"".md5($sExecId)."\">KPIs</a> - $sRequest</h3>");
|
||||
self::Report("<p>".date('Y-m-d H:i:s', $fItopStarted)."</p>");
|
||||
self::Report("<p>log_kpi_user_id: ".UserRights::GetUserId()."</p>");
|
||||
self::Report("<div>");
|
||||
@@ -200,8 +250,6 @@ class ExecutionKPI
|
||||
|
||||
self::Report("<p><a href=\"#end-".md5($sExecId)."\">Next page stats</a></p>");
|
||||
|
||||
$fSlowQueries = MetaModel::GetConfig()->Get('log_kpi_slow_queries');
|
||||
|
||||
// Report operation details
|
||||
foreach (self::$m_aStats as $sOperation => $aOpStats)
|
||||
{
|
||||
@@ -245,7 +293,7 @@ class ExecutionKPI
|
||||
$sTotalInter = round($fTotalInter, 3);
|
||||
$sMinInter = round($fMinInter, 3);
|
||||
$sMaxInter = round($fMaxInter, 3);
|
||||
if (($fTotalInter >= $fSlowQueries))
|
||||
if (($fTotalInter >= self::$m_fSlowQueries))
|
||||
{
|
||||
if ($bDisplayHeader)
|
||||
{
|
||||
@@ -271,11 +319,19 @@ class ExecutionKPI
|
||||
self::Report('<a name="end-'.md5($sExecId).'"> </a>');
|
||||
}
|
||||
|
||||
public static function InitStats()
|
||||
{
|
||||
// Invoke extensions to initialize the KPI statistics
|
||||
/** @var \iKPILoggerExtension $oExtensionInstance */
|
||||
foreach (MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance) {
|
||||
$oExtensionInstance->InitStats();
|
||||
}
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->ResetCounters();
|
||||
}
|
||||
}
|
||||
|
||||
// Get the duration since startup, and reset the counter for the next measure
|
||||
//
|
||||
@@ -283,8 +339,14 @@ class ExecutionKPI
|
||||
{
|
||||
global $fItopStarted;
|
||||
|
||||
if (!self::IsEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$aNewEntry = null;
|
||||
|
||||
$fStarted = $this->m_fStarted;
|
||||
$fStopped = $this->m_fStarted;
|
||||
if (self::$m_bEnabled_Duration)
|
||||
{
|
||||
$fStopped = MyHelpers::getmicrotime();
|
||||
@@ -297,6 +359,9 @@ class ExecutionKPI
|
||||
$this->m_fStarted = $fStopped;
|
||||
}
|
||||
|
||||
$iInitialMemory = is_null($this->m_iInitialMemory) ? 0 : $this->m_iInitialMemory;
|
||||
$iCurrentMemory = 0;
|
||||
$iPeakMemory = 0;
|
||||
if (self::$m_bEnabled_Memory)
|
||||
{
|
||||
$iCurrentMemory = self::memory_get_usage();
|
||||
@@ -306,41 +371,102 @@ class ExecutionKPI
|
||||
}
|
||||
$aNewEntry['mem_begin'] = $this->m_iInitialMemory;
|
||||
$aNewEntry['mem_end'] = $iCurrentMemory;
|
||||
if (function_exists('memory_get_peak_usage'))
|
||||
{
|
||||
$aNewEntry['mem_peak'] = memory_get_peak_usage();
|
||||
}
|
||||
$iPeakMemory = self::memory_get_peak_usage();
|
||||
$aNewEntry['mem_peak'] = $iPeakMemory;
|
||||
// Reset for the next operation (if the object is recycled)
|
||||
$this->m_iInitialMemory = $iCurrentMemory;
|
||||
}
|
||||
|
||||
if (!is_null($aNewEntry))
|
||||
if (self::$m_bEnabled_Duration || self::$m_bEnabled_Memory) {
|
||||
// Invoke extensions to log the KPI operation
|
||||
/** @var \iKPILoggerExtension $oExtensionInstance */
|
||||
foreach(MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance)
|
||||
{
|
||||
$sExtension = ModuleService::GetInstance()->GetModuleNameFromCallStack(1);
|
||||
$oKPILogData = new KpiLogData(
|
||||
KpiLogData::TYPE_REPORT,
|
||||
'Step',
|
||||
$sOperationDesc,
|
||||
$fStarted,
|
||||
$fStopped,
|
||||
$sExtension,
|
||||
$iInitialMemory,
|
||||
$iCurrentMemory,
|
||||
$iPeakMemory);
|
||||
$oExtensionInstance->LogOperation($oKPILogData);
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_null($aNewEntry) && self::$m_bGenerateLegacyReport)
|
||||
{
|
||||
self::$m_aExecData[] = $aNewEntry;
|
||||
}
|
||||
$this->ResetCounters();
|
||||
}
|
||||
|
||||
public function ComputeStatsForExtension($object, $sMethod)
|
||||
{
|
||||
if (!self::IsEnabled()) {
|
||||
return;
|
||||
}
|
||||
$sSignature = ModuleService::GetInstance()->GetModuleMethodSignature($object, $sMethod);
|
||||
if (utils::StartsWith($sSignature, '[')) {
|
||||
$this->ComputeStats('Extension', $sSignature);
|
||||
}
|
||||
}
|
||||
|
||||
public function ComputeStats($sOperation, $sArguments)
|
||||
{
|
||||
if (!self::IsEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self::$m_bEnabled_Duration)
|
||||
{
|
||||
$fStopped = MyHelpers::getmicrotime();
|
||||
$fDuration = $fStopped - $this->m_fStarted;
|
||||
if (self::$m_bBlameCaller)
|
||||
{
|
||||
self::$m_aStats[$sOperation][$sArguments][] = array(
|
||||
'time' => $fDuration,
|
||||
'callers' => MyHelpers::get_callstack(1),
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
self::$m_aStats[$sOperation][$sArguments][] = array(
|
||||
'time' => $fDuration
|
||||
);
|
||||
}
|
||||
}
|
||||
$aCallstack = [];
|
||||
if (self::$m_bGenerateLegacyReport) {
|
||||
if (self::$m_bBlameCaller) {
|
||||
$aCallstack = MyHelpers::get_callstack(1);
|
||||
self::$m_aStats[$sOperation][$sArguments][] = [
|
||||
'time' => $fDuration,
|
||||
'callers' => $aCallstack,
|
||||
];
|
||||
} else {
|
||||
self::$m_aStats[$sOperation][$sArguments][] = [
|
||||
'time' => $fDuration
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$iInitialMemory = is_null($this->m_iInitialMemory) ? 0 : $this->m_iInitialMemory;
|
||||
$iCurrentMemory = 0;
|
||||
$iPeakMemory = 0;
|
||||
if (self::$m_bEnabled_Memory)
|
||||
{
|
||||
$iCurrentMemory = self::memory_get_usage();
|
||||
$iPeakMemory = self::memory_get_peak_usage();
|
||||
}
|
||||
|
||||
// Invoke extensions to log the KPI operation
|
||||
/** @var \iKPILoggerExtension $oExtensionInstance */
|
||||
foreach (MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance) {
|
||||
$sExtension = ModuleService::GetInstance()->GetModuleNameFromCallStack(1);
|
||||
$oKPILogData = new KpiLogData(
|
||||
KpiLogData::TYPE_STATS,
|
||||
$sOperation,
|
||||
$sArguments,
|
||||
$this->m_fStarted,
|
||||
$fStopped,
|
||||
$sExtension,
|
||||
$iInitialMemory,
|
||||
$iCurrentMemory,
|
||||
$iPeakMemory,
|
||||
$aCallstack);
|
||||
$oExtensionInstance->LogOperation($oKPILogData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function ResetCounters()
|
||||
@@ -370,35 +496,7 @@ class ExecutionKPI
|
||||
|
||||
static protected function memory_get_usage()
|
||||
{
|
||||
if (function_exists('memory_get_usage'))
|
||||
{
|
||||
return memory_get_usage(true);
|
||||
}
|
||||
|
||||
// Copied from the PHP manual
|
||||
//
|
||||
//If its Windows
|
||||
//Tested on Win XP Pro SP2. Should work on Win 2003 Server too
|
||||
//Doesn't work for 2000
|
||||
//If you need it to work for 2000 look at http://us2.php.net/manual/en/function.memory-get-usage.php#54642
|
||||
if (substr(PHP_OS,0,3) == 'WIN')
|
||||
{
|
||||
$output = array();
|
||||
exec('tasklist /FI "PID eq ' . getmypid() . '" /FO LIST', $output);
|
||||
|
||||
return preg_replace( '/[\D]/', '', $output[5] ) * 1024;
|
||||
}
|
||||
else
|
||||
{
|
||||
//We now assume the OS is UNIX
|
||||
//Tested on Mac OS X 10.4.6 and Linux Red Hat Enterprise 4
|
||||
//This should work on most UNIX systems
|
||||
$pid = getmypid();
|
||||
exec("ps -eo%mem,rss,pid | grep $pid", $output);
|
||||
$output = explode(" ", $output[0]);
|
||||
//rss is given in 1024 byte units
|
||||
return $output[1] * 1024;
|
||||
}
|
||||
return memory_get_usage(true);
|
||||
}
|
||||
|
||||
static public function memory_get_peak_usage($bRealUsage = false)
|
||||
|
||||
@@ -549,6 +549,12 @@ class LogChannels
|
||||
|
||||
const DEADLOCK = 'DeadLock';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @since 2.7.9
|
||||
*/
|
||||
const EXPORT = 'export';
|
||||
|
||||
const INLINE_IMAGE = 'InlineImage';
|
||||
|
||||
/**
|
||||
@@ -735,7 +741,9 @@ class ToolsLog extends LogAPI
|
||||
|
||||
/**
|
||||
* @see \CMDBSource::LogDeadLock()
|
||||
* @since 2.7.1
|
||||
* @since 2.7.1 PR #139
|
||||
*
|
||||
* @link https://dev.mysql.com/doc/refman/5.7/en/innodb-deadlocks.html
|
||||
*/
|
||||
class DeadLockLog extends LogAPI
|
||||
{
|
||||
@@ -755,14 +763,15 @@ class DeadLockLog extends LogAPI
|
||||
parent::Enable($sTargetFile);
|
||||
}
|
||||
|
||||
/** @noinspection PhpUnreachableStatementInspection */
|
||||
private static function GetChannelFromMysqlErrorNo($iMysqlErrorNo)
|
||||
{
|
||||
switch ($iMysqlErrorNo)
|
||||
{
|
||||
case 1205:
|
||||
case CMDBSource::MYSQL_ERRNO_WAIT_TIMEOUT:
|
||||
return self::CHANNEL_WAIT_TIMEOUT;
|
||||
break;
|
||||
case 1213:
|
||||
case CMDBSource::MYSQL_ERRNO_DEADLOCK:
|
||||
return self::CHANNEL_DEADLOCK_FOUND;
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -2778,7 +2778,7 @@ abstract class MetaModel
|
||||
|
||||
// Build the list of available extensions
|
||||
//
|
||||
$aInterfaces = array('iApplicationUIExtension', 'iPreferencesExtension', 'iApplicationObjectExtension', 'iLoginFSMExtension', 'iLoginUIExtension', 'iLogoutExtension', 'iQueryModifier', 'iOnClassInitialization', 'iPopupMenuExtension', 'iPageUIExtension', 'iPortalUIExtension', 'ModuleHandlerApiInterface', 'iNewsroomProvider', 'iModuleExtension');
|
||||
$aInterfaces = array('iApplicationUIExtension', 'iPreferencesExtension', 'iApplicationObjectExtension', 'iLoginFSMExtension', 'iLoginUIExtension', 'iLogoutExtension', 'iQueryModifier', 'iOnClassInitialization', 'iPopupMenuExtension', 'iPageUIExtension', 'iPortalUIExtension', 'ModuleHandlerApiInterface', 'iNewsroomProvider', 'iModuleExtension', 'iKPILoggerExtension');
|
||||
foreach($aInterfaces as $sInterface)
|
||||
{
|
||||
self::$m_aExtensionClasses[$sInterface] = array();
|
||||
@@ -6348,7 +6348,9 @@ abstract class MetaModel
|
||||
|
||||
ExecutionKPI::EnableDuration(self::$m_oConfig->Get('log_kpi_duration'));
|
||||
ExecutionKPI::EnableMemory(self::$m_oConfig->Get('log_kpi_memory'));
|
||||
ExecutionKPI::SetAllowedUser(self::$m_oConfig->Get('log_kpi_user_id'));
|
||||
ExecutionKPI::SetAllowedUser(self::$m_oConfig->Get('log_kpi_user_id'));
|
||||
ExecutionKPI::SetGenerateLegacyReport(self::$m_oConfig->Get('log_kpi_generate_legacy_report'));
|
||||
ExecutionKPI::SetSlowQueries(self::$m_oConfig->Get('log_kpi_slow_queries'));
|
||||
|
||||
self::$m_bSkipCheckToWrite = self::$m_oConfig->Get('skip_check_to_write');
|
||||
self::$m_bSkipCheckExtKeys = self::$m_oConfig->Get('skip_check_ext_keys');
|
||||
@@ -6485,6 +6487,7 @@ abstract class MetaModel
|
||||
|
||||
CMDBSource::InitFromConfig(self::$m_oConfig);
|
||||
// Later when timezone implementation is correctly done: CMDBSource::SetTimezone($sDBTimezone);
|
||||
ExecutionKPI::InitStats();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -257,7 +257,7 @@ class iTopMutex
|
||||
$this->hDBLink = CMDBSource::GetMysqliInstance($sServer, $sUser, $sPwd, $sSource, $bTlsEnabled, $sTlsCA, false);
|
||||
|
||||
if (!$this->hDBLink) {
|
||||
throw new Exception("Could not connect to the DB server (host=$sServer, user=$sUser): ".mysqli_connect_error().' (mysql errno: '.mysqli_connect_errno().')');
|
||||
throw new MySQLException('Could not connect to the DB server '.mysqli_connect_error().' (mysql errno: '.mysqli_connect_errno(), array('host' => $sDBHost, 'user' => $sDBUser));
|
||||
}
|
||||
|
||||
// Make sure that the server variable `wait_timeout` is at least 86400 seconds for this connection,
|
||||
|
||||
@@ -25,6 +25,19 @@
|
||||
|
||||
class PDFBulkExport extends HTMLBulkExport
|
||||
{
|
||||
/**
|
||||
* @var string For sample purposes
|
||||
* @internal
|
||||
* @since 2.7.8
|
||||
*/
|
||||
const ENUM_OUTPUT_TYPE_SAMPLE = 'sample';
|
||||
/**
|
||||
* @var string For the real export
|
||||
* @internal
|
||||
* @since 2.7.8
|
||||
*/
|
||||
const ENUM_OUTPUT_TYPE_REAL = 'real';
|
||||
|
||||
public function DisplayUsage(Page $oP)
|
||||
{
|
||||
$oP->p(" * pdf format options:");
|
||||
@@ -190,46 +203,46 @@ EOF
|
||||
return $sPDF;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @since 2.7.8
|
||||
*/
|
||||
protected function GetSampleData($oObj, $sAttCode)
|
||||
{
|
||||
if ($sAttCode !== 'id')
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
|
||||
|
||||
// As sample data will be displayed in the web browser, AttributeImage needs to be rendered with a regular HTML format, meaning its "src" looking like "data:image/png;base64,iVBORw0KGgoAAAANSUh..."
|
||||
// Whereas for the PDF generation it needs to be rendered with a TCPPDF-compatible format, meaning its "src" looking like "@iVBORw0KGgoAAAANSUh..."
|
||||
if ($oAttDef instanceof AttributeImage) {
|
||||
return $this->GetAttributeImageValue($oObj, $sAttCode, static::ENUM_OUTPUT_TYPE_SAMPLE);
|
||||
}
|
||||
}
|
||||
return parent::GetSampleData($oObj, $sAttCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DBObject $oObj
|
||||
* @param string $sAttCode
|
||||
*
|
||||
* @return int|string
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function GetValue($oObj, $sAttCode)
|
||||
{
|
||||
switch($sAttCode)
|
||||
{
|
||||
switch ($sAttCode) {
|
||||
case 'id':
|
||||
$sRet = parent::GetValue($oObj, $sAttCode);
|
||||
break;
|
||||
|
||||
default:
|
||||
$value = $oObj->Get($sAttCode);
|
||||
if ($value instanceof ormDocument)
|
||||
{
|
||||
if ($value instanceof ormDocument) {
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
|
||||
if ($oAttDef instanceof AttributeImage)
|
||||
{
|
||||
// To limit the image size in the PDF output, we have to enforce the size as height/width because max-width/max-height have no effect
|
||||
//
|
||||
$iDefaultMaxWidthPx = 48;
|
||||
$iDefaultMaxHeightPx = 48;
|
||||
if ($value->IsEmpty())
|
||||
{
|
||||
$iNewWidth = $iDefaultMaxWidthPx;
|
||||
$iNewHeight = $iDefaultMaxHeightPx;
|
||||
|
||||
$sUrl = $oAttDef->Get('default_image');
|
||||
}
|
||||
else
|
||||
{
|
||||
list($iWidth, $iHeight) = utils::GetImageSize($value->GetData());
|
||||
$iMaxWidthPx = min($iDefaultMaxWidthPx, $oAttDef->Get('display_max_width'));
|
||||
$iMaxHeightPx = min($iDefaultMaxHeightPx, $oAttDef->Get('display_max_height'));
|
||||
|
||||
$fScale = min($iMaxWidthPx / $iWidth, $iMaxHeightPx / $iHeight);
|
||||
$iNewWidth = $iWidth * $fScale;
|
||||
$iNewHeight = $iHeight * $fScale;
|
||||
|
||||
$sUrl = 'data:'.$value->GetMimeType().';base64,'.base64_encode($value->GetData());
|
||||
}
|
||||
$sRet = ($sUrl !== null) ? '<img src="'.$sUrl.'" style="width: '.$iNewWidth.'px; height: '.$iNewHeight.'px">' : '';
|
||||
$sRet = '<div class="view-image">'.$sRet.'</div>';
|
||||
$sRet = $this->GetAttributeImageValue($oObj, $sAttCode, static::ENUM_OUTPUT_TYPE_REAL);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -258,4 +271,74 @@ EOF
|
||||
{
|
||||
return 'pdf';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DBObject $oObj
|
||||
* @param string $sAttCode
|
||||
* @param string $sOutputType {@see \PDFBulkExport::ENUM_OUTPUT_TYPE_SAMPLE}, {@see \PDFBulkExport::ENUM_OUTPUT_TYPE_REAL}
|
||||
*
|
||||
* @return string Rendered value of $oAttDef / $oValue according to the desired $sOutputType
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
*
|
||||
* @since 2.7.8 N°2244 method creation
|
||||
* @since 2.7.9 N°5588 signature change to get the object so that we can log all the needed information
|
||||
*/
|
||||
protected function GetAttributeImageValue(DBObject $oObj, string $sAttCode, string $sOutputType)
|
||||
{
|
||||
$oValue = $oObj->Get($sAttCode);
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
|
||||
|
||||
// To limit the image size in the PDF output, we have to enforce the size as height/width because max-width/max-height have no effect
|
||||
//
|
||||
$iDefaultMaxWidthPx = 48;
|
||||
$iDefaultMaxHeightPx = 48;
|
||||
if ($oValue->IsEmpty()) {
|
||||
$iNewWidth = $iDefaultMaxWidthPx;
|
||||
$iNewHeight = $iDefaultMaxHeightPx;
|
||||
|
||||
$sUrl = $oAttDef->Get('default_image');
|
||||
} else {
|
||||
$iMaxWidthPx = min($iDefaultMaxWidthPx, $oAttDef->Get('display_max_width'));
|
||||
$iMaxHeightPx = min($iDefaultMaxHeightPx, $oAttDef->Get('display_max_height'));
|
||||
|
||||
list($iWidth, $iHeight) = utils::GetImageSize($oValue->GetData());
|
||||
if ((is_null($iWidth)) || (is_null($iHeight)) || ($iWidth === 0) || ($iHeight === 0)) {
|
||||
// Avoid division by zero exception (SVGs, corrupted images, ...)
|
||||
$iNewWidth = $iDefaultMaxWidthPx;
|
||||
$iNewHeight = $iDefaultMaxHeightPx;
|
||||
|
||||
$sAttCode = $oAttDef->GetCode();
|
||||
IssueLog::Warning('AttributeImage: Cannot read image size', LogChannels::EXPORT, [
|
||||
'ObjClass' => get_class($oObj),
|
||||
'ObjKey' => $oObj->GetKey(),
|
||||
'ObjFriendlyName' => $oObj->GetName(),
|
||||
'AttCode' => $sAttCode,
|
||||
]);
|
||||
} else {
|
||||
$fScale = min($iMaxWidthPx / $iWidth, $iMaxHeightPx / $iHeight);
|
||||
$iNewWidth = $iWidth * $fScale;
|
||||
$iNewHeight = $iHeight * $fScale;
|
||||
}
|
||||
|
||||
$sValueAsBase64 = base64_encode($oValue->GetData());
|
||||
switch ($sOutputType) {
|
||||
case static::ENUM_OUTPUT_TYPE_SAMPLE:
|
||||
$sUrl = 'data:'.$oValue->GetMimeType().';base64,'.$sValueAsBase64;
|
||||
break;
|
||||
|
||||
case static::ENUM_OUTPUT_TYPE_REAL:
|
||||
default:
|
||||
// TCPDF requires base64-encoded images to be rendered without the usual "data:<MIMETYPE>;base64" header but with an "@"
|
||||
// @link https://tcpdf.org/examples/example_009/
|
||||
$sUrl = '@'.$sValueAsBase64;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$sRet = ($sUrl !== null) ? '<img src="'.$sUrl.'" style="width: '.$iNewWidth.'px; height: '.$iNewHeight.'px; vertical-align: middle; text-align:center;">' : '';
|
||||
$sRet = '<div class="view-image">'.$sRet.'</div>';
|
||||
|
||||
return $sRet;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ class ObjectResult
|
||||
/**
|
||||
* REST response for services managing objects. Derive this structure to add information and/or constants
|
||||
*
|
||||
* @package Extensibility
|
||||
* @package RESTExtensibilityAPI
|
||||
* @package REST Services
|
||||
* @api
|
||||
*/
|
||||
@@ -206,7 +206,7 @@ class RestResultWithRelations extends RestResultWithObjects
|
||||
/**
|
||||
* Deletion result codes for a target object (either deleted or updated)
|
||||
*
|
||||
* @package Extensibility
|
||||
* @package RESTExtensibilityAPI
|
||||
* @api
|
||||
* @since 2.0.1
|
||||
*/
|
||||
|
||||
@@ -126,7 +126,9 @@ abstract class Trigger extends cmdbAbstractObject
|
||||
$oAction = MetaModel::GetObject('Action', $iActionId);
|
||||
if ($oAction->IsActive())
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
$oAction->DoExecute($this, $aContextArgs);
|
||||
$oKPI->ComputeStatsForExtension($oAction, 'DoExecute');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -259,21 +261,48 @@ abstract class TriggerOnObject extends Trigger
|
||||
public function IsTargetObject($iObjectId, $aChanges = array())
|
||||
{
|
||||
$sFilter = trim($this->Get('filter'));
|
||||
if (strlen($sFilter) > 0)
|
||||
{
|
||||
if (strlen($sFilter) > 0) {
|
||||
$oSearch = DBObjectSearch::FromOQL($sFilter);
|
||||
$oSearch->AddCondition('id', $iObjectId, '=');
|
||||
$oSearch->AllowAllData();
|
||||
$oSet = new DBObjectSet($oSearch);
|
||||
$bRet = ($oSet->Count() > 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$bRet = true;
|
||||
}
|
||||
|
||||
return $bRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Exception $oException
|
||||
* @param \DBObject $oObject
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @uses \IssueLog::Error()
|
||||
*
|
||||
* @since 2.7.9 3.0.3 3.1.0 N°5893
|
||||
*/
|
||||
public function LogException($oException, $oObject)
|
||||
{
|
||||
$sObjectKey = $oObject->GetKey(); // if object wasn't persisted yet, then we'll have a negative value
|
||||
|
||||
$aContext = [
|
||||
'exception.class' => get_class($oException),
|
||||
'exception.message' => $oException->getMessage(),
|
||||
'trigger.class' => get_class($this),
|
||||
'trigger.id' => $this->GetKey(),
|
||||
'trigger.friendlyname' => $this->GetRawName(),
|
||||
'object.class' => get_class($oObject),
|
||||
'object.id' => $sObjectKey,
|
||||
'object.friendlyname' => $oObject->GetRawName(),
|
||||
'current_user' => UserRights::GetUser(),
|
||||
'exception.stack' => $oException->getTraceAsString(),
|
||||
];
|
||||
|
||||
IssueLog::Error('A trigger did throw an exception', null, $aContext);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
|
||||
// Beware the version number MUST be enclosed with quotes otherwise v2.3.0 becomes v2 0.3 .0
|
||||
$version: "v2.7.8";
|
||||
$version: "v2.7.9";
|
||||
$approot-relative: "../../../../../" !default; // relative to env-***/branding/themes/***/main.css
|
||||
|
||||
// Base colors
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'authent-cas/2.7.8',
|
||||
'authent-cas/2.7.9',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -47,6 +47,11 @@ class CASLoginExtension extends AbstractLoginFSMExtension implements iLogoutExte
|
||||
|
||||
protected function OnReadCredentials(&$iErrorCode)
|
||||
{
|
||||
if (LoginWebPage::getIOnExit() === LoginWebPage::EXIT_RETURN) {
|
||||
// Not allowed if not already connected
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
|
||||
if (!isset($_SESSION['login_mode']) || ($_SESSION['login_mode'] == 'cas'))
|
||||
{
|
||||
static::InitCASClient();
|
||||
@@ -71,7 +76,8 @@ class CASLoginExtension extends AbstractLoginFSMExtension implements iLogoutExte
|
||||
return LoginWebPage::LOGIN_FSM_ERROR;
|
||||
}
|
||||
}
|
||||
$_SESSION['login_mode'] = 'cas';
|
||||
|
||||
$_SESSION['login_mode'] = 'cas';
|
||||
phpCAS::forceAuthentication(); // Redirect to CAS and exit
|
||||
}
|
||||
}
|
||||
@@ -80,7 +86,7 @@ class CASLoginExtension extends AbstractLoginFSMExtension implements iLogoutExte
|
||||
|
||||
protected function OnCheckCredentials(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'cas')
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'cas')
|
||||
{
|
||||
if (!isset($_SESSION['auth_user']))
|
||||
{
|
||||
@@ -97,7 +103,7 @@ class CASLoginExtension extends AbstractLoginFSMExtension implements iLogoutExte
|
||||
|
||||
protected function OnCredentialsOK(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'cas')
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'cas')
|
||||
{
|
||||
$sAuthUser = $_SESSION['auth_user'];
|
||||
if (!LoginWebPage::CheckUser($sAuthUser))
|
||||
@@ -112,9 +118,15 @@ class CASLoginExtension extends AbstractLoginFSMExtension implements iLogoutExte
|
||||
|
||||
protected function OnError(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'cas')
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'cas')
|
||||
{
|
||||
unset($_SESSION['phpCAS']);
|
||||
unset($_SESSION['phpCAS']);
|
||||
|
||||
if (LoginWebPage::getIOnExit() === LoginWebPage::EXIT_RETURN) {
|
||||
// don't display the login page
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
|
||||
if ($iErrorCode != LoginWebPage::EXIT_CODE_MISSINGLOGIN)
|
||||
{
|
||||
$oLoginWebPage = new LoginWebPage();
|
||||
@@ -127,7 +139,7 @@ class CASLoginExtension extends AbstractLoginFSMExtension implements iLogoutExte
|
||||
|
||||
protected function OnConnected(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'cas')
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'cas')
|
||||
{
|
||||
$_SESSION['can_logoff'] = true;
|
||||
return LoginWebPage::CheckLoggedUser($iErrorCode);
|
||||
@@ -156,7 +168,7 @@ class CASLoginExtension extends AbstractLoginFSMExtension implements iLogoutExte
|
||||
{
|
||||
phpCAS::setLogger(new CASLogger(APPROOT.'log/cas.log'));
|
||||
}
|
||||
|
||||
|
||||
// Initialize phpCAS
|
||||
$sCASVersion = Config::Get('cas_version');
|
||||
$sCASHost = Config::Get('cas_host');
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'authent-external/2.7.8',
|
||||
'authent-external/2.7.9',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -9,7 +9,7 @@ if (function_exists('ldap_connect'))
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'authent-ldap/2.7.8',
|
||||
'authent-ldap/2.7.9',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'authent-local/2.7.8',
|
||||
'authent-local/2.7.9',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'combodo-db-tools/2.7.8',
|
||||
'combodo-db-tools/2.7.9',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-attachments/2.7.8',
|
||||
'itop-attachments/2.7.9',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-backup/2.7.8',
|
||||
'itop-backup/2.7.9',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-bridge-virtualization-storage/2.7.8',
|
||||
'itop-bridge-virtualization-storage/2.7.9',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-change-mgmt-itil/2.7.8',
|
||||
'itop-change-mgmt-itil/2.7.9',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-change-mgmt/2.7.8',
|
||||
'itop-change-mgmt/2.7.9',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-config-mgmt/2.7.8',
|
||||
'itop-config-mgmt/2.7.9',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-config/2.7.8',
|
||||
'itop-config/2.7.9',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-core-update/2.7.8',
|
||||
'itop-core-update/2.7.9',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-datacenter-mgmt/2.7.8',
|
||||
'itop-datacenter-mgmt/2.7.9',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-endusers-devices/2.7.8',
|
||||
'itop-endusers-devices/2.7.9',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-files-information/2.7.8',
|
||||
'itop-files-information/2.7.9',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-full-itil/2.7.8',
|
||||
'itop-full-itil/2.7.9',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-hub-connector/2.7.8',
|
||||
'itop-hub-connector/2.7.9',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-incident-mgmt-itil/2.7.8',
|
||||
'itop-incident-mgmt-itil/2.7.9',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-knownerror-mgmt/2.7.8',
|
||||
'itop-knownerror-mgmt/2.7.9',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -77,11 +77,10 @@ const oOpenSignInWindow = function (url, name) {
|
||||
};
|
||||
|
||||
|
||||
const OAuthConnect = function(sClass, sId, sAjaxUri, sReturnUri) {
|
||||
const OAuthConnect = function(sClass, sId, sAjaxUri) {
|
||||
sOAuthAjaxURI = sAjaxUri;
|
||||
sOAuthObjClass = sClass;
|
||||
sOAuthObjKey = sId;
|
||||
sOAuthReturnURI = sReturnUri;
|
||||
|
||||
$.post(
|
||||
sOAuthAjaxURI,
|
||||
|
||||
@@ -52,12 +52,12 @@
|
||||
<default_value/>
|
||||
<is_null_allowed>true</is_null_allowed>
|
||||
</field>
|
||||
<field id="client_id" xsi:type="AttributeText">
|
||||
<field id="client_id" xsi:type="AttributeString">
|
||||
<sql>client_id</sql>
|
||||
<default_value/>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
</field>
|
||||
<field id="client_secret" xsi:type="AttributeText">
|
||||
<field id="client_secret" xsi:type="AttributePassword">
|
||||
<sql>client_secret</sql>
|
||||
<default_value/>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
@@ -202,6 +202,7 @@
|
||||
$this->Set('refresh_token', $oAccessToken->getRefreshToken());
|
||||
}
|
||||
$this->Set('status', 'active');
|
||||
$this->AllowWrite();
|
||||
$this->DBUpdate();
|
||||
}
|
||||
]]></code>
|
||||
@@ -293,7 +294,6 @@
|
||||
<attributes>
|
||||
<attribute id="provider"/>
|
||||
<attribute id="client_id"/>
|
||||
<attribute id="client_secret"/>
|
||||
</attributes>
|
||||
<is_blocking>true</is_blocking>
|
||||
</rule>
|
||||
@@ -441,21 +441,6 @@
|
||||
}
|
||||
]]></code>
|
||||
</method>
|
||||
<method id="OnUpdate">
|
||||
<static>false</static>
|
||||
<access>protected</access>
|
||||
<type>Overload-DBObject</type>
|
||||
<code><![CDATA[
|
||||
protected function OnUpdate()
|
||||
{
|
||||
$aChanges = $this->ListChanges();
|
||||
if (array_key_exists('client_id', $aChanges) || array_key_exists('client_secret', $aChanges) || array_key_exists('redirect_url', $aChanges)) {
|
||||
$sMessage = Dict::S('itop-oauth-client:Message:RegenerateToken');
|
||||
self::SetSessionMessage(get_class($this), $this->GetKey(), 'RegenerateToken', $sMessage, 'info', 1);
|
||||
}
|
||||
}
|
||||
]]></code>
|
||||
</method>
|
||||
<method id="DoCheckToWrite">
|
||||
<static>false</static>
|
||||
<access>public</access>
|
||||
@@ -501,6 +486,12 @@
|
||||
$this->Set('used_scope', 'advanced');
|
||||
$this->Set('scope', '');
|
||||
}
|
||||
$aChanges = $this->ListChanges();
|
||||
if (array_key_exists('client_id', $aChanges) || array_key_exists('client_secret', $aChanges) || array_key_exists('redirect_url', $aChanges)) {
|
||||
$sMessage = Dict::S('itop-oauth-client:Message:RegenerateToken');
|
||||
self::SetSessionMessage(get_class($this), $this->GetKey(), 'RegenerateToken', $sMessage, 'info', 1);
|
||||
$this->Set('status', 'inactive');
|
||||
}
|
||||
}
|
||||
]]></code>
|
||||
</method>
|
||||
@@ -604,7 +595,6 @@
|
||||
<attributes>
|
||||
<attribute id="provider"/>
|
||||
<attribute id="client_id"/>
|
||||
<attribute id="client_secret"/>
|
||||
</attributes>
|
||||
<is_blocking>true</is_blocking>
|
||||
</rule>
|
||||
@@ -799,6 +789,12 @@
|
||||
$this->Set('used_scope', 'advanced');
|
||||
$this->Set('scope', '');
|
||||
}
|
||||
$aChanges = $this->ListChanges();
|
||||
if (array_key_exists('client_id', $aChanges) || array_key_exists('client_secret', $aChanges) || array_key_exists('redirect_url', $aChanges)) {
|
||||
$sMessage = Dict::S('itop-oauth-client:Message:RegenerateToken');
|
||||
self::SetSessionMessage(get_class($this), $this->GetKey(), 'RegenerateToken', $sMessage, 'info', 1);
|
||||
$this->Set('status', 'inactive');
|
||||
}
|
||||
}
|
||||
]]></code>
|
||||
</method>
|
||||
@@ -872,21 +868,6 @@
|
||||
}
|
||||
]]></code>
|
||||
</method>
|
||||
<method id="OnUpdate">
|
||||
<static>false</static>
|
||||
<access>protected</access>
|
||||
<type>Overload-DBObject</type>
|
||||
<code><![CDATA[
|
||||
protected function OnUpdate()
|
||||
{
|
||||
$aChanges = $this->ListChanges();
|
||||
if (array_key_exists('client_id', $aChanges) || array_key_exists('client_secret', $aChanges) || array_key_exists('redirect_url', $aChanges)) {
|
||||
$sMessage = Dict::S('itop-oauth-client:Message:RegenerateToken');
|
||||
self::SetSessionMessage(get_class($this), $this->GetKey(), 'RegenerateToken', $sMessage, 'info', 1);
|
||||
}
|
||||
}
|
||||
]]></code>
|
||||
</method>
|
||||
</methods>
|
||||
</class>
|
||||
</classes>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-oauth-client/2.7.8',
|
||||
'itop-oauth-client/2.7.9',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -11,6 +11,7 @@ use Combodo\iTop\Application\TwigBase\Controller\Controller;
|
||||
use Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProviderFactory;
|
||||
use Dict;
|
||||
use IssueLog;
|
||||
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
|
||||
use MetaModel;
|
||||
use utils;
|
||||
|
||||
@@ -64,13 +65,15 @@ class AjaxOauthClientController extends Controller
|
||||
}
|
||||
if (isset($aQuery['code'])) {
|
||||
$sCode = $aQuery['code'];
|
||||
$oAccessToken = OAuthClientProviderFactory::GetAccessTokenFromCode($oOAuthClient, $sCode);
|
||||
|
||||
$oOAuthClient->SetAccessToken($oAccessToken);
|
||||
|
||||
|
||||
|
||||
$aResult['status'] = 'success';
|
||||
try {
|
||||
$oAccessToken = OAuthClientProviderFactory::GetAccessTokenFromCode($oOAuthClient, $sCode);
|
||||
$oOAuthClient->SetAccessToken($oAccessToken);
|
||||
$aResult['status'] = 'success';
|
||||
}
|
||||
catch (IdentityProviderException $e) {
|
||||
$aResult['status'] = 'error';
|
||||
$aResult['error_description'] = $e->getMessage();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$aResult['status'] = 'error';
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
namespace Combodo\iTop\OAuthClient\Service;
|
||||
|
||||
use ApplicationContext;
|
||||
use Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProviderFactory;
|
||||
use Dict;
|
||||
use iPopupMenuExtension;
|
||||
use JSPopupMenuItem;
|
||||
@@ -42,11 +41,10 @@ class PopupMenuExtension implements \iPopupMenuExtension
|
||||
$sAjaxUri = utils::GetAbsoluteUrlModulePage(static::MODULE_CODE, 'ajax.php');
|
||||
// Add a new menu item that triggers a custom JS function defined in our own javascript file: js/sample.js
|
||||
$sJSFileUrl = utils::GetAbsoluteUrlModulesRoot().static::MODULE_CODE.'/assets/js/oauth_connect.js';
|
||||
$sRedirectUri = OAuthClientProviderFactory::GetRedirectUri();
|
||||
$aResult[] = new JSPopupMenuItem(
|
||||
$sMenu.' from '.$sObjClass,
|
||||
Dict::S($sMenu),
|
||||
"OAuthConnect('$sClass', $sId, '$sAjaxUri', '$sRedirectUri')",
|
||||
"OAuthConnect('$sClass', $sId, '$sAjaxUri')",
|
||||
[$sJSFileUrl]
|
||||
);
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-portal-base/2.7.8', array(
|
||||
'itop-portal-base/2.7.9', array(
|
||||
// Identification
|
||||
'label' => 'Portal Development Library',
|
||||
'category' => 'Portal',
|
||||
|
||||
@@ -31,6 +31,7 @@ use Combodo\iTop\Form\Field\LabelField;
|
||||
use Combodo\iTop\Form\Form;
|
||||
use Combodo\iTop\Form\FormManager;
|
||||
use Combodo\iTop\Portal\Helper\ApplicationHelper;
|
||||
use CoreCannotSaveObjectException;
|
||||
use DBObject;
|
||||
use DBObjectSearch;
|
||||
use DBObjectSet;
|
||||
@@ -1145,6 +1146,9 @@ class ObjectFormManager extends FormManager
|
||||
{
|
||||
$this->oObject->DBWrite();
|
||||
}
|
||||
catch (CoreCannotSaveObjectException $e) {
|
||||
throw new Exception($e->getHtmlMessage());
|
||||
}
|
||||
catch (Exception $e) {
|
||||
if ($bIsNew) {
|
||||
throw new Exception(Dict::S('Portal:Error:ObjectCannotBeCreated'));
|
||||
|
||||
@@ -19,7 +19,9 @@
|
||||
|
||||
namespace Combodo\iTop\Portal\Twig;
|
||||
|
||||
use Closure;
|
||||
use Dict;
|
||||
use IssueLog;
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig_SimpleFilter;
|
||||
use Twig_SimpleFunction;
|
||||
@@ -98,17 +100,96 @@ class AppExtension extends AbstractExtension
|
||||
return $sUrl;
|
||||
});
|
||||
//since 2.7.7 3.0.2 3.1.0 N°4867 "Twig content not allowed" error when use the extkey widget search icon in the user portal
|
||||
//overwrite native twig filter : disable use of 'system' filter
|
||||
// Since 2.7.8 filter more functions as filter 'filter' is used by the portal
|
||||
$filters[] = new Twig_SimpleFilter('filter', function ($array, $arrow) {
|
||||
if ($arrow == 'system'){
|
||||
return json_encode($array);
|
||||
$ret = $this->SanitizeFilter($array, $arrow);
|
||||
if ($ret !== false) {
|
||||
IssueLog::Error('Twig "filter" filter has limited capabilities');
|
||||
return [$ret];
|
||||
}
|
||||
return twig_array_filter($array, $arrow);
|
||||
return twig_array_filter($array, $arrow);
|
||||
});
|
||||
// Since 2.7.8 deactivate map
|
||||
$filters[] = new Twig_SimpleFilter('map', function ($array, $arrow) {
|
||||
IssueLog::Error('Twig "map" filter is deactivated');
|
||||
return $array;
|
||||
});
|
||||
// Since 2.7.8 deactivate reduce
|
||||
$filters[] = new Twig_SimpleFilter('reduce', function ($array, $arrow, $initial = null) {
|
||||
IssueLog::Error('Twig "reduce" filter is deactivated');
|
||||
return $array;
|
||||
});
|
||||
|
||||
return $filters;
|
||||
}
|
||||
|
||||
private function SanitizeFilter($array, $arrow)
|
||||
{
|
||||
$aRestricted = [
|
||||
'system',
|
||||
'exec',
|
||||
'passthru',
|
||||
'popen',
|
||||
'proc_open',
|
||||
'shell_exec',
|
||||
'file_get_contents',
|
||||
'file_put_contents',
|
||||
'eval',
|
||||
'pcntl_exec',
|
||||
'chgrp',
|
||||
'chmod',
|
||||
'chown',
|
||||
'lchgrp',
|
||||
'lchown',
|
||||
'umask',
|
||||
'copy',
|
||||
'delete',
|
||||
'unlink',
|
||||
'link',
|
||||
'mkdir',
|
||||
'rmdir',
|
||||
'rename',
|
||||
'symlink',
|
||||
'tempnam',
|
||||
'tmpfile',
|
||||
'touch',
|
||||
'fgetc',
|
||||
'fgetcsv',
|
||||
'fgets',
|
||||
'fgetss',
|
||||
'file',
|
||||
'flock',
|
||||
'fopen',
|
||||
'fpassthru',
|
||||
'fputcsv',
|
||||
'fputs',
|
||||
'fread',
|
||||
'fscanf',
|
||||
'ftruncate',
|
||||
'fwrite',
|
||||
'glob',
|
||||
'readfile',
|
||||
'readlink',
|
||||
'parse_ini_file',
|
||||
'mail',
|
||||
];
|
||||
$aRestrictedStartWith = ['ftp_', 'zip_', 'stream_'];
|
||||
|
||||
if (is_string($arrow)) {
|
||||
if (in_array(strtolower($arrow), $aRestricted)) {
|
||||
return json_encode($array);
|
||||
}
|
||||
foreach ($aRestrictedStartWith as $sRestrictedStartWith) {
|
||||
if (utils::StartsWith($arrow, $sRestrictedStartWith)) {
|
||||
return json_encode($array);
|
||||
}
|
||||
}
|
||||
} elseif ($arrow instanceof Closure) {
|
||||
return json_encode($array);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|\Twig\TwigFunction[]|\Twig_SimpleFunction[]
|
||||
*/
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-portal/2.7.8', array(
|
||||
'itop-portal/2.7.9', array(
|
||||
// Identification
|
||||
'label' => 'Enhanced Customer Portal',
|
||||
'category' => 'Portal',
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-problem-mgmt/2.7.8',
|
||||
'itop-problem-mgmt/2.7.9',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-profiles-itil/2.7.8',
|
||||
'itop-profiles-itil/2.7.9',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-request-mgmt-itil/2.7.8',
|
||||
'itop-request-mgmt-itil/2.7.9',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-request-mgmt/2.7.8',
|
||||
'itop-request-mgmt/2.7.9',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-service-mgmt-provider/2.7.8',
|
||||
'itop-service-mgmt-provider/2.7.9',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-service-mgmt/2.7.8',
|
||||
'itop-service-mgmt/2.7.9',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-sla-computation/2.7.8',
|
||||
'itop-sla-computation/2.7.9',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-storage-mgmt/2.7.8',
|
||||
'itop-storage-mgmt/2.7.9',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__,
|
||||
'itop-tickets/2.7.8',
|
||||
'itop-tickets/2.7.9',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-virtualization-mgmt/2.7.8',
|
||||
'itop-virtualization-mgmt/2.7.9',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-welcome-itil/2.7.8',
|
||||
'itop-welcome-itil/2.7.9',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<information>
|
||||
<version>2.7.8</version>
|
||||
<version>2.7.9</version>
|
||||
</information>
|
||||
|
||||
@@ -496,6 +496,7 @@ Dict::Add('EN US', 'English', 'English', array(
|
||||
'UI:Loading' => 'Loading...',
|
||||
'UI:Menu:Actions' => 'Actions',
|
||||
'UI:Menu:OtherActions' => 'Other Actions',
|
||||
'UI:Menu:Toolkit' => 'Toolkit',
|
||||
'UI:Menu:New' => 'New...',
|
||||
'UI:Menu:Add' => 'Add...',
|
||||
'UI:Menu:Manage' => 'Manage...',
|
||||
@@ -560,6 +561,7 @@ Dict::Add('EN US', 'English', 'English', array(
|
||||
'UI:Login:NewPasswordPrompt' => 'New password',
|
||||
'UI:Login:RetypeNewPasswordPrompt' => 'Retype new password',
|
||||
'UI:Login:IncorrectOldPassword' => 'Error: the old password is incorrect',
|
||||
'UI:PowerMenu' => 'Open user menu',
|
||||
'UI:LogOffMenu' => 'Log off',
|
||||
'UI:LogOff:ThankYou' => 'Thank you for using '.ITOP_APPLICATION,
|
||||
'UI:LogOff:ClickHereToLoginAgain' => 'Click here to login again...',
|
||||
|
||||
@@ -479,6 +479,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
|
||||
'UI:Loading' => 'Chargement...',
|
||||
'UI:Menu:Actions' => 'Actions',
|
||||
'UI:Menu:OtherActions' => 'Autres Actions',
|
||||
'UI:Menu:Toolkit' => 'Trousse à outils',
|
||||
'UI:Menu:New' => 'Créer...',
|
||||
'UI:Menu:Add' => 'Ajouter...',
|
||||
'UI:Menu:Manage' => 'Gérer...',
|
||||
@@ -543,6 +544,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
|
||||
'UI:Login:NewPasswordPrompt' => 'Nouveau mot de passe',
|
||||
'UI:Login:RetypeNewPasswordPrompt' => 'Resaisir le nouveau mot de passe',
|
||||
'UI:Login:IncorrectOldPassword' => 'Erreur: l\'ancien mot de passe est incorrect',
|
||||
'UI:PowerMenu' => 'Ouvre le menu utilisateur',
|
||||
'UI:LogOffMenu' => 'Déconnexion',
|
||||
'UI:LogOff:ThankYou' => 'Merci d\'avoir utilisé iTop',
|
||||
'UI:LogOff:ClickHereToLoginAgain' => 'Cliquez ici pour vous reconnecter...',
|
||||
|
||||
@@ -77,6 +77,7 @@ $(function()
|
||||
sBreadCrumbHtml += '<div class="breadcrumb-item breadcrumb-current" breadcrumb-entry="'+iEntry+'" title="'+sTitle+'">'+sIconSpec+'<span class="truncate">'+sLabel+'</span></div>';
|
||||
} else {
|
||||
var sSanitizedUrl = StripArchiveArgument(oEntry['url']);
|
||||
sSanitizedUrl = EncodeHtml(sSanitizedUrl, false);
|
||||
sBreadCrumbHtml += '<div class="breadcrumb-item"><a class="breadcrumb-link" breadcrumb-entry="'+iEntry+'" href="'+sSanitizedUrl+'" title="'+sTitle+'">'+sIconSpec+'<span class="truncate">'+sLabel+'</span></a></div>';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,7 +345,7 @@ $(function()
|
||||
oParams.dashletid = sTempDashletId;
|
||||
|
||||
$.post(this.options.new_dashletid_endpoint, oParams, function(data) {
|
||||
var sFinalDashletId = data;
|
||||
var sFinalDashletId = data.trim();
|
||||
me.add_dashlet_prepare(options, sFinalDashletId);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
/*!
|
||||
* jQuery BBQ: Back Button & Query Library - v1.2.1 - 2/17/2010
|
||||
* Combodo: issued from https://github.com/cee-chen/jquery-bbq
|
||||
* fork from http://github.com/cowboy/jquery-bbq/
|
||||
*
|
||||
* IMPORTANT: Part for detecting MSIE has been removed as it was not present in the previous version and is making the app crash.
|
||||
* It's far from perfect but as mentioned in the previous commit, we don't have much choices for now
|
||||
*/
|
||||
|
||||
/*!
|
||||
* jQuery BBQ: Back Button & Query Library - v1.3pre - 8/26/2010
|
||||
* http://benalman.com/projects/jquery-bbq-plugin/
|
||||
*
|
||||
* Copyright (c) 2010 "Cowboy" Ben Alman
|
||||
@@ -9,12 +17,12 @@
|
||||
|
||||
// Script: jQuery BBQ: Back Button & Query Library
|
||||
//
|
||||
// *Version: 1.2.1, Last updated: 2/17/2010*
|
||||
// *Version: 1.3pre, Last updated: 8/26/2010*
|
||||
//
|
||||
// Project Home - http://benalman.com/projects/jquery-bbq-plugin/
|
||||
// GitHub - http://github.com/cowboy/jquery-bbq/
|
||||
// Source - http://github.com/cowboy/jquery-bbq/raw/master/jquery.ba-bbq.js
|
||||
// (Minified) - http://github.com/cowboy/jquery-bbq/raw/master/jquery.ba-bbq.min.js (4.0kb)
|
||||
// (Minified) - http://github.com/cowboy/jquery-bbq/raw/master/jquery.ba-bbq.min.js (2.2kb gzipped)
|
||||
//
|
||||
// About: License
|
||||
//
|
||||
@@ -38,13 +46,21 @@
|
||||
// tested with, what browsers it has been tested in, and where the unit tests
|
||||
// reside (so you can test it yourself).
|
||||
//
|
||||
// jQuery Versions - 1.3.2, 1.4.1, 1.4.2
|
||||
// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.7, Safari 3-4,
|
||||
// Chrome 4-5, Opera 9.6-10.1.
|
||||
// jQuery Versions - 1.2.6, 1.3.2, 1.4.1, 1.4.2
|
||||
// Browsers Tested - Internet Explorer 6-8, Firefox 2-4, Chrome 5-6, Safari 3.2-5,
|
||||
// Opera 9.6-10.60, iPhone 3.1, Android 1.6-2.2, BlackBerry 4.6-5.
|
||||
// Unit Tests - http://benalman.com/code/projects/jquery-bbq/unit/
|
||||
//
|
||||
// About: Release History
|
||||
//
|
||||
// 1.3pre - (8/26/2010) Integrated <jQuery hashchange event> v1.3, which adds
|
||||
// document.title and document.domain support in IE6/7, BlackBerry
|
||||
// support, better Iframe hiding for accessibility reasons, and the new
|
||||
// <jQuery.fn.hashchange> "shortcut" method. Added the
|
||||
// <jQuery.param.sorted> method which reduces the possibility of
|
||||
// extraneous hashchange event triggering. Added the
|
||||
// <jQuery.param.fragment.ajaxCrawlable> method which can be used to
|
||||
// enable Google "AJAX Crawlable mode."
|
||||
// 1.2.1 - (2/17/2010) Actually fixed the stale window.location Safari bug from
|
||||
// <jQuery hashchange event> in BBQ, which was the main reason for the
|
||||
// previous release!
|
||||
@@ -87,6 +103,7 @@
|
||||
|
||||
// Method / object references.
|
||||
jq_param = $.param,
|
||||
jq_param_sorted,
|
||||
jq_param_fragment,
|
||||
jq_deparam,
|
||||
jq_deparam_fragment,
|
||||
@@ -94,22 +111,25 @@
|
||||
jq_bbq_pushState,
|
||||
jq_bbq_getState,
|
||||
jq_elemUrlAttr,
|
||||
jq_event_special = $.event.special,
|
||||
special = $.event.special,
|
||||
|
||||
// Reused strings.
|
||||
str_hashchange = 'hashchange',
|
||||
str_querystring = 'querystring',
|
||||
str_fragment = 'fragment',
|
||||
str_elemUrlAttr = 'elemUrlAttr',
|
||||
str_location = 'location',
|
||||
str_href = 'href',
|
||||
str_src = 'src',
|
||||
|
||||
// Reused RegExp.
|
||||
re_trim_querystring = /^.*\?|#.*$/g,
|
||||
re_trim_fragment = /^.*\#/,
|
||||
re_params_querystring = /^.*\?|#.*$/g,
|
||||
re_params_fragment,
|
||||
re_fragment,
|
||||
re_no_escape,
|
||||
|
||||
ajax_crawlable,
|
||||
fragment_prefix,
|
||||
|
||||
// Used by jQuery.elemUrlAttr.
|
||||
elemUrlAttr_cache = {};
|
||||
|
||||
@@ -132,7 +152,7 @@
|
||||
// Get location.hash (or what you'd expect location.hash to be) sans any
|
||||
// leading #. Thanks for making this necessary, Firefox!
|
||||
function get_fragment( url ) {
|
||||
return url.replace( /^[^#]*#?(.*)$/, '$1' );
|
||||
return url.replace( re_fragment, '$2' );
|
||||
};
|
||||
|
||||
// Get location.search (or what you'd expect location.search to be) sans any
|
||||
@@ -146,7 +166,7 @@
|
||||
// Method: jQuery.param.querystring
|
||||
//
|
||||
// Retrieve the query string from a URL or if no arguments are passed, the
|
||||
// current window.location.
|
||||
// current window.location.href.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
@@ -155,7 +175,7 @@
|
||||
// Arguments:
|
||||
//
|
||||
// url - (String) A URL containing query string params to be parsed. If url
|
||||
// is not passed, the current window.location is used.
|
||||
// is not passed, the current window.location.href is used.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
@@ -189,13 +209,12 @@
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// (String) Either a params string with urlencoded data or a URL with a
|
||||
// urlencoded query string in the format 'a=b&c=d&e=f'.
|
||||
// (String) A URL with a urlencoded query string in the format '?a=b&c=d&e=f'.
|
||||
|
||||
// Method: jQuery.param.fragment
|
||||
//
|
||||
// Retrieve the fragment (hash) from a URL or if no arguments are passed, the
|
||||
// current window.location.
|
||||
// current window.location.href.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
@@ -204,7 +223,7 @@
|
||||
// Arguments:
|
||||
//
|
||||
// url - (String) A URL containing fragment (hash) params to be parsed. If
|
||||
// url is not passed, the current window.location is used.
|
||||
// url is not passed, the current window.location.href is used.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
@@ -238,8 +257,7 @@
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// (String) Either a params string with urlencoded data or a URL with a
|
||||
// urlencoded fragment (hash) in the format 'a=b&c=d&e=f'.
|
||||
// (String) A URL with a urlencoded fragment (hash) in the format '#a=b&c=d&e=f'.
|
||||
|
||||
function jq_param_sub( is_fragment, get_func, url, params, merge_mode ) {
|
||||
var result,
|
||||
@@ -254,7 +272,7 @@
|
||||
// matches[1] = url part that precedes params, not including trailing ?/#
|
||||
// matches[2] = params, not including leading ?/#
|
||||
// matches[3] = if in 'querystring' mode, hash including leading #, otherwise ''
|
||||
matches = url.match( is_fragment ? /^([^#]*)\#?(.*)$/ : /^([^#?]*)\??([^#]*)(#?.*)/ );
|
||||
matches = url.match( is_fragment ? re_fragment : /^([^#?]*)\??([^#]*)(#?.*)/ );
|
||||
|
||||
// Get the hash if in 'querystring' mode, and it exists.
|
||||
hash = matches[3] || '';
|
||||
@@ -262,7 +280,7 @@
|
||||
if ( merge_mode === 2 && is_string( params ) ) {
|
||||
// If merge_mode is 2 and params is a string, merge the fragment / query
|
||||
// string into the URL wholesale, without converting it into an object.
|
||||
qs = params.replace( is_fragment ? re_trim_fragment : re_trim_querystring, '' );
|
||||
qs = params.replace( is_fragment ? re_params_fragment : re_params_querystring, '' );
|
||||
|
||||
} else {
|
||||
// Convert relevant params in url to object.
|
||||
@@ -280,8 +298,8 @@
|
||||
: merge_mode === 1 ? $.extend( {}, params, url_params ) // url params override passed params
|
||||
: $.extend( {}, url_params, params ); // passed params override url params
|
||||
|
||||
// Convert params object to a string.
|
||||
qs = jq_param( qs );
|
||||
// Convert params object into a sorted params string.
|
||||
qs = jq_param_sorted( qs );
|
||||
|
||||
// Unescape characters specified via $.param.noEscape. Since only hash-
|
||||
// history users have requested this feature, it's only enabled for
|
||||
@@ -294,12 +312,12 @@
|
||||
// Build URL from the base url, querystring and hash. In 'querystring'
|
||||
// mode, ? is only added if a query string exists. In 'fragment' mode, #
|
||||
// is always added.
|
||||
result = matches[1] + ( is_fragment ? '#' : qs || !matches[1] ? '?' : '' ) + qs + hash;
|
||||
result = matches[1] + ( is_fragment ? fragment_prefix : qs || !matches[1] ? '?' : '' ) + qs + hash;
|
||||
|
||||
} else {
|
||||
// If URL was passed in, parse params from URL string, otherwise parse
|
||||
// params from window.location.
|
||||
result = get_func( url !== undefined ? url : window[ str_location ][ str_href ] );
|
||||
// params from window.location.href.
|
||||
result = get_func( url !== undefined ? url : location.href );
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -308,6 +326,56 @@
|
||||
jq_param[ str_querystring ] = curry( jq_param_sub, 0, get_querystring );
|
||||
jq_param[ str_fragment ] = jq_param_fragment = curry( jq_param_sub, 1, get_fragment );
|
||||
|
||||
// Method: jQuery.param.sorted
|
||||
//
|
||||
// Returns a params string equivalent to that returned by the internal
|
||||
// jQuery.param method, but sorted, which makes it suitable for use as a
|
||||
// cache key.
|
||||
//
|
||||
// For example, in most browsers jQuery.param({z:1,a:2}) returns "z=1&a=2"
|
||||
// and jQuery.param({a:2,z:1}) returns "a=2&z=1". Even though both the
|
||||
// objects being serialized and the resulting params strings are equivalent,
|
||||
// if these params strings were set into the location.hash fragment
|
||||
// sequentially, the hashchange event would be triggered unnecessarily, since
|
||||
// the strings are different (even though the data described by them is the
|
||||
// same). By sorting the params string, unecessary hashchange event triggering
|
||||
// can be avoided.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// > jQuery.param.sorted( obj [, traditional ] );
|
||||
//
|
||||
// Arguments:
|
||||
//
|
||||
// obj - (Object) An object to be serialized.
|
||||
// traditional - (Boolean) Params deep/shallow serialization mode. See the
|
||||
// documentation at http://api.jquery.com/jQuery.param/ for more detail.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// (String) A sorted params string.
|
||||
|
||||
jq_param.sorted = jq_param_sorted = function( a, traditional ) {
|
||||
var arr = [],
|
||||
obj = {};
|
||||
|
||||
$.each( jq_param( a, traditional ).split( '&' ), function(i,v){
|
||||
var key = v.replace( /(?:%5B|=).*$/, '' ),
|
||||
key_obj = obj[ key ];
|
||||
|
||||
if ( !key_obj ) {
|
||||
key_obj = obj[ key ] = [];
|
||||
arr.push( key );
|
||||
}
|
||||
|
||||
key_obj.push( v );
|
||||
});
|
||||
|
||||
return $.map( arr.sort(), function(v){
|
||||
return obj[ v ];
|
||||
}).join( '&' );
|
||||
};
|
||||
|
||||
// Method: jQuery.param.fragment.noEscape
|
||||
//
|
||||
// Specify characters that will be left unescaped when fragments are created
|
||||
@@ -346,6 +414,41 @@
|
||||
// "uglifying up the URL" the most.
|
||||
jq_param_fragment.noEscape( ',/' );
|
||||
|
||||
// Method: jQuery.param.fragment.ajaxCrawlable
|
||||
//
|
||||
// TODO: DESCRIBE
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// > jQuery.param.fragment.ajaxCrawlable( [ state ] );
|
||||
//
|
||||
// Arguments:
|
||||
//
|
||||
// state - (Boolean) TODO: DESCRIBE
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// (Boolean) The current ajaxCrawlable state.
|
||||
|
||||
jq_param_fragment.ajaxCrawlable = function( state ) {
|
||||
if ( state !== undefined ) {
|
||||
if ( state ) {
|
||||
re_params_fragment = /^.*(?:#!|#)/;
|
||||
re_fragment = /^([^#]*)(?:#!|#)?(.*)$/;
|
||||
fragment_prefix = '#!';
|
||||
} else {
|
||||
re_params_fragment = /^.*#/;
|
||||
re_fragment = /^([^#]*)#?(.*)$/;
|
||||
fragment_prefix = '#';
|
||||
}
|
||||
ajax_crawlable = !!state;
|
||||
}
|
||||
|
||||
return ajax_crawlable;
|
||||
};
|
||||
|
||||
jq_param_fragment.ajaxCrawlable( 0 );
|
||||
|
||||
// Section: Deparam (from string)
|
||||
//
|
||||
// Method: jQuery.deparam
|
||||
@@ -369,7 +472,7 @@
|
||||
// (Object) An object representing the deserialized params string.
|
||||
|
||||
$.deparam = jq_deparam = function( params, coerce ) {
|
||||
var obj = {},
|
||||
var obj = Object.create(null),
|
||||
coerce_types = { 'true': !0, 'false': !1, 'null': null };
|
||||
|
||||
// Iterate over all name=value pairs.
|
||||
@@ -426,7 +529,7 @@
|
||||
for ( ; i <= keys_last; i++ ) {
|
||||
key = keys[i] === '' ? cur.length : keys[i];
|
||||
cur = cur[key] = i < keys_last
|
||||
? cur[key] || ( keys[i+1] && isNaN( keys[i+1] ) ? {} : [] )
|
||||
? cur[key] || ( keys[i+1] && isNaN( keys[i+1] ) ? Object.create(null) : [] )
|
||||
: val;
|
||||
}
|
||||
|
||||
@@ -462,7 +565,7 @@
|
||||
|
||||
// Method: jQuery.deparam.querystring
|
||||
//
|
||||
// Parse the query string from a URL or the current window.location,
|
||||
// Parse the query string from a URL or the current window.location.href,
|
||||
// deserializing it into an object, optionally coercing numbers, booleans,
|
||||
// null and undefined values.
|
||||
//
|
||||
@@ -473,8 +576,8 @@
|
||||
// Arguments:
|
||||
//
|
||||
// url - (String) An optional params string or URL containing query string
|
||||
// params to be parsed. If url is omitted, the current window.location
|
||||
// is used.
|
||||
// params to be parsed. If url is omitted, the current
|
||||
// window.location.href is used.
|
||||
// coerce - (Boolean) If true, coerces any numbers or true, false, null, and
|
||||
// undefined to their actual value. Defaults to false if omitted.
|
||||
//
|
||||
@@ -484,7 +587,7 @@
|
||||
|
||||
// Method: jQuery.deparam.fragment
|
||||
//
|
||||
// Parse the fragment (hash) from a URL or the current window.location,
|
||||
// Parse the fragment (hash) from a URL or the current window.location.href,
|
||||
// deserializing it into an object, optionally coercing numbers, booleans,
|
||||
// null and undefined values.
|
||||
//
|
||||
@@ -495,7 +598,7 @@
|
||||
// Arguments:
|
||||
//
|
||||
// url - (String) An optional params string or URL containing fragment (hash)
|
||||
// params to be parsed. If url is omitted, the current window.location
|
||||
// params to be parsed. If url is omitted, the current window.location.href
|
||||
// is used.
|
||||
// coerce - (Boolean) If true, coerces any numbers or true, false, null, and
|
||||
// undefined to their actual value. Defaults to false if omitted.
|
||||
@@ -511,7 +614,7 @@
|
||||
url_or_params = jq_param[ is_fragment ? str_fragment : str_querystring ]();
|
||||
} else {
|
||||
url_or_params = is_string( url_or_params )
|
||||
? url_or_params.replace( is_fragment ? re_trim_fragment : re_trim_querystring, '' )
|
||||
? url_or_params.replace( is_fragment ? re_params_fragment : re_params_querystring, '' )
|
||||
: url_or_params;
|
||||
}
|
||||
|
||||
@@ -715,13 +818,12 @@
|
||||
|
||||
var has_args = params !== undefined,
|
||||
// Merge params into window.location using $.param.fragment.
|
||||
url = jq_param_fragment( window[ str_location ][ str_href ],
|
||||
url = jq_param_fragment( location.href,
|
||||
has_args ? params : {}, has_args ? merge_mode : 2 );
|
||||
|
||||
// Set new window.location.href. If hash is empty, use just # to prevent
|
||||
// browser from reloading the page. Note that Safari 3 & Chrome barf on
|
||||
// location.hash = '#'.
|
||||
window[ str_location ][ str_href ] = url + ( /#/.test( url ) ? '' : '#' );
|
||||
// Set new window.location.href. Note that Safari 3 & Chrome barf on
|
||||
// location.hash = '#' so the entire URL is set.
|
||||
location.href = url;
|
||||
};
|
||||
|
||||
// Method: jQuery.bbq.getState
|
||||
@@ -850,7 +952,7 @@
|
||||
// required to enable the augmented event object in jQuery 1.4.2 and newer.
|
||||
// * See <jQuery hashchange event> for more detailed information.
|
||||
|
||||
jq_event_special[ str_hashchange ] = $.extend( jq_event_special[ str_hashchange ], {
|
||||
special[ str_hashchange ] = $.extend( special[ str_hashchange ], {
|
||||
|
||||
// Augmenting the event object with the .fragment property and .getState
|
||||
// method requires jQuery 1.4 or newer. Note: with 1.3.2, everything will
|
||||
@@ -892,7 +994,7 @@
|
||||
})(jQuery,this);
|
||||
|
||||
/*!
|
||||
* jQuery hashchange event - v1.2 - 2/11/2010
|
||||
* jQuery hashchange event - v1.3 - 7/21/2010
|
||||
* http://benalman.com/projects/jquery-hashchange-plugin/
|
||||
*
|
||||
* Copyright (c) 2010 "Cowboy" Ben Alman
|
||||
@@ -902,12 +1004,12 @@
|
||||
|
||||
// Script: jQuery hashchange event
|
||||
//
|
||||
// *Version: 1.2, Last updated: 2/11/2010*
|
||||
// *Version: 1.3, Last updated: 7/21/2010*
|
||||
//
|
||||
// Project Home - http://benalman.com/projects/jquery-hashchange-plugin/
|
||||
// GitHub - http://github.com/cowboy/jquery-hashchange/
|
||||
// Source - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.js
|
||||
// (Minified) - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.min.js (1.1kb)
|
||||
// (Minified) - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.min.js (0.8kb gzipped)
|
||||
//
|
||||
// About: License
|
||||
//
|
||||
@@ -917,10 +1019,11 @@
|
||||
//
|
||||
// About: Examples
|
||||
//
|
||||
// This working example, complete with fully commented code, illustrate one way
|
||||
// in which this plugin can be used.
|
||||
// These working examples, complete with fully commented code, illustrate a few
|
||||
// ways in which this plugin can be used.
|
||||
//
|
||||
// hashchange event - http://benalman.com/code/projects/jquery-hashchange/examples/hashchange/
|
||||
// document.domain - http://benalman.com/code/projects/jquery-hashchange/examples/document_domain/
|
||||
//
|
||||
// About: Support and Testing
|
||||
//
|
||||
@@ -928,24 +1031,40 @@
|
||||
// tested with, what browsers it has been tested in, and where the unit tests
|
||||
// reside (so you can test it yourself).
|
||||
//
|
||||
// jQuery Versions - 1.3.2, 1.4.1, 1.4.2
|
||||
// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.7, Safari 3-4, Chrome, Opera 9.6-10.1.
|
||||
// jQuery Versions - 1.2.6, 1.3.2, 1.4.1, 1.4.2
|
||||
// Browsers Tested - Internet Explorer 6-8, Firefox 2-4, Chrome 5-6, Safari 3.2-5,
|
||||
// Opera 9.6-10.60, iPhone 3.1, Android 1.6-2.2, BlackBerry 4.6-5.
|
||||
// Unit Tests - http://benalman.com/code/projects/jquery-hashchange/unit/
|
||||
//
|
||||
// About: Known issues
|
||||
//
|
||||
// While this jQuery hashchange event implementation is quite stable and robust,
|
||||
// there are a few unfortunate browser bugs surrounding expected hashchange
|
||||
// event-based behaviors, independent of any JavaScript window.onhashchange
|
||||
// abstraction. See the following examples for more information:
|
||||
// While this jQuery hashchange event implementation is quite stable and
|
||||
// robust, there are a few unfortunate browser bugs surrounding expected
|
||||
// hashchange event-based behaviors, independent of any JavaScript
|
||||
// window.onhashchange abstraction. See the following examples for more
|
||||
// information:
|
||||
//
|
||||
// Chrome: Back Button - http://benalman.com/code/projects/jquery-hashchange/examples/bug-chrome-back-button/
|
||||
// Firefox: Remote XMLHttpRequest - http://benalman.com/code/projects/jquery-hashchange/examples/bug-firefox-remote-xhr/
|
||||
// WebKit: Back Button in an Iframe - http://benalman.com/code/projects/jquery-hashchange/examples/bug-webkit-hash-iframe/
|
||||
// Safari: Back Button from a different domain - http://benalman.com/code/projects/jquery-hashchange/examples/bug-safari-back-from-diff-domain/
|
||||
//
|
||||
// Also note that should a browser natively support the window.onhashchange
|
||||
// event, but not report that it does, the fallback polling loop will be used.
|
||||
//
|
||||
// About: Release History
|
||||
//
|
||||
// 1.3 - (7/21/2010) Reorganized IE6/7 Iframe code to make it more
|
||||
// "removable" for mobile-only development. Added IE6/7 document.title
|
||||
// support. Attempted to make Iframe as hidden as possible by using
|
||||
// techniques from http://www.paciellogroup.com/blog/?p=604. Added
|
||||
// support for the "shortcut" format $(window).hashchange( fn ) and
|
||||
// $(window).hashchange() like jQuery provides for built-in events.
|
||||
// Renamed jQuery.hashchangeDelay to <jQuery.fn.hashchange.delay> and
|
||||
// lowered its default value to 50. Added <jQuery.fn.hashchange.domain>
|
||||
// and <jQuery.fn.hashchange.src> properties plus document-domain.html
|
||||
// file to address access denied issues when setting document.domain in
|
||||
// IE6/7.
|
||||
// 1.2 - (2/11/2010) Fixed a bug where coming back to a page using this plugin
|
||||
// from a page on another domain would cause an error in Safari 4. Also,
|
||||
// IE6/7 Iframe is now inserted after the body (this actually works),
|
||||
@@ -964,63 +1083,144 @@
|
||||
(function($,window,undefined){
|
||||
'$:nomunge'; // Used by YUI compressor.
|
||||
|
||||
// Method / object references.
|
||||
var fake_onhashchange,
|
||||
jq_event_special = $.event.special,
|
||||
// Reused string.
|
||||
var str_hashchange = 'hashchange',
|
||||
|
||||
// Reused strings.
|
||||
str_location = 'location',
|
||||
str_hashchange = 'hashchange',
|
||||
str_href = 'href',
|
||||
// Method / object references.
|
||||
doc = document,
|
||||
fake_onhashchange,
|
||||
special = $.event.special,
|
||||
|
||||
mode = document.documentMode,
|
||||
is_old_ie = false,
|
||||
|
||||
// Does the browser support window.onhashchange? Test for IE version, since
|
||||
// IE8 incorrectly reports this when in "IE7" or "IE8 Compatibility View"!
|
||||
supports_onhashchange = 'on' + str_hashchange in window && !is_old_ie;
|
||||
// Does the browser support window.onhashchange? Note that IE8 running in
|
||||
// IE7 compatibility mode reports true for 'onhashchange' in window, even
|
||||
// though the event isn't supported, so also test document.documentMode.
|
||||
doc_mode = doc.documentMode,
|
||||
supports_onhashchange = 'on' + str_hashchange in window && ( doc_mode === undefined || doc_mode > 7 );
|
||||
|
||||
// Get location.hash (or what you'd expect location.hash to be) sans any
|
||||
// leading #. Thanks for making this necessary, Firefox!
|
||||
function get_fragment( url ) {
|
||||
url = url || window[ str_location ][ str_href ];
|
||||
return url.replace( /^[^#]*#?(.*)$/, '$1' );
|
||||
url = url || location.href;
|
||||
return '#' + url.replace( /^[^#]*#?(.*)$/, '$1' );
|
||||
};
|
||||
|
||||
// Property: jQuery.hashchangeDelay
|
||||
// Method: jQuery.fn.hashchange
|
||||
//
|
||||
// Bind a handler to the window.onhashchange event or trigger all bound
|
||||
// window.onhashchange event handlers. This behavior is consistent with
|
||||
// jQuery's built-in event handlers.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// > jQuery(window).hashchange( [ handler ] );
|
||||
//
|
||||
// Arguments:
|
||||
//
|
||||
// handler - (Function) Optional handler to be bound to the hashchange
|
||||
// event. This is a "shortcut" for the more verbose form:
|
||||
// jQuery(window).bind( 'hashchange', handler ). If handler is omitted,
|
||||
// all bound window.onhashchange event handlers will be triggered. This
|
||||
// is a shortcut for the more verbose
|
||||
// jQuery(window).trigger( 'hashchange' ). These forms are described in
|
||||
// the <hashchange event> section.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// (jQuery) The initial jQuery collection of elements.
|
||||
|
||||
// Allow the "shortcut" format $(elem).hashchange( fn ) for binding and
|
||||
// $(elem).hashchange() for triggering, like jQuery does for built-in events.
|
||||
$.fn[ str_hashchange ] = function( fn ) {
|
||||
return fn ? this.bind( str_hashchange, fn ) : this.trigger( str_hashchange );
|
||||
};
|
||||
|
||||
// Property: jQuery.fn.hashchange.delay
|
||||
//
|
||||
// The numeric interval (in milliseconds) at which the <hashchange event>
|
||||
// polling loop executes. Defaults to 100.
|
||||
// polling loop executes. Defaults to 50.
|
||||
|
||||
$[ str_hashchange + 'Delay' ] = 100;
|
||||
// Property: jQuery.fn.hashchange.domain
|
||||
//
|
||||
// If you're setting document.domain in your JavaScript, and you want hash
|
||||
// history to work in IE6/7, not only must this property be set, but you must
|
||||
// also set document.domain BEFORE jQuery is loaded into the page. This
|
||||
// property is only applicable if you are supporting IE6/7 (or IE8 operating
|
||||
// in "IE7 compatibility" mode).
|
||||
//
|
||||
// In addition, the <jQuery.fn.hashchange.src> property must be set to the
|
||||
// path of the included "document-domain.html" file, which can be renamed or
|
||||
// modified if necessary (note that the document.domain specified must be the
|
||||
// same in both your main JavaScript as well as in this file).
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// jQuery.fn.hashchange.domain = document.domain;
|
||||
|
||||
// Property: jQuery.fn.hashchange.src
|
||||
//
|
||||
// If, for some reason, you need to specify an Iframe src file (for example,
|
||||
// when setting document.domain as in <jQuery.fn.hashchange.domain>), you can
|
||||
// do so using this property. Note that when using this property, history
|
||||
// won't be recorded in IE6/7 until the Iframe src file loads. This property
|
||||
// is only applicable if you are supporting IE6/7 (or IE8 operating in "IE7
|
||||
// compatibility" mode).
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// jQuery.fn.hashchange.src = 'path/to/file.html';
|
||||
|
||||
$.fn[ str_hashchange ].delay = 50;
|
||||
/*
|
||||
$.fn[ str_hashchange ].domain = null;
|
||||
$.fn[ str_hashchange ].src = null;
|
||||
*/
|
||||
|
||||
// Event: hashchange event
|
||||
//
|
||||
// Fired when location.hash changes. In browsers that support it, the native
|
||||
// window.onhashchange event is used (IE8, FF3.6), otherwise a polling loop is
|
||||
// initialized, running every <jQuery.hashchangeDelay> milliseconds to see if
|
||||
// the hash has changed. In IE 6 and 7, a hidden Iframe is created to allow
|
||||
// the back button and hash-based history to work.
|
||||
// HTML5 window.onhashchange event is used, otherwise a polling loop is
|
||||
// initialized, running every <jQuery.fn.hashchange.delay> milliseconds to
|
||||
// see if the hash has changed. In IE6/7 (and IE8 operating in "IE7
|
||||
// compatibility" mode), a hidden Iframe is created to allow the back button
|
||||
// and hash-based history to work.
|
||||
//
|
||||
// Usage:
|
||||
// Usage as described in <jQuery.fn.hashchange>:
|
||||
//
|
||||
// > $(window).bind( 'hashchange', function(e) {
|
||||
// > // Bind an event handler.
|
||||
// > jQuery(window).hashchange( function(e) {
|
||||
// > var hash = location.hash;
|
||||
// > ...
|
||||
// > });
|
||||
// >
|
||||
// > // Manually trigger the event handler.
|
||||
// > jQuery(window).hashchange();
|
||||
//
|
||||
// A more verbose usage that allows for event namespacing:
|
||||
//
|
||||
// > // Bind an event handler.
|
||||
// > jQuery(window).bind( 'hashchange', function(e) {
|
||||
// > var hash = location.hash;
|
||||
// > ...
|
||||
// > });
|
||||
// >
|
||||
// > // Manually trigger the event handler.
|
||||
// > jQuery(window).trigger( 'hashchange' );
|
||||
//
|
||||
// Additional Notes:
|
||||
//
|
||||
// * The polling loop and Iframe are not created until at least one callback
|
||||
// is actually bound to 'hashchange'.
|
||||
// * If you need the bound callback(s) to execute immediately, in cases where
|
||||
// the page 'state' exists on page load (via bookmark or page refresh, for
|
||||
// example) use $(window).trigger( 'hashchange' );
|
||||
// * The polling loop and Iframe are not created until at least one handler
|
||||
// is actually bound to the 'hashchange' event.
|
||||
// * If you need the bound handler(s) to execute immediately, in cases where
|
||||
// a location.hash exists on page load, via bookmark or page refresh for
|
||||
// example, use jQuery(window).hashchange() or the more verbose
|
||||
// jQuery(window).trigger( 'hashchange' ).
|
||||
// * The event can be bound before DOM ready, but since it won't be usable
|
||||
// before then in IE6/7 (due to the necessary Iframe), recommended usage is
|
||||
// to bind it inside a $(document).ready() callback.
|
||||
// to bind it inside a DOM ready handler.
|
||||
|
||||
jq_event_special[ str_hashchange ] = $.extend( jq_event_special[ str_hashchange ], {
|
||||
// Override existing $.event.special.hashchange methods (allowing this plugin
|
||||
// to be defined after jQuery BBQ in BBQ's source code).
|
||||
special[ str_hashchange ] = $.extend( special[ str_hashchange ], {
|
||||
|
||||
// Called only when the first 'hashchange' event is bound to window.
|
||||
setup: function() {
|
||||
@@ -1051,83 +1251,44 @@
|
||||
fake_onhashchange = (function(){
|
||||
var self = {},
|
||||
timeout_id,
|
||||
iframe,
|
||||
set_history,
|
||||
get_history;
|
||||
|
||||
// Initialize. In IE 6/7, creates a hidden Iframe for history handling.
|
||||
function init(){
|
||||
// Most browsers don't need special methods here..
|
||||
set_history = get_history = function(val){ return val; };
|
||||
|
||||
// But IE6/7 do!
|
||||
if ( is_old_ie ) {
|
||||
|
||||
// Create hidden Iframe after the end of the body to prevent initial
|
||||
// page load from scrolling unnecessarily.
|
||||
iframe = $('<iframe src="javascript:0"/>').hide().insertAfter( 'body' )[0].contentWindow;
|
||||
|
||||
// Get history by looking at the hidden Iframe's location.hash.
|
||||
get_history = function() {
|
||||
return get_fragment( iframe.document[ str_location ][ str_href ] );
|
||||
};
|
||||
|
||||
// Set a new history item by opening and then closing the Iframe
|
||||
// document, *then* setting its location.hash.
|
||||
set_history = function( hash, history_hash ) {
|
||||
if ( hash !== history_hash ) {
|
||||
var doc = iframe.document;
|
||||
doc.open().close();
|
||||
doc[ str_location ].hash = '#' + hash;
|
||||
}
|
||||
};
|
||||
|
||||
// Set initial history.
|
||||
set_history( get_fragment() );
|
||||
}
|
||||
};
|
||||
// Remember the initial hash so it doesn't get triggered immediately.
|
||||
last_hash = get_fragment(),
|
||||
|
||||
fn_retval = function(val){ return val; },
|
||||
history_set = fn_retval,
|
||||
history_get = fn_retval;
|
||||
|
||||
// Start the polling loop.
|
||||
self.start = function() {
|
||||
// Polling loop is already running!
|
||||
if ( timeout_id ) { return; }
|
||||
|
||||
// Remember the initial hash so it doesn't get triggered immediately.
|
||||
var last_hash = get_fragment();
|
||||
|
||||
// Initialize if not yet initialized.
|
||||
set_history || init();
|
||||
|
||||
// This polling loop checks every $.hashchangeDelay milliseconds to see if
|
||||
// location.hash has changed, and triggers the 'hashchange' event on
|
||||
// window when necessary.
|
||||
(function loopy(){
|
||||
var hash = get_fragment(),
|
||||
history_hash = get_history( last_hash );
|
||||
|
||||
if ( hash !== last_hash ) {
|
||||
set_history( last_hash = hash, history_hash );
|
||||
|
||||
$(window).trigger( str_hashchange );
|
||||
|
||||
} else if ( history_hash !== last_hash ) {
|
||||
window[ str_location ][ str_href ] = window[ str_location ][ str_href ].replace( /#.*/, '' ) + '#' + history_hash;
|
||||
}
|
||||
|
||||
timeout_id = setTimeout( loopy, $[ str_hashchange + 'Delay' ] );
|
||||
})();
|
||||
timeout_id || poll();
|
||||
};
|
||||
|
||||
// Stop the polling loop, but only if an IE6/7 Iframe wasn't created. In
|
||||
// that case, even if there are no longer any bound event handlers, the
|
||||
// polling loop is still necessary for back/next to work at all!
|
||||
// Stop the polling loop.
|
||||
self.stop = function() {
|
||||
if ( !iframe ) {
|
||||
timeout_id && clearTimeout( timeout_id );
|
||||
timeout_id = 0;
|
||||
}
|
||||
timeout_id && clearTimeout( timeout_id );
|
||||
timeout_id = undefined;
|
||||
};
|
||||
|
||||
// This polling loop checks every $.fn.hashchange.delay milliseconds to see
|
||||
// if location.hash has changed, and triggers the 'hashchange' event on
|
||||
// window when necessary.
|
||||
function poll() {
|
||||
var hash = get_fragment(),
|
||||
history_hash = history_get( last_hash );
|
||||
|
||||
if ( hash !== last_hash ) {
|
||||
history_set( last_hash = hash, history_hash );
|
||||
|
||||
$(window).trigger( str_hashchange );
|
||||
|
||||
} else if ( history_hash !== last_hash ) {
|
||||
location.href = location.href.replace( /#.*/, '' ) + history_hash;
|
||||
}
|
||||
|
||||
timeout_id = setTimeout( poll, $.fn[ str_hashchange ].delay );
|
||||
};
|
||||
|
||||
return self;
|
||||
})();
|
||||
|
||||
|
||||
20
js/jquery.ba-bbq.min.js
vendored
20
js/jquery.ba-bbq.min.js
vendored
@@ -1,9 +1,25 @@
|
||||
/*!
|
||||
* Combodo: issued from https://github.com/cee-chen/jquery-bbq
|
||||
* fork from http://github.com/cowboy/jquery-bbq/
|
||||
*
|
||||
* IMPORTANT: Part for detecting MSIE has been removed as it was not present in the previous version and is making the app crash.
|
||||
* It's far from perfect but as mentioned in the previous commit, we don't have much choices for now
|
||||
*/
|
||||
/*
|
||||
* jQuery BBQ: Back Button & Query Library - v1.2.1 - 2/17/2010
|
||||
* jQuery BBQ: Back Button & Query Library - v1.3pre - 8/26/2010
|
||||
* http://benalman.com/projects/jquery-bbq-plugin/
|
||||
*
|
||||
* Copyright (c) 2010 "Cowboy" Ben Alman
|
||||
* Dual licensed under the MIT and GPL licenses.
|
||||
* http://benalman.com/about/license/
|
||||
*/
|
||||
(function(e,t){"$:nomunge";function N(e){return typeof e==="string"}function C(e){var t=r.call(arguments,1);return function(){return e.apply(this,t.concat(r.call(arguments)))}}function k(e){return e.replace(/^[^#]*#?(.*)$/,"$1")}function L(e){return e.replace(/(?:^[^?#]*\?([^#]*).*$)?.*/,"$1")}function A(r,o,a,f,l){var c,h,p,d,g;if(f!==n){p=a.match(r?/^([^#]*)\#?(.*)$/:/^([^#?]*)\??([^#]*)(#?.*)/);g=p[3]||"";if(l===2&&N(f)){h=f.replace(r?S:E,"")}else{d=u(p[2]);f=N(f)?u[r?m:v](f):f;h=l===2?f:l===1?e.extend({},f,d):e.extend({},d,f);h=s(h);if(r){h=h.replace(x,i)}}c=p[1]+(r?"#":h||!p[1]?"?":"")+h+g}else{c=o(a!==n?a:t[y][b])}return c}function O(e,t,r){if(t===n||typeof t==="boolean"){r=t;t=s[e?m:v]()}else{t=N(t)?t.replace(e?S:E,""):t}return u(t,r)}function M(t,r,i,o){if(!N(i)&&typeof i!=="object"){o=i;i=r;r=n}return this.each(function(){var n=e(this),u=r||h()[(this.nodeName||"").toLowerCase()]||"",a=u&&n.attr(u)||"";n.attr(u,s[t](a,i,o))})}var n,r=Array.prototype.slice,i=decodeURIComponent,s=e.param,o,u,a,f=e.bbq=e.bbq||{},l,c,h,p=e.event.special,d="hashchange",v="querystring",m="fragment",g="elemUrlAttr",y="location",b="href",w="src",E=/^.*\?|#.*$/g,S=/^.*\#/,x,T={};s[v]=C(A,0,L);s[m]=o=C(A,1,k);o.noEscape=function(t){t=t||"";var n=e.map(t.split(""),encodeURIComponent);x=new RegExp(n.join("|"),"g")};o.noEscape(",/");e.deparam=u=function(t,r){var s={},o={"true":!0,"false":!1,"null":null};e.each(t.replace(/\+/g," ").split("&"),function(t,u){var a=u.split("="),f=i(a[0]),l,c=s,h=0,p=f.split("]["),d=p.length-1;if(/\[/.test(p[0])&&/\]$/.test(p[d])){p[d]=p[d].replace(/\]$/,"");p=p.shift().split("[").concat(p);d=p.length-1}else{d=0}if(a.length===2){l=i(a[1]);if(r){l=l&&!isNaN(l)?+l:l==="undefined"?n:o[l]!==n?o[l]:l}if(d){for(;h<=d;h++){f=p[h]===""?c.length:p[h];c=c[f]=h<d?c[f]||(p[h+1]&&isNaN(p[h+1])?{}:[]):l}}else{if(e.isArray(s[f])){s[f].push(l)}else if(s[f]!==n){s[f]=[s[f],l]}else{s[f]=l}}}else if(f){s[f]=r?n:""}});return s};u[v]=C(O,0);u[m]=a=C(O,1);e[g]||(e[g]=function(t){return e.extend(T,t)})({a:b,base:b,iframe:w,img:w,input:w,form:"action",link:b,script:w});h=e[g];e.fn[v]=C(M,v);e.fn[m]=C(M,m);f.pushState=l=function(e,r){if(N(e)&&/^#/.test(e)&&r===n){r=2}var i=e!==n,s=o(t[y][b],i?e:{},i?r:2);t[y][b]=s+(/#/.test(s)?"":"#")};f.getState=c=function(e,t){return e===n||typeof e==="boolean"?a(e):a(t)[e]};f.removeState=function(t){var r={};if(t!==n){r=c();e.each(e.isArray(t)?t:arguments,function(e,t){delete r[t]})}l(r,2)};p[d]=e.extend(p[d],{add:function(t){function i(e){var t=e[m]=o();e.getState=function(e,r){return e===n||typeof e==="boolean"?u(t,e):u(t,r)[e]};r.apply(this,arguments)}var r;if(e.isFunction(t)){r=t;return i}else{r=t.handler;t.handler=i}}})})(jQuery,this);(function(e,t,n){"$:nomunge";function c(e){e=e||t[s][u];return e.replace(/^[^#]*#?(.*)$/,"$1")}var r,i=e.event.special,s="location",o="hashchange",u="href",a=document.documentMode,f=false,l="on"+o in t&&!f;e[o+"Delay"]=100;i[o]=e.extend(i[o],{setup:function(){if(l){return false}e(r.start)},teardown:function(){if(l){return false}e(r.stop)}});r=function(){function h(){a=l=function(e){return e};if(f){i=e('<iframe src="javascript:0"/>').hide().insertAfter("body")[0].contentWindow;l=function(){return c(i.document[s][u])};a=function(e,t){if(e!==t){var n=i.document;n.open().close();n[s].hash="#"+e}};a(c())}}var n={},r,i,a,l;n.start=function(){if(r){return}var n=c();a||h();(function i(){var f=c(),h=l(n);if(f!==n){a(n=f,h);e(t).trigger(o)}else if(h!==n){t[s][u]=t[s][u].replace(/#.*/,"")+"#"+h}r=setTimeout(i,e[o+"Delay"])})()};n.stop=function(){if(!i){r&&clearTimeout(r);r=0}};return n}()})(jQuery,this)
|
||||
(function($,r){var h,n=Array.prototype.slice,t=decodeURIComponent,a=$.param,j,c,m,y,b=$.bbq=$.bbq||{},s,x,k,e=$.event.special,d="hashchange",B="querystring",F="fragment",z="elemUrlAttr",l="href",w="src",p=/^.*\?|#.*$/g,u,H,g,i,C,E={};function G(I){return typeof I==="string"}function D(J){var I=n.call(arguments,1);return function(){return J.apply(this,I.concat(n.call(arguments)))}}function o(I){return I.replace(H,"$2")}function q(I){return I.replace(/(?:^[^?#]*\?([^#]*).*$)?.*/,"$1")}function f(K,P,I,L,J){var R,O,N,Q,M;if(L!==h){N=I.match(K?H:/^([^#?]*)\??([^#]*)(#?.*)/);M=N[3]||"";if(J===2&&G(L)){O=L.replace(K?u:p,"")}else{Q=m(N[2]);L=G(L)?m[K?F:B](L):L;O=J===2?L:J===1?$.extend({},L,Q):$.extend({},Q,L);O=j(O);if(K){O=O.replace(g,t)}}R=N[1]+(K?C:O||!N[1]?"?":"")+O+M}else{R=P(I!==h?I:location.href)}return R}a[B]=D(f,0,q);a[F]=c=D(f,1,o);a.sorted=j=function(J,K){var I=[],L={};$.each(a(J,K).split("&"),function(P,M){var O=M.replace(/(?:%5B|=).*$/,""),N=L[O];if(!N){N=L[O]=[];I.push(O)}N.push(M)});return $.map(I.sort(),function(M){return L[M]}).join("&")};c.noEscape=function(J){J=J||"";var I=$.map(J.split(""),encodeURIComponent);g=new RegExp(I.join("|"),"g")};c.noEscape(",/");c.ajaxCrawlable=function(I){if(I!==h){if(I){u=/^.*(?:#!|#)/;H=/^([^#]*)(?:#!|#)?(.*)$/;C="#!"}else{u=/^.*#/;H=/^([^#]*)#?(.*)$/;C="#"}i=!!I}return i};c.ajaxCrawlable(0);$.deparam=m=function(L,I){var K=Object.create(null),J={"true":!0,"false":!1,"null":null};$.each(L.replace(/\+/g," ").split("&"),function(O,T){var N=T.split("="),S=t(N[0]),M,R=K,P=0,U=S.split("]["),Q=U.length-1;if(/\[/.test(U[0])&&/\]$/.test(U[Q])){U[Q]=U[Q].replace(/\]$/,"");U=U.shift().split("[").concat(U);Q=U.length-1}else{Q=0}if(N.length===2){M=t(N[1]);if(I){M=M&&!isNaN(M)?+M:M==="undefined"?h:J[M]!==h?J[M]:M}if(Q){for(;P<=Q;P++){S=U[P]===""?R.length:U[P];R=R[S]=P<Q?R[S]||(U[P+1]&&isNaN(U[P+1])?Object.create(null):[]):M}}else{if($.isArray(K[S])){K[S].push(M)}else{if(K[S]!==h){K[S]=[K[S],M]}else{K[S]=M}}}}else{if(S){K[S]=I?h:""}}});return K};function A(K,I,J){if(I===h||typeof I==="boolean"){J=I;I=a[K?F:B]()}else{I=G(I)?I.replace(K?u:p,""):I}return m(I,J)}m[B]=D(A,0);m[F]=y=D(A,1);$[z]||($[z]=function(I){return $.extend(E,I)})({a:l,base:l,iframe:w,img:w,input:w,form:"action",link:l,script:w});k=$[z];function v(L,J,K,I){if(!G(K)&&typeof K!=="object"){I=K;K=J;J=h}return this.each(function(){var O=$(this),M=J||k()[(this.nodeName||"").toLowerCase()]||"",N=M&&O.attr(M)||"";O.attr(M,a[L](N,K,I))})}$.fn[B]=D(v,B);$.fn[F]=D(v,F);b.pushState=s=function(L,I){if(G(L)&&/^#/.test(L)&&I===h){I=2}var K=L!==h,J=c(location.href,K?L:{},K?I:2);location.href=J};b.getState=x=function(I,J){return I===h||typeof I==="boolean"?y(I):y(J)[I]};b.removeState=function(I){var J={};if(I!==h){J=x();$.each($.isArray(I)?I:arguments,function(L,K){delete J[K]})}s(J,2)};e[d]=$.extend(e[d],{add:function(I){var K;function J(M){var L=M[F]=c();M.getState=function(N,O){return N===h||typeof N==="boolean"?m(L,N):m(L,O)[N]};K.apply(this,arguments)}if($.isFunction(I)){K=I;return J}else{K=I.handler;I.handler=J}}})})(jQuery,this);
|
||||
/*
|
||||
* jQuery hashchange event - v1.3 - 7/21/2010
|
||||
* http://benalman.com/projects/jquery-hashchange-plugin/
|
||||
*
|
||||
* Copyright (c) 2010 "Cowboy" Ben Alman
|
||||
* Dual licensed under the MIT and GPL licenses.
|
||||
* http://benalman.com/about/license/
|
||||
*/
|
||||
(function($,e,b){var c="hashchange",h=document,f,g=$.event.special,i=h.documentMode,d="on"+c in e&&(i===b||i>7);function a(j){j=j||location.href;return"#"+j.replace(/^[^#]*#?(.*)$/,"$1")}$.fn[c]=function(j){return j?this.bind(c,j):this.trigger(c)};$.fn[c].delay=50;g[c]=$.extend(g[c],{setup:function(){if(d){return false}$(f.start)},teardown:function(){if(d){return false}$(f.stop)}});f=(function(){var j={},p,m=a(),k=function(q){return q},l=k,o=k;j.start=function(){p||n()};j.stop=function(){p&&clearTimeout(p);p=b};function n(){var r=a(),q=o(m);if(r!==m){l(m=r,q);$(e).trigger(c)}else{if(q!==m){location.href=location.href.replace(/#.*/,"")+q}}p=setTimeout(n,$.fn[c].delay)}return j})()})(jQuery,this);
|
||||
@@ -151,7 +151,7 @@ function WizardHelper(sClass, sFormPrefix, sState, sInitialState, sStimulus)
|
||||
var sString = "$('#"+aRefreshed[i]+"').trigger('change').trigger('update');";
|
||||
window.setTimeout(sString, 1); // Synchronous 'trigger' does nothing, call it asynchronously
|
||||
}
|
||||
if($('.blockUI').length == 0) {
|
||||
if($('[data-field-status="blocked"]').length === 0) {
|
||||
$('.disabledDuringFieldLoading').prop("disabled", false).removeClass('disabledDuringFieldLoading');
|
||||
}
|
||||
};
|
||||
@@ -181,9 +181,11 @@ function WizardHelper(sClass, sFormPrefix, sState, sInitialState, sStimulus)
|
||||
{ operation: 'wizard_helper', json_obj: this.ToJSON() },
|
||||
function(html){
|
||||
$('#ajax_content').html(html);
|
||||
$('.blockUI').parent().unblock();
|
||||
$('[data-field-status="blocked"]')
|
||||
.attr('data-field-status', 'ready')
|
||||
.unblock();
|
||||
|
||||
if($('.blockUI').length == 0) {
|
||||
if($('[data-field-status="blocked"]').length === 0) {
|
||||
$('.disabledDuringFieldLoading').prop("disabled", false).removeClass('disabledDuringFieldLoading');
|
||||
}
|
||||
//console.log('data received:', oWizardHelper);
|
||||
@@ -229,16 +231,18 @@ function WizardHelper(sClass, sFormPrefix, sState, sInitialState, sStimulus)
|
||||
sFieldId = this.GetFieldId(sAttCode);
|
||||
if (sFieldId !== undefined) {
|
||||
$('#fstatus_' + sFieldId).html('<img src="../images/indicator.gif" />');
|
||||
$('#field_' + sFieldId).find('div').block({
|
||||
message: '',
|
||||
overlayCSS: {backgroundColor: '#f1f1f1', opacity: 0.3}
|
||||
$('#field_' + sFieldId).find('div')
|
||||
.attr('data-field-status', 'blocked')
|
||||
.block({
|
||||
message: '',
|
||||
overlayCSS: {backgroundColor: '#f1f1f1', opacity: 0.3}
|
||||
});
|
||||
fieldForm = $('#field_' + sFieldId).closest('form');
|
||||
this.RequestAllowedValues(sAttCode);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
if ((fieldForm !== null) && ($('.blockUI').length > 0)) {
|
||||
if ((fieldForm !== null) && ($('[data-field-status="blocked"]').length > 0)) {
|
||||
fieldForm.find('button[type=submit]:not(:disabled)').prop("disabled", true).addClass('disabledDuringFieldLoading');
|
||||
}
|
||||
this.AjaxQueryServer();
|
||||
|
||||
@@ -158,8 +158,10 @@ return array(
|
||||
'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderGoogle' => $baseDir . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderGoogle.php',
|
||||
'Combodo\\iTop\\Core\\Email\\EmailFactory' => $baseDir . '/sources/Core/Email/EmailFactory.php',
|
||||
'Combodo\\iTop\\Core\\Email\\iEMail' => $baseDir . '/sources/Core/Email/iEMail.php',
|
||||
'Combodo\\iTop\\Core\\Kpi\\KpiLogData' => $baseDir . '/sources/Core/Kpi/KpiLogData.php',
|
||||
'Combodo\\iTop\\DesignDocument' => $baseDir . '/core/designdocument.class.inc.php',
|
||||
'Combodo\\iTop\\DesignElement' => $baseDir . '/core/designdocument.class.inc.php',
|
||||
'Combodo\\iTop\\Service\\Module\\ModuleService' => $baseDir . '/sources/Service/Module/ModuleService.php',
|
||||
'Combodo\\iTop\\TwigExtension' => $baseDir . '/application/twigextension.class.inc.php',
|
||||
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
||||
'Config' => $baseDir . '/core/config.class.inc.php',
|
||||
@@ -338,6 +340,7 @@ return array(
|
||||
'FilterFromAttribute' => $baseDir . '/core/filterdef.class.inc.php',
|
||||
'FilterPrivateKey' => $baseDir . '/core/filterdef.class.inc.php',
|
||||
'Firebase\\JWT\\BeforeValidException' => $vendorDir . '/firebase/php-jwt/src/BeforeValidException.php',
|
||||
'Firebase\\JWT\\CachedKeySet' => $vendorDir . '/firebase/php-jwt/src/CachedKeySet.php',
|
||||
'Firebase\\JWT\\ExpiredException' => $vendorDir . '/firebase/php-jwt/src/ExpiredException.php',
|
||||
'Firebase\\JWT\\JWK' => $vendorDir . '/firebase/php-jwt/src/JWK.php',
|
||||
'Firebase\\JWT\\JWT' => $vendorDir . '/firebase/php-jwt/src/JWT.php',
|
||||
@@ -825,6 +828,7 @@ return array(
|
||||
'MySQLHasGoneAwayException' => $baseDir . '/core/cmdbsource.class.inc.php',
|
||||
'MySQLNoTransactionException' => $baseDir . '/core/cmdbsource.class.inc.php',
|
||||
'MySQLQueryHasNoResultException' => $baseDir . '/core/cmdbsource.class.inc.php',
|
||||
'MySQLTransactionNotClosedException' => $baseDir . '/core/cmdbsource.class.inc.php',
|
||||
'NestedQueryExpression' => $baseDir . '/core/oql/expression.class.inc.php',
|
||||
'NestedQueryOqlExpression' => $baseDir . '/core/oql/oqlquery.class.inc.php',
|
||||
'NewObjectMenuNode' => $baseDir . '/application/menunode.class.inc.php',
|
||||
@@ -2710,6 +2714,7 @@ return array(
|
||||
'iDBObjectSetIterator' => $baseDir . '/core/dbobjectiterator.php',
|
||||
'iDBObjectURLMaker' => $baseDir . '/application/applicationcontext.class.inc.php',
|
||||
'iDisplay' => $baseDir . '/core/dbobject.class.php',
|
||||
'iKPILoggerExtension' => $baseDir . '/application/applicationextension.inc.php',
|
||||
'iLogFileNameBuilder' => $baseDir . '/core/log.class.inc.php',
|
||||
'iLoginExtension' => $baseDir . '/application/applicationextension.inc.php',
|
||||
'iLoginFSMExtension' => $baseDir . '/application/applicationextension.inc.php',
|
||||
|
||||
@@ -526,8 +526,10 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
|
||||
'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderGoogle' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderGoogle.php',
|
||||
'Combodo\\iTop\\Core\\Email\\EmailFactory' => __DIR__ . '/../..' . '/sources/Core/Email/EmailFactory.php',
|
||||
'Combodo\\iTop\\Core\\Email\\iEMail' => __DIR__ . '/../..' . '/sources/Core/Email/iEMail.php',
|
||||
'Combodo\\iTop\\Core\\Kpi\\KpiLogData' => __DIR__ . '/../..' . '/sources/Core/Kpi/KpiLogData.php',
|
||||
'Combodo\\iTop\\DesignDocument' => __DIR__ . '/../..' . '/core/designdocument.class.inc.php',
|
||||
'Combodo\\iTop\\DesignElement' => __DIR__ . '/../..' . '/core/designdocument.class.inc.php',
|
||||
'Combodo\\iTop\\Service\\Module\\ModuleService' => __DIR__ . '/../..' . '/sources/Service/Module/ModuleService.php',
|
||||
'Combodo\\iTop\\TwigExtension' => __DIR__ . '/../..' . '/application/twigextension.class.inc.php',
|
||||
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
||||
'Config' => __DIR__ . '/../..' . '/core/config.class.inc.php',
|
||||
@@ -706,6 +708,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
|
||||
'FilterFromAttribute' => __DIR__ . '/../..' . '/core/filterdef.class.inc.php',
|
||||
'FilterPrivateKey' => __DIR__ . '/../..' . '/core/filterdef.class.inc.php',
|
||||
'Firebase\\JWT\\BeforeValidException' => __DIR__ . '/..' . '/firebase/php-jwt/src/BeforeValidException.php',
|
||||
'Firebase\\JWT\\CachedKeySet' => __DIR__ . '/..' . '/firebase/php-jwt/src/CachedKeySet.php',
|
||||
'Firebase\\JWT\\ExpiredException' => __DIR__ . '/..' . '/firebase/php-jwt/src/ExpiredException.php',
|
||||
'Firebase\\JWT\\JWK' => __DIR__ . '/..' . '/firebase/php-jwt/src/JWK.php',
|
||||
'Firebase\\JWT\\JWT' => __DIR__ . '/..' . '/firebase/php-jwt/src/JWT.php',
|
||||
@@ -1193,6 +1196,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
|
||||
'MySQLHasGoneAwayException' => __DIR__ . '/../..' . '/core/cmdbsource.class.inc.php',
|
||||
'MySQLNoTransactionException' => __DIR__ . '/../..' . '/core/cmdbsource.class.inc.php',
|
||||
'MySQLQueryHasNoResultException' => __DIR__ . '/../..' . '/core/cmdbsource.class.inc.php',
|
||||
'MySQLTransactionNotClosedException' => __DIR__ . '/../..' . '/core/cmdbsource.class.inc.php',
|
||||
'NestedQueryExpression' => __DIR__ . '/../..' . '/core/oql/expression.class.inc.php',
|
||||
'NestedQueryOqlExpression' => __DIR__ . '/../..' . '/core/oql/oqlquery.class.inc.php',
|
||||
'NewObjectMenuNode' => __DIR__ . '/../..' . '/application/menunode.class.inc.php',
|
||||
@@ -3078,6 +3082,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
|
||||
'iDBObjectSetIterator' => __DIR__ . '/../..' . '/core/dbobjectiterator.php',
|
||||
'iDBObjectURLMaker' => __DIR__ . '/../..' . '/application/applicationcontext.class.inc.php',
|
||||
'iDisplay' => __DIR__ . '/../..' . '/core/dbobject.class.php',
|
||||
'iKPILoggerExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
|
||||
'iLogFileNameBuilder' => __DIR__ . '/../..' . '/core/log.class.inc.php',
|
||||
'iLoginExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
|
||||
'iLoginFSMExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
|
||||
|
||||
@@ -260,29 +260,35 @@
|
||||
},
|
||||
{
|
||||
"name": "firebase/php-jwt",
|
||||
"version": "v5.5.1",
|
||||
"version_normalized": "5.5.1.0",
|
||||
"version": "v6.4.0",
|
||||
"version_normalized": "6.4.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/firebase/php-jwt.git",
|
||||
"reference": "83b609028194aa042ea33b5af2d41a7427de80e6"
|
||||
"reference": "4dd1e007f22a927ac77da5a3fbb067b42d3bc224"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/firebase/php-jwt/zipball/83b609028194aa042ea33b5af2d41a7427de80e6",
|
||||
"reference": "83b609028194aa042ea33b5af2d41a7427de80e6",
|
||||
"url": "https://api.github.com/repos/firebase/php-jwt/zipball/4dd1e007f22a927ac77da5a3fbb067b42d3bc224",
|
||||
"reference": "4dd1e007f22a927ac77da5a3fbb067b42d3bc224",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.0"
|
||||
"php": "^7.1||^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": ">=4.8 <=9"
|
||||
"guzzlehttp/guzzle": "^6.5||^7.4",
|
||||
"phpspec/prophecy-phpunit": "^1.1",
|
||||
"phpunit/phpunit": "^7.5||^9.5",
|
||||
"psr/cache": "^1.0||^2.0",
|
||||
"psr/http-client": "^1.0",
|
||||
"psr/http-factory": "^1.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-sodium": "Support EdDSA (Ed25519) signatures",
|
||||
"paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present"
|
||||
},
|
||||
"time": "2021-11-08T20:18:51+00:00",
|
||||
"time": "2023-02-09T21:01:23+00:00",
|
||||
"type": "library",
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
@@ -314,7 +320,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/firebase/php-jwt/issues",
|
||||
"source": "https://github.com/firebase/php-jwt/tree/v5.5.1"
|
||||
"source": "https://github.com/firebase/php-jwt/tree/v6.4.0"
|
||||
},
|
||||
"install-path": "../firebase/php-jwt"
|
||||
},
|
||||
@@ -525,17 +531,17 @@
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/psr7",
|
||||
"version": "1.9.0",
|
||||
"version_normalized": "1.9.0.0",
|
||||
"version": "1.9.1",
|
||||
"version_normalized": "1.9.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/psr7.git",
|
||||
"reference": "e98e3e6d4f86621a9b75f623996e6bbdeb4b9318"
|
||||
"reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/psr7/zipball/e98e3e6d4f86621a9b75f623996e6bbdeb4b9318",
|
||||
"reference": "e98e3e6d4f86621a9b75f623996e6bbdeb4b9318",
|
||||
"url": "https://api.github.com/repos/guzzle/psr7/zipball/e4490cabc77465aaee90b20cfc9a770f8c04be6b",
|
||||
"reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -553,13 +559,8 @@
|
||||
"suggest": {
|
||||
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
|
||||
},
|
||||
"time": "2022-06-20T21:43:03+00:00",
|
||||
"time": "2023-04-17T16:00:37+00:00",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.9-dev"
|
||||
}
|
||||
},
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"files": [
|
||||
@@ -618,7 +619,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/guzzle/psr7/issues",
|
||||
"source": "https://github.com/guzzle/psr7/tree/1.9.0"
|
||||
"source": "https://github.com/guzzle/psr7/tree/1.9.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -4708,25 +4709,27 @@
|
||||
},
|
||||
{
|
||||
"name": "thenetworg/oauth2-azure",
|
||||
"version": "v2.0.1",
|
||||
"version_normalized": "2.0.1.0",
|
||||
"version": "v2.1.1",
|
||||
"version_normalized": "2.1.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/TheNetworg/oauth2-azure.git",
|
||||
"reference": "2649422a0dc74af32d21d9d738d37abcd5b03998"
|
||||
"reference": "06fb2d620fb6e6c934f632c7ec7c5ea2e978a844"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/TheNetworg/oauth2-azure/zipball/2649422a0dc74af32d21d9d738d37abcd5b03998",
|
||||
"reference": "2649422a0dc74af32d21d9d738d37abcd5b03998",
|
||||
"url": "https://api.github.com/repos/TheNetworg/oauth2-azure/zipball/06fb2d620fb6e6c934f632c7ec7c5ea2e978a844",
|
||||
"reference": "06fb2d620fb6e6c934f632c7ec7c5ea2e978a844",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"firebase/php-jwt": "~3.0||~4.0||~5.0",
|
||||
"ext-json": "*",
|
||||
"ext-openssl": "*",
|
||||
"firebase/php-jwt": "~3.0||~4.0||~5.0||~6.0",
|
||||
"league/oauth2-client": "~2.0",
|
||||
"php": "^5.6|^7.0|^8.0"
|
||||
"php": "^7.1|^8.0"
|
||||
},
|
||||
"time": "2021-01-11T12:20:12+00:00",
|
||||
"time": "2022-06-23T10:35:36+00:00",
|
||||
"type": "library",
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
@@ -4760,7 +4763,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/TheNetworg/oauth2-azure/issues",
|
||||
"source": "https://github.com/TheNetworg/oauth2-azure/tree/v2.0.1"
|
||||
"source": "https://github.com/TheNetworg/oauth2-azure/tree/v2.1.1"
|
||||
},
|
||||
"install-path": "../thenetworg/oauth2-azure"
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
'name' => 'combodo/itop',
|
||||
'pretty_version' => 'dev-develop',
|
||||
'version' => 'dev-develop',
|
||||
'reference' => 'd388c3fd3d2a11983b61d268b2323a4ff0d0dbcb',
|
||||
'reference' => 'fff46d99fc2a37875ddb8ba2385b89dcfbcd73db',
|
||||
'type' => 'project',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
@@ -13,7 +13,7 @@
|
||||
'combodo/itop' => array(
|
||||
'pretty_version' => 'dev-develop',
|
||||
'version' => 'dev-develop',
|
||||
'reference' => 'd388c3fd3d2a11983b61d268b2323a4ff0d0dbcb',
|
||||
'reference' => 'fff46d99fc2a37875ddb8ba2385b89dcfbcd73db',
|
||||
'type' => 'project',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
@@ -62,9 +62,9 @@
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'firebase/php-jwt' => array(
|
||||
'pretty_version' => 'v5.5.1',
|
||||
'version' => '5.5.1.0',
|
||||
'reference' => '83b609028194aa042ea33b5af2d41a7427de80e6',
|
||||
'pretty_version' => 'v6.4.0',
|
||||
'version' => '6.4.0.0',
|
||||
'reference' => '4dd1e007f22a927ac77da5a3fbb067b42d3bc224',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../firebase/php-jwt',
|
||||
'aliases' => array(),
|
||||
@@ -89,9 +89,9 @@
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'guzzlehttp/psr7' => array(
|
||||
'pretty_version' => '1.9.0',
|
||||
'version' => '1.9.0.0',
|
||||
'reference' => 'e98e3e6d4f86621a9b75f623996e6bbdeb4b9318',
|
||||
'pretty_version' => '1.9.1',
|
||||
'version' => '1.9.1.0',
|
||||
'reference' => 'e4490cabc77465aaee90b20cfc9a770f8c04be6b',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../guzzlehttp/psr7',
|
||||
'aliases' => array(),
|
||||
@@ -636,9 +636,9 @@
|
||||
),
|
||||
),
|
||||
'thenetworg/oauth2-azure' => array(
|
||||
'pretty_version' => 'v2.0.1',
|
||||
'version' => '2.0.1.0',
|
||||
'reference' => '2649422a0dc74af32d21d9d738d37abcd5b03998',
|
||||
'pretty_version' => 'v2.1.1',
|
||||
'version' => '2.1.1.0',
|
||||
'reference' => '06fb2d620fb6e6c934f632c7ec7c5ea2e978a844',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../thenetworg/oauth2-azure',
|
||||
'aliases' => array(),
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
$issues = array();
|
||||
|
||||
if (!(PHP_VERSION_ID >= 70008)) {
|
||||
$issues[] = 'Your Composer dependencies require a PHP version ">= 7.0.8". You are running ' . PHP_VERSION . '.';
|
||||
if (!(PHP_VERSION_ID >= 70103)) {
|
||||
$issues[] = 'Your Composer dependencies require a PHP version ">= 7.1.3". You are running ' . PHP_VERSION . '.';
|
||||
}
|
||||
|
||||
if ($issues) {
|
||||
|
||||
105
lib/firebase/php-jwt/CHANGELOG.md
Normal file
105
lib/firebase/php-jwt/CHANGELOG.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# Changelog
|
||||
|
||||
## [6.4.0](https://github.com/firebase/php-jwt/compare/v6.3.2...v6.4.0) (2023-02-08)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add support for W3C ES256K ([#462](https://github.com/firebase/php-jwt/issues/462)) ([213924f](https://github.com/firebase/php-jwt/commit/213924f51936291fbbca99158b11bd4ae56c2c95))
|
||||
* improve caching by only decoding jwks when necessary ([#486](https://github.com/firebase/php-jwt/issues/486)) ([78d3ed1](https://github.com/firebase/php-jwt/commit/78d3ed1073553f7d0bbffa6c2010009a0d483d5c))
|
||||
|
||||
## [6.3.2](https://github.com/firebase/php-jwt/compare/v6.3.1...v6.3.2) (2022-11-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* check kid before using as array index ([bad1b04](https://github.com/firebase/php-jwt/commit/bad1b040d0c736bbf86814c6b5ae614f517cf7bd))
|
||||
|
||||
## [6.3.1](https://github.com/firebase/php-jwt/compare/v6.3.0...v6.3.1) (2022-11-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* casing of GET for PSR compat ([#451](https://github.com/firebase/php-jwt/issues/451)) ([60b52b7](https://github.com/firebase/php-jwt/commit/60b52b71978790eafcf3b95cfbd83db0439e8d22))
|
||||
* string interpolation format for php 8.2 ([#446](https://github.com/firebase/php-jwt/issues/446)) ([2e07d8a](https://github.com/firebase/php-jwt/commit/2e07d8a1524d12b69b110ad649f17461d068b8f2))
|
||||
|
||||
## 6.3.0 / 2022-07-15
|
||||
|
||||
- Added ES256 support to JWK parsing ([#399](https://github.com/firebase/php-jwt/pull/399))
|
||||
- Fixed potential caching error in `CachedKeySet` by caching jwks as strings ([#435](https://github.com/firebase/php-jwt/pull/435))
|
||||
|
||||
## 6.2.0 / 2022-05-14
|
||||
|
||||
- Added `CachedKeySet` ([#397](https://github.com/firebase/php-jwt/pull/397))
|
||||
- Added `$defaultAlg` parameter to `JWT::parseKey` and `JWT::parseKeySet` ([#426](https://github.com/firebase/php-jwt/pull/426)).
|
||||
|
||||
## 6.1.0 / 2022-03-23
|
||||
|
||||
- Drop support for PHP 5.3, 5.4, 5.5, 5.6, and 7.0
|
||||
- Add parameter typing and return types where possible
|
||||
|
||||
## 6.0.0 / 2022-01-24
|
||||
|
||||
- **Backwards-Compatibility Breaking Changes**: See the [Release Notes](https://github.com/firebase/php-jwt/releases/tag/v6.0.0) for more information.
|
||||
- New Key object to prevent key/algorithm type confusion (#365)
|
||||
- Add JWK support (#273)
|
||||
- Add ES256 support (#256)
|
||||
- Add ES384 support (#324)
|
||||
- Add Ed25519 support (#343)
|
||||
|
||||
## 5.0.0 / 2017-06-26
|
||||
- Support RS384 and RS512.
|
||||
See [#117](https://github.com/firebase/php-jwt/pull/117). Thanks [@joostfaassen](https://github.com/joostfaassen)!
|
||||
- Add an example for RS256 openssl.
|
||||
See [#125](https://github.com/firebase/php-jwt/pull/125). Thanks [@akeeman](https://github.com/akeeman)!
|
||||
- Detect invalid Base64 encoding in signature.
|
||||
See [#162](https://github.com/firebase/php-jwt/pull/162). Thanks [@psignoret](https://github.com/psignoret)!
|
||||
- Update `JWT::verify` to handle OpenSSL errors.
|
||||
See [#159](https://github.com/firebase/php-jwt/pull/159). Thanks [@bshaffer](https://github.com/bshaffer)!
|
||||
- Add `array` type hinting to `decode` method
|
||||
See [#101](https://github.com/firebase/php-jwt/pull/101). Thanks [@hywak](https://github.com/hywak)!
|
||||
- Add all JSON error types.
|
||||
See [#110](https://github.com/firebase/php-jwt/pull/110). Thanks [@gbalduzzi](https://github.com/gbalduzzi)!
|
||||
- Bugfix 'kid' not in given key list.
|
||||
See [#129](https://github.com/firebase/php-jwt/pull/129). Thanks [@stampycode](https://github.com/stampycode)!
|
||||
- Miscellaneous cleanup, documentation and test fixes.
|
||||
See [#107](https://github.com/firebase/php-jwt/pull/107), [#115](https://github.com/firebase/php-jwt/pull/115),
|
||||
[#160](https://github.com/firebase/php-jwt/pull/160), [#161](https://github.com/firebase/php-jwt/pull/161), and
|
||||
[#165](https://github.com/firebase/php-jwt/pull/165). Thanks [@akeeman](https://github.com/akeeman),
|
||||
[@chinedufn](https://github.com/chinedufn), and [@bshaffer](https://github.com/bshaffer)!
|
||||
|
||||
## 4.0.0 / 2016-07-17
|
||||
- Add support for late static binding. See [#88](https://github.com/firebase/php-jwt/pull/88) for details. Thanks to [@chappy84](https://github.com/chappy84)!
|
||||
- Use static `$timestamp` instead of `time()` to improve unit testing. See [#93](https://github.com/firebase/php-jwt/pull/93) for details. Thanks to [@josephmcdermott](https://github.com/josephmcdermott)!
|
||||
- Fixes to exceptions classes. See [#81](https://github.com/firebase/php-jwt/pull/81) for details. Thanks to [@Maks3w](https://github.com/Maks3w)!
|
||||
- Fixes to PHPDoc. See [#76](https://github.com/firebase/php-jwt/pull/76) for details. Thanks to [@akeeman](https://github.com/akeeman)!
|
||||
|
||||
## 3.0.0 / 2015-07-22
|
||||
- Minimum PHP version updated from `5.2.0` to `5.3.0`.
|
||||
- Add `\Firebase\JWT` namespace. See
|
||||
[#59](https://github.com/firebase/php-jwt/pull/59) for details. Thanks to
|
||||
[@Dashron](https://github.com/Dashron)!
|
||||
- Require a non-empty key to decode and verify a JWT. See
|
||||
[#60](https://github.com/firebase/php-jwt/pull/60) for details. Thanks to
|
||||
[@sjones608](https://github.com/sjones608)!
|
||||
- Cleaner documentation blocks in the code. See
|
||||
[#62](https://github.com/firebase/php-jwt/pull/62) for details. Thanks to
|
||||
[@johanderuijter](https://github.com/johanderuijter)!
|
||||
|
||||
## 2.2.0 / 2015-06-22
|
||||
- Add support for adding custom, optional JWT headers to `JWT::encode()`. See
|
||||
[#53](https://github.com/firebase/php-jwt/pull/53/files) for details. Thanks to
|
||||
[@mcocaro](https://github.com/mcocaro)!
|
||||
|
||||
## 2.1.0 / 2015-05-20
|
||||
- Add support for adding a leeway to `JWT:decode()` that accounts for clock skew
|
||||
between signing and verifying entities. Thanks to [@lcabral](https://github.com/lcabral)!
|
||||
- Add support for passing an object implementing the `ArrayAccess` interface for
|
||||
`$keys` argument in `JWT::decode()`. Thanks to [@aztech-dev](https://github.com/aztech-dev)!
|
||||
|
||||
## 2.0.0 / 2015-04-01
|
||||
- **Note**: It is strongly recommended that you update to > v2.0.0 to address
|
||||
known security vulnerabilities in prior versions when both symmetric and
|
||||
asymmetric keys are used together.
|
||||
- Update signature for `JWT::decode(...)` to require an array of supported
|
||||
algorithms to use when verifying token signatures.
|
||||
@@ -1,4 +1,4 @@
|
||||
[](https://travis-ci.org/firebase/php-jwt)
|
||||

|
||||
[](https://packagist.org/packages/firebase/php-jwt)
|
||||
[](https://packagist.org/packages/firebase/php-jwt)
|
||||
[](https://packagist.org/packages/firebase/php-jwt)
|
||||
@@ -29,13 +29,13 @@ Example
|
||||
use Firebase\JWT\JWT;
|
||||
use Firebase\JWT\Key;
|
||||
|
||||
$key = "example_key";
|
||||
$payload = array(
|
||||
"iss" => "http://example.org",
|
||||
"aud" => "http://example.com",
|
||||
"iat" => 1356999524,
|
||||
"nbf" => 1357000000
|
||||
);
|
||||
$key = 'example_key';
|
||||
$payload = [
|
||||
'iss' => 'http://example.org',
|
||||
'aud' => 'http://example.com',
|
||||
'iat' => 1356999524,
|
||||
'nbf' => 1357000000
|
||||
];
|
||||
|
||||
/**
|
||||
* IMPORTANT:
|
||||
@@ -98,12 +98,12 @@ ehde/zUxo6UvS7UrBQIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
EOD;
|
||||
|
||||
$payload = array(
|
||||
"iss" => "example.org",
|
||||
"aud" => "example.com",
|
||||
"iat" => 1356999524,
|
||||
"nbf" => 1357000000
|
||||
);
|
||||
$payload = [
|
||||
'iss' => 'example.org',
|
||||
'aud' => 'example.com',
|
||||
'iat' => 1356999524,
|
||||
'nbf' => 1357000000
|
||||
];
|
||||
|
||||
$jwt = JWT::encode($payload, $privateKey, 'RS256');
|
||||
echo "Encode:\n" . print_r($jwt, true) . "\n";
|
||||
@@ -139,12 +139,12 @@ $privateKey = openssl_pkey_get_private(
|
||||
$passphrase
|
||||
);
|
||||
|
||||
$payload = array(
|
||||
"iss" => "example.org",
|
||||
"aud" => "example.com",
|
||||
"iat" => 1356999524,
|
||||
"nbf" => 1357000000
|
||||
);
|
||||
$payload = [
|
||||
'iss' => 'example.org',
|
||||
'aud' => 'example.com',
|
||||
'iat' => 1356999524,
|
||||
'nbf' => 1357000000
|
||||
];
|
||||
|
||||
$jwt = JWT::encode($payload, $privateKey, 'RS256');
|
||||
echo "Encode:\n" . print_r($jwt, true) . "\n";
|
||||
@@ -173,12 +173,12 @@ $privateKey = base64_encode(sodium_crypto_sign_secretkey($keyPair));
|
||||
|
||||
$publicKey = base64_encode(sodium_crypto_sign_publickey($keyPair));
|
||||
|
||||
$payload = array(
|
||||
"iss" => "example.org",
|
||||
"aud" => "example.com",
|
||||
"iat" => 1356999524,
|
||||
"nbf" => 1357000000
|
||||
);
|
||||
$payload = [
|
||||
'iss' => 'example.org',
|
||||
'aud' => 'example.com',
|
||||
'iat' => 1356999524,
|
||||
'nbf' => 1357000000
|
||||
];
|
||||
|
||||
$jwt = JWT::encode($payload, $privateKey, 'EdDSA');
|
||||
echo "Encode:\n" . print_r($jwt, true) . "\n";
|
||||
@@ -198,72 +198,115 @@ use Firebase\JWT\JWT;
|
||||
// this endpoint: https://www.gstatic.com/iap/verify/public_key-jwk
|
||||
$jwks = ['keys' => []];
|
||||
|
||||
// JWK::parseKeySet($jwks) returns an associative array of **kid** to private
|
||||
// key. Pass this as the second parameter to JWT::decode.
|
||||
// NOTE: The deprecated $supportedAlgorithm must be supplied when parsing from JWK.
|
||||
JWT::decode($payload, JWK::parseKeySet($jwks), $supportedAlgorithm);
|
||||
// JWK::parseKeySet($jwks) returns an associative array of **kid** to Firebase\JWT\Key
|
||||
// objects. Pass this as the second parameter to JWT::decode.
|
||||
JWT::decode($payload, JWK::parseKeySet($jwks));
|
||||
```
|
||||
|
||||
Changelog
|
||||
---------
|
||||
Using Cached Key Sets
|
||||
---------------------
|
||||
|
||||
#### 5.0.0 / 2017-06-26
|
||||
- Support RS384 and RS512.
|
||||
See [#117](https://github.com/firebase/php-jwt/pull/117). Thanks [@joostfaassen](https://github.com/joostfaassen)!
|
||||
- Add an example for RS256 openssl.
|
||||
See [#125](https://github.com/firebase/php-jwt/pull/125). Thanks [@akeeman](https://github.com/akeeman)!
|
||||
- Detect invalid Base64 encoding in signature.
|
||||
See [#162](https://github.com/firebase/php-jwt/pull/162). Thanks [@psignoret](https://github.com/psignoret)!
|
||||
- Update `JWT::verify` to handle OpenSSL errors.
|
||||
See [#159](https://github.com/firebase/php-jwt/pull/159). Thanks [@bshaffer](https://github.com/bshaffer)!
|
||||
- Add `array` type hinting to `decode` method
|
||||
See [#101](https://github.com/firebase/php-jwt/pull/101). Thanks [@hywak](https://github.com/hywak)!
|
||||
- Add all JSON error types.
|
||||
See [#110](https://github.com/firebase/php-jwt/pull/110). Thanks [@gbalduzzi](https://github.com/gbalduzzi)!
|
||||
- Bugfix 'kid' not in given key list.
|
||||
See [#129](https://github.com/firebase/php-jwt/pull/129). Thanks [@stampycode](https://github.com/stampycode)!
|
||||
- Miscellaneous cleanup, documentation and test fixes.
|
||||
See [#107](https://github.com/firebase/php-jwt/pull/107), [#115](https://github.com/firebase/php-jwt/pull/115),
|
||||
[#160](https://github.com/firebase/php-jwt/pull/160), [#161](https://github.com/firebase/php-jwt/pull/161), and
|
||||
[#165](https://github.com/firebase/php-jwt/pull/165). Thanks [@akeeman](https://github.com/akeeman),
|
||||
[@chinedufn](https://github.com/chinedufn), and [@bshaffer](https://github.com/bshaffer)!
|
||||
The `CachedKeySet` class can be used to fetch and cache JWKS (JSON Web Key Sets) from a public URI.
|
||||
This has the following advantages:
|
||||
|
||||
#### 4.0.0 / 2016-07-17
|
||||
- Add support for late static binding. See [#88](https://github.com/firebase/php-jwt/pull/88) for details. Thanks to [@chappy84](https://github.com/chappy84)!
|
||||
- Use static `$timestamp` instead of `time()` to improve unit testing. See [#93](https://github.com/firebase/php-jwt/pull/93) for details. Thanks to [@josephmcdermott](https://github.com/josephmcdermott)!
|
||||
- Fixes to exceptions classes. See [#81](https://github.com/firebase/php-jwt/pull/81) for details. Thanks to [@Maks3w](https://github.com/Maks3w)!
|
||||
- Fixes to PHPDoc. See [#76](https://github.com/firebase/php-jwt/pull/76) for details. Thanks to [@akeeman](https://github.com/akeeman)!
|
||||
1. The results are cached for performance.
|
||||
2. If an unrecognized key is requested, the cache is refreshed, to accomodate for key rotation.
|
||||
3. If rate limiting is enabled, the JWKS URI will not make more than 10 requests a second.
|
||||
|
||||
#### 3.0.0 / 2015-07-22
|
||||
- Minimum PHP version updated from `5.2.0` to `5.3.0`.
|
||||
- Add `\Firebase\JWT` namespace. See
|
||||
[#59](https://github.com/firebase/php-jwt/pull/59) for details. Thanks to
|
||||
[@Dashron](https://github.com/Dashron)!
|
||||
- Require a non-empty key to decode and verify a JWT. See
|
||||
[#60](https://github.com/firebase/php-jwt/pull/60) for details. Thanks to
|
||||
[@sjones608](https://github.com/sjones608)!
|
||||
- Cleaner documentation blocks in the code. See
|
||||
[#62](https://github.com/firebase/php-jwt/pull/62) for details. Thanks to
|
||||
[@johanderuijter](https://github.com/johanderuijter)!
|
||||
```php
|
||||
use Firebase\JWT\CachedKeySet;
|
||||
use Firebase\JWT\JWT;
|
||||
|
||||
#### 2.2.0 / 2015-06-22
|
||||
- Add support for adding custom, optional JWT headers to `JWT::encode()`. See
|
||||
[#53](https://github.com/firebase/php-jwt/pull/53/files) for details. Thanks to
|
||||
[@mcocaro](https://github.com/mcocaro)!
|
||||
// The URI for the JWKS you wish to cache the results from
|
||||
$jwksUri = 'https://www.gstatic.com/iap/verify/public_key-jwk';
|
||||
|
||||
#### 2.1.0 / 2015-05-20
|
||||
- Add support for adding a leeway to `JWT:decode()` that accounts for clock skew
|
||||
between signing and verifying entities. Thanks to [@lcabral](https://github.com/lcabral)!
|
||||
- Add support for passing an object implementing the `ArrayAccess` interface for
|
||||
`$keys` argument in `JWT::decode()`. Thanks to [@aztech-dev](https://github.com/aztech-dev)!
|
||||
// Create an HTTP client (can be any PSR-7 compatible HTTP client)
|
||||
$httpClient = new GuzzleHttp\Client();
|
||||
|
||||
#### 2.0.0 / 2015-04-01
|
||||
- **Note**: It is strongly recommended that you update to > v2.0.0 to address
|
||||
known security vulnerabilities in prior versions when both symmetric and
|
||||
asymmetric keys are used together.
|
||||
- Update signature for `JWT::decode(...)` to require an array of supported
|
||||
algorithms to use when verifying token signatures.
|
||||
// Create an HTTP request factory (can be any PSR-17 compatible HTTP request factory)
|
||||
$httpFactory = new GuzzleHttp\Psr\HttpFactory();
|
||||
|
||||
// Create a cache item pool (can be any PSR-6 compatible cache item pool)
|
||||
$cacheItemPool = Phpfastcache\CacheManager::getInstance('files');
|
||||
|
||||
$keySet = new CachedKeySet(
|
||||
$jwksUri,
|
||||
$httpClient,
|
||||
$httpFactory,
|
||||
$cacheItemPool,
|
||||
null, // $expiresAfter int seconds to set the JWKS to expire
|
||||
true // $rateLimit true to enable rate limit of 10 RPS on lookup of invalid keys
|
||||
);
|
||||
|
||||
$jwt = 'eyJhbGci...'; // Some JWT signed by a key from the $jwkUri above
|
||||
$decoded = JWT::decode($jwt, $keySet);
|
||||
```
|
||||
|
||||
Miscellaneous
|
||||
-------------
|
||||
|
||||
#### Exception Handling
|
||||
|
||||
When a call to `JWT::decode` is invalid, it will throw one of the following exceptions:
|
||||
|
||||
```php
|
||||
use Firebase\JWT\JWT;
|
||||
use Firebase\JWT\SignatureInvalidException;
|
||||
use Firebase\JWT\BeforeValidException;
|
||||
use Firebase\JWT\ExpiredException;
|
||||
use DomainException;
|
||||
use InvalidArgumentException;
|
||||
use UnexpectedValueException;
|
||||
|
||||
try {
|
||||
$decoded = JWT::decode($payload, $keys);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
// provided key/key-array is empty or malformed.
|
||||
} catch (DomainException $e) {
|
||||
// provided algorithm is unsupported OR
|
||||
// provided key is invalid OR
|
||||
// unknown error thrown in openSSL or libsodium OR
|
||||
// libsodium is required but not available.
|
||||
} catch (SignatureInvalidException $e) {
|
||||
// provided JWT signature verification failed.
|
||||
} catch (BeforeValidException $e) {
|
||||
// provided JWT is trying to be used before "nbf" claim OR
|
||||
// provided JWT is trying to be used before "iat" claim.
|
||||
} catch (ExpiredException $e) {
|
||||
// provided JWT is trying to be used after "exp" claim.
|
||||
} catch (UnexpectedValueException $e) {
|
||||
// provided JWT is malformed OR
|
||||
// provided JWT is missing an algorithm / using an unsupported algorithm OR
|
||||
// provided JWT algorithm does not match provided key OR
|
||||
// provided key ID in key/key-array is empty or invalid.
|
||||
}
|
||||
```
|
||||
|
||||
All exceptions in the `Firebase\JWT` namespace extend `UnexpectedValueException`, and can be simplified
|
||||
like this:
|
||||
|
||||
```php
|
||||
try {
|
||||
$decoded = JWT::decode($payload, $keys);
|
||||
} catch (LogicException $e) {
|
||||
// errors having to do with environmental setup or malformed JWT Keys
|
||||
} catch (UnexpectedValueException $e) {
|
||||
// errors having to do with JWT signature and claims
|
||||
}
|
||||
```
|
||||
|
||||
#### Casting to array
|
||||
|
||||
The return value of `JWT::decode` is the generic PHP object `stdClass`. If you'd like to handle with arrays
|
||||
instead, you can do the following:
|
||||
|
||||
```php
|
||||
// return type is stdClass
|
||||
$decoded = JWT::decode($payload, $keys);
|
||||
|
||||
// cast to array
|
||||
$decoded = json_decode(json_encode($decoded), true);
|
||||
```
|
||||
|
||||
Tests
|
||||
-----
|
||||
|
||||
@@ -20,10 +20,11 @@
|
||||
],
|
||||
"license": "BSD-3-Clause",
|
||||
"require": {
|
||||
"php": ">=5.3.0"
|
||||
"php": "^7.1||^8.0"
|
||||
},
|
||||
"suggest": {
|
||||
"paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present"
|
||||
"paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present",
|
||||
"ext-sodium": "Support EdDSA (Ed25519) signatures"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
@@ -31,6 +32,11 @@
|
||||
}
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": ">=4.8 <=9"
|
||||
"guzzlehttp/guzzle": "^6.5||^7.4",
|
||||
"phpspec/prophecy-phpunit": "^1.1",
|
||||
"phpunit/phpunit": "^7.5||^9.5",
|
||||
"psr/cache": "^1.0||^2.0",
|
||||
"psr/http-client": "^1.0",
|
||||
"psr/http-factory": "^1.0"
|
||||
}
|
||||
}
|
||||
|
||||
258
lib/firebase/php-jwt/src/CachedKeySet.php
Normal file
258
lib/firebase/php-jwt/src/CachedKeySet.php
Normal file
@@ -0,0 +1,258 @@
|
||||
<?php
|
||||
|
||||
namespace Firebase\JWT;
|
||||
|
||||
use ArrayAccess;
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
use OutOfBoundsException;
|
||||
use Psr\Cache\CacheItemInterface;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
use Psr\Http\Client\ClientInterface;
|
||||
use Psr\Http\Message\RequestFactoryInterface;
|
||||
use RuntimeException;
|
||||
use UnexpectedValueException;
|
||||
|
||||
/**
|
||||
* @implements ArrayAccess<string, Key>
|
||||
*/
|
||||
class CachedKeySet implements ArrayAccess
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $jwksUri;
|
||||
/**
|
||||
* @var ClientInterface
|
||||
*/
|
||||
private $httpClient;
|
||||
/**
|
||||
* @var RequestFactoryInterface
|
||||
*/
|
||||
private $httpFactory;
|
||||
/**
|
||||
* @var CacheItemPoolInterface
|
||||
*/
|
||||
private $cache;
|
||||
/**
|
||||
* @var ?int
|
||||
*/
|
||||
private $expiresAfter;
|
||||
/**
|
||||
* @var ?CacheItemInterface
|
||||
*/
|
||||
private $cacheItem;
|
||||
/**
|
||||
* @var array<string, array<mixed>>
|
||||
*/
|
||||
private $keySet;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $cacheKey;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $cacheKeyPrefix = 'jwks';
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $maxKeyLength = 64;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $rateLimit;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $rateLimitCacheKey;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $maxCallsPerMinute = 10;
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $defaultAlg;
|
||||
|
||||
public function __construct(
|
||||
string $jwksUri,
|
||||
ClientInterface $httpClient,
|
||||
RequestFactoryInterface $httpFactory,
|
||||
CacheItemPoolInterface $cache,
|
||||
int $expiresAfter = null,
|
||||
bool $rateLimit = false,
|
||||
string $defaultAlg = null
|
||||
) {
|
||||
$this->jwksUri = $jwksUri;
|
||||
$this->httpClient = $httpClient;
|
||||
$this->httpFactory = $httpFactory;
|
||||
$this->cache = $cache;
|
||||
$this->expiresAfter = $expiresAfter;
|
||||
$this->rateLimit = $rateLimit;
|
||||
$this->defaultAlg = $defaultAlg;
|
||||
$this->setCacheKeys();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $keyId
|
||||
* @return Key
|
||||
*/
|
||||
public function offsetGet($keyId): Key
|
||||
{
|
||||
if (!$this->keyIdExists($keyId)) {
|
||||
throw new OutOfBoundsException('Key ID not found');
|
||||
}
|
||||
return JWK::parseKey($this->keySet[$keyId], $this->defaultAlg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $keyId
|
||||
* @return bool
|
||||
*/
|
||||
public function offsetExists($keyId): bool
|
||||
{
|
||||
return $this->keyIdExists($keyId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $offset
|
||||
* @param Key $value
|
||||
*/
|
||||
public function offsetSet($offset, $value): void
|
||||
{
|
||||
throw new LogicException('Method not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $offset
|
||||
*/
|
||||
public function offsetUnset($offset): void
|
||||
{
|
||||
throw new LogicException('Method not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<mixed>
|
||||
*/
|
||||
private function formatJwksForCache(string $jwks): array
|
||||
{
|
||||
$jwks = json_decode($jwks, true);
|
||||
|
||||
if (!isset($jwks['keys'])) {
|
||||
throw new UnexpectedValueException('"keys" member must exist in the JWK Set');
|
||||
}
|
||||
|
||||
if (empty($jwks['keys'])) {
|
||||
throw new InvalidArgumentException('JWK Set did not contain any keys');
|
||||
}
|
||||
|
||||
$keys = [];
|
||||
foreach ($jwks['keys'] as $k => $v) {
|
||||
$kid = isset($v['kid']) ? $v['kid'] : $k;
|
||||
$keys[(string) $kid] = $v;
|
||||
}
|
||||
|
||||
return $keys;
|
||||
}
|
||||
|
||||
private function keyIdExists(string $keyId): bool
|
||||
{
|
||||
if (null === $this->keySet) {
|
||||
$item = $this->getCacheItem();
|
||||
// Try to load keys from cache
|
||||
if ($item->isHit()) {
|
||||
// item found! retrieve it
|
||||
$this->keySet = $item->get();
|
||||
// If the cached item is a string, the JWKS response was cached (previous behavior).
|
||||
// Parse this into expected format array<kid, jwk> instead.
|
||||
if (\is_string($this->keySet)) {
|
||||
$this->keySet = $this->formatJwksForCache($this->keySet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($this->keySet[$keyId])) {
|
||||
if ($this->rateLimitExceeded()) {
|
||||
return false;
|
||||
}
|
||||
$request = $this->httpFactory->createRequest('GET', $this->jwksUri);
|
||||
$jwksResponse = $this->httpClient->sendRequest($request);
|
||||
$this->keySet = $this->formatJwksForCache((string) $jwksResponse->getBody());
|
||||
|
||||
if (!isset($this->keySet[$keyId])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$item = $this->getCacheItem();
|
||||
$item->set($this->keySet);
|
||||
if ($this->expiresAfter) {
|
||||
$item->expiresAfter($this->expiresAfter);
|
||||
}
|
||||
$this->cache->save($item);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function rateLimitExceeded(): bool
|
||||
{
|
||||
if (!$this->rateLimit) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$cacheItem = $this->cache->getItem($this->rateLimitCacheKey);
|
||||
if (!$cacheItem->isHit()) {
|
||||
$cacheItem->expiresAfter(1); // # of calls are cached each minute
|
||||
}
|
||||
|
||||
$callsPerMinute = (int) $cacheItem->get();
|
||||
if (++$callsPerMinute > $this->maxCallsPerMinute) {
|
||||
return true;
|
||||
}
|
||||
$cacheItem->set($callsPerMinute);
|
||||
$this->cache->save($cacheItem);
|
||||
return false;
|
||||
}
|
||||
|
||||
private function getCacheItem(): CacheItemInterface
|
||||
{
|
||||
if (\is_null($this->cacheItem)) {
|
||||
$this->cacheItem = $this->cache->getItem($this->cacheKey);
|
||||
}
|
||||
|
||||
return $this->cacheItem;
|
||||
}
|
||||
|
||||
private function setCacheKeys(): void
|
||||
{
|
||||
if (empty($this->jwksUri)) {
|
||||
throw new RuntimeException('JWKS URI is empty');
|
||||
}
|
||||
|
||||
// ensure we do not have illegal characters
|
||||
$key = preg_replace('|[^a-zA-Z0-9_\.!]|', '', $this->jwksUri);
|
||||
|
||||
// add prefix
|
||||
$key = $this->cacheKeyPrefix . $key;
|
||||
|
||||
// Hash keys if they exceed $maxKeyLength of 64
|
||||
if (\strlen($key) > $this->maxKeyLength) {
|
||||
$key = substr(hash('sha256', $key), 0, $this->maxKeyLength);
|
||||
}
|
||||
|
||||
$this->cacheKey = $key;
|
||||
|
||||
if ($this->rateLimit) {
|
||||
// add prefix
|
||||
$rateLimitKey = $this->cacheKeyPrefix . 'ratelimit' . $key;
|
||||
|
||||
// Hash keys if they exceed $maxKeyLength of 64
|
||||
if (\strlen($rateLimitKey) > $this->maxKeyLength) {
|
||||
$rateLimitKey = substr(hash('sha256', $rateLimitKey), 0, $this->maxKeyLength);
|
||||
}
|
||||
|
||||
$this->rateLimitCacheKey = $rateLimitKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,12 +20,25 @@ use UnexpectedValueException;
|
||||
*/
|
||||
class JWK
|
||||
{
|
||||
private const OID = '1.2.840.10045.2.1';
|
||||
private const ASN1_OBJECT_IDENTIFIER = 0x06;
|
||||
private const ASN1_SEQUENCE = 0x10; // also defined in JWT
|
||||
private const ASN1_BIT_STRING = 0x03;
|
||||
private const EC_CURVES = [
|
||||
'P-256' => '1.2.840.10045.3.1.7', // Len: 64
|
||||
'secp256k1' => '1.3.132.0.10', // Len: 64
|
||||
// 'P-384' => '1.3.132.0.34', // Len: 96 (not yet supported)
|
||||
// 'P-521' => '1.3.132.0.35', // Len: 132 (not supported)
|
||||
];
|
||||
|
||||
/**
|
||||
* Parse a set of JWK keys
|
||||
*
|
||||
* @param array $jwks The JSON Web Key Set as an associative array
|
||||
* @param array<mixed> $jwks The JSON Web Key Set as an associative array
|
||||
* @param string $defaultAlg The algorithm for the Key object if "alg" is not set in the
|
||||
* JSON Web Key Set
|
||||
*
|
||||
* @return array An associative array that represents the set of keys
|
||||
* @return array<string, Key> An associative array of key IDs (kid) to Key objects
|
||||
*
|
||||
* @throws InvalidArgumentException Provided JWK Set is empty
|
||||
* @throws UnexpectedValueException Provided JWK Set was invalid
|
||||
@@ -33,21 +46,22 @@ class JWK
|
||||
*
|
||||
* @uses parseKey
|
||||
*/
|
||||
public static function parseKeySet(array $jwks)
|
||||
public static function parseKeySet(array $jwks, string $defaultAlg = null): array
|
||||
{
|
||||
$keys = array();
|
||||
$keys = [];
|
||||
|
||||
if (!isset($jwks['keys'])) {
|
||||
throw new UnexpectedValueException('"keys" member must exist in the JWK Set');
|
||||
}
|
||||
|
||||
if (empty($jwks['keys'])) {
|
||||
throw new InvalidArgumentException('JWK Set did not contain any keys');
|
||||
}
|
||||
|
||||
foreach ($jwks['keys'] as $k => $v) {
|
||||
$kid = isset($v['kid']) ? $v['kid'] : $k;
|
||||
if ($key = self::parseKey($v)) {
|
||||
$keys[$kid] = $key;
|
||||
if ($key = self::parseKey($v, $defaultAlg)) {
|
||||
$keys[(string) $kid] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,9 +75,11 @@ class JWK
|
||||
/**
|
||||
* Parse a JWK key
|
||||
*
|
||||
* @param array $jwk An individual JWK
|
||||
* @param array<mixed> $jwk An individual JWK
|
||||
* @param string $defaultAlg The algorithm for the Key object if "alg" is not set in the
|
||||
* JSON Web Key Set
|
||||
*
|
||||
* @return resource|array An associative array that represents the key
|
||||
* @return Key The key object for the JWK
|
||||
*
|
||||
* @throws InvalidArgumentException Provided JWK is empty
|
||||
* @throws UnexpectedValueException Provided JWK was invalid
|
||||
@@ -71,15 +87,27 @@ class JWK
|
||||
*
|
||||
* @uses createPemFromModulusAndExponent
|
||||
*/
|
||||
public static function parseKey(array $jwk)
|
||||
public static function parseKey(array $jwk, string $defaultAlg = null): ?Key
|
||||
{
|
||||
if (empty($jwk)) {
|
||||
throw new InvalidArgumentException('JWK must not be empty');
|
||||
}
|
||||
|
||||
if (!isset($jwk['kty'])) {
|
||||
throw new UnexpectedValueException('JWK must contain a "kty" parameter');
|
||||
}
|
||||
|
||||
if (!isset($jwk['alg'])) {
|
||||
if (\is_null($defaultAlg)) {
|
||||
// The "alg" parameter is optional in a KTY, but an algorithm is required
|
||||
// for parsing in this library. Use the $defaultAlg parameter when parsing the
|
||||
// key set in order to prevent this error.
|
||||
// @see https://datatracker.ietf.org/doc/html/rfc7517#section-4.4
|
||||
throw new UnexpectedValueException('JWK must contain an "alg" parameter');
|
||||
}
|
||||
$jwk['alg'] = $defaultAlg;
|
||||
}
|
||||
|
||||
switch ($jwk['kty']) {
|
||||
case 'RSA':
|
||||
if (!empty($jwk['d'])) {
|
||||
@@ -96,11 +124,72 @@ class JWK
|
||||
'OpenSSL error: ' . \openssl_error_string()
|
||||
);
|
||||
}
|
||||
return $publicKey;
|
||||
return new Key($publicKey, $jwk['alg']);
|
||||
case 'EC':
|
||||
if (isset($jwk['d'])) {
|
||||
// The key is actually a private key
|
||||
throw new UnexpectedValueException('Key data must be for a public key');
|
||||
}
|
||||
|
||||
if (empty($jwk['crv'])) {
|
||||
throw new UnexpectedValueException('crv not set');
|
||||
}
|
||||
|
||||
if (!isset(self::EC_CURVES[$jwk['crv']])) {
|
||||
throw new DomainException('Unrecognised or unsupported EC curve');
|
||||
}
|
||||
|
||||
if (empty($jwk['x']) || empty($jwk['y'])) {
|
||||
throw new UnexpectedValueException('x and y not set');
|
||||
}
|
||||
|
||||
$publicKey = self::createPemFromCrvAndXYCoordinates($jwk['crv'], $jwk['x'], $jwk['y']);
|
||||
return new Key($publicKey, $jwk['alg']);
|
||||
default:
|
||||
// Currently only RSA is supported
|
||||
break;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the EC JWK values to pem format.
|
||||
*
|
||||
* @param string $crv The EC curve (only P-256 is supported)
|
||||
* @param string $x The EC x-coordinate
|
||||
* @param string $y The EC y-coordinate
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function createPemFromCrvAndXYCoordinates(string $crv, string $x, string $y): string
|
||||
{
|
||||
$pem =
|
||||
self::encodeDER(
|
||||
self::ASN1_SEQUENCE,
|
||||
self::encodeDER(
|
||||
self::ASN1_SEQUENCE,
|
||||
self::encodeDER(
|
||||
self::ASN1_OBJECT_IDENTIFIER,
|
||||
self::encodeOID(self::OID)
|
||||
)
|
||||
. self::encodeDER(
|
||||
self::ASN1_OBJECT_IDENTIFIER,
|
||||
self::encodeOID(self::EC_CURVES[$crv])
|
||||
)
|
||||
) .
|
||||
self::encodeDER(
|
||||
self::ASN1_BIT_STRING,
|
||||
\chr(0x00) . \chr(0x04)
|
||||
. JWT::urlsafeB64Decode($x)
|
||||
. JWT::urlsafeB64Decode($y)
|
||||
)
|
||||
);
|
||||
|
||||
return sprintf(
|
||||
"-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----\n",
|
||||
wordwrap(base64_encode($pem), 64, "\n", true)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,22 +202,22 @@ class JWK
|
||||
*
|
||||
* @uses encodeLength
|
||||
*/
|
||||
private static function createPemFromModulusAndExponent($n, $e)
|
||||
{
|
||||
$modulus = JWT::urlsafeB64Decode($n);
|
||||
$publicExponent = JWT::urlsafeB64Decode($e);
|
||||
private static function createPemFromModulusAndExponent(
|
||||
string $n,
|
||||
string $e
|
||||
): string {
|
||||
$mod = JWT::urlsafeB64Decode($n);
|
||||
$exp = JWT::urlsafeB64Decode($e);
|
||||
|
||||
$components = array(
|
||||
'modulus' => \pack('Ca*a*', 2, self::encodeLength(\strlen($modulus)), $modulus),
|
||||
'publicExponent' => \pack('Ca*a*', 2, self::encodeLength(\strlen($publicExponent)), $publicExponent)
|
||||
);
|
||||
$modulus = \pack('Ca*a*', 2, self::encodeLength(\strlen($mod)), $mod);
|
||||
$publicExponent = \pack('Ca*a*', 2, self::encodeLength(\strlen($exp)), $exp);
|
||||
|
||||
$rsaPublicKey = \pack(
|
||||
'Ca*a*a*',
|
||||
48,
|
||||
self::encodeLength(\strlen($components['modulus']) + \strlen($components['publicExponent'])),
|
||||
$components['modulus'],
|
||||
$components['publicExponent']
|
||||
self::encodeLength(\strlen($modulus) + \strlen($publicExponent)),
|
||||
$modulus,
|
||||
$publicExponent
|
||||
);
|
||||
|
||||
// sequence(oid(1.2.840.113549.1.1.1), null)) = rsaEncryption.
|
||||
@@ -143,11 +232,9 @@ class JWK
|
||||
$rsaOID . $rsaPublicKey
|
||||
);
|
||||
|
||||
$rsaPublicKey = "-----BEGIN PUBLIC KEY-----\r\n" .
|
||||
return "-----BEGIN PUBLIC KEY-----\r\n" .
|
||||
\chunk_split(\base64_encode($rsaPublicKey), 64) .
|
||||
'-----END PUBLIC KEY-----';
|
||||
|
||||
return $rsaPublicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -159,7 +246,7 @@ class JWK
|
||||
* @param int $length
|
||||
* @return string
|
||||
*/
|
||||
private static function encodeLength($length)
|
||||
private static function encodeLength(int $length): string
|
||||
{
|
||||
if ($length <= 0x7F) {
|
||||
return \chr($length);
|
||||
@@ -169,4 +256,68 @@ class JWK
|
||||
|
||||
return \pack('Ca*', 0x80 | \strlen($temp), $temp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a value into a DER object.
|
||||
* Also defined in Firebase\JWT\JWT
|
||||
*
|
||||
* @param int $type DER tag
|
||||
* @param string $value the value to encode
|
||||
* @return string the encoded object
|
||||
*/
|
||||
private static function encodeDER(int $type, string $value): string
|
||||
{
|
||||
$tag_header = 0;
|
||||
if ($type === self::ASN1_SEQUENCE) {
|
||||
$tag_header |= 0x20;
|
||||
}
|
||||
|
||||
// Type
|
||||
$der = \chr($tag_header | $type);
|
||||
|
||||
// Length
|
||||
$der .= \chr(\strlen($value));
|
||||
|
||||
return $der . $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a string into a DER-encoded OID.
|
||||
*
|
||||
* @param string $oid the OID string
|
||||
* @return string the binary DER-encoded OID
|
||||
*/
|
||||
private static function encodeOID(string $oid): string
|
||||
{
|
||||
$octets = explode('.', $oid);
|
||||
|
||||
// Get the first octet
|
||||
$first = (int) array_shift($octets);
|
||||
$second = (int) array_shift($octets);
|
||||
$oid = \chr($first * 40 + $second);
|
||||
|
||||
// Iterate over subsequent octets
|
||||
foreach ($octets as $octet) {
|
||||
if ($octet == 0) {
|
||||
$oid .= \chr(0x00);
|
||||
continue;
|
||||
}
|
||||
$bin = '';
|
||||
|
||||
while ($octet) {
|
||||
$bin .= \chr(0x80 | ($octet & 0x7f));
|
||||
$octet >>= 7;
|
||||
}
|
||||
$bin[0] = $bin[0] & \chr(0x7f);
|
||||
|
||||
// Convert to big endian if necessary
|
||||
if (pack('V', 65534) == pack('L', 65534)) {
|
||||
$oid .= strrev($bin);
|
||||
} else {
|
||||
$oid .= $bin;
|
||||
}
|
||||
}
|
||||
|
||||
return $oid;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
namespace Firebase\JWT;
|
||||
|
||||
use ArrayAccess;
|
||||
use DateTime;
|
||||
use DomainException;
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use OpenSSLAsymmetricKey;
|
||||
use OpenSSLCertificate;
|
||||
use stdClass;
|
||||
use UnexpectedValueException;
|
||||
use DateTime;
|
||||
|
||||
/**
|
||||
* JSON Web Token implementation, based on this spec:
|
||||
@@ -25,52 +27,58 @@ use DateTime;
|
||||
*/
|
||||
class JWT
|
||||
{
|
||||
const ASN1_INTEGER = 0x02;
|
||||
const ASN1_SEQUENCE = 0x10;
|
||||
const ASN1_BIT_STRING = 0x03;
|
||||
private const ASN1_INTEGER = 0x02;
|
||||
private const ASN1_SEQUENCE = 0x10;
|
||||
private const ASN1_BIT_STRING = 0x03;
|
||||
|
||||
/**
|
||||
* When checking nbf, iat or expiration times,
|
||||
* we want to provide some extra leeway time to
|
||||
* account for clock skew.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public static $leeway = 0;
|
||||
|
||||
/**
|
||||
* Allow the current timestamp to be specified.
|
||||
* Useful for fixing a value within unit testing.
|
||||
*
|
||||
* Will default to PHP time() value if null.
|
||||
*
|
||||
* @var ?int
|
||||
*/
|
||||
public static $timestamp = null;
|
||||
|
||||
public static $supported_algs = array(
|
||||
'ES384' => array('openssl', 'SHA384'),
|
||||
'ES256' => array('openssl', 'SHA256'),
|
||||
'HS256' => array('hash_hmac', 'SHA256'),
|
||||
'HS384' => array('hash_hmac', 'SHA384'),
|
||||
'HS512' => array('hash_hmac', 'SHA512'),
|
||||
'RS256' => array('openssl', 'SHA256'),
|
||||
'RS384' => array('openssl', 'SHA384'),
|
||||
'RS512' => array('openssl', 'SHA512'),
|
||||
'EdDSA' => array('sodium_crypto', 'EdDSA'),
|
||||
);
|
||||
/**
|
||||
* @var array<string, string[]>
|
||||
*/
|
||||
public static $supported_algs = [
|
||||
'ES384' => ['openssl', 'SHA384'],
|
||||
'ES256' => ['openssl', 'SHA256'],
|
||||
'ES256K' => ['openssl', 'SHA256'],
|
||||
'HS256' => ['hash_hmac', 'SHA256'],
|
||||
'HS384' => ['hash_hmac', 'SHA384'],
|
||||
'HS512' => ['hash_hmac', 'SHA512'],
|
||||
'RS256' => ['openssl', 'SHA256'],
|
||||
'RS384' => ['openssl', 'SHA384'],
|
||||
'RS512' => ['openssl', 'SHA512'],
|
||||
'EdDSA' => ['sodium_crypto', 'EdDSA'],
|
||||
];
|
||||
|
||||
/**
|
||||
* Decodes a JWT string into a PHP object.
|
||||
*
|
||||
* @param string $jwt The JWT
|
||||
* @param Key|array<Key>|mixed $keyOrKeyArray The Key or array of Key objects.
|
||||
* If the algorithm used is asymmetric, this is the public key
|
||||
* Each Key object contains an algorithm and matching key.
|
||||
* Supported algorithms are 'ES384','ES256', 'HS256', 'HS384',
|
||||
* 'HS512', 'RS256', 'RS384', and 'RS512'
|
||||
* @param array $allowed_algs [DEPRECATED] List of supported verification algorithms. Only
|
||||
* should be used for backwards compatibility.
|
||||
* @param string $jwt The JWT
|
||||
* @param Key|array<string,Key> $keyOrKeyArray The Key or associative array of key IDs (kid) to Key objects.
|
||||
* If the algorithm used is asymmetric, this is the public key
|
||||
* Each Key object contains an algorithm and matching key.
|
||||
* Supported algorithms are 'ES384','ES256', 'HS256', 'HS384',
|
||||
* 'HS512', 'RS256', 'RS384', and 'RS512'
|
||||
*
|
||||
* @return object The JWT's payload as a PHP object
|
||||
* @return stdClass The JWT's payload as a PHP object
|
||||
*
|
||||
* @throws InvalidArgumentException Provided JWT was empty
|
||||
* @throws InvalidArgumentException Provided key/key-array was empty or malformed
|
||||
* @throws DomainException Provided JWT is malformed
|
||||
* @throws UnexpectedValueException Provided JWT was invalid
|
||||
* @throws SignatureInvalidException Provided JWT was invalid because the signature verification failed
|
||||
* @throws BeforeValidException Provided JWT is trying to be used before it's eligible as defined by 'nbf'
|
||||
@@ -80,27 +88,37 @@ class JWT
|
||||
* @uses jsonDecode
|
||||
* @uses urlsafeB64Decode
|
||||
*/
|
||||
public static function decode($jwt, $keyOrKeyArray, array $allowed_algs = array())
|
||||
{
|
||||
public static function decode(
|
||||
string $jwt,
|
||||
$keyOrKeyArray
|
||||
): stdClass {
|
||||
// Validate JWT
|
||||
$timestamp = \is_null(static::$timestamp) ? \time() : static::$timestamp;
|
||||
|
||||
if (empty($keyOrKeyArray)) {
|
||||
throw new InvalidArgumentException('Key may not be empty');
|
||||
}
|
||||
$tks = \explode('.', $jwt);
|
||||
if (\count($tks) != 3) {
|
||||
if (\count($tks) !== 3) {
|
||||
throw new UnexpectedValueException('Wrong number of segments');
|
||||
}
|
||||
list($headb64, $bodyb64, $cryptob64) = $tks;
|
||||
if (null === ($header = static::jsonDecode(static::urlsafeB64Decode($headb64)))) {
|
||||
$headerRaw = static::urlsafeB64Decode($headb64);
|
||||
if (null === ($header = static::jsonDecode($headerRaw))) {
|
||||
throw new UnexpectedValueException('Invalid header encoding');
|
||||
}
|
||||
if (null === $payload = static::jsonDecode(static::urlsafeB64Decode($bodyb64))) {
|
||||
$payloadRaw = static::urlsafeB64Decode($bodyb64);
|
||||
if (null === ($payload = static::jsonDecode($payloadRaw))) {
|
||||
throw new UnexpectedValueException('Invalid claims encoding');
|
||||
}
|
||||
if (false === ($sig = static::urlsafeB64Decode($cryptob64))) {
|
||||
throw new UnexpectedValueException('Invalid signature encoding');
|
||||
if (\is_array($payload)) {
|
||||
// prevent PHP Fatal Error in edge-cases when payload is empty array
|
||||
$payload = (object) $payload;
|
||||
}
|
||||
if (!$payload instanceof stdClass) {
|
||||
throw new UnexpectedValueException('Payload must be a JSON object');
|
||||
}
|
||||
$sig = static::urlsafeB64Decode($cryptob64);
|
||||
if (empty($header->alg)) {
|
||||
throw new UnexpectedValueException('Empty algorithm');
|
||||
}
|
||||
@@ -108,31 +126,18 @@ class JWT
|
||||
throw new UnexpectedValueException('Algorithm not supported');
|
||||
}
|
||||
|
||||
list($keyMaterial, $algorithm) = self::getKeyMaterialAndAlgorithm(
|
||||
$keyOrKeyArray,
|
||||
empty($header->kid) ? null : $header->kid
|
||||
);
|
||||
$key = self::getKey($keyOrKeyArray, property_exists($header, 'kid') ? $header->kid : null);
|
||||
|
||||
if (empty($algorithm)) {
|
||||
// Use deprecated "allowed_algs" to determine if the algorithm is supported.
|
||||
// This opens up the possibility of an attack in some implementations.
|
||||
// @see https://github.com/firebase/php-jwt/issues/351
|
||||
if (!\in_array($header->alg, $allowed_algs)) {
|
||||
throw new UnexpectedValueException('Algorithm not allowed');
|
||||
}
|
||||
} else {
|
||||
// Check the algorithm
|
||||
if (!self::constantTimeEquals($algorithm, $header->alg)) {
|
||||
// See issue #351
|
||||
throw new UnexpectedValueException('Incorrect key for this algorithm');
|
||||
}
|
||||
// Check the algorithm
|
||||
if (!self::constantTimeEquals($key->getAlgorithm(), $header->alg)) {
|
||||
// See issue #351
|
||||
throw new UnexpectedValueException('Incorrect key for this algorithm');
|
||||
}
|
||||
if ($header->alg === 'ES256' || $header->alg === 'ES384') {
|
||||
// OpenSSL expects an ASN.1 DER sequence for ES256/ES384 signatures
|
||||
if (\in_array($header->alg, ['ES256', 'ES256K', 'ES384'], true)) {
|
||||
// OpenSSL expects an ASN.1 DER sequence for ES256/ES256K/ES384 signatures
|
||||
$sig = self::signatureToDER($sig);
|
||||
}
|
||||
|
||||
if (!static::verify("$headb64.$bodyb64", $sig, $keyMaterial, $header->alg)) {
|
||||
if (!self::verify("{$headb64}.{$bodyb64}", $sig, $key->getKeyMaterial(), $header->alg)) {
|
||||
throw new SignatureInvalidException('Signature verification failed');
|
||||
}
|
||||
|
||||
@@ -162,34 +167,37 @@ class JWT
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts and signs a PHP object or array into a JWT string.
|
||||
* Converts and signs a PHP array into a JWT string.
|
||||
*
|
||||
* @param object|array $payload PHP object or array
|
||||
* @param string|resource $key The secret key.
|
||||
* If the algorithm used is asymmetric, this is the private key
|
||||
* @param string $alg The signing algorithm.
|
||||
* Supported algorithms are 'ES384','ES256', 'HS256', 'HS384',
|
||||
* 'HS512', 'RS256', 'RS384', and 'RS512'
|
||||
* @param mixed $keyId
|
||||
* @param array $head An array with header elements to attach
|
||||
* @param array<mixed> $payload PHP array
|
||||
* @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key.
|
||||
* @param string $alg Supported algorithms are 'ES384','ES256', 'ES256K', 'HS256',
|
||||
* 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512'
|
||||
* @param string $keyId
|
||||
* @param array<string, string> $head An array with header elements to attach
|
||||
*
|
||||
* @return string A signed JWT
|
||||
*
|
||||
* @uses jsonEncode
|
||||
* @uses urlsafeB64Encode
|
||||
*/
|
||||
public static function encode($payload, $key, $alg = 'HS256', $keyId = null, $head = null)
|
||||
{
|
||||
$header = array('typ' => 'JWT', 'alg' => $alg);
|
||||
public static function encode(
|
||||
array $payload,
|
||||
$key,
|
||||
string $alg,
|
||||
string $keyId = null,
|
||||
array $head = null
|
||||
): string {
|
||||
$header = ['typ' => 'JWT', 'alg' => $alg];
|
||||
if ($keyId !== null) {
|
||||
$header['kid'] = $keyId;
|
||||
}
|
||||
if (isset($head) && \is_array($head)) {
|
||||
$header = \array_merge($head, $header);
|
||||
}
|
||||
$segments = array();
|
||||
$segments[] = static::urlsafeB64Encode(static::jsonEncode($header));
|
||||
$segments[] = static::urlsafeB64Encode(static::jsonEncode($payload));
|
||||
$segments = [];
|
||||
$segments[] = static::urlsafeB64Encode((string) static::jsonEncode($header));
|
||||
$segments[] = static::urlsafeB64Encode((string) static::jsonEncode($payload));
|
||||
$signing_input = \implode('.', $segments);
|
||||
|
||||
$signature = static::sign($signing_input, $key, $alg);
|
||||
@@ -201,67 +209,84 @@ class JWT
|
||||
/**
|
||||
* Sign a string with a given key and algorithm.
|
||||
*
|
||||
* @param string $msg The message to sign
|
||||
* @param string|resource $key The secret key
|
||||
* @param string $alg The signing algorithm.
|
||||
* Supported algorithms are 'ES384','ES256', 'HS256', 'HS384',
|
||||
* 'HS512', 'RS256', 'RS384', and 'RS512'
|
||||
* @param string $msg The message to sign
|
||||
* @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key.
|
||||
* @param string $alg Supported algorithms are 'ES384','ES256', 'ES256K', 'HS256',
|
||||
* 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512'
|
||||
*
|
||||
* @return string An encrypted message
|
||||
*
|
||||
* @throws DomainException Unsupported algorithm or bad key was specified
|
||||
*/
|
||||
public static function sign($msg, $key, $alg = 'HS256')
|
||||
{
|
||||
public static function sign(
|
||||
string $msg,
|
||||
$key,
|
||||
string $alg
|
||||
): string {
|
||||
if (empty(static::$supported_algs[$alg])) {
|
||||
throw new DomainException('Algorithm not supported');
|
||||
}
|
||||
list($function, $algorithm) = static::$supported_algs[$alg];
|
||||
switch ($function) {
|
||||
case 'hash_hmac':
|
||||
if (!\is_string($key)) {
|
||||
throw new InvalidArgumentException('key must be a string when using hmac');
|
||||
}
|
||||
return \hash_hmac($algorithm, $msg, $key, true);
|
||||
case 'openssl':
|
||||
$signature = '';
|
||||
$success = \openssl_sign($msg, $signature, $key, $algorithm);
|
||||
$success = \openssl_sign($msg, $signature, $key, $algorithm); // @phpstan-ignore-line
|
||||
if (!$success) {
|
||||
throw new DomainException("OpenSSL unable to sign data");
|
||||
throw new DomainException('OpenSSL unable to sign data');
|
||||
}
|
||||
if ($alg === 'ES256') {
|
||||
if ($alg === 'ES256' || $alg === 'ES256K') {
|
||||
$signature = self::signatureFromDER($signature, 256);
|
||||
} elseif ($alg === 'ES384') {
|
||||
$signature = self::signatureFromDER($signature, 384);
|
||||
}
|
||||
return $signature;
|
||||
case 'sodium_crypto':
|
||||
if (!function_exists('sodium_crypto_sign_detached')) {
|
||||
if (!\function_exists('sodium_crypto_sign_detached')) {
|
||||
throw new DomainException('libsodium is not available');
|
||||
}
|
||||
if (!\is_string($key)) {
|
||||
throw new InvalidArgumentException('key must be a string when using EdDSA');
|
||||
}
|
||||
try {
|
||||
// The last non-empty line is used as the key.
|
||||
$lines = array_filter(explode("\n", $key));
|
||||
$key = base64_decode(end($lines));
|
||||
$key = base64_decode((string) end($lines));
|
||||
if (\strlen($key) === 0) {
|
||||
throw new DomainException('Key cannot be empty string');
|
||||
}
|
||||
return sodium_crypto_sign_detached($msg, $key);
|
||||
} catch (Exception $e) {
|
||||
throw new DomainException($e->getMessage(), 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
throw new DomainException('Algorithm not supported');
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a signature with the message, key and method. Not all methods
|
||||
* are symmetric, so we must have a separate verify and sign method.
|
||||
*
|
||||
* @param string $msg The original message (header and body)
|
||||
* @param string $signature The original signature
|
||||
* @param string|resource $key For HS*, a string key works. for RS*, must be a resource of an openssl public key
|
||||
* @param string $alg The algorithm
|
||||
* @param string $msg The original message (header and body)
|
||||
* @param string $signature The original signature
|
||||
* @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $keyMaterial For HS*, a string key works. for RS*, must be an instance of OpenSSLAsymmetricKey
|
||||
* @param string $alg The algorithm
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @throws DomainException Invalid Algorithm, bad key, or OpenSSL failure
|
||||
*/
|
||||
private static function verify($msg, $signature, $key, $alg)
|
||||
{
|
||||
private static function verify(
|
||||
string $msg,
|
||||
string $signature,
|
||||
$keyMaterial,
|
||||
string $alg
|
||||
): bool {
|
||||
if (empty(static::$supported_algs[$alg])) {
|
||||
throw new DomainException('Algorithm not supported');
|
||||
}
|
||||
@@ -269,10 +294,11 @@ class JWT
|
||||
list($function, $algorithm) = static::$supported_algs[$alg];
|
||||
switch ($function) {
|
||||
case 'openssl':
|
||||
$success = \openssl_verify($msg, $signature, $key, $algorithm);
|
||||
$success = \openssl_verify($msg, $signature, $keyMaterial, $algorithm); // @phpstan-ignore-line
|
||||
if ($success === 1) {
|
||||
return true;
|
||||
} elseif ($success === 0) {
|
||||
}
|
||||
if ($success === 0) {
|
||||
return false;
|
||||
}
|
||||
// returns 1 on success, 0 on failure, -1 on error.
|
||||
@@ -280,21 +306,33 @@ class JWT
|
||||
'OpenSSL error: ' . \openssl_error_string()
|
||||
);
|
||||
case 'sodium_crypto':
|
||||
if (!function_exists('sodium_crypto_sign_verify_detached')) {
|
||||
throw new DomainException('libsodium is not available');
|
||||
}
|
||||
try {
|
||||
// The last non-empty line is used as the key.
|
||||
$lines = array_filter(explode("\n", $key));
|
||||
$key = base64_decode(end($lines));
|
||||
return sodium_crypto_sign_verify_detached($signature, $msg, $key);
|
||||
} catch (Exception $e) {
|
||||
throw new DomainException($e->getMessage(), 0, $e);
|
||||
}
|
||||
if (!\function_exists('sodium_crypto_sign_verify_detached')) {
|
||||
throw new DomainException('libsodium is not available');
|
||||
}
|
||||
if (!\is_string($keyMaterial)) {
|
||||
throw new InvalidArgumentException('key must be a string when using EdDSA');
|
||||
}
|
||||
try {
|
||||
// The last non-empty line is used as the key.
|
||||
$lines = array_filter(explode("\n", $keyMaterial));
|
||||
$key = base64_decode((string) end($lines));
|
||||
if (\strlen($key) === 0) {
|
||||
throw new DomainException('Key cannot be empty string');
|
||||
}
|
||||
if (\strlen($signature) === 0) {
|
||||
throw new DomainException('Signature cannot be empty string');
|
||||
}
|
||||
return sodium_crypto_sign_verify_detached($signature, $msg, $key);
|
||||
} catch (Exception $e) {
|
||||
throw new DomainException($e->getMessage(), 0, $e);
|
||||
}
|
||||
case 'hash_hmac':
|
||||
default:
|
||||
$hash = \hash_hmac($algorithm, $msg, $key, true);
|
||||
return self::constantTimeEquals($signature, $hash);
|
||||
if (!\is_string($keyMaterial)) {
|
||||
throw new InvalidArgumentException('key must be a string when using hmac');
|
||||
}
|
||||
$hash = \hash_hmac($algorithm, $msg, $keyMaterial, true);
|
||||
return self::constantTimeEquals($hash, $signature);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,30 +341,16 @@ class JWT
|
||||
*
|
||||
* @param string $input JSON string
|
||||
*
|
||||
* @return object Object representation of JSON string
|
||||
* @return mixed The decoded JSON string
|
||||
*
|
||||
* @throws DomainException Provided string was invalid JSON
|
||||
*/
|
||||
public static function jsonDecode($input)
|
||||
public static function jsonDecode(string $input)
|
||||
{
|
||||
if (\version_compare(PHP_VERSION, '5.4.0', '>=') && !(\defined('JSON_C_VERSION') && PHP_INT_SIZE > 4)) {
|
||||
/** In PHP >=5.4.0, json_decode() accepts an options parameter, that allows you
|
||||
* to specify that large ints (like Steam Transaction IDs) should be treated as
|
||||
* strings, rather than the PHP default behaviour of converting them to floats.
|
||||
*/
|
||||
$obj = \json_decode($input, false, 512, JSON_BIGINT_AS_STRING);
|
||||
} else {
|
||||
/** Not all servers will support that, however, so for older versions we must
|
||||
* manually detect large ints in the JSON string and quote them (thus converting
|
||||
*them to strings) before decoding, hence the preg_replace() call.
|
||||
*/
|
||||
$max_int_length = \strlen((string) PHP_INT_MAX) - 1;
|
||||
$json_without_bigints = \preg_replace('/:\s*(-?\d{'.$max_int_length.',})/', ': "$1"', $input);
|
||||
$obj = \json_decode($json_without_bigints);
|
||||
}
|
||||
$obj = \json_decode($input, false, 512, JSON_BIGINT_AS_STRING);
|
||||
|
||||
if ($errno = \json_last_error()) {
|
||||
static::handleJsonError($errno);
|
||||
self::handleJsonError($errno);
|
||||
} elseif ($obj === null && $input !== 'null') {
|
||||
throw new DomainException('Null result with non-null input');
|
||||
}
|
||||
@@ -334,22 +358,30 @@ class JWT
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a PHP object into a JSON string.
|
||||
* Encode a PHP array into a JSON string.
|
||||
*
|
||||
* @param object|array $input A PHP object or array
|
||||
* @param array<mixed> $input A PHP array
|
||||
*
|
||||
* @return string JSON representation of the PHP object or array
|
||||
* @return string JSON representation of the PHP array
|
||||
*
|
||||
* @throws DomainException Provided object could not be encoded to valid JSON
|
||||
*/
|
||||
public static function jsonEncode($input)
|
||||
public static function jsonEncode(array $input): string
|
||||
{
|
||||
$json = \json_encode($input);
|
||||
if (PHP_VERSION_ID >= 50400) {
|
||||
$json = \json_encode($input, \JSON_UNESCAPED_SLASHES);
|
||||
} else {
|
||||
// PHP 5.3 only
|
||||
$json = \json_encode($input);
|
||||
}
|
||||
if ($errno = \json_last_error()) {
|
||||
static::handleJsonError($errno);
|
||||
self::handleJsonError($errno);
|
||||
} elseif ($json === 'null' && $input !== null) {
|
||||
throw new DomainException('Null result with non-null input');
|
||||
}
|
||||
if ($json === false) {
|
||||
throw new DomainException('Provided object could not be encoded to valid JSON');
|
||||
}
|
||||
return $json;
|
||||
}
|
||||
|
||||
@@ -359,8 +391,10 @@ class JWT
|
||||
* @param string $input A Base64 encoded string
|
||||
*
|
||||
* @return string A decoded string
|
||||
*
|
||||
* @throws InvalidArgumentException invalid base64 characters
|
||||
*/
|
||||
public static function urlsafeB64Decode($input)
|
||||
public static function urlsafeB64Decode(string $input): string
|
||||
{
|
||||
$remainder = \strlen($input) % 4;
|
||||
if ($remainder) {
|
||||
@@ -377,7 +411,7 @@ class JWT
|
||||
*
|
||||
* @return string The base64 encode of what you passed in
|
||||
*/
|
||||
public static function urlsafeB64Encode($input)
|
||||
public static function urlsafeB64Encode(string $input): string
|
||||
{
|
||||
return \str_replace('=', '', \strtr(\base64_encode($input), '+/', '-_'));
|
||||
}
|
||||
@@ -386,67 +420,54 @@ class JWT
|
||||
/**
|
||||
* Determine if an algorithm has been provided for each Key
|
||||
*
|
||||
* @param Key|array<Key>|mixed $keyOrKeyArray
|
||||
* @param string|null $kid
|
||||
* @param Key|ArrayAccess<string,Key>|array<string,Key> $keyOrKeyArray
|
||||
* @param string|null $kid
|
||||
*
|
||||
* @throws UnexpectedValueException
|
||||
*
|
||||
* @return array containing the keyMaterial and algorithm
|
||||
* @return Key
|
||||
*/
|
||||
private static function getKeyMaterialAndAlgorithm($keyOrKeyArray, $kid = null)
|
||||
{
|
||||
if (
|
||||
is_string($keyOrKeyArray)
|
||||
|| is_resource($keyOrKeyArray)
|
||||
|| $keyOrKeyArray instanceof OpenSSLAsymmetricKey
|
||||
) {
|
||||
return array($keyOrKeyArray, null);
|
||||
}
|
||||
|
||||
private static function getKey(
|
||||
$keyOrKeyArray,
|
||||
?string $kid
|
||||
): Key {
|
||||
if ($keyOrKeyArray instanceof Key) {
|
||||
return array($keyOrKeyArray->getKeyMaterial(), $keyOrKeyArray->getAlgorithm());
|
||||
return $keyOrKeyArray;
|
||||
}
|
||||
|
||||
if (is_array($keyOrKeyArray) || $keyOrKeyArray instanceof ArrayAccess) {
|
||||
if (!isset($kid)) {
|
||||
throw new UnexpectedValueException('"kid" empty, unable to lookup correct key');
|
||||
}
|
||||
if (!isset($keyOrKeyArray[$kid])) {
|
||||
throw new UnexpectedValueException('"kid" invalid, unable to lookup correct key');
|
||||
}
|
||||
|
||||
$key = $keyOrKeyArray[$kid];
|
||||
|
||||
if ($key instanceof Key) {
|
||||
return array($key->getKeyMaterial(), $key->getAlgorithm());
|
||||
}
|
||||
|
||||
return array($key, null);
|
||||
if (empty($kid)) {
|
||||
throw new UnexpectedValueException('"kid" empty, unable to lookup correct key');
|
||||
}
|
||||
|
||||
throw new UnexpectedValueException(
|
||||
'$keyOrKeyArray must be a string|resource key, an array of string|resource keys, '
|
||||
. 'an instance of Firebase\JWT\Key key or an array of Firebase\JWT\Key keys'
|
||||
);
|
||||
if ($keyOrKeyArray instanceof CachedKeySet) {
|
||||
// Skip "isset" check, as this will automatically refresh if not set
|
||||
return $keyOrKeyArray[$kid];
|
||||
}
|
||||
|
||||
if (!isset($keyOrKeyArray[$kid])) {
|
||||
throw new UnexpectedValueException('"kid" invalid, unable to lookup correct key');
|
||||
}
|
||||
|
||||
return $keyOrKeyArray[$kid];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $left
|
||||
* @param string $right
|
||||
* @param string $left The string of known length to compare against
|
||||
* @param string $right The user-supplied string
|
||||
* @return bool
|
||||
*/
|
||||
public static function constantTimeEquals($left, $right)
|
||||
public static function constantTimeEquals(string $left, string $right): bool
|
||||
{
|
||||
if (\function_exists('hash_equals')) {
|
||||
return \hash_equals($left, $right);
|
||||
}
|
||||
$len = \min(static::safeStrlen($left), static::safeStrlen($right));
|
||||
$len = \min(self::safeStrlen($left), self::safeStrlen($right));
|
||||
|
||||
$status = 0;
|
||||
for ($i = 0; $i < $len; $i++) {
|
||||
$status |= (\ord($left[$i]) ^ \ord($right[$i]));
|
||||
}
|
||||
$status |= (static::safeStrlen($left) ^ static::safeStrlen($right));
|
||||
$status |= (self::safeStrlen($left) ^ self::safeStrlen($right));
|
||||
|
||||
return ($status === 0);
|
||||
}
|
||||
@@ -456,17 +477,19 @@ class JWT
|
||||
*
|
||||
* @param int $errno An error number from json_last_error()
|
||||
*
|
||||
* @throws DomainException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function handleJsonError($errno)
|
||||
private static function handleJsonError(int $errno): void
|
||||
{
|
||||
$messages = array(
|
||||
$messages = [
|
||||
JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
|
||||
JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON',
|
||||
JSON_ERROR_CTRL_CHAR => 'Unexpected control character found',
|
||||
JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON',
|
||||
JSON_ERROR_UTF8 => 'Malformed UTF-8 characters' //PHP >= 5.3.3
|
||||
);
|
||||
];
|
||||
throw new DomainException(
|
||||
isset($messages[$errno])
|
||||
? $messages[$errno]
|
||||
@@ -481,7 +504,7 @@ class JWT
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private static function safeStrlen($str)
|
||||
private static function safeStrlen(string $str): int
|
||||
{
|
||||
if (\function_exists('mb_strlen')) {
|
||||
return \mb_strlen($str, '8bit');
|
||||
@@ -495,10 +518,11 @@ class JWT
|
||||
* @param string $sig The ECDSA signature to convert
|
||||
* @return string The encoded DER object
|
||||
*/
|
||||
private static function signatureToDER($sig)
|
||||
private static function signatureToDER(string $sig): string
|
||||
{
|
||||
// Separate the signature into r-value and s-value
|
||||
list($r, $s) = \str_split($sig, (int) (\strlen($sig) / 2));
|
||||
$length = max(1, (int) (\strlen($sig) / 2));
|
||||
list($r, $s) = \str_split($sig, $length);
|
||||
|
||||
// Trim leading zeros
|
||||
$r = \ltrim($r, "\x00");
|
||||
@@ -525,9 +549,10 @@ class JWT
|
||||
*
|
||||
* @param int $type DER tag
|
||||
* @param string $value the value to encode
|
||||
*
|
||||
* @return string the encoded object
|
||||
*/
|
||||
private static function encodeDER($type, $value)
|
||||
private static function encodeDER(int $type, string $value): string
|
||||
{
|
||||
$tag_header = 0;
|
||||
if ($type === self::ASN1_SEQUENCE) {
|
||||
@@ -548,9 +573,10 @@ class JWT
|
||||
*
|
||||
* @param string $der binary signature in DER format
|
||||
* @param int $keySize the number of bits in the key
|
||||
*
|
||||
* @return string the signature
|
||||
*/
|
||||
private static function signatureFromDER($der, $keySize)
|
||||
private static function signatureFromDER(string $der, int $keySize): string
|
||||
{
|
||||
// OpenSSL returns the ECDSA signatures as a binary ASN.1 DER SEQUENCE
|
||||
list($offset, $_) = self::readDER($der);
|
||||
@@ -575,9 +601,10 @@ class JWT
|
||||
* @param string $der the binary data in DER format
|
||||
* @param int $offset the offset of the data stream containing the object
|
||||
* to decode
|
||||
* @return array [$offset, $data] the new offset and the decoded object
|
||||
*
|
||||
* @return array{int, string|null} the new offset and the decoded object
|
||||
*/
|
||||
private static function readDER($der, $offset = 0)
|
||||
private static function readDER(string $der, int $offset = 0): array
|
||||
{
|
||||
$pos = $offset;
|
||||
$size = \strlen($der);
|
||||
@@ -595,7 +622,7 @@ class JWT
|
||||
}
|
||||
|
||||
// Value
|
||||
if ($type == self::ASN1_BIT_STRING) {
|
||||
if ($type === self::ASN1_BIT_STRING) {
|
||||
$pos++; // Skip the first contents octet (padding indicator)
|
||||
$data = \substr($der, $pos, $len - 1);
|
||||
$pos += $len - 1;
|
||||
@@ -606,6 +633,6 @@ class JWT
|
||||
$data = null;
|
||||
}
|
||||
|
||||
return array($pos, $data);
|
||||
return [$pos, $data];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,37 +4,42 @@ namespace Firebase\JWT;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use OpenSSLAsymmetricKey;
|
||||
use OpenSSLCertificate;
|
||||
use TypeError;
|
||||
|
||||
class Key
|
||||
{
|
||||
/** @var string $algorithm */
|
||||
/** @var string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate */
|
||||
private $keyMaterial;
|
||||
/** @var string */
|
||||
private $algorithm;
|
||||
|
||||
/** @var string|resource|OpenSSLAsymmetricKey $keyMaterial */
|
||||
private $keyMaterial;
|
||||
|
||||
/**
|
||||
* @param string|resource|OpenSSLAsymmetricKey $keyMaterial
|
||||
* @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $keyMaterial
|
||||
* @param string $algorithm
|
||||
*/
|
||||
public function __construct($keyMaterial, $algorithm)
|
||||
{
|
||||
public function __construct(
|
||||
$keyMaterial,
|
||||
string $algorithm
|
||||
) {
|
||||
if (
|
||||
!is_string($keyMaterial)
|
||||
&& !is_resource($keyMaterial)
|
||||
!\is_string($keyMaterial)
|
||||
&& !$keyMaterial instanceof OpenSSLAsymmetricKey
|
||||
&& !$keyMaterial instanceof OpenSSLCertificate
|
||||
&& !\is_resource($keyMaterial)
|
||||
) {
|
||||
throw new InvalidArgumentException('Type error: $keyMaterial must be a string, resource, or OpenSSLAsymmetricKey');
|
||||
throw new TypeError('Key material must be a string, resource, or OpenSSLAsymmetricKey');
|
||||
}
|
||||
|
||||
if (empty($keyMaterial)) {
|
||||
throw new InvalidArgumentException('Type error: $keyMaterial must not be empty');
|
||||
throw new InvalidArgumentException('Key material must not be empty');
|
||||
}
|
||||
|
||||
if (!is_string($algorithm)|| empty($keyMaterial)) {
|
||||
throw new InvalidArgumentException('Type error: $algorithm must be a string');
|
||||
if (empty($algorithm)) {
|
||||
throw new InvalidArgumentException('Algorithm must not be empty');
|
||||
}
|
||||
|
||||
// TODO: Remove in PHP 8.0 in favor of class constructor property promotion
|
||||
$this->keyMaterial = $keyMaterial;
|
||||
$this->algorithm = $algorithm;
|
||||
}
|
||||
@@ -44,13 +49,13 @@ class Key
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAlgorithm()
|
||||
public function getAlgorithm(): string
|
||||
{
|
||||
return $this->algorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|resource|OpenSSLAsymmetricKey
|
||||
* @return string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate
|
||||
*/
|
||||
public function getKeyMaterial()
|
||||
{
|
||||
|
||||
8
lib/guzzlehttp/psr7/.github/workflows/ci.yml
vendored
8
lib/guzzlehttp/psr7/.github/workflows/ci.yml
vendored
@@ -6,7 +6,7 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
max-parallel: 10
|
||||
matrix:
|
||||
@@ -21,11 +21,7 @@ jobs:
|
||||
extensions: mbstring
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Mimic PHP 8.0
|
||||
run: composer config platform.php 8.0.999
|
||||
if: matrix.php > 8
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer update --no-interaction --no-progress
|
||||
|
||||
@@ -4,14 +4,13 @@ on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
max-parallel: 10
|
||||
matrix:
|
||||
php: ['7.2', '7.3', '7.4', '8.0']
|
||||
php: ['7.2', '7.3', '7.4', '8.0', '8.1']
|
||||
|
||||
steps:
|
||||
- name: Set up PHP
|
||||
@@ -21,7 +20,7 @@ jobs:
|
||||
coverage: none
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Download dependencies
|
||||
uses: ramsey/composer-install@v1
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user