Compare commits

...

498 Commits

Author SHA1 Message Date
Denis Flaven
62510e2b04 Fix for Trac #670: XSS vulnerability issue.
SVN:1.2[2588]
2013-01-22 17:38:01 +00:00
Denis Flaven
f33c821306 Merged, the implementation of Track #582: "stable name" for synchro_data_xxx tables
SVN:1.2[2404]
2012-10-29 15:36:55 +00:00
Denis Flaven
11d0e9f52b Do not perform time consuming computations for building the menus if there are too many objects in a list (limit is configurable).
SVN:1.2[2344]
2012-10-24 14:04:57 +00:00
Denis Flaven
3330fd79d0 Bug fix: regression introduced by revision 2278.
SVN:1.2[2340]
2012-10-24 13:20:26 +00:00
Denis Flaven
02e0a61dcf Optimization to speed-up the "group-by" tables. (The complete solution is implemented in 2.0)
SVN:1.2[2338]
2012-10-24 13:13:15 +00:00
Romain Quetiez
2bb8028d4d Fixed regression (CSV import - unknown method utils::GetConfig)
SVN:1.2[2322]
2012-10-22 08:07:46 +00:00
Romain Quetiez
891b22909a #583 Losing attachments when performing massive change
SVN:1.2[2286]
2012-10-18 09:39:37 +00:00
Denis Flaven
463748b6da Properly handle all types of fields when entering values via the state transition "wizard" screen
SVN:1.2[2279]
2012-10-17 14:05:10 +00:00
Denis Flaven
fc2878b6d1 Properly handle a change of an external key...
SVN:1.2[2276]
2012-10-17 13:25:32 +00:00
Romain Quetiez
17919f5389 Portal: enable adding dependent attributes in the request creation form -reintegrated from trunk
SVN:1.2[2274]
2012-10-17 12:54:19 +00:00
Romain Quetiez
e54b1d2ff4 Portal: enable adding dependent attributes in the request creation form -reintegrated from trunk
SVN:1.2[2271]
2012-10-17 12:17:28 +00:00
Romain Quetiez
523fb8bd25 Fixed issue in the portal: the list of opened requests and closed request where messed up when pagination was activated on both lists -reintegrated from trunk
SVN:1.2[2268]
2012-10-17 09:12:01 +00:00
Romain Quetiez
fad77fb9fa CSV import: added a flag to disable the history tab (too long to display, when the feature is heavily used), reintegrated from trunk
SVN:1.2[2265]
2012-10-16 14:47:22 +00:00
Erwan Taloc
8da92c9251 add iTop favicon
SVN:1.2[2171]
2012-09-10 09:39:31 +00:00
Erwan Taloc
977d616ef2 Rename translation of Friendly name attribute to Friendly Name
SVN:1.2[2148]
2012-08-09 12:59:44 +00:00
Romain Quetiez
29faa739c1 Reintegrated fixes from branch 1.2.1 (2116,2118,2119)
- HTML attributes > 64 Kb
- Log of notification displayed as HTML

SVN:1.2[2120]
2012-07-04 09:23:12 +00:00
Denis Flaven
b986f63a67 Bug fix: $this->label(attcode)$ used inside Email Notifications should not contain HTML entities since this not an HTML fragment.
SVN:1.2[2117]
2012-06-29 15:11:35 +00:00
Denis Flaven
8fa3a6d47d Localization of the title of the login page
SVN:1.2[2112]
2012-06-15 13:30:07 +00:00
Denis Flaven
0e6876b068 Localization of the title of the login page
SVN:1.2[2111]
2012-06-15 13:27:16 +00:00
Denis Flaven
8d962f4bdb Fix for Trac #559: ldap user can login with blank password
SVN:1.2[2109]
2012-06-14 16:28:14 +00:00
Denis Flaven
3b8f945c44 Fixed Trac #558: properly parse strings containing hexadecimal sequences (i.e. 'QWERTY0xCUIOP').
Note that for now hexadecimal numbers are parsed but not interpreted properly...

SVN:1.2[2104]
2012-06-14 09:22:01 +00:00
Romain Quetiez
9600c89a1f Fixed regression (See #556) due to the existence of an overload of the protected API GetUserOrgs, reintegrated from branch 1.2.1
SVN:1.2[2100]
2012-06-14 09:12:25 +00:00
Denis Flaven
d01202ba33 Reload the impact/depends on graph only on demand for better performance, via the new Refresh button
SVN:1.2[2088]
2012-06-07 13:57:17 +00:00
Romain Quetiez
1bc1a0a1b2 #556 Merged in branch 1.2
SVN:1.2[2083]
2012-06-05 15:46:24 +00:00
Romain Quetiez
37ea4cb5e3 Reintegrated changes from trunk, to uncompile legacy data models
SVN:1.2[2080]
2012-06-05 11:18:10 +00:00
Denis Flaven
c4a003f620 Make the 'filter' conditions on ExtKey applicable also when searching on derived classes.
SVN:1.2[1991]
2012-05-16 12:23:54 +00:00
Denis Flaven
7a8ee0353a Protects against too long strings when logging web services events
SVN:1.2[1961]
2012-04-19 11:17:20 +00:00
Romain Quetiez
6b526ba455 #541 Fixed bug in the export for spreadsheet (time format)
SVN:1.2[1946]
2012-04-06 09:41:43 +00:00
Denis Flaven
9d9b923b7e Properly log-off (and report the issue in the log) in case we fail to create a user during the CAS Synchro
SVN:1.2[1942]
2012-04-04 10:09:07 +00:00
Erwan Taloc
fcaad0cd07 Add web link to link class in schema.php for an attribute linkset
SVN:1.2[1937]
2012-04-03 11:18:23 +00:00
Romain Quetiez
da875dd945 #540 Data synchro: the option "write if empty" was not implemented
SVN:1.2[1933]
2012-03-29 13:32:35 +00:00
Denis Flaven
2a71bf5008 Bug fix: to do not try to access a DataSource while it's being deleted
SVN:1.2[1929]
2012-03-27 11:26:31 +00:00
Denis Flaven
9b36ebc106 Bug fix: support [+] button inside linkedsets.. with constraints
SVN:1.2[1926]
2012-03-22 17:19:06 +00:00
Denis Flaven
1a507b7aa4 CAS integration. Done.
SVN:1.2[1921]
2012-03-22 15:30:23 +00:00
Denis Flaven
178ee28596 CAS integration continuing
SVN:1.2[1919]
2012-03-22 15:16:22 +00:00
Denis Flaven
e44a5d2980 CAS integration:
- regression fix: support patterns for the MemberOf groups filtering
- activate/de-activate the profiles synchronization using the 'cas_update_profiles' configuration flag
- provide default profile(s) when creating a new user from CAS, only if no match is found for assigning profiles from the CAS MemberOf group(s).

SVN:1.2[1917]
2012-03-22 10:53:09 +00:00
Denis Flaven
828c02db0b Bug fix: support [+] button inside linkedsets.. with constraints
SVN:1.2[1914]
2012-03-22 09:38:03 +00:00
Denis Flaven
d1e4e2109f Bug fix: support [+] button inside linkedsets.. with constraints
SVN:1.2[1913]
2012-03-22 09:35:25 +00:00
Denis Flaven
b7a9b340b8 Bug fix: support [+] button inside linkedsets.. with constraints
SVN:1.2[1912]
2012-03-22 09:11:45 +00:00
Denis Flaven
3731cf6dc1 The date picker fills the "time" part of the field with 00:00:00 when picking a DateTime instead of just a Date.
SVN:1.2[1910]
2012-03-21 10:53:33 +00:00
Denis Flaven
0e7fc5e5c4 Bug fix: support [+] button inside linkedsets
SVN:1.2[1908]
2012-03-21 10:36:55 +00:00
Denis Flaven
bc62c06894 Rollback of the modification: For forward compatibility... It was already implemented !
SVN:1.2[1907]
2012-03-21 10:28:45 +00:00
Denis Flaven
4ed85c23de Allow to specify the name of the PDF to download
SVN:1.2[1905]
2012-03-20 16:55:20 +00:00
Denis Flaven
b6c1347f27 For forward compatibility...
SVN:1.2[1904]
2012-03-20 16:54:13 +00:00
Denis Flaven
237eefcbc6 LinkedSets can be read-only too...
SVN:1.2[1898]
2012-03-19 17:17:45 +00:00
Denis Flaven
5ee3c69898 Make the class "TriggerOnPortalUpdate" importable
SVN:1.2[1896]
2012-03-16 12:58:23 +00:00
Denis Flaven
53b3ae8016 Enhanced fix for Trac #503. Don't drop the column before re-creating it, in case the data can be converted by MySQL.
SVN:1.2[1892]
2012-03-14 16:15:53 +00:00
Denis Flaven
26b6bfaf7f Added detecting of missing columns in the synchro_data_xxx tables (in case of duplicate SQL column names in the orignal data model). See Trac #503.
SVN:1.2[1891]
2012-03-14 16:02:39 +00:00
Denis Flaven
1a659cc4d0 Experimental support of PDF output for iTop pages, provided that mPDF is installed in lib/MPDF
SVN:1.2[1876]
2012-03-07 16:40:22 +00:00
Denis Flaven
b000900d6c Stylesheet enhancements to support printing...
SVN:1.2[1875]
2012-03-07 14:42:28 +00:00
Denis Flaven
f68ec1cef1 Typo
SVN:1.2[1874]
2012-03-04 18:13:06 +00:00
Denis Flaven
3fb867d393 - Current block Id not passed to the chart ?
SVN:1.2[1873]
2012-03-04 17:53:28 +00:00
Denis Flaven
fe559eb492 Small fix for genericity
SVN:1.2[1872]
2012-03-04 17:51:53 +00:00
Denis Flaven
0041afd6d0 - Current block Id not passed to the chart ?
SVN:1.2[1871]
2012-03-04 17:48:03 +00:00
Denis Flaven
dc1b5b0d4c - Bug fix: the hierarchical key in Organizations is not always named 'parent_id' !
SVN:1.2[1868]
2012-02-27 16:15:08 +00:00
Denis Flaven
10a930b7b2 Typo!
SVN:1.2[1862]
2012-02-21 14:03:35 +00:00
Denis Flaven
72b6089db8 Added the ability to Find then Remove a tab inside a page
SVN:1.2[1860]
2012-02-20 17:13:53 +00:00
Denis Flaven
0cfb1c3a83 Fix in case there is only one non-shared organization.
SVN:1.2[1858]
2012-02-17 12:52:35 +00:00
Denis Flaven
36a73535a5 Delay the storage of the dictionary in the cache to allow for its alteration during the initialization of the classes
SVN:1.2[1856]
2012-02-17 12:47:58 +00:00
Denis Flaven
9382b89277 Distinguish between creation and modification rights
SVN:1.2[1854]
2012-02-17 12:43:55 +00:00
Romain Quetiez
6d14da15cf Implemented the capability to implemented a separate module for sharing objects between the silos:
+ possibility for a plugin to alter the definition of a class (add an attribute)
+ fixed a bug (low exposure) in the cache for MetaModel::GetObject()
+ possibility to have computed fields on links (list of fields in the form now based on the ZList 'list')

SVN:1.2[1853]
2012-02-17 09:35:06 +00:00
Denis Flaven
3113205f88 Allow to add some headers like content-type.
SVN:1.2[1852]
2012-02-10 12:42:34 +00:00
Denis Flaven
09bd8052d7 Allow more than 64K for the email body (including attachments)
SVN:1.2[1850]
2012-02-10 12:25:21 +00:00
Denis Flaven
85f0b79203 Update to the Italian translation
SVN:1.2[1848]
2012-02-08 14:39:30 +00:00
Romain Quetiez
81145d7b1c Improved the change tracking to simplify the development of plugins (1st step... still to be drastically simplified)
SVN:1.2[1847]
2012-02-08 14:24:45 +00:00
Denis Flaven
7e6d1c2ce4 Performance improvements of the autocomplete: don't trigger a search when there is no expression to search !
SVN:1.2[1846]
2012-02-07 13:34:40 +00:00
Denis Flaven
364259daa5 Restore the previous state of URLMaker after building a notification
SVN:1.2[1845]
2012-02-07 09:32:29 +00:00
Denis Flaven
7b270294f6 Fix for query modifiers plug-ins
SVN:1.2[1843]
2012-02-07 09:12:45 +00:00
Romain Quetiez
c7aa00e81a Implemented the capability to modify queries by the mean of a plugin (make it work with APC cache)
SVN:1.2[1842]
2012-02-06 10:54:35 +00:00
Romain Quetiez
f9e7446e7b Reverted (removed test code)
SVN:1.2[1841]
2012-02-03 17:22:54 +00:00
Romain Quetiez
493ab80965 Implemented the capability to modify queries by the mean of a plugin (beta)
SVN:1.2[1840]
2012-02-03 17:16:27 +00:00
Denis Flaven
e623467782 Readme updated for the 1.2.1 release.
SVN:1.2[1838]
2012-02-01 10:27:10 +00:00
Denis Flaven
32208fcbfc Readme updated for the 1.2.1 release.
SVN:1.2[1837]
2012-02-01 10:24:31 +00:00
Denis Flaven
dcbff406f7 Fix to the Brazilian translation: thanks to Google translate !
SVN:1.2[1836]
2012-01-31 17:51:15 +00:00
Denis Flaven
017dfe641c Update to the Brazilian translation, thanks to Marco Tulio
SVN:1.2[1834]
2012-01-31 17:48:02 +00:00
Denis Flaven
fad258cd2d (Tried to) improve the display of the Synchronization Tooltip that "sometimes" does not work on IE 8...
SVN:1.2[1832]
2012-01-31 17:30:59 +00:00
Denis Flaven
3470ce18e8 Removed a (useless) hardcoded reference to FunctionalCI
SVN:1.2[1830]
2012-01-31 15:39:35 +00:00
Denis Flaven
af710c549f SQL Block with parameters were always displayed as table, whatever their type...
SVN:1.2[1828]
2012-01-31 15:04:23 +00:00
Denis Flaven
ee938d674d Fixed typos during the copy/paste for automatic account creation
SVN:1.2[1826]
2012-01-31 12:36:05 +00:00
Denis Flaven
423de35cf5 Fixed the encoding of mail headers !
SVN:1.2[1825]
2012-01-31 12:35:34 +00:00
Denis Flaven
6a7af8ad73 Make the OQLQuery class import-able
SVN:1.2[1822]
2012-01-30 17:46:00 +00:00
Denis Flaven
89732d6e52 Put some default reconciliation keys to ease the use of CSV import
SVN:1.2[1820]
2012-01-30 17:42:54 +00:00
Denis Flaven
cddeab2c90 Protect against empty list of reconciliation keys
SVN:1.2[1818]
2012-01-30 17:38:45 +00:00
Denis Flaven
d9d84703ae Fix to have the proper use of GetEditValue... thanks to C. Naud
SVN:1.2[1817]
2012-01-30 12:49:04 +00:00
Romain Quetiez
c3de9ecf10 Export for spreadsheets: transform keys (id of the queried object or external keys) into the corresponding friendly name
SVN:1.2[1815]
2012-01-30 10:58:21 +00:00
Denis Flaven
cf37a50b3d Enhancements to the German localization
SVN:1.2[1814]
2012-01-30 10:45:27 +00:00
Romain Quetiez
4db30648c2 Object details: log always displayed AFTER plugin data
SVN:1.2[1812]
2012-01-27 13:51:17 +00:00
Denis Flaven
8de84d5ec7 Fixed Trac#518 : Properly pass the context (i.e. currently selected org) to the auto-refresh lists
SVN:1.2[1810]
2012-01-25 10:27:23 +00:00
Denis Flaven
459a271d11 Fixed Trac#522: issue with non-ASCII characters in notifications subject.
SVN:1.2[1809]
2012-01-24 18:24:34 +00:00
Denis Flaven
9da00b83b2 Added the ability to display a custom welcome/disclaimer message at the bottom of the login form.
SVN:1.2[1807]
2012-01-24 15:24:25 +00:00
Denis Flaven
0c8ef6a690 Why put "APPROOT/modules/" as the AbsoluteUrlAppRoot in the portal ??
SVN:1.2[1804]
2012-01-24 14:26:39 +00:00
Denis Flaven
cdefd7a4c6 Hmmm, also may cause troubles for bug #519...
SVN:1.2[1801]
2012-01-24 13:04:39 +00:00
Romain Quetiez
4f057ac29f Improved the check on data model consistency: detection of SQL columns used by two attributes
SVN:1.2[1800]
2012-01-24 13:01:55 +00:00
Denis Flaven
ab16588f87 Fix for Trac#519 - change password bug !
SVN:1.2[1799]
2012-01-24 13:00:15 +00:00
Denis Flaven
07d8da9d99 Added the "search" form on top of the list of users
SVN:1.2[1796]
2012-01-20 15:24:33 +00:00
Romain Quetiez
c539f19ce9 Optimized memory usage when auditing large volumes of CIs (10'000 items was requiring 200 Mb, it now runs with 32 Mb -including the 30Mb overhead!)
SVN:1.2[1795]
2012-01-20 15:03:07 +00:00
Denis Flaven
3635e60850 Typo fix
SVN:1.2[1794]
2012-01-20 14:19:56 +00:00
Denis Flaven
b278b84f46 Fixed #481: localized characters in Service / Service Category name and description were not properly displayed.
SVN:1.2[1793]
2012-01-20 14:16:20 +00:00
Romain Quetiez
04647970a8 Case log now largely bigger than 64 Kilobytes... (reintegrated change made in trunk)
SVN:1.2[1791]
2012-01-19 15:32:48 +00:00
Romain Quetiez
fe58f6bd19 Fixed regression in the tool to test queries: losing the query when there is a syntax error
SVN:1.2[1788]
2012-01-19 10:13:36 +00:00
Romain Quetiez
0d6cd529a1 #516 and #517 Improved the export (specify fields for multi-column queries) and web queries (default field list)
SVN:1.2[1786]
2012-01-18 16:59:09 +00:00
Denis Flaven
2a155fe8ee Added SetMessageId for forward compatibility with SwiftMailer
SVN:1.2[1785]
2012-01-18 10:05:12 +00:00
Romain Quetiez
3f381a3530 Partially de-hardcoded against the subdirectory "modules", so that 1.2.1 should be compatible with modules running under 2.0.
SVN:1.2[1778]
2012-01-17 15:26:02 +00:00
Denis Flaven
7fadb5e08b Added self-registering / user synchronization extensibility
SVN:1.2[1760]
2012-01-11 16:01:11 +00:00
Denis Flaven
5d4476f48b Added self-registering / user synchronization extensibility
SVN:1.2[1757]
2012-01-11 14:31:44 +00:00
Romain Quetiez
2fbb37cc2f Readme updated, ready for 1.2.1 beta
SVN:1.2[1753]
2011-12-22 10:34:00 +00:00
Denis Flaven
6801ecb266 Updated the readme file to prepare for the 1.2.1 beta release
SVN:1.2[1752]
2011-12-22 10:29:27 +00:00
Romain Quetiez
fa821d3a9b #512 Allow the CLI mode for export.php
SVN:1.2[1750]
2011-12-21 15:28:50 +00:00
Denis Flaven
00f9deeaa5 Updated the readme file to prepare for the 1.2.1 beta release
SVN:1.2[1747]
2011-12-21 15:14:41 +00:00
Romain Quetiez
58cfc1d51b CSV import (both std and related to the data synchro) can be used with a tab (keyword 'tab', case insensitive)
SVN:1.2[1746]
2011-12-21 14:46:03 +00:00
Denis Flaven
af8b3b972d Fixed Trac #480: properly take into account the 'min_autocomplete_chars' settings.
SVN:1.2[1745]
2011-12-21 14:23:20 +00:00
Denis Flaven
07671f40fd Allow a module to restrict the access to a given menu/group by redeclaring the menu with restricted rights. All rights are combined with the AND operator.
SVN:1.2[1743]
2011-12-21 14:17:03 +00:00
Denis Flaven
3da5c65fe4 Allow to filter which class(es) of objects are displayed in the graphical impact analysis view
SVN:1.2[1742]
2011-12-21 14:15:27 +00:00
Romain Quetiez
489be44b90 #489 Data synchro: reintegrated the latest improvements from trunk.
SVN:1.2[1740]
2011-12-21 13:45:07 +00:00
Denis Flaven
912088d017 Fixed Trac #486: SQL dashboards limitations
SVN:1.2[1730]
2011-12-20 15:17:14 +00:00
Denis Flaven
06620133b6 Bug fix: missing (but useless) parameter in ExpandArgs causes a notice...
SVN:1.2[1728]
2011-12-20 12:41:05 +00:00
Denis Flaven
95d7a24630 Keep the current value iin the search form when reloading the search form for a different (sub)class. For example Contact => Person.
SVN:1.2[1726]
2011-12-19 17:30:02 +00:00
Denis Flaven
cb0e1d8ef3 When searching objects to add to the current object (n:n relationship), set the default search params in order to stay in the current silo.
SVN:1.2[1724]
2011-12-19 16:50:39 +00:00
Denis Flaven
d321ebc8e4 Bug fix: apply the AllowedValues constraints(as default values) when selecting elements via the "magnifier" button or creating an new element via the "plus" button... also make sure that allowed values is enforced
SVN:1.2[1721]
2011-12-15 10:46:48 +00:00
Romain Quetiez
c149ec8e2c #485 Improved the end-user experience with Excel and the web queries (added a phrasebook) + link to test the OQL attributes (query phrasebook or email actions, etc.) including the handlink of query arguments) + fixed wrong prototypes for a few implementations of GetBareProperties()
SVN:1.2[1719]
2011-12-14 17:44:06 +00:00
Denis Flaven
281adfb043 Bug fix: apply the AllowedValues constraints(as default values) when selecting elements via the "magnifier" button or creating an new element via the "plus" button.
SVN:1.2[1715]
2011-12-13 14:43:33 +00:00
Romain Quetiez
4244029087 #485 Export for MS Excel web queries: format=spreadsheet
SVN:1.2[1713]
2011-12-13 14:30:36 +00:00
Denis Flaven
78e173d5fb Bug fix: paginated lists were broken in the Impact Analysis "List" tab
SVN:1.2[1708]
2011-12-07 10:41:13 +00:00
Romain Quetiez
6b8abce03a - commited merge information (done earlier) -
SVN:1.2[1705]
2011-12-06 13:52:44 +00:00
Denis Flaven
33a7005069 - Bug ! Incorrectly appending a parameter ?version= to linked scripts already containing a parameter in their URL !
- changed the parameter name to itopversion to avoid collisions

SVN:1.2[1702]
2011-12-01 17:18:47 +00:00
Denis Flaven
006a6037d1 Increased Suhosin minimum value for get_max_value to 2048 due to a bug seen on some installations
SVN:1.2[1700]
2011-11-30 13:23:32 +00:00
Denis Flaven
e2f8be1745 Use the default language when creating a new user from CAS
SVN:1.2[1698]
2011-11-29 15:56:24 +00:00
Denis Flaven
ebae45f6a5 Support patterns for casMemberof
SVN:1.2[1696]
2011-11-29 15:36:58 +00:00
Denis Flaven
0685835d49 Allow to log entries on behalf of another user
SVN:1.2[1694]
2011-11-24 17:22:19 +00:00
Romain Quetiez
1b1e88f9a4 In french, organiZation takes an S
SVN:1.2[1691]
2011-11-23 15:30:11 +00:00
Denis Flaven
b90f443e75 - Don't create warnings for empty ext keys (i.e. empty string)
- Properly record multiple warnings
- Don't record warnings in case of creation error (error has precedence)

SVN:1.2[1689]
2011-11-23 09:55:44 +00:00
Romain Quetiez
4da64a64b1 Cosmetic on the API of the dictionnary (internal)
SVN:1.2[1686]
2011-11-21 17:03:36 +00:00
Denis Flaven
6673e171dc Properly handle restrictions (e.g. AllowedValues) on objects that are used as n:n relationships managed via LinksWidget.
SVN:1.2[1685]
2011-11-21 13:08:10 +00:00
Denis Flaven
afee7297cc Properly handle restrictions (e.g. AllowedValues) on objects that are used as n:n relationships managed via LinksWidget.
SVN:1.2[1683]
2011-11-21 10:57:50 +00:00
Denis Flaven
3cc8b5b88a Enhancement: when an (optional) external key cannot be reconciled, log a warning on the replica. the replicas containing a warning are then processed everytime in case the ext key changes
Also improved the search/display of replicas

SVN:1.2[1678]
2011-11-18 11:42:29 +00:00
Romain Quetiez
b2e6981b24 Fixed bug in change tracking: it was impossible to factorize code creating objects in the background either in the context of an object update or in the context of the application of a stimulus
SVN:1.2[1674]
2011-11-18 10:46:42 +00:00
Denis Flaven
c0a79fa573 Prevent warnings when checking the available stimuli in the menu...
SVN:1.2[1672]
2011-11-16 17:26:27 +00:00
Denis Flaven
02ad6d19fe Prevent Javascript errors in case a name contains a quote.
SVN:1.2[1670]
2011-11-15 13:12:22 +00:00
Denis Flaven
d16308ab62 - Reload the object after applying a stimulus, in case an action has an effect on an external field...
SVN:1.2[1665]
2011-11-14 10:44:31 +00:00
Denis Flaven
4598959bc2 - Reload the object after applying a stimulus, in case an action has an effect on an external field...
SVN:1.2[1664]
2011-11-14 10:43:06 +00:00
Denis Flaven
65a3755f81 - Regression from previous fix: don't Reload an object before saving it !
SVN:1.2[1662]
2011-11-10 16:37:18 +00:00
Denis Flaven
dc46c65499 - Don't activate triggers if the transition fails
- Reload the object, in case some custom action changed an external object

SVN:1.2[1659]
2011-11-10 10:12:43 +00:00
Denis Flaven
9d691c8e56 Removed obsolete code which caused a warning in bulk_stimulus
SVN:1.2[1658]
2011-11-10 10:07:28 +00:00
Denis Flaven
99f897bff7 Better error handling in case of OQL error
SVN:1.2[1657]
2011-11-09 17:07:25 +00:00
Denis Flaven
8d83447222 Added the capability for plug-ins to "listen" to add/remove attachment events.
SVN:1.2[1652]
2011-10-28 12:46:22 +00:00
Romain Quetiez
dcc8ad08a4 New helper class: TemplateString - to allow extended syntaxes such as $this->location_id->org_id->parent_id->name$... to be progressively introduced and replace the heavy ToArgs()
SVN:1.2[1646]
2011-10-24 13:45:49 +00:00
Denis Flaven
c1b0b73b51 Bug fix: Trac #494. It seems that PHPSoap does not understand the <wsdl:documentation> tag.
SVN:1.2[1644]
2011-10-21 08:42:13 +00:00
Romain Quetiez
81173decca Fixed issue: nobody in the list of persons to notify for portal users (security takes precedence)
SVN:1.2[1641]
2011-10-21 08:05:07 +00:00
Denis Flaven
9aca062bf5 Enhancement to provide a forward compatible API for some external plugins: support adding attachments to an email in a "clean" way.
SVN:1.2[1640]
2011-10-20 16:33:46 +00:00
Denis Flaven
09aba95d0a Fixed Trac #493: incorrect display of Users' Grant Matrix
SVN:1.2[1635]
2011-10-17 09:27:23 +00:00
Denis Flaven
1683ca2dd6 Merged some enhancements fro the trunk to better keep track of sent emails
SVN:1.2[1634]
2011-10-13 15:42:59 +00:00
Denis Flaven
9e732d6045 Fixed Trac #487: resizable text areas disappeared when located on the second tab !
SVN:1.2[1628]
2011-10-04 10:52:03 +00:00
Denis Flaven
69df343bd2 Automatic synchro of CAS/LDAP users
SVN:1.2[1627]
2011-10-03 14:07:29 +00:00
Denis Flaven
eb8f49ebfe Initializes the admin contact's phone number, in case it is a mandatory field in the data model...
SVN:1.2[1623]
2011-09-30 08:03:45 +00:00
Denis Flaven
398e294604 Prevent crash when trying to load the favicon during the setup !
SVN:1.2[1621]
2011-09-29 15:27:25 +00:00
Denis Flaven
d04c6bccd5 Added a link to a favicon (icon in the browser's bar and tab)
SVN:1.2[1619]
2011-09-29 14:54:03 +00:00
Denis Flaven
f00c7c6bc2 GetValueLabel is used in some dashboards... make sure that it is available for any attribute
SVN:1.2[1617]
2011-09-29 09:46:58 +00:00
Denis Flaven
d30e8c359f Make sure that the organisation's drop-down list is not bigger than the left menu...
SVN:1.2[1616]
2011-09-29 09:44:38 +00:00
Romain Quetiez
2bd4a61c00 #485 Export.php improved for integration into Excel / web queries (bug with IIS/HTTPS, limitation on the size of the OQL)
SVN:1.2[1613]
2011-09-29 08:12:28 +00:00
Romain Quetiez
ba3080594c #485 Export.php improved for integration into Excel / web queries (bug with IIS/HTTPS, limitation on the size of the OQL)
SVN:trunk[1612]
2011-09-29 08:07:37 +00:00
Romain Quetiez
e35c8323df Merge most recent (few) bug fixes from trunk
SVN:1.2[1611]
2011-09-28 14:38:37 +00:00
Romain Quetiez
e4e814281d #484 Fixed issue with IIS ("Wrong password" at first prompt)
SVN:1.2[1610]
2011-09-28 12:59:40 +00:00
Romain Quetiez
a3a94cccbb #484 Fixed issue with IIS ("Wrong password" at first prompt)
SVN:trunk[1609]
2011-09-28 12:55:15 +00:00
Denis Flaven
635cb424a2 Fixed Trac #482: OpenSearch broken.
SVN:1.2[1608]
2011-09-28 10:50:58 +00:00
Romain Quetiez
e8c8e4a0d7 #481 Special characters not transcribed correctly in Portal (regression in 1.2)
SVN:trunk[1607]
2011-09-28 10:17:53 +00:00
Denis Flaven
5b3350f6d5 Fixed Trac #482: link to OpenSearch Xml page was broken
SVN:trunk[1606]
2011-09-28 10:07:27 +00:00
Denis Flaven
b454b24e0f Rename the cron.params file to avoid overwriting the current config when updating.
SVN:trunk[1605]
2011-09-23 14:14:58 +00:00
Romain Quetiez
e95aa6cc69 Merged latest changes in module Attachments (bug fix on install and cosmetic improvements)
SVN:1.2[1604]
2011-09-23 13:49:26 +00:00
Romain Quetiez
c58fd17fc9 Fixed regression on attribute labels; introduced in [1582]
SVN:1.2[1603]
2011-09-23 13:36:57 +00:00
Romain Quetiez
099afb5451 Fixed regression on attribute labels; introduced in [1581]
SVN:trunk[1602]
2011-09-23 13:30:21 +00:00
Denis Flaven
ec226ce76d Cosmetic fix: the "loading..." indicator was visible/spinning when reloading any other field of the form !
SVN:trunk[1601]
2011-09-22 16:37:47 +00:00
Denis Flaven
5080bd58d5 Removed some Debug/Warning messages
SVN:trunk[1600]
2011-09-22 16:35:33 +00:00
Romain Quetiez
efdec7a343 #478 Fixed issue in the audit: the results are wrong whenever an organization is selected
SVN:1.2[1599]
2011-09-22 12:08:11 +00:00
Denis Flaven
5ec37fd26a - Fix a problem with the upgrade (case sensitive table name & prefix)
SVN:trunk[1598]
2011-09-22 11:58:42 +00:00
Romain Quetiez
2352c05d36 Fixed security issue: the attachments were visible by anybody (by forming URLs manually), whatever the allowed organizations. The change requires the execution of the setup/migration procedure.
SVN:1.2[1597]
2011-09-22 11:58:31 +00:00
Denis Flaven
8ae92cb50e - Fix a problem with the upgrade (case sensitive table name & prefix)
- Display a message when there is no attachment

SVN:trunk[1596]
2011-09-22 11:53:49 +00:00
Romain Quetiez
f82a4ada23 #478 Fixed issue in the audit: the results are wrong whenever an organization is selected
SVN:trunk[1595]
2011-09-22 11:40:30 +00:00
Denis Flaven
e5ce12ed0a Make the template system work for real !
SVN:trunk[1594]
2011-09-22 11:15:40 +00:00
Denis Flaven
428f1a26cc Typo ! Impact: Unknown ?
SVN:trunk[1593]
2011-09-22 11:08:34 +00:00
Romain Quetiez
9976999a88 Portal: updated dictionary for new buttton "reopen request"
SVN:trunk[1592]
2011-09-22 10:08:36 +00:00
Romain Quetiez
da2b8ab4c0 Fixed security issue: the attachments were visible by anybody (by forming URLs manually), whatever the allowed organizations. The change requires the execution of the setup/migration procedure.
SVN:trunk[1591]
2011-09-22 09:04:12 +00:00
Romain Quetiez
0de75db474 Portal (regression with the latest implementation, causing a warning)
SVN:trunk[1590]
2011-09-21 12:59:16 +00:00
Romain Quetiez
23634964a5 #477 Could not specify more than one reconciliation key (regression) + took the opportunity to enhance protection against XSS injection (using column names in the data)
SVN:1.2[1589]
2011-09-21 12:39:22 +00:00
Romain Quetiez
b014596172 #477 Could not specify more than one reconciliation key (regression) + took the opportunity to enhance protection against XSS injection (using column names in the data)
SVN:trunk[1588]
2011-09-21 12:27:08 +00:00
Romain Quetiez
b46ed4764d Fixed bug in the new portal: the wizard validation was inoperant when several selection lists were proposed
SVN:trunk[1587]
2011-09-20 13:50:11 +00:00
Romain Quetiez
2b1aecda53 Fixed recent regression on the portal (confusion btw GetContactId and GetUserId)
SVN:trunk[1586]
2011-09-20 08:22:54 +00:00
Romain Quetiez
619252db99 Merge fix on previous change (fixed #473, but getting a warning)
SVN:1.2[1585]
2011-09-19 11:49:43 +00:00
Romain Quetiez
19318973ca Fixed warning (strict standards) on previous fix
SVN:trunk[1584]
2011-09-19 11:44:04 +00:00
Romain Quetiez
ca4dbd833c (record merge info)
SVN:1.2[1583]
2011-09-19 10:58:40 +00:00
Romain Quetiez
f62a3b22a3 #473 Could not load NW interfaces (reconciliation issue) - merged from trunk
SVN:1.2[1582]
2011-09-19 10:55:24 +00:00
Romain Quetiez
cac76a7e08 #473 Could not load NW interfaces (reconciliation issue)
SVN:trunk[1581]
2011-09-19 10:36:47 +00:00
Romain Quetiez
88416bca5d Created branch 1.2
SVN:1.2[1580]
2011-09-19 10:30:29 +00:00
Romain Quetiez
abd645ca98 Fixed regressions in the portal
SVN:trunk[1579]
2011-09-15 15:25:30 +00:00
Romain Quetiez
7301cb9369 Updated the dictionnaries for the latest changes into the portal
SVN:trunk[1578]
2011-09-15 10:26:50 +00:00
Romain Quetiez
559de3d38f Portal reworked (code factorization, allowing for paginated lists) + added the list of closed tickets (+search)
SVN:trunk[1577]
2011-09-15 10:06:46 +00:00
Denis Flaven
552ffd79b1 Always use a drop-down list for external keys in search forms
SVN:trunk[1575]
2011-09-14 07:38:40 +00:00
Denis Flaven
aae8ca7b8b Added credentials for the security advices
SVN:trunk[1574]
2011-09-13 14:58:40 +00:00
Denis Flaven
13c636cab6 Prevent email header injection
SVN:trunk[1573]
2011-09-13 14:55:34 +00:00
Denis Flaven
cc40fc5d91 Exclude a (risky) and unused file for the build
SVN:trunk[1572]
2011-09-13 14:38:52 +00:00
Denis Flaven
bddfdb4a28 Fixed Trac #468: wrong path to the doc#installationInstructions
SVN:trunk[1571]
2011-09-12 11:22:29 +00:00
Denis Flaven
6bc639c6ce Enhanced error message in case of "file too big error" (more user friendly and not admin oriented)
SVN:trunk[1570]
2011-09-12 09:48:54 +00:00
Denis Flaven
9b8ae03413 - Properly display names containing XML entities (& < > and ")
- Don't drag items by their (invisible) tooltip !

SVN:trunk[1569]
2011-09-10 16:24:26 +00:00
Denis Flaven
17f00198d3 Fix for #404: context was lost when performing a drill-down in the flash navigator.
SVN:trunk[1568]
2011-09-10 16:20:50 +00:00
Denis Flaven
5041ec2e0c Regression on accentuated characters due to bad (forgotten) encoding on htmlentities (Trac #446)
SVN:trunk[1567]
2011-09-09 16:18:01 +00:00
Denis Flaven
ee4f7e5b6a Don't forget to track the count of updated objects...
SVN:trunk[1566]
2011-09-09 16:11:51 +00:00
Denis Flaven
cbb40b85dc Typo in a (not yet used) function !
SVN:trunk[1565]
2011-09-08 15:29:35 +00:00
Denis Flaven
9aca772209 Fix for Trac#446: prevent XSS vulnerabilities
SVN:trunk[1564]
2011-09-08 13:33:47 +00:00
Denis Flaven
c4db9cd84e More fixes for Trac#446: XSS vulnerabilities with vectors containing double quotes
SVN:trunk[1563]
2011-09-08 13:21:32 +00:00
Romain Quetiez
205e80f8a5 #446 XSS vector through the page title
SVN:trunk[1562]
2011-09-08 10:30:18 +00:00
Romain Quetiez
06e9bd0c25 #446 XSS vector on the login web page
SVN:trunk[1561]
2011-09-08 10:28:14 +00:00
Denis Flaven
1c812f8b34 Bug fix: subsequent audit results were wrong when a 'negative' rule was used.
SVN:trunk[1560]
2011-09-07 12:12:10 +00:00
Romain Quetiez
f3c5759721 Updated the readme file
SVN:trunk[1559]
2011-09-07 07:58:22 +00:00
Denis Flaven
69368a39cb Fix radio buttons for enum values.
SVN:trunk[1558]
2011-09-06 12:47:42 +00:00
Denis Flaven
ab5849b3cd Fix: properly manage validation field for vertical radio buttons.
SVN:trunk[1557]
2011-09-06 10:25:06 +00:00
Denis Flaven
1ab796bd40 One more fix for #404: context was lost when performing a bulk delete.
SVN:trunk[1556]
2011-09-06 08:07:18 +00:00
Denis Flaven
c1221054b4 Prevent a crash when displaying the details of a change (from the CSV "history" tab)
SVN:trunk[1555]
2011-09-05 16:28:31 +00:00
Denis Flaven
3bd681bc9d Bug fix: hyperlink to create a new object was wrong (typo)
SVN:trunk[1554]
2011-09-02 15:54:32 +00:00
Denis Flaven
b33989ec62 Added the ability to specify programmatically the scope for "favorite organizations". Note the the end-user can still restrict this list even further using the "Preferences" page.
SVN:trunk[1553]
2011-09-01 14:06:51 +00:00
Denis Flaven
b3121eebae Typos in the Brazilian localization, found by Bruno Cassaro. Thanks Bruno !
SVN:trunk[1552]
2011-09-01 13:07:09 +00:00
Denis Flaven
7d8971139b Missing sanitization filter for 1 parameter: fixed !
SVN:trunk[1551]
2011-09-01 11:50:34 +00:00
Denis Flaven
04f20860dc Add the name of the synchro data source as an external field in case we need to make a CSV export
SVN:trunk[1550]
2011-09-01 10:50:57 +00:00
Denis Flaven
8f21b9dabb Bug fix: prevent a warning when doing modify-all as a non-admin user.
SVN:trunk[1549]
2011-09-01 10:49:56 +00:00
Romain Quetiez
eb89610465 Readme file ready for release 1.2
SVN:trunk[1548]
2011-08-31 15:37:48 +00:00
Denis Flaven
13f4e90ee6 Make sure that we trigger a change event whenever we pickup a different value for an external key (either via the magnifier button or via the hierarchy button)
SVN:trunk[1547]
2011-08-31 14:39:55 +00:00
Denis Flaven
29f1dd5a69 Prevent an exception in some (rare?) conditions where an incorrect value was set in a temporary object....cf Trac #466
SVN:trunk[1546]
2011-08-31 14:28:41 +00:00
Romain Quetiez
a7e937ec58 Regression: the default language was German!
SVN:trunk[1545]
2011-08-31 13:09:50 +00:00
Denis Flaven
bc2a8a8c4a Improved verification to the PHP file upload settings to avoid troubles later... (Trac #284)
SVN:trunk[1544]
2011-08-31 09:03:26 +00:00
Denis Flaven
c9b0a2b561 Enhance ObjectDetailsTemplate to manage bulk actions and synchronized attributes.
SVN:trunk[1543]
2011-08-30 15:47:57 +00:00
Romain Quetiez
5fcea9bf69 #437 Regressions in the beta - wrong reporting of the bulk update
SVN:trunk[1542]
2011-08-30 15:09:54 +00:00
Denis Flaven
3b42c86f46 Regression due to the interaction of the two previous fixes. (Trac #444 and Trac #448)
SVN:trunk[1541]
2011-08-30 11:12:14 +00:00
Romain Quetiez
038fe30fbe #363 Charts not displaying with IE8 + IIS + HTTPS
SVN:trunk[1540]
2011-08-30 10:13:58 +00:00
Denis Flaven
aa95e51e00 Fixed Trac #444: preserve sort order and page size when reloading a paginated list.
SVN:trunk[1539]
2011-08-30 09:38:06 +00:00
Romain Quetiez
6a78c7dcc2 Internal: Fixed regression introduced in [1365] and present in the beta version (overviews not working with HTTPS)
SVN:trunk[1538]
2011-08-30 09:20:29 +00:00
Romain Quetiez
17aa1d1fc0 #464 While upgrading iTop, copy the values from change/start_date to ticket/start_date (iif the current value is null)
SVN:trunk[1537]
2011-08-29 16:28:08 +00:00
Romain Quetiez
24244f1bac Rollback: restored attributes removed by mistake
SVN:trunk[1536]
2011-08-29 16:26:17 +00:00
Romain Quetiez
f7be35848c Internal: detect unused columns while upgrading
SVN:trunk[1535]
2011-08-29 15:37:58 +00:00
Denis Flaven
1aa7ff14fe Removing duplicate fields
SVN:trunk[1534]
2011-08-29 15:02:59 +00:00
Denis Flaven
c33a508376 Removing duplicate fields
SVN:trunk[1533]
2011-08-29 15:02:12 +00:00
Romain Quetiez
37ec8b12f3 Upgrade/toolkit: improved the data sources update procedure (no need to create "direct" linkset attributes, remove old attributes, more verbose)
SVN:trunk[1532]
2011-08-29 13:23:57 +00:00
Romain Quetiez
dde3efc0c7 Upgrade: adjust data sources, and be more verbose about upgrades in the DB (HKeys and data sources)
SVN:trunk[1531]
2011-08-29 13:20:44 +00:00
Romain Quetiez
54f3dec7a9 Upgrade: fails to recreate a view when is has become invalid (missing attribute)
SVN:trunk[1530]
2011-08-29 13:18:30 +00:00
Denis Flaven
6be497ee7d Fixed Trac#465: incorrect logic when resetting the 'ConnectedToInterface'
SVN:trunk[1529]
2011-08-29 11:55:13 +00:00
Denis Flaven
5d9d44dc5e Fix: detection of the Suhosin extension during the installation and tell the user if the get_max_value is too small.
SVN:trunk[1528]
2011-08-29 11:49:22 +00:00
Denis Flaven
d547394ba2 Fixed Trac#448: paginated display broken after reloading a page containing a list where the page size is set to 'All'
SVN:trunk[1527]
2011-08-26 11:07:29 +00:00
Denis Flaven
ace0a3e278 Finishing fix of Trac449:display selection of favorite organizations
SVN:trunk[1526]
2011-08-26 10:45:46 +00:00
Denis Flaven
dd9cb27971 Fixed Trac #449: properly reload the current selection when the list of Favorite Organizations is a paginated list.
SVN:trunk[1525]
2011-08-26 10:34:54 +00:00
Denis Flaven
a3dc8ab3d7 Added the ability to reload a selection fro a paginated list.
SVN:trunk[1524]
2011-08-26 10:33:37 +00:00
Denis Flaven
496bf386be Force the rebuilding of hierarchical keys on every upgrade, since in 1.2-beta the rebuilding of HK was buggy. (May be the cause of the bugs: #459, #460 and #461)
SVN:trunk[1523]
2011-08-26 09:08:54 +00:00
Denis Flaven
c337616fea Fix: regression for tickets with just a default initial state.
SVN:trunk[1522]
2011-08-26 07:26:30 +00:00
Denis Flaven
186ba76092 Added a flag to force the import to cinutnue even in case of SQL error... useful for debugging don't use in production ;-)
SVN:trunk[1521]
2011-08-25 17:37:56 +00:00
Denis Flaven
20a06c9212 Fixed a warning...
SVN:trunk[1520]
2011-08-25 17:03:12 +00:00
Romain Quetiez
e515dfb434 Cosmetics: regression due to an unnecessary protection against XSS attacks
SVN:trunk[1519]
2011-08-25 16:35:38 +00:00
Romain Quetiez
2ee85dd407 Customization: added a check against reuse of an attribute code (collision can happen easily with inheritance)
SVN:trunk[1518]
2011-08-25 16:10:57 +00:00
Denis Flaven
6df6e4a9cc - Allow creation of an ticket in a different initial state via the new 'initial_state_path' attribute.
- Support update of CaseLog fields in bulk_modify mode.

SVN:trunk[1517]
2011-08-25 16:04:58 +00:00
Romain Quetiez
c74c955973 Internal: it is possible to override the user authorized organizations
SVN:trunk[1516]
2011-08-25 15:45:45 +00:00
Romain Quetiez
a3a4fd0a75 Dictionary: English was proposed twice in the list of available languages!
SVN:trunk[1515]
2011-08-25 12:01:04 +00:00
Romain Quetiez
c7f3ac0361 Internal: suppressed a warning on ormCaseLog
SVN:trunk[1514]
2011-08-25 11:57:18 +00:00
Romain Quetiez
21002574ce Notifications: case log in plain text (this->case_log) or the latest entry (this->head(case_log))
SVN:trunk[1513]
2011-08-25 11:49:15 +00:00
Romain Quetiez
64cae5b58b Portal: activate TriggerOnPortalUpdate
SVN:trunk[1512]
2011-08-25 11:46:23 +00:00
Romain Quetiez
cc1105ff60 Email test: under IIS it was not detecting Windows correctly, and the help message was therefore completely wrong.
SVN:trunk[1511]
2011-08-25 10:04:21 +00:00
Romain Quetiez
71655f0632 Fixed regression (shortcut actions, display a list of objects not deriving from cmdbAbstract)
SVN:trunk[1510]
2011-08-25 09:29:52 +00:00
Denis Flaven
6e4e5be35d Allow TriggerOnPortalUpdate to work also on derived classes: i.e a trigger defined for the class 'Ticket' will be called when a 'UserRequest' (derived from Ticket) will be updated from the portal.
SVN:trunk[1509]
2011-08-25 08:03:39 +00:00
Denis Flaven
872a424a78 Bug fix: allow modifying an object as if in target state, before applying the stimulus (cf PortalWebpage).
SVN:trunk[1508]
2011-08-24 16:00:28 +00:00
Denis Flaven
612c355e03 Bug fix: allow modifying an object as if in target state, before applying the stimulus (cf PortalWebpage).
SVN:trunk[1507]
2011-08-24 15:58:56 +00:00
Denis Flaven
ffa9b21364 Bug fix: allow modifying an object as if in target state, before applying the stimulus (cf PortalWebpage).
SVN:trunk[1506]
2011-08-24 15:58:22 +00:00
Denis Flaven
180311fd0a Fixed a side effect of Trac#446 (XSS)
SVN:trunk[1505]
2011-08-24 15:56:48 +00:00
Denis Flaven
16b7714139 Default shortcut actions are now a parameter
SVN:trunk[1504]
2011-08-24 14:11:57 +00:00
Denis Flaven
c6865cf63d Default shortcut actions are now a parameter
SVN:trunk[1503]
2011-08-24 14:11:25 +00:00
Denis Flaven
905d9b5931 Apply organization filtering to subclasses of Organization as well.
SVN:trunk[1502]
2011-08-24 13:54:27 +00:00
Denis Flaven
0850370275 Bug fix: let accented characters pass through the autocomplete
SVN:trunk[1501]
2011-08-24 13:53:19 +00:00
Romain Quetiez
38591f444e Fixed regression on latest fix (Hkeys and inheritance)
SVN:trunk[1500]
2011-08-24 10:49:39 +00:00
Denis Flaven
ae36aa1f74 German localization update, thanks to Stephan Rosenke.
SVN:trunk[1499]
2011-08-23 16:55:01 +00:00
Denis Flaven
264af56591 Pass the final class to GetShortcutActions
SVN:trunk[1498]
2011-08-23 13:56:09 +00:00
Denis Flaven
0a2c9d0e37 Automatic rebuild of the dictionary list during setup, based on the content of the 'dictionaries' folder.
SVN:trunk[1497]
2011-08-23 13:13:08 +00:00
Denis Flaven
9d9d53c967 Fix regression on hierarchical keys
SVN:trunk[1496]
2011-08-23 11:14:25 +00:00
Denis Flaven
54315f41e4 Bug fix: computation of allowed stimuli was wrong
SVN:trunk[1495]
2011-08-23 09:17:19 +00:00
Denis Flaven
a04e7ea375 Bug fix in HierarchicalKey: host class can be different from target class in case of derived classes.
SVN:trunk[1494]
2011-08-23 07:24:55 +00:00
Erwan Taloc
795296e6e3 update after test made by Erwan
SVN:trunk[1493]
2011-08-23 07:13:28 +00:00
Denis Flaven
98ac85c098 Added the display of the total count of objects above 'group by' blocks.
SVN:trunk[1492]
2011-08-22 16:42:28 +00:00
Romain Quetiez
e53c07393c Internal: improved the API of PortalWebPage
SVN:trunk[1491]
2011-08-22 10:09:21 +00:00
Denis Flaven
48d66447b7 Support an abstract "Organization" class: if it's the case don't create any Org neither Person during the setup.
SVN:trunk[1490]
2011-08-22 10:06:48 +00:00
Romain Quetiez
54b4f67ed6 Fixed regression due to the enhanced security (ignore a field in CSV import)
SVN:trunk[1489]
2011-08-22 08:49:52 +00:00
Denis Flaven
9836797da9 Regression due to XSS fix (#446): auto-refresh block was showing an assertion failed message. Fixed.
SVN:trunk[1488]
2011-08-22 08:37:08 +00:00
Romain Quetiez
931a260435 Fixed regression due to the enhanced security (param file)
SVN:trunk[1487]
2011-08-22 08:32:25 +00:00
Denis Flaven
2a6d36b9cd Bug fix: (post 1.2-beta) warning message (undefined variable aFavoriteActions) when displaying a list for a non-admin user...
SVN:trunk[1486]
2011-08-22 08:19:35 +00:00
Denis Flaven
9420c14c13 Fixed a (post 1.2-beta) regression: bulk modify and bluk apply stimulus were broken.
SVN:trunk[1485]
2011-08-22 08:18:07 +00:00
Romain Quetiez
272807ba56 Export.php - fixed a regression (specifying no field list resulting in an error)
SVN:trunk[1484]
2011-08-22 07:38:38 +00:00
Denis Flaven
d05ea1863c Fixed a (post 1.2-beta) regression: bulk modify and bluk apply stimulus were broken.
SVN:trunk[1483]
2011-08-21 16:41:23 +00:00
Denis Flaven
6c8e021727 Fixed Trac# 457: crash when deleting two organizations.
SVN:trunk[1482]
2011-08-19 17:14:06 +00:00
Romain Quetiez
266135b1b9 #452 Export.php - field list can be specified also for HTML output
SVN:trunk[1481]
2011-08-19 16:14:50 +00:00
Denis Flaven
02fc610a18 Bug fix: "shortcut" actions were not perserving the current tab. Fixed.
SVN:trunk[1480]
2011-08-19 16:08:27 +00:00
Denis Flaven
e2aa3cc69f Enhancement: provide a warning (popup) message when navigating away from an edition form.
SVN:trunk[1479]
2011-08-19 15:50:17 +00:00
Denis Flaven
de516c0ce4 Fixed Trac #458: back button was asking to fill the mandatory fields !
SVN:trunk[1478]
2011-08-19 15:41:05 +00:00
Romain Quetiez
92ac1fe2ca #148 Allow overloading attribute/enum labels in the dictionary
SVN:trunk[1477]
2011-08-19 15:30:43 +00:00
Romain Quetiez
cc7844ea10 Security: protected bulk modify against HTTP/Post piracy
SVN:trunk[1476]
2011-08-19 15:26:05 +00:00
Denis Flaven
22bdc02b1b Support the selection (via an autocomplete) in a list that contains duplicates
SVN:trunk[1475]
2011-08-19 15:00:33 +00:00
Romain Quetiez
e8586515e3 Fixed regression (HKeys)
SVN:trunk[1474]
2011-08-19 08:51:16 +00:00
Romain Quetiez
071a30d928 Cosmetic on the portal
SVN:trunk[1473]
2011-08-19 07:42:19 +00:00
Denis Flaven
488f1c47bf Fixed Trac #456: 'contact_list' was considered as read-only on tickets.
SVN:trunk[1472]
2011-08-19 07:32:19 +00:00
Romain Quetiez
c15976f148 Internal: leverage display block for displaying link sets in the portal
SVN:trunk[1471]
2011-08-18 16:32:31 +00:00
Romain Quetiez
6f8e2e60ce Internal: the transaction mechanism was inoperant when a form was built within the target page.
SVN:trunk[1470]
2011-08-18 15:00:39 +00:00
Denis Flaven
b7b8da0848 Fixed the computation of IPs in a subnet that failed (returned negative numbers) on some versions of PHP compiled in 32-bit.
SVN:trunk[1469]
2011-08-18 12:05:30 +00:00
Denis Flaven
2f1803b3b6 A mandatory case log field is now considered as 'filled' if it contains a previous entry
SVN:trunk[1468]
2011-08-18 10:45:41 +00:00
Denis Flaven
58e61c9d6b A mandatory case log field is now considered as 'filled' if it contains a previous entry
SVN:trunk[1467]
2011-08-18 10:44:37 +00:00
Denis Flaven
ddd98b9335 Different display for 'Date' fields: shorter field than DateTime since there is no "time" part.
SVN:trunk[1466]
2011-08-18 09:51:10 +00:00
Denis Flaven
278b84ee38 The default value for Date fields is different than for DateTime fields: no 'time' part at the end (use the attribute's own format)
SVN:trunk[1465]
2011-08-18 09:50:16 +00:00
Denis Flaven
c0764bce74 Added smart search for 'Date' fields (and not only DateTime)
SVN:trunk[1464]
2011-08-18 09:48:44 +00:00
Denis Flaven
7cf0e2f747 Bug fix: incorrect detection of the need to fix HK, leading to incorrect data in case of upgrade.
SVN:trunk[1463]
2011-08-18 07:01:08 +00:00
Romain Quetiez
c7e8627679 Fixed regression on the paginated lists
SVN:trunk[1462]
2011-08-17 15:56:34 +00:00
Denis Flaven
1c7d5b8fd2 Added support for an abstract Organization (or Person) class during setup !
SVN:trunk[1461]
2011-08-17 15:20:32 +00:00
Denis Flaven
18217e4adb Bug fix: (side effect of fixing #445) paginate the lists when performing a selection.
SVN:trunk[1460]
2011-08-16 17:07:54 +00:00
Denis Flaven
1357e7e225 Bug fix: paginated display was always displaying 5 items by default...
SVN:trunk[1459]
2011-08-16 16:42:02 +00:00
Denis Flaven
a941c2d14e Allow updating/recomputing of read-only attributes on the fly in the edit form.
SVN:trunk[1458]
2011-08-16 16:16:26 +00:00
Denis Flaven
cb55a75bcf Allow updating/recomputing of read-only attributes on the fly in the edit form.
Also fixed a regerssion due to #446

SVN:trunk[1457]
2011-08-16 15:58:09 +00:00
Denis Flaven
1e20253229 Bug fix: don't assume that the hierachical key is called 'parent_id'
SVN:trunk[1456]
2011-08-16 15:54:22 +00:00
Denis Flaven
53a48dc8e0 Fixed Trac #447: interfaces not showing up on the details of a server when an org is selected: there were collisions in the internal query parameters names ! This is now fixed.
SVN:trunk[1455]
2011-08-16 10:01:14 +00:00
Denis Flaven
2ad9d69396 Implemented the OQL operator ABOVE (and its variants ABOVE STRICT, NOT ABOVE and NOT ABOVE STRICT) that returns the (indirect) parents of a given node in a hierarchy. It's the opposite to 'BELOW' that returns the (indirect) children.
SVN:trunk[1454]
2011-08-16 08:14:11 +00:00
Denis Flaven
74baca3a7b Enhanced interface for complex SLA computations...
SVN:trunk[1453]
2011-08-15 14:06:03 +00:00
Denis Flaven
2bd8700c90 Implementing Trac #419: provide means for the toolkit to fix synchro data sources
SVN:trunk[1452]
2011-08-15 14:01:51 +00:00
Denis Flaven
51bd6cdf97 #446 continuing: sanitizing parameters...
SVN:trunk[1451]
2011-08-15 13:55:59 +00:00
Denis Flaven
b02021a4ff Still fixing regressions caused by Trac#446: XSS vulnerabilities...
SVN:trunk[1450]
2011-08-12 10:06:33 +00:00
Denis Flaven
96f3350029 Fixed the display of 'Used IP Addresses' (i.e. Network interfaces) in the details of a Subnet object.
SVN:trunk[1449]
2011-08-12 09:19:53 +00:00
Romain Quetiez
e2e4c53b0d Fix after security fix
SVN:trunk[1448]
2011-08-11 16:31:59 +00:00
Denis Flaven
9c6605f7e7 Allow export of the config information for documentation purposes
SVN:trunk[1447]
2011-08-11 12:01:53 +00:00
Denis Flaven
f481652666 Trac #445: output of export.php should not be "paginated" !
Bug fix: the number of items displayed in the "paginated" view should be "min_display_limit", as listed in the drop down-box and not always 10 !

SVN:trunk[1446]
2011-08-11 11:59:55 +00:00
Denis Flaven
212389f43e One more regression due to #446: fixed !
SVN:trunk[1445]
2011-08-11 11:57:44 +00:00
Denis Flaven
f68680ada1 Still working on #446: oops, one parameter missing...
SVN:trunk[1444]
2011-08-11 10:53:37 +00:00
Denis Flaven
6859326646 Fixed Trac#446: XSS vulnerabilities... to be tested !
Also fixed the display/download links on documents that were both doing exactly the same thing !

SVN:trunk[1443]
2011-08-11 10:17:03 +00:00
Romain Quetiez
a129c9814f Readme for 1.2 Beta
SVN:trunk[1441]
2011-08-09 09:37:48 +00:00
Denis Flaven
05ee0caec2 Fixed the "more information" url in the itop-config-mgmt module
SVN:trunk[1440]
2011-08-09 08:55:29 +00:00
Denis Flaven
6fff9d6a20 Make the 'agent_id' visible in the state "new" to allow the creation and assigment of a ticket in one click.
SVN:trunk[1439]
2011-08-09 08:34:14 +00:00
Denis Flaven
d2955557bf Allow $SERVER_NAME$ as a placeholder for the server's name in the application root url. Useful for multiple virtual hosts or test systems using DHCP...
SVN:trunk[1438]
2011-08-09 07:57:11 +00:00
Denis Flaven
aa00cd9ae7 "Shortcut" actions are displayed as buttons on the same line as the usual "Actions" button.
SVN:trunk[1437]
2011-08-09 07:51:08 +00:00
Denis Flaven
aa9d9ed578 New config parameter: buttons_position: top | bottom | both
SVN:trunk[1436]
2011-08-09 07:47:50 +00:00
Romain Quetiez
9bd6fa61c5 SQLBlock - removed Warnings in case of empty data set
SVN:trunk[1435]
2011-08-08 16:02:28 +00:00
Denis Flaven
f16997fb2d Productivity enhancement: apply directly the 'next_action' when creating or modifying an object with a life-cycle !
SVN:trunk[1434]
2011-08-08 15:40:51 +00:00
Romain Quetiez
f4c1cf0818 Fixed typo
SVN:trunk[1433]
2011-08-08 14:24:35 +00:00
Denis Flaven
01dd63f623 Cleanup of the way objects are displayed/edited: DisplayBareProperties is now used for displaying (i.e. read-only) or modifying/creating an object.
SVN:trunk[1432]
2011-08-08 12:52:32 +00:00
Denis Flaven
3393808c7a - Enhancement: can now specify 'order_by' in 'list' templates.
- New 'Shortcut Actions' displayed as buttons next to the 'Actions' popup menu.

SVN:trunk[1431]
2011-08-08 12:35:43 +00:00
Denis Flaven
772c892b15 New option DEL_MOVEUP (move the children up one level) for 'on_target_delete' for HierarchicalKey attributes.
SVN:trunk[1430]
2011-08-07 12:24:45 +00:00
Denis Flaven
435d943f47 Cleanup of the way objects are displayed/edited: DisplayBareProperties is now used for displaying (i.e. read-only) or modifying/creating an object. The means that:
1) the display/edition can be customized by overloading this method
2) the plug-ins's method OnDisplayProperties is now called in edition as well

A new class of template is introduced for building custom object displays: ObjectDetailsTemplate.

SVN:trunk[1429]
2011-08-07 08:12:07 +00:00
Denis Flaven
eab1060f8e Adapted the portal to support enhanced attachments either in 'properties' or 'relations'... this is transparent for the end-user of the portal.
SVN:trunk[1428]
2011-08-07 07:55:16 +00:00
Denis Flaven
cb8774370f Enhancement: new configuration parameter 'position' to enable 'attachments' to be displayed either as a separate tab 'Attachments' on inline in the 'Properties' tab (for example when used with a custom 'DisplayBareProperties'.)
SVN:trunk[1427]
2011-08-07 07:52:39 +00:00
Denis Flaven
23b1d15b64 New attribute 'display_style' for Strings, Enums and ExternalKeys: when the list is short, display it as a set of radio buttons instead of a drop-down list. Possible values: 'radio', 'radio_vertical' and 'radio_horizontal'. ('radio' == 'radio_vertical').
SVN:trunk[1426]
2011-08-06 09:51:06 +00:00
Denis Flaven
61727aca02 New configuration parameter: display_actions_at_top: avoid scrolling to see the buttons when assigning a ticket !
SVN:trunk[1425]
2011-08-05 13:28:21 +00:00
Denis Flaven
8cfebdf723 - New configuration parameter deadline_format to specify how to display Deadlines (date or time difference or a mix of the two)
SVN:trunk[1424]
2011-08-05 13:08:56 +00:00
Denis Flaven
506702c50b Enhancement: added the capability to filter (based on user's defined preferences the list of organizations displayed in the drop-down menu)
SVN:trunk[1423]
2011-08-05 11:45:12 +00:00
Denis Flaven
b9de1df6a8 Release note review
SVN:trunk[1422]
2011-08-03 13:49:27 +00:00
Denis Flaven
a7175007be Preparing for the 1.2-beta release
SVN:trunk[1421]
2011-08-03 10:37:55 +00:00
Denis Flaven
5d8acbb41a New type of trigger: TriggerOnPortalUpdate, called when the end-user updates a ticket from the portal.
SVN:trunk[1420]
2011-08-03 10:36:34 +00:00
Denis Flaven
3ccbeac996 Oops, missing Portuguese (Brazilian) dictionary files...
SVN:trunk[1419]
2011-08-03 09:25:19 +00:00
Denis Flaven
1643ba8e9c Added support for the 'Attachments' plug-in into the portal
SVN:trunk[1418]
2011-08-02 10:36:43 +00:00
Denis Flaven
768dbd8946 New module to easily manage attachments
SVN:trunk[1417]
2011-08-02 10:29:59 +00:00
Denis Flaven
c2d02a2394 Put back wsdl documentation
SVN:trunk[1416]
2011-08-02 08:30:43 +00:00
Denis Flaven
b26f2738f6 German localization enhancement by Stephan Rosenke. Thanks Stephan !
SVN:trunk[1415]
2011-08-02 07:53:39 +00:00
Denis Flaven
781ec7e33a In CLI mode, do not depend on the current directory: the script can be run from anywhere.
SVN:trunk[1414]
2011-08-01 16:13:37 +00:00
Denis Flaven
d09db3a920 Better error message if the configuration file exists but is not readable
SVN:trunk[1413]
2011-08-01 16:12:24 +00:00
Denis Flaven
998a8692cb Use a hierarchical key for the parent/child relationship with groups
SVN:trunk[1412]
2011-08-01 15:53:55 +00:00
Denis Flaven
b83ac447e8 Don't make the case log hidden, since it's not hidden in the portal !
SVN:trunk[1411]
2011-08-01 15:46:23 +00:00
Denis Flaven
6688f1811e Improved error handling when loading linkedset as attributes in one go
SVN:trunk[1410]
2011-08-01 15:08:22 +00:00
Denis Flaven
ec26750b58 Bug fix: when changing the currently selected organization, go back to the initial (Welcome) menu instead of trying to stay on the same menu... which caused troubles (e.g. "New Contact" => assertion failed)
SVN:trunk[1409]
2011-08-01 14:17:43 +00:00
Denis Flaven
0f0abc0501 Added a new web service to create UserRequest tickets (similarly to Incidents tickets). Based on code from Vincenzo Todisco.
SVN:trunk[1408]
2011-08-01 14:07:54 +00:00
Denis Flaven
9ad6f2c994 Draft of the 1.2 readme file...
SVN:trunk[1407]
2011-08-01 13:50:07 +00:00
Denis Flaven
abab47558b Adding Japanese translation kindly provided by Tadeshi Kaneda
SVN:trunk[1406]
2011-08-01 13:34:48 +00:00
Denis Flaven
f3147be8f6 Adding Japanese translation
SVN:trunk[1405]
2011-08-01 13:33:34 +00:00
Denis Flaven
af185b79c8 Bug fix: missing title for the menu 'All Opened Changes'
SVN:trunk[1404]
2011-08-01 12:50:26 +00:00
Denis Flaven
3fc686e403 Bug fix: do not modify the page's address (by changing the final anchor) when clicking on a page in a paginated display. Otherwise this interferes with the tabbed navigation.
SVN:trunk[1403]
2011-08-01 12:37:05 +00:00
Denis Flaven
f5ea073c2b Bug fix: when removing a module (like Incident Management) that contained Triggers to such objects, the remaining triggers must be deleted because they reference a non-existing class.
SVN:trunk[1402]
2011-08-01 11:51:11 +00:00
Denis Flaven
aa6d7578d1 Fixed issues when adding/removing modules during the setup:
- When adding modules: the data model was not refreshed in the cache before attempting to load "structure" (or "sample") data
- When removing a module: remaining (invalid) triggers were still used.

SVN:trunk[1401]
2011-08-01 10:30:22 +00:00
Denis Flaven
05338caa23 Modified misleading/outdated comment in the configuration file.
SVN:trunk[1400]
2011-08-01 10:24:36 +00:00
Denis Flaven
a6ac78d7c1 Fix: Make sure that the flash object respects the z-order otherwise the hierarchy/organization picker appears behind the Flash in Chrome and IE.
SVN:trunk[1399]
2011-07-31 19:38:27 +00:00
Denis Flaven
9bcf78bfd6 Enhancement: the number of items is displayed (statically) in the header of the each "details" tab.
SVN:trunk[1398]
2011-07-31 10:45:03 +00:00
Denis Flaven
9c97df5186 MapContextParam: call the parent just in case...
SVN:trunk[1397]
2011-07-31 09:40:00 +00:00
Denis Flaven
c2a8fd4e09 Enhancement: allow to search on Organization's parent and Location's parent.
SVN:trunk[1396]
2011-07-31 09:38:26 +00:00
Denis Flaven
c8b6ec08e7 Bug fix: properly process field names followed by a star (which simply indicates an important field in the CSV import)
SVN:trunk[1395]
2011-07-31 09:30:55 +00:00
Denis Flaven
4293230416 Don't display an error (assertion failed) if the user selects nothing (i.e -- select one --) in the "CSV template" tab.
SVN:trunk[1394]
2011-07-31 09:12:41 +00:00
Denis Flaven
bfddec242b - Fixed the combination of the "PointingTo" conditions, especially when combining several "BELOW" conditions for the same criteria (used for security and context purposes)
- Make sure that the "security" limitiation provided by GetSelectFilter is applied only once to a given filter to avoid the creation of unneeded complex queries

SVN:trunk[1393]
2011-07-31 09:01:45 +00:00
Denis Flaven
ee3652acad Added a useful function for debugging: DebugBacktrace which provides a "light" version of PHP's built-in debug_backtrace().
SVN:trunk[1392]
2011-07-31 08:57:04 +00:00
Denis Flaven
5baa213e6a Better initialization of objects from the "Context" values:
- map the parameters (for example for Provider Contract)
- set the default values when creating a "secondary" object via the (+) button

SVN:trunk[1391]
2011-07-31 08:54:59 +00:00
Denis Flaven
ca8ea8dd02 Fixed the parsing of OQL error messages
SVN:trunk[1390]
2011-07-30 17:25:49 +00:00
Denis Flaven
308d2626a9 Added test cases for the new BELOW operator
SVN:trunk[1389]
2011-07-30 17:23:39 +00:00
Denis Flaven
8cab8dd7b7 Filter audit results using the hierarchies
SVN:trunk[1388]
2011-07-29 10:29:28 +00:00
Denis Flaven
0a5e37c592 Oops: Make sure that the default add-on for SLA computation is enabled by default
SVN:trunk[1387]
2011-07-29 10:27:37 +00:00
Denis Flaven
72326435ce Make sure that the default add-on for SLA computation is enabled by default
SVN:trunk[1386]
2011-07-29 10:14:44 +00:00
Denis Flaven
ca9f17d6e1 Fixed a bug when using a hierarchy of locations, since locatins genreally depends on the object's organization
SVN:trunk[1385]
2011-07-29 10:09:14 +00:00
Denis Flaven
ff89c4d424 CAS authentication improvements:
- Check if the user is part of a group (memberOf)
- Fixed the use of the 'redirect_service' when logging-out

SVN:trunk[1384]
2011-07-28 17:39:49 +00:00
Denis Flaven
fa3b7ce545 Thumbs.db has nothing to do in SVN
SVN:trunk[1383]
2011-07-28 15:21:33 +00:00
Denis Flaven
c85feb7cea - Use the new HierarchicalKeys for Organization and Locations and use the hierarchy of organization for the profiles/user rights.
SVN:trunk[1382]
2011-07-28 15:14:49 +00:00
Denis Flaven
d50f812694 Fix: broken context usage when no context (neither org nor menu) at all
SVN:trunk[1381]
2011-07-28 15:12:16 +00:00
Denis Flaven
c7e27a836a Make sure that the root URL ends with a slash
SVN:trunk[1380]
2011-07-28 13:45:51 +00:00
Denis Flaven
c8a1380bef - Fixed the reporting about the previous version
SVN:trunk[1379]
2011-07-28 13:19:58 +00:00
Denis Flaven
7732c9bc6a Exclude build.bash from the build
SVN:trunk[1378]
2011-07-28 11:26:58 +00:00
Denis Flaven
82c57972c6 Fixing bug #404: context lost when doing certain actions. What was fixed:
- Run Query
- Display Data Model Schema
- Drill-down in charts (OQL & SQL)
- Paginated lists (actually a regression)

What remains:
- Global search...
- Drill-down in Flash "impacts / depends on"

SVN:trunk[1377]
2011-07-27 16:39:17 +00:00
Denis Flaven
0cc0c820a5 Fixed Trac #410: added translation for ticket status (and other enum fields) when displaying the History tab.
SVN:trunk[1376]
2011-07-27 13:06:45 +00:00
Denis Flaven
9150a569a7 Make sure that the dependent fields are initialized in the proper order when applying a stimulus on a n object.
SVN:trunk[1375]
2011-07-27 12:55:16 +00:00
Denis Flaven
3c76b90a48 Added an option to the paginated display: display all elements on one page.
SVN:trunk[1374]
2011-07-27 10:24:06 +00:00
Denis Flaven
5f2a5d9cfa - Fixed Trac #433: triggers creation was incorrect when iTop was installed with a 'prefix' for the DB tables.
- Fixed a bug due to the previous renaming of UpdateObject...

SVN:trunk[1372]
2011-07-26 16:45:02 +00:00
Denis Flaven
4e40702809 Fixed the construction of the menus' URLs:
- The context was not passed all the time (due to typos)
- When the context was empty an extra ampersand was added at the end of the URL, which caused a malformed URL for the 'tab selector' when modifying an object. (i.e the object always opened on the 'Properties' tab instead of the current tab).

SVN:trunk[1371]
2011-07-26 16:22:24 +00:00
Denis Flaven
a2b8813628 - Renamed the method 'UpdateObject' into 'UpdateObjectFromPostedForm' to avoid misunderstandings.
- Fixed (again !) Trac #427: unable to empty a linkset when editing an object.

SVN:trunk[1370]
2011-07-26 16:18:19 +00:00
Denis Flaven
751ab5a4b4 Implemented enhancement #130: keywords to narrow the scope of the global search
SVN:trunk[1369]
2011-07-26 14:18:33 +00:00
Denis Flaven
b6bcade4c0 Better (?) handling of deletion issues during the synchro...
SVN:trunk[1368]
2011-07-26 13:27:03 +00:00
Denis Flaven
d6be6ca92c Fix display issues with the portal's top menu bar.
SVN:trunk[1367]
2011-07-26 13:25:37 +00:00
Denis Flaven
9024e0b2db Use absolute URLs as much as possible to be independent from the page being executed...
SVN:trunk[1366]
2011-07-26 13:24:28 +00:00
Denis Flaven
ecca1aa070 Use absolute URLs as much as possible to be independent from the page being executed...
SVN:trunk[1365]
2011-07-26 13:22:45 +00:00
Denis Flaven
3fde682653 Make sure that the AJax calls use a full/absolute path to the page they call so that they can be embedded anywhere inside the application (i.e. in plug-ins supplied pages as well as 'regular' app pages).
SVN:trunk[1364]
2011-07-26 12:21:53 +00:00
Denis Flaven
aee8a98d84 Implemented two new options for CAS:
- logout_redirect_service
- memberOf

SVN:trunk[1363]
2011-07-26 09:43:21 +00:00
Denis Flaven
3ab670e8c2 Implemented two new options for CAS:
- logout_redirect_service
- memberOf

SVN:trunk[1362]
2011-07-26 09:42:46 +00:00
Denis Flaven
1fc7ce3b81 - Allow search forms to be open by default
- Don't make textarea resizable

SVN:trunk[1361]
2011-07-26 09:41:27 +00:00
Denis Flaven
e8c44951a1 Added two new options for CAS:
- logout_redirect_service
- memberOf

SVN:trunk[1360]
2011-07-26 09:39:45 +00:00
Denis Flaven
c11ca679d4 - Make sure that resulsts tables are always sortable.
SVN:trunk[1359]
2011-07-25 14:32:11 +00:00
Denis Flaven
46781c349f - Setup tested under IE8
- Added migration code, during the setup, for the hierarchical keys

SVN:trunk[1358]
2011-07-25 14:14:05 +00:00
Denis Flaven
189b802452 - Always display the number of elements (and the number of elements selected) when displaying a list.
- Nicer display of paginated lists

SVN:trunk[1357]
2011-07-25 12:25:50 +00:00
Denis Flaven
6b7687c80b - Nicer display of paginated lists
SVN:trunk[1356]
2011-07-25 12:24:23 +00:00
Denis Flaven
906abd5fe6 - Always display the number of elements (and the number of elements selected) when displaying a list.
- Nicer display of paginated lists

SVN:trunk[1355]
2011-07-25 12:23:40 +00:00
Denis Flaven
0c539aada9 Always display the number of elements (and the number of elements selected) when displaying a list.
SVN:trunk[1354]
2011-07-25 12:22:49 +00:00
Denis Flaven
a3611f7f63 Added support of hierarchy in the manipulation of external keys:
- widget

SVN:trunk[1353]
2011-07-22 12:21:21 +00:00
Denis Flaven
d62d6e14c8 Added support of hierarchy in the manipulation of external keys:
- widget

SVN:trunk[1352]
2011-07-22 12:15:49 +00:00
Denis Flaven
4cd4d91225 Added support of hierarchy in the manipulation of external keys:
- widget
- search criteria

SVN:trunk[1351]
2011-07-22 12:15:25 +00:00
Denis Flaven
5257287421 Added support of hierarchy in the manipulation of external keys:
- widget
- search criteria

SVN:trunk[1350]
2011-07-22 12:14:59 +00:00
Denis Flaven
7e60a9fce7 Implementation of a new type of ExternalKey attribute: HierarchicalKey. This attribute implements the "nested set" model and is used to define a hierarchy of an arbitrary depth of objects of the same class. With this new feature it is possible to retrieve in one OQL query (and in one sql query as well) all the children of a given organization.
I'm still keeping (commented out) some of the traces helpful for debugging the construction of the OQL queries.

SVN:trunk[1349]
2011-07-22 12:07:09 +00:00
Denis Flaven
b80cc36fb5 TO BE TESTED: New step in the wizard confirmation of the URL to be used for accessing the application.
SVN:trunk[1348]
2011-07-22 11:55:41 +00:00
Denis Flaven
0e75824247 For the setup: try to make the URL detection work even if the actual PHP file is not part of the URL (i.e when index.php is implicitely added when browsing a folder)
SVN:trunk[1347]
2011-07-22 11:53:02 +00:00
Denis Flaven
7470c8e72e Use the SLAComputation utility to allow plug-ins to implement more complex SLA computation schemes.
SVN:trunk[1346]
2011-07-18 15:08:44 +00:00
Denis Flaven
a04f0ee816 Helper module to plug complex SLA computation schemes. This basic modules performs the simplest computation corresponding to a 24x7 (no holidays) model.
SVN:trunk[1345]
2011-07-18 15:00:50 +00:00
Denis Flaven
d37244e6e2 - Search forms are now expandable/collapsible in the portal, like in the main UI
SVN:trunk[1344]
2011-07-12 11:07:56 +00:00
Denis Flaven
82c4f43c4f Manage properly default parameters for linksets
SVN:trunk[1343]
2011-07-12 11:07:04 +00:00
Romain Quetiez
1c2c244273 Veepee - fixed issue
SVN:trunk[1342]
2011-07-08 12:34:07 +00:00
Romain Quetiez
a991a84a8e New feature: online help on search inputs (date format and operators)
SVN:trunk[1341]
2011-07-08 11:40:57 +00:00
Romain Quetiez
532912984d Preset the object creation form with values (scalars or lists) by the mean of GET arguments. E.g. .../UI.php&operation=new&class=Team&default[org_id]=161&default[member_list][-1234][id]=&default[member_list][-1234][role]=manager
SVN:trunk[1340]
2011-07-07 15:45:35 +00:00
Romain Quetiez
265ef1e1ce Typo in french dictionary
SVN:trunk[1339]
2011-07-07 09:34:40 +00:00
Denis Flaven
70ad369ad6 - removed some debug traces !!
SVN:trunk[1337]
2011-07-06 17:12:18 +00:00
Denis Flaven
723685b4cb - Fixed Trac #429: web browser can crash when a text field contains several times the same URL !!!
SVN:trunk[1336]
2011-07-06 17:09:36 +00:00
Denis Flaven
061711afc8 Added support for OnFormCancel
SVN:trunk[1335]
2011-07-06 16:29:40 +00:00
Denis Flaven
8b6a8f02a4 - Bug fix: n:n wizard, context was lost when searching for objects of a derived class to be added.
- Bug fix: 'Apply stimulus multiple" was saying: "Please select at least one object"

SVN:trunk[1334]
2011-07-06 16:23:54 +00:00
Denis Flaven
893ec0f097 - Allow plug-ins to record specific changes in the object's history
SVN:trunk[1333]
2011-07-06 16:21:19 +00:00
Denis Flaven
7c9d4c76a9 Protect against a multiple stimulus on a set based on an abstract class.
SVN:trunk[1332]
2011-07-05 14:07:02 +00:00
Denis Flaven
52fb63c0ee Handle the OnBeforeUnload events to call OnFormCancel handlers when needed so that the plug-ins have a chance to perform some cleanup.
SVN:trunk[1328]
2011-07-05 08:46:31 +00:00
Denis Flaven
8a547bf104 Internal regressions fixes !!!
SVN:trunk[1327]
2011-07-04 16:36:07 +00:00
Denis Flaven
0019ab216a Fixed a typo: utils::ReadMultipleSelection
SVN:trunk[1326]
2011-07-04 15:44:07 +00:00
Denis Flaven
8d52cd3a58 Fixed a typo: utils::ReadMultipleSelection
SVN:trunk[1325]
2011-07-04 15:02:36 +00:00
Romain Quetiez
c7fbe56423 PPortal web page improvements: protected update feature (class/attcodes explicitely restricted)
SVN:trunk[1324]
2011-07-04 09:54:58 +00:00
Denis Flaven
5ff5ea71de Fixed Trac#424: error when updating the Data Synchro statistics
SVN:trunk[1323]
2011-07-04 08:29:16 +00:00
Denis Flaven
6297526da6 Fixed Trac #427: unable to remove all items from a linkset when editing an object.
SVN:trunk[1322]
2011-07-04 08:21:19 +00:00
Romain Quetiez
56ac89f6a6 Portal web page improvements: wizard buttons and welcome message
SVN:trunk[1321]
2011-07-04 07:26:16 +00:00
Romain Quetiez
aa46ab1a67 Portal users: limit their access depending on their organization!!!!
SVN:trunk[1320]
2011-07-01 15:09:29 +00:00
Romain Quetiez
645b02b2d1 #415 Could not limit user on some organization (symptom: wrong queries... org_id does not exist...)
SVN:trunk[1319]
2011-07-01 15:08:38 +00:00
Romain Quetiez
b2c5f183ec Portal web page improvements: integrating the attachments
SVN:trunk[1318]
2011-07-01 07:48:09 +00:00
Romain Quetiez
c6b60731d8 Internal cosmetic on duration attribute
SVN:trunk[1317]
2011-07-01 07:43:53 +00:00
Denis Flaven
fd2f40f070 - Protect against invalid attributes...
- Use the new absolute URL mechanism
- Added custom sort and Zebra tables for non paginated lists

SVN:trunk[1316]
2011-06-30 17:08:59 +00:00
Denis Flaven
9fef41404f - Added drill-down capability for OQL charts (pie and bars)
- fixed asbolute root path for drill down links

SVN:trunk[1315]
2011-06-30 16:04:18 +00:00
Denis Flaven
017aec3ae8 Added drill-down capability to the SQL blocks
SVN:trunk[1314]
2011-06-30 15:34:56 +00:00
Denis Flaven
2ae4aca687 Added support of 'drill-down' (i.e on_click) on bar charts
SVN:trunk[1313]
2011-06-30 14:38:13 +00:00
Denis Flaven
692fd91801 Finalizing the pagination: a few bug fixes and some cleanup...
SVN:trunk[1312]
2011-06-29 15:09:10 +00:00
Denis Flaven
12ec9c3ba1 Finalizing the pagination: a few bug fixes and some cleanup...
SVN:trunk[1311]
2011-06-29 15:08:36 +00:00
Romain Quetiez
ac5e40f4ed Factorized some code into PortalWebPage (still needs to be used in the std portal implementation)
SVN:trunk[1310]
2011-06-29 09:10:07 +00:00
Denis Flaven
f44afb8ee6 New implementation for displaying long lists: pagination
SVN:trunk[1309]
2011-06-29 08:57:39 +00:00
Denis Flaven
9e01277e49 New implementation for displaying long lists: pagination
SVN:trunk[1308]
2011-06-29 08:56:27 +00:00
Denis Flaven
73dc5eb922 New implementation for displaying long lists: pagination
SVN:trunk[1307]
2011-06-29 08:53:26 +00:00
Romain Quetiez
a5091d9af0 Fixed regression: magic_quote_runtime could be deprecated with recent versions of PHP (setup)
SVN:trunk[1306]
2011-06-28 15:13:10 +00:00
Romain Quetiez
a573d3cfa0 #423 Finalized
SVN:trunk[1305]
2011-06-28 14:28:01 +00:00
Romain Quetiez
c4b7497770 #423 Fixed issues with application root URL = f(mode CLI, modules, web server techno, etc.)
SVN:trunk[1304]
2011-06-28 10:30:03 +00:00
Romain Quetiez
1964658584 Fixed an issue in the internal error reporting (user rights not sufficient to update an object)
SVN:trunk[1303]
2011-06-28 10:23:41 +00:00
Romain Quetiez
077b7be2a4 Fixed regressions with DBObjectSet, impacting the new paginated lists and their menus
SVN:trunk[1302]
2011-06-28 09:32:13 +00:00
Denis Flaven
22c6df1a7a Fixed Trac #422 (detection of magic_quotes_runtime)
SVN:trunk[1301]
2011-06-24 14:46:21 +00:00
Romain Quetiez
54d5497f0a Fixed issues with pagination + core limitations in the use of Object Sets (wrong filter when adding objects to a set)
SVN:trunk[1300]
2011-06-24 09:53:45 +00:00
Romain Quetiez
9d2a8d0a67 Fixed regression with the display of lists based on class that is not a leaf (aka standalone) class
SVN:trunk[1299]
2011-06-23 12:32:15 +00:00
Denis Flaven
20024729ec New implementation for displaying long lists: pagination
SVN:trunk[1298]
2011-06-23 11:01:33 +00:00
Denis Flaven
43315f50ea New implementation for displaying long lists: pagination
SVN:trunk[1297]
2011-06-23 11:01:03 +00:00
Denis Flaven
46edbbbd2b New implementation for displaying long lists: pagination
SVN:trunk[1296]
2011-06-23 10:47:05 +00:00
Denis Flaven
dc3713f0ea New implementation for displaying long lists: pagination
SVN:trunk[1295]
2011-06-23 10:46:10 +00:00
Denis Flaven
01a4a04364 New implementation for displaying long lists: pagination
SVN:trunk[1294]
2011-06-23 10:43:41 +00:00
Romain Quetiez
7384191fb6 Fixed issues in ExtKey Widget
SVN:trunk[1293]
2011-06-22 15:26:36 +00:00
Romain Quetiez
b8c930016f Cosmetic
SVN:trunk[1292]
2011-06-22 08:16:59 +00:00
Romain Quetiez
5d37201391 Fixed issue in the computation of the application absolute path (Windows)
SVN:trunk[1291]
2011-06-22 08:15:30 +00:00
Romain Quetiez
2c8963784c #420 - Data synchro logs: increased the size of the attribute last_error
SVN:trunk[1290]
2011-06-21 15:01:56 +00:00
Romain Quetiez
c42fa13b92 Templates: new type of block = sqlblock, allows for displaying tables/charts based in SQL queries (much quicker for Group By operations)
SVN:trunk[1289]
2011-06-21 13:12:02 +00:00
Romain Quetiez
c02d62044c Dashboard templates: fixed issue with asynchronous mode (still some cosmetic issues) with itopblock and the table format
SVN:trunk[1288]
2011-06-21 13:10:11 +00:00
Denis Flaven
7bcb4ed3b2 New implementation of GetAbsoluteUrlRootPath that no longer requires any parameter.
SVN:trunk[1287]
2011-06-20 14:18:24 +00:00
Denis Flaven
834e9c48e1 Added a display value for the null (i.e empty) value of a combo/autocomplete.
SVN:trunk[1286]
2011-06-20 14:17:24 +00:00
Denis Flaven
ddfc84db6a - New implementation of the UIExtKeyWidget that is no longer limited to editing an attribute of an object
- When needed the drop-down list of organizations is replaced by an autocomplete

SVN:trunk[1285]
2011-06-15 17:06:46 +00:00
Denis Flaven
b4a7d0b86c - New implementation of the UIExtKeyWidget that is no longer limited to editing an attribute of an object
- When needed the drop-down list of organizations is replaced by an autocomplete

SVN:trunk[1284]
2011-06-15 17:06:26 +00:00
Denis Flaven
02cafd0e0b - New implementation of the UIExtKeyWidget that is no longer limited to editing an attribute of an object
- When needed the drop-down list of organizations is replaced by an autocomplete

SVN:trunk[1283]
2011-06-15 17:05:58 +00:00
Denis Flaven
f5891a531e New icon to trigger the filtering on organizations
SVN:trunk[1282]
2011-06-15 17:01:44 +00:00
Romain Quetiez
4681b03646 #122 Optimized the load of data set (do not load unused columns, was provoking the swapping of MySQL tmp memory tables)
SVN:trunk[1281]
2011-06-15 11:59:25 +00:00
Denis Flaven
733953ac99 CAS integration: added support of JA-SIG Central Authentication Service (CAS) with log-off support, using phpCAS API.
SVN:trunk[1280]
2011-06-10 14:51:17 +00:00
Romain Quetiez
4837984ac3 Optimization: autocomplete = do not load every object when determining the list of matches
SVN:trunk[1279]
2011-06-10 09:02:42 +00:00
Romain Quetiez
4efd93defe Optimization: cache the Count of items in an object set
SVN:trunk[1278]
2011-06-08 14:42:51 +00:00
Romain Quetiez
35f7d143b8 Optimization: displaying 1000 object would take real long if many organizations are loaded into iTop (querying all the orgs for each object)
SVN:trunk[1277]
2011-06-08 14:31:22 +00:00
Denis Flaven
d48fd1a12e First prototype (not yet tested) of CAS integration.
SVN:trunk[1276]
2011-06-08 13:34:43 +00:00
Romain Quetiez
a0900cd732 Optimization: do not load the full set of items when it comes to displaying an autocomplete!
SVN:trunk[1275]
2011-06-08 13:30:56 +00:00
Romain Quetiez
64b4922499 Fixed issue in the data model consistency check
SVN:trunk[1274]
2011-05-24 10:26:16 +00:00
Romain Quetiez
4574126428 #408 Case log not working with PHP < 5.3 - the fix preserves the compatibility with installed version (but the dates are lost)
SVN:trunk[1273]
2011-05-23 14:56:09 +00:00
Romain Quetiez
9704dd8e35 Improved import.php and synchro_import.php: added 'date_format' (example: %d/%m/%Y %H:%i:%s)
SVN:trunk[1272]
2011-05-23 13:42:33 +00:00
Romain Quetiez
f8d794bc93 Reduces the annoyance of bug #408 (dates not visible in the case log if PHP < 5.3)
SVN:trunk[1271]
2011-05-20 13:42:57 +00:00
Romain Quetiez
5c7c7b98ee Dehardcoded against 'ticket_log', allowing for several case log attributes
SVN:trunk[1270]
2011-05-19 14:38:46 +00:00
Romain Quetiez
3826c1f886 #405 Could not install without the module 'User Request Management'
SVN:trunk[1269]
2011-05-13 16:00:21 +00:00
Romain Quetiez
58844b04ef #403 Partial installation not working (error on ticket form)
SVN:trunk[1268]
2011-05-13 15:09:55 +00:00
Romain Quetiez
178f7f3426 Typo in dictionary
SVN:trunk[1267]
2011-05-06 09:05:22 +00:00
213 changed files with 27650 additions and 10860 deletions

View File

@@ -274,7 +274,7 @@ class UserRightsMatrix extends UserRightsAddOnAPI
return true;
}
public function GetSelectFilter($oUser, $sClass)
public function GetSelectFilter($oUser, $sClass, $aSettings = array())
{
$oNullFilter = new DBObjectSearch($sClass);
return $oNullFilter;

View File

@@ -47,7 +47,7 @@ class UserRightsNull extends UserRightsAddOnAPI
return true;
}
public function GetSelectFilter($oUser, $sClass)
public function GetSelectFilter($oUser, $sClass, $aSettings = array())
{
$oNullFilter = new DBObjectSearch($sClass);
return $oNullFilter;

View File

@@ -265,7 +265,7 @@ class URP_Profiles extends UserRightsBaseClassGUI
function DoShowGrantSumary($oPage)
{
if ($this->GetName() == "Administrator")
if ($this->GetRawName() == "Administrator")
{
// Looks dirty, but ok that's THE ONE
$oPage->p(Dict::S('UI:UserManagement:AdminProfile+'));
@@ -538,41 +538,39 @@ class UserRightsProfile extends UserRightsAddOnAPI
$oChange->Set("userinfo", "Initialization");
$iChangeId = $oChange->DBInsert();
// Support drastic data model changes: no organization class !
if (MetaModel::IsValidClass('Organization'))
$iContactId = 0;
// Support drastic data model changes: no organization class (or not writable)!
if (MetaModel::IsValidClass('Organization') && !MetaModel::IsAbstract('Organization'))
{
$oOrg = new Organization();
$oOrg->Set('name', 'My Company/Department');
$oOrg->Set('code', 'SOMECODE');
$iOrgId = $oOrg->DBInsertTrackedNoReload($oChange, true /* skip security */);
}
else
{
$iOrgId = 0;
// Support drastic data model changes: no Person class (or not writable)!
if (MetaModel::IsValidClass('Person') && !MetaModel::IsAbstract('Person'))
{
$oContact = new Person();
$oContact->Set('name', 'My last name');
$oContact->Set('first_name', 'My first name');
if (MetaModel::IsValidAttCode('Person', 'org_id'))
{
$oContact->Set('org_id', $iOrgId);
}
if (MetaModel::IsValidAttCode('Person', 'phone'))
{
$oContact->Set('phone', '+00 000 000 000');
}
$oContact->Set('email', 'my.email@foo.org');
$iContactId = $oContact->DBInsertTrackedNoReload($oChange, true /* skip security */);
}
}
// Support drastic data model changes: no Person class !
if (MetaModel::IsValidClass('Person'))
{
$oContact = new Person();
$oContact->Set('name', 'My last name');
$oContact->Set('first_name', 'My first name');
if (MetaModel::IsValidAttCode('Person', 'org_id'))
{
$oContact->Set('org_id', $iOrgId);
}
$oContact->Set('email', 'my.email@foo.org');
$iContactId = $oContact->DBInsertTrackedNoReload($oChange, true /* skip security */);
}
else
{
$iContactId = 0;
}
$oUser = new UserLocal();
$oUser->Set('login', $sAdminUser);
$oUser->Set('password', $sAdminPwd);
if (MetaModel::IsValidAttCode('UserLocal', 'contactid'))
if (MetaModel::IsValidAttCode('UserLocal', 'contactid') && ($iContactId != 0))
{
$oUser->Set('contactid', $iContactId);
}
@@ -599,12 +597,12 @@ class UserRightsProfile extends UserRightsAddOnAPI
}
protected $m_aAdmins; // id of users being linked to the well-known admin profile
protected $m_aPortalUsers; // id of users being linked to the well-known admin profile
protected $m_aAdmins = array(); // id -> bool, true if the user has the well-known admin profile
protected $m_aPortalUsers = array(); // id -> bool, true if the user has the well-known portal user profile
protected $m_aProfiles; // id -> object
protected $m_aUserProfiles; // userid,profileid -> object
protected $m_aUserOrgs; // userid,orgid -> object
protected $m_aUserProfiles = array(); // userid,profileid -> object
protected $m_aUserOrgs = array(); // userid -> array of orgid
// Those arrays could be completed on demand (inheriting parent permissions)
protected $m_aClassActionGrants = null; // profile, class, action -> actiongrantid (or false if NO, or null/missing if undefined)
@@ -613,15 +611,80 @@ class UserRightsProfile extends UserRightsAddOnAPI
// Built on demand, could be optimized if necessary (doing a query for each attribute that needs to be read)
protected $m_aObjectActionGrants = array();
/**
* Read and cache organizations allowed to the given user
*
* @param oUser
* @param sClass -not used here but can be used in overloads
*/
protected function GetUserOrgs($oUser, $sClass)
{
$iUser = $oUser->GetKey();
if (!array_key_exists($iUser, $this->m_aUserOrgs))
{
$this->m_aUserOrgs[$iUser] = array();
$sHierarchicalKeyCode = MetaModel::IsHierarchicalClass('Organization');
if ($sHierarchicalKeyCode !== false)
{
$sUserOrgQuery = 'SELECT UserOrg, Org FROM Organization AS Org JOIN Organization AS Root ON Org.'.$sHierarchicalKeyCode.' BELOW Root.id JOIN URP_UserOrg AS UserOrg ON UserOrg.allowed_org_id = Root.id WHERE UserOrg.userid = :userid';
$oUserOrgSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData($sUserOrgQuery), array(), array('userid' => $iUser));
while ($aRow = $oUserOrgSet->FetchAssoc())
{
$oUserOrg = $aRow['UserOrg'];
$oOrg = $aRow['Org'];
$this->m_aUserOrgs[$iUser][] = $oOrg->GetKey();
}
}
else
{
$oSearch = new DBObjectSearch('URP_UserOrg');
$oSearch->AllowAllData();
$oCondition = new BinaryExpression(new FieldExpression('userid'), '=', new VariableExpression('userid'));
$oSearch->AddConditionExpression($oCondition);
$oUserOrgSet = new DBObjectSet($oSearch, array(), array('userid' => $iUser));
while ($oUserOrg = $oUserOrgSet->Fetch())
{
$this->m_aUserOrgs[$iUser][] = $oUserOrg->Get('allowed_org_id');
}
}
}
return $this->m_aUserOrgs[$iUser];
}
/**
* Read and cache profiles of the given user
*/
protected function GetUserProfiles($iUser)
{
if (!array_key_exists($iUser, $this->m_aUserProfiles))
{
$oSearch = new DBObjectSearch('URP_UserProfile');
$oSearch->AllowAllData();
$oCondition = new BinaryExpression(new FieldExpression('userid'), '=', new VariableExpression('userid'));
$oSearch->AddConditionExpression($oCondition);
$this->m_aUserProfiles[$iUser] = array();
$oUserProfileSet = new DBObjectSet($oSearch, array(), array('userid' => $iUser));
while ($oUserProfile = $oUserProfileSet->Fetch())
{
$this->m_aUserProfiles[$iUser][$oUserProfile->Get('profileid')] = $oUserProfile;
}
}
return $this->m_aUserProfiles[$iUser];
}
public function ResetCache()
{
// Loaded by Load cache
$this->m_aProfiles = null;
$this->m_aUserProfiles = null;
$this->m_aUserOrgs = null;
$this->m_aUserProfiles = array();
$this->m_aUserOrgs = array();
$this->m_aAdmins = null;
$this->m_aPortalUsers = null;
$this->m_aAdmins = array();
$this->m_aPortalUsers = array();
// Loaded on demand (time consuming as compared to the others)
$this->m_aClassActionGrants = null;
@@ -656,6 +719,11 @@ class UserRightsProfile extends UserRightsAddOnAPI
$oKPI = new ExecutionKPI();
if (self::HasSharing())
{
SharedObject::InitSharedClassProperties();
}
$oProfileSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_Profiles"));
$this->m_aProfiles = array();
while ($oProfile = $oProfileSet->Fetch())
@@ -663,30 +731,6 @@ class UserRightsProfile extends UserRightsAddOnAPI
$this->m_aProfiles[$oProfile->GetKey()] = $oProfile;
}
$oUserProfileSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_UserProfile"));
$this->m_aUserProfiles = array();
$this->m_aAdmins = array();
$this->m_aPortalUsers = array();
while ($oUserProfile = $oUserProfileSet->Fetch())
{
$this->m_aUserProfiles[$oUserProfile->Get('userid')][$oUserProfile->Get('profileid')] = $oUserProfile;
if ($oUserProfile->Get('profile') == ADMIN_PROFILE_NAME)
{
$this->m_aAdmins[] = $oUserProfile->Get('userid');
}
elseif ($oUserProfile->Get('profile') == PORTAL_PROFILE_NAME)
{
$this->m_aPortalUsers[] = $oUserProfile->Get('userid');
}
}
$oUserOrgSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_UserOrg"));
$this->m_aUserOrgs = array();
while ($oUserOrg = $oUserOrgSet->Fetch())
{
$this->m_aUserOrgs[$oUserOrg->Get('userid')][$oUserOrg->Get('allowed_org_id')] = $oUserOrg;
}
$this->m_aClassStimulusGrants = array();
$oStimGrantSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_StimulusGrant"));
$this->m_aStimGrants = array();
@@ -713,33 +757,45 @@ exit;
public function IsAdministrator($oUser)
{
$this->LoadCache();
if (in_array($oUser->GetKey(), $this->m_aAdmins))
//$this->LoadCache();
$iUser = $oUser->GetKey();
if (!array_key_exists($iUser, $this->m_aAdmins))
{
return true;
}
else
$bIsAdmin = false;
foreach($this->GetUserProfiles($iUser) as $oUserProfile)
{
return false;
if ($oUserProfile->Get('profile') == ADMIN_PROFILE_NAME)
{
$bIsAdmin = true;
break;
}
}
$this->m_aAdmins[$iUser] = $bIsAdmin;
}
return $this->m_aAdmins[$iUser];
}
public function IsPortalUser($oUser)
{
$this->LoadCache();
if (in_array($oUser->GetKey(), $this->m_aPortalUsers))
//$this->LoadCache();
$iUser = $oUser->GetKey();
if (!array_key_exists($iUser, $this->m_aPortalUsers))
{
return true;
}
else
$bIsPortalUser = false;
foreach($this->GetUserProfiles($iUser) as $oUserProfile)
{
return false;
if ($oUserProfile->Get('profile') == PORTAL_PROFILE_NAME)
{
$bIsPortalUser = true;
break;
}
}
$this->m_aPortalUsers[$iUser] = $bIsPortalUser;
}
return $this->m_aPortalUsers[$iUser];
}
public function GetSelectFilter($oUser, $sClass)
public function GetSelectFilter($oUser, $sClass, $aSettings = array())
{
$this->LoadCache();
@@ -751,45 +807,84 @@ exit;
// Determine how to position the objects of this class
//
if ($sClass == 'Organization')
$sAttCode = self::GetOwnerOrganizationAttCode($sClass);
if (is_null($sAttCode))
{
$sAttCode = 'id';
}
elseif (is_callable("$sClass::MapContextParam"))
{
$sAttCode = eval("return $sClass::MapContextParam('org_id');"); // Returns null when there is no mapping for this parameter
if ($sAttCode == null)
{
return true;
}
}
elseif(MetaModel::IsValidAttCode($sClass, 'org_id'))
{
$sAttCode = 'org_id';
}
else
{
// The objects of this class are not positioned in this dimension
// All of them are visible
// No filtering for this object
return true;
}
$oExpression = new FieldExpression($sAttCode, $sClass);
// Position the user
//
@$aUserOrgs = $this->m_aUserOrgs[$oUser->GetKey()];
if (!isset($aUserOrgs) || count($aUserOrgs) == 0)
$aUserOrgs = $this->GetUserOrgs($oUser, $sClass);
if (count($aUserOrgs) == 0)
{
// No position means 'Everywhere'
// No org means 'any org'
return true;
}
$aIds = array_keys($aUserOrgs);
$oListExpr = ListExpression::FromScalars($aIds);
$oCondition = new BinaryExpression($oExpression, 'IN', $oListExpr);
$oExpression = new FieldExpression($sAttCode, $sClass);
$oFilter = new DBObjectSearch($sClass);
$oListExpr = ListExpression::FromScalars($aUserOrgs);
$oCondition = new BinaryExpression($oExpression, 'IN', $oListExpr);
$oFilter->AddConditionExpression($oCondition);
if (self::HasSharing())
{
if (($sAttCode == 'id') && isset($aSettings['bSearchMode']) && $aSettings['bSearchMode'])
{
// Querying organizations (or derived)
// and the expected list of organizations will be used as a search criteria
// Therefore the query can also return organization having objects shared with the allowed organizations
//
// 1) build the list of organizations sharing something with the allowed organizations
// Organization <== sharing_org_id == SharedObject having org_id IN {user orgs}
$oShareSearch = new DBObjectSearch('SharedObject');
$oOrgField = new FieldExpression('org_id', 'SharedObject');
$oShareSearch->AddConditionExpression(new BinaryExpression($oOrgField, 'IN', $oListExpr));
$oSearchSharers = new DBObjectSearch('Organization');
$oSearchSharers->AllowAllData();
$oSearchSharers->AddCondition_ReferencedBy($oShareSearch, 'sharing_org_id');
$aSharers = array();
foreach($oSearchSharers->ToDataArray(array('id')) as $aRow)
{
$aSharers[] = $aRow['id'];
}
// 2) Enlarge the overall results: ... OR id IN(id1, id2, id3)
if (count($aSharers) > 0)
{
$oSharersList = ListExpression::FromScalars($aSharers);
$oFilter->MergeConditionExpression(new BinaryExpression($oExpression, 'IN', $oSharersList));
}
}
$aShareProperties = SharedObject::GetSharedClassProperties($sClass);
if ($aShareProperties)
{
$sShareClass = $aShareProperties['share_class'];
$sShareAttCode = $aShareProperties['attcode'];
$oSearchShares = new DBObjectSearch($sShareClass);
$oSearchShares->AllowAllData();
$sHierarchicalKeyCode = MetaModel::IsHierarchicalClass('Organization');
$oOrgField = new FieldExpression('org_id', $sShareClass);
$oSearchShares->AddConditionExpression(new BinaryExpression($oOrgField, 'IN', $oListExpr));
$aShared = array();
foreach($oSearchShares->ToDataArray(array($sShareAttCode)) as $aRow)
{
$aShared[] = $aRow[$sShareAttCode];
}
if (count($aShared) > 0)
{
$oObjId = new FieldExpression('id', $sClass);
$oSharedIdList = ListExpression::FromScalars($aShared);
$oFilter->MergeConditionExpression(new BinaryExpression($oObjId, 'IN', $oSharedIdList));
}
}
} // if HasSharing
return $oFilter;
}
@@ -835,10 +930,8 @@ exit;
$iPermission = UR_ALLOWED_NO;
$aAttributes = array();
if (isset($this->m_aUserProfiles[$iUser]))
foreach($this->GetUserProfiles($iUser) as $iProfile => $oProfile)
{
foreach($this->m_aUserProfiles[$iUser] as $iProfile => $oProfile)
{
$iGrant = $this->GetProfileActionGrant($iProfile, $sClass, $sAction);
if (is_null($iGrant) || !$iGrant)
{
@@ -847,7 +940,7 @@ exit;
else
{
$iPermission = UR_ALLOWED_YES;
// update the list of attributes with those allowed for this profile
//
$oSearch = DBObjectSearch::FromOQL_AllData("SELECT URP_AttributeGrant WHERE actiongrantid = :actiongrantid");
@@ -864,7 +957,6 @@ exit;
}
}
}
}
$aRes = array(
'permission' => $iPermission,
@@ -878,10 +970,76 @@ exit;
{
$this->LoadCache();
// Note: The object set is ignored because it was interesting to optimize for huge data sets
// and acceptable to consider only the root class of the object set
$aObjectPermissions = $this->GetUserActionGrant($oUser, $sClass, $iActionCode);
return $aObjectPermissions['permission'];
$iPermission = $aObjectPermissions['permission'];
// Note: In most cases the object set is ignored because it was interesting to optimize for huge data sets
// and acceptable to consider only the root class of the object set
if ($iPermission != UR_ALLOWED_YES)
{
// It is already NO for everyone... that's the final word!
}
elseif ($iActionCode == UR_ACTION_READ)
{
// We are protected by GetSelectFilter: the object set contains objects allowed or shared for reading
}
elseif ($iActionCode == UR_ACTION_BULK_READ)
{
// We are protected by GetSelectFilter: the object set contains objects allowed or shared for reading
}
elseif ($oInstanceSet)
{
// We are protected by GetSelectFilter: the object set contains objects allowed or shared for reading
// We have to answer NO for objects shared for reading purposes
if (self::HasSharing())
{
$aClassProps = SharedObject::GetSharedClassProperties($sClass);
if ($aClassProps)
{
// This class is shared, GetSelectFilter may allow some objects for read only
// But currently we are checking wether the objects might be written...
// Let's exclude the objects based on the relevant criteria
$sOrgAttCode = self::GetOwnerOrganizationAttCode($sClass);
if (!is_null($sOrgAttCode))
{
$aUserOrgs = $this->GetUserOrgs($oUser, $sClass);
if (!is_null($aUserOrgs) && count($aUserOrgs) > 0)
{
$iCountNO = 0;
$iCountYES = 0;
$oInstanceSet->Rewind();
while($oObject = $oInstanceSet->Fetch())
{
$iOrg = $oObject->Get($sOrgAttCode);
if (in_array($iOrg, $aUserOrgs))
{
$iCountYES++;
}
else
{
$iCountNO++;
}
}
if ($iCountNO == 0)
{
$iPermission = UR_ALLOWED_YES;
}
elseif ($iCountYES == 0)
{
$iPermission = UR_ALLOWED_NO;
}
else
{
$iPermission = UR_ALLOWED_DEPENDS;
}
}
}
}
}
}
return $iPermission;
}
public function IsActionAllowedOnAttribute($oUser, $sClass, $sAttCode, $iActionCode, $oInstanceSet = null)
@@ -926,10 +1084,8 @@ exit;
// Note: The object set is ignored because it was interesting to optimize for huge data sets
// and acceptable to consider only the root class of the object set
$iPermission = UR_ALLOWED_NO;
if (isset($this->m_aUserProfiles[$iUser]))
foreach($this->GetUserProfiles($iUser) as $iProfile => $oProfile)
{
foreach($this->m_aUserProfiles[$iUser] as $iProfile => $oProfile)
{
$oGrantRecord = $this->GetClassStimulusGrant($iProfile, $sClass, $sStimulusCode);
if (!is_null($oGrantRecord))
{
@@ -937,7 +1093,6 @@ exit;
$iPermission = UR_ALLOWED_YES;
}
}
}
return $iPermission;
}
@@ -945,6 +1100,49 @@ exit;
{
$this->ResetCache();
}
/**
* Find out which attribute is corresponding the the dimension 'owner org'
* returns null if no such attribute has been found (no filtering should occur)
*/
public static function GetOwnerOrganizationAttCode($sClass)
{
$sAttCode = null;
$aCallSpec = array($sClass, 'MapContextParam');
if (($sClass == 'Organization') || is_subclass_of($sClass, 'Organization'))
{
$sAttCode = 'id';
}
elseif (is_callable($aCallSpec))
{
$sAttCode = call_user_func($aCallSpec, 'org_id'); // Returns null when there is no mapping for this parameter
if (!MetaModel::IsValidAttCode($sClass, $sAttCode))
{
// Skip silently. The data model checker will tell you something about this...
$sAttCode = null;
}
}
elseif(MetaModel::IsValidAttCode($sClass, 'org_id'))
{
$sAttCode = 'org_id';
}
return $sAttCode;
}
/**
* Determine wether the objects can be shared by the mean of a class SharedObject
**/
protected static function HasSharing()
{
static $bHasSharing;
if (!isset($bHasSharing))
{
$bHasSharing = class_exists('SharedObject');
}
return $bHasSharing;
}
}

View File

@@ -97,7 +97,7 @@ class URP_Profiles extends UserRightsBaseClass
function DoShowGrantSumary($oPage)
{
if ($this->GetName() == "Administrator")
if ($this->GetRawName() == "Administrator")
{
// Looks dirty, but ok that's THE ONE
$oPage->p(Dict::S('UI:UserManagement:AdminProfile+'));
@@ -734,7 +734,7 @@ exit;
return true;
}
public function GetSelectFilter($oUser, $sClass)
public function GetSelectFilter($oUser, $sClass, $aSettings = array())
{
$aConditions = array();
foreach ($this->m_aDimensions as $iDimension => $oDimension)

View File

@@ -45,11 +45,13 @@ class ajax_page extends WebPage
{
parent::__construct($s_title);
$this->m_sReadyScript = "";
$this->add_header("Content-type: text/html; charset=utf-8");
//$this->add_header("Content-type: text/html; charset=utf-8");
$this->add_header("Cache-control: no-cache");
$this->m_sCurrentTabContainer = '';
$this->m_sCurrentTab = '';
$this->m_aTabs = array();
$this->sContentType = 'text/html';
$this->sContentDisposition = 'inline';
}
public function AddTabContainer($sTabContainer, $sPrefix = '')
@@ -86,17 +88,29 @@ class ajax_page extends WebPage
return $sPreviousTab;
}
public function GetCurrentTab()
{
return $this->m_sCurrentTab;
}
/**
* Echoes the content of the whole page
* @return void
*/
public function output()
{
if (!empty($this->sContentType))
{
$this->add_header('Content-type: '.$this->sContentType);
}
if (!empty($this->sContentDisposition))
{
$this->add_header('Content-Disposition: '.$this->sContentDisposition.'; filename="'.$this->sContentFileName.'"');
}
foreach($this->a_headers as $s_header)
{
header($s_header);
}
if (count($this->m_aTabs) > 0)
{
$this->add_ready_script(
@@ -165,7 +179,15 @@ EOF
$s_captured_output = ob_get_contents();
ob_end_clean();
echo $this->s_content;
if (($this->sContentType == 'text/html') && ($this->sContentDisposition == 'inline'))
{
// inline content != attachment && html => filter all scripts for malicious XSS scripts
echo self::FilterXSS($this->s_content);
}
else
{
echo $this->s_content;
}
//echo $this->s_deferred_content;
if (count($this->a_scripts) > 0)
{
@@ -176,7 +198,7 @@ EOF
if (!empty($this->s_deferred_content))
{
echo "<script type=\"text/javascript\">\n";
echo "\$('body').append('".$this->s_deferred_content."');\n";
echo "\$('body').append('".addslashes(str_replace("\n", '', $this->s_deferred_content))."');\n";
echo "\n</script>\n";
}
if (!empty($this->m_sReadyScript))
@@ -187,7 +209,7 @@ EOF
}
if (trim($s_captured_output) != "")
{
echo $s_captured_output;
echo self::FilterXSS($s_captured_output);
}
}
@@ -212,6 +234,64 @@ EOF
parent::add($sHtml);
}
}
/**
* Records the current state of the 'html' part of the page output
* @return mixed The current state of the 'html' output
*/
public function start_capture()
{
if (!empty($this->m_sCurrentTabContainer) && !empty($this->m_sCurrentTab))
{
$iOffset = isset($this->m_aTabs[$this->m_sCurrentTabContainer]['content'][$this->m_sCurrentTab]) ? strlen($this->m_aTabs[$this->m_sCurrentTabContainer]['content'][$this->m_sCurrentTab]): 0;
return array('tc' => $this->m_sCurrentTabContainer, 'tab' => $this->m_sCurrentTab, 'offset' => $iOffset);
}
else
{
return parent::start_capture();
}
}
/**
* Returns the part of the html output that occurred since the call to start_capture
* and removes this part from the current html output
* @param $offset mixed The value returned by start_capture
* @return string The part of the html output that was added since the call to start_capture
*/
public function end_capture($offset)
{
if (is_array($offset))
{
if (isset($this->m_aTabs[$offset['tc']]['content'][$offset['tab']]))
{
$sCaptured = substr($this->m_aTabs[$offset['tc']]['content'][$offset['tab']], $offset['offset']);
$this->m_aTabs[$offset['tc']]['content'][$offset['tab']] = substr($this->m_aTabs[$offset['tc']]['content'][$offset['tab']], 0, $offset['offset']);
}
else
{
$sCaptured = '';
}
}
else
{
$sCaptured = parent::end_capture($offset);
}
return $sCaptured;
}
/**
* Add any text or HTML fragment (identified by an ID) at the end of the body of the page
* This is useful to add hidden content, DIVs or FORMs that should not
* be embedded into each other.
*/
public function add_at_the_end($s_html, $sId = '')
{
if ($sId != '')
{
$this->add_script("$('#{$sId}').remove();"); // Remove any previous instance of the same Id
}
$this->s_deferred_content .= $s_html;
}
/**
* Adds a script to be executed when the DOM is ready (typical JQuery use)
@@ -220,9 +300,6 @@ EOF
*/
public function add_ready_script($sScript)
{
// Does nothing in ajax rendered content.. for now...
// Maybe we should add this as a simple <script> tag at the end of the output
// considering that at this time everything in the page is "ready"...
$this->m_sReadyScript .= $sScript;
}
@@ -236,6 +313,10 @@ EOF
return 0;
}
public static function FilterXSS($sHTML)
{
return str_ireplace(array('<script', '</script>'), array('<!-- <removed-script', '</removed-script> -->'), $sHTML);
}
}
?>

View File

@@ -26,8 +26,10 @@
require_once(APPROOT.'/application/applicationcontext.class.inc.php');
require_once(APPROOT.'/application/cmdbabstract.class.inc.php');
require_once(APPROOT.'/application/displayblock.class.inc.php');
require_once(APPROOT.'/application/sqlblock.class.inc.php');
require_once(APPROOT.'/application/audit.category.class.inc.php');
require_once(APPROOT.'/application/audit.rule.class.inc.php');
require_once(APPROOT.'/application/query.class.inc.php');
//require_once(APPROOT.'/application/menunode.class.inc.php');
require_once(APPROOT.'/application/utils.inc.php');

View File

@@ -24,6 +24,43 @@
*/
require_once(APPROOT."/application/utils.inc.php");
/**
* Interface for directing end-users to the relevant application
*/
interface iDBObjectURLMaker
{
public static function MakeObjectURL($sClass, $iId);
}
/**
* Direct end-users to the standard iTop application: UI.php
*/
class iTopStandardURLMaker implements iDBObjectURLMaker
{
public static function MakeObjectURL($sClass, $iId)
{
$sPage = DBObject::ComputeStandardUIPage($sClass);
$sAbsoluteUrl = utils::GetAbsoluteUrlAppRoot();
$sUrl = "{$sAbsoluteUrl}pages/$sPage?operation=details&class=$sClass&id=$iId";
return $sUrl;
}
}
/**
* Direct end-users to the standard Portal application
*/
class PortalURLMaker implements iDBObjectURLMaker
{
public static function MakeObjectURL($sClass, $iId)
{
$sAbsoluteUrl = utils::GetAbsoluteUrlAppRoot();
$sUrl = "{$sAbsoluteUrl}portal/index.php?operation=details&class=$sClass&id=$iId";
return $sUrl;
}
}
/**
* Helper class to store and manipulate the parameters that make the application's context
*
@@ -40,12 +77,16 @@ class ApplicationContext
protected $aValues;
protected static $aDefaultValues; // Cache shared among all instances
public function __construct()
public function __construct($bReadContext = true)
{
$this->aNames = array(
'org_id', 'menu'
);
$this->ReadContext();
if ($bReadContext)
{
$this->ReadContext();
}
}
/**
@@ -54,7 +95,7 @@ class ApplicationContext
*/
protected function ReadContext()
{
if (empty(self::$aDefaultValues))
if (!isset(self::$aDefaultValues))
{
self::$aDefaultValues = array();
$aContext = utils::ReadParam('c', array());
@@ -74,6 +115,7 @@ class ApplicationContext
if (MetaModel::IsValidClass('Organization'))
{
$oSearchFilter = new DBObjectSearch('Organization');
$oSearchFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', true);
$oSet = new CMDBObjectSet($oSearchFilter);
$iCount = $oSet->Count();
if ($iCount == 1)
@@ -126,7 +168,7 @@ class ApplicationContext
$sContext = "";
foreach($this->aValues as $sName => $sValue)
{
$sContext .= "<input type=\"hidden\" name=\"c[$sName]\" value=\"$sValue\" />\n";
$sContext .= "<input type=\"hidden\" name=\"c[$sName]\" value=\"".htmlentities($sValue, ENT_QUOTES, 'UTF-8')."\" />\n";
}
return $sContext;
}
@@ -166,5 +208,154 @@ class ApplicationContext
unset($this->aValues[$sParamName]);
}
}
/**
* Initializes the given object with the default values provided by the context
*/
public function InitObjectFromContext(DBObject &$oObj)
{
$sClass = get_class($oObj);
foreach($this->GetNames() as $key)
{
$aCallSpec = array($sClass, 'MapContextParam');
if (is_callable($aCallSpec))
{
$sAttCode = call_user_func($aCallSpec, $key); // Returns null when there is no mapping for this parameter
}
if (MetaModel::IsValidAttCode($sClass, $sAttCode))
{
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
if ($oAttDef->IsWritable())
{
$value = $this->GetCurrentValue($key, null);
if (!is_null($value))
{
$oObj->Set($sAttCode, $value);
}
}
}
}
}
static $m_sUrlMakerClass = null;
/**
* Set the current application url provider
* @param sClass string Class implementing iDBObjectURLMaker
* @return void
*/
public static function SetUrlMakerClass($sClass = 'iTopStandardURLMaker')
{
$sPrevious = self::GetUrlMakerClass();
self::$m_sUrlMakerClass = $sClass;
$_SESSION['UrlMakerClass'] = $sClass;
return $sPrevious;
}
/**
* Get the current application url provider
* @return string the name of the class
*/
public static function GetUrlMakerClass()
{
if (is_null(self::$m_sUrlMakerClass))
{
if (isset($_SESSION['UrlMakerClass']))
{
self::$m_sUrlMakerClass = $_SESSION['UrlMakerClass'];
}
else
{
self::$m_sUrlMakerClass = 'iTopStandardURLMaker';
}
}
return self::$m_sUrlMakerClass;
}
/**
* Get the current application url provider
* @return string the name of the class
*/
public static function MakeObjectUrl($sObjClass, $sObjKey, $sUrlMakerClass = null, $bWithNavigationContext = true)
{
$oAppContext = new ApplicationContext();
if (is_null($sUrlMakerClass))
{
$sUrlMakerClass = self::GetUrlMakerClass();
}
$sUrl = call_user_func(array($sUrlMakerClass, 'MakeObjectUrl'), $sObjClass, $sObjKey);
if (strlen($sUrl) > 0)
{
if ($bWithNavigationContext)
{
return $sUrl."&".$oAppContext->GetForLink();
}
else
{
return $sUrl;
}
}
else
{
return '';
}
}
protected static $m_aPluginProperties = null;
/**
* Load plugin properties for the current session
* @return void
*/
protected static function LoadPluginProperties()
{
if (isset($_SESSION['PluginProperties']))
{
self::$m_aPluginProperties = $_SESSION['PluginProperties'];
}
else
{
self::$m_aPluginProperties = array();
}
}
/**
* Set plugin properties
* @param sPluginClass string Class implementing any plugin interface
* @param sProperty string Name of the property
* @param value scalar Value (numeric or string)
* @return void
*/
public static function SetPluginProperty($sPluginClass, $sProperty, $value)
{
if (is_null(self::$m_aPluginProperties)) self::LoadPluginProperties();
self::$m_aPluginProperties[$sPluginClass][$sProperty] = $value;
$_SESSION['PluginProperties'][$sPluginClass][$sProperty] = $value;
}
/**
* Get plugin properties
* @param sPluginClass string Class implementing any plugin interface
* @return array of sProperty=>value pairs
*/
public static function GetPluginProperties($sPluginClass)
{
if (is_null(self::$m_aPluginProperties)) self::LoadPluginProperties();
if (array_key_exists($sPluginClass, self::$m_aPluginProperties))
{
return self::$m_aPluginProperties[$sPluginClass];
}
else
{
return array();
}
}
}
?>

File diff suppressed because it is too large Load Diff

View File

@@ -178,36 +178,6 @@ class DisplayBlock
public function Display(WebPage $oPage, $sId, $aExtraParams = array())
{
$oPage->add($this->GetDisplay($oPage, $sId, $aExtraParams));
/*
$aExtraParams = array_merge($aExtraParams, $this->m_aParams);
$aExtraParams['block_id'] = $sId;
if (!$this->m_bAsynchronous)
{
// render now
$oPage->add("<div id=\"$sId\" class=\"display_block\">\n");
$this->RenderContent($oPage, $aExtraParams);
$oPage->add("</div>\n");
}
else
{
// render it as an Ajax (asynchronous) call
$sFilter = $this->m_oFilter->serialize();
$oPage->add("<div id=\"$sId\" class=\"display_block loading\">\n");
$oPage->p("<img src=\"../images/indicator_arrows.gif\"> Loading...");
$oPage->add("</div>\n");
$oPage->add('
<script language="javascript">
$.post("ajax.render.php?style='.$this->m_sStyle.'",
{ operation: "ajax", filter: "$sFilter" },
function(data){
$("#'.$sId.'").empty();
$("#'.$sId.'").append(data);
$("#'.$sId.'").removeClass("loading");
}
);
</script>'); // TO DO: add support for $aExtraParams in asynchronous/Ajax mode
}
*/
}
public function GetDisplay(WebPage $oPage, $sId, $aExtraParams = array())
@@ -262,41 +232,20 @@ class DisplayBlock
$sHtml .= "<div id=\"$sId\" class=\"display_block loading\">\n";
$sHtml .= $oPage->GetP("<img src=\"../images/indicator_arrows.gif\"> ".Dict::S('UI:Loading'));
$sHtml .= "</div>\n";
$sHtml .= '
<script language="javascript">
$oPage->add_script('
$.post("ajax.render.php?style='.$this->m_sStyle.'",
{ operation: "ajax", filter: "'.$sFilter.'", extra_params: "'.$sExtraParams.'" },
function(data){
$("#'.$sId.'").empty();
$("#'.$sId.'").append(data);
$("#'.$sId.'").removeClass("loading");
// Check each "listResults" table for a checkbox in the first column and make the first column sortable only if it does not contain a checkbox in the header
$("#'.$sId.'".listResults").each( function()
{
var table = $(this);
var id = $(this).parent();
var checkbox = (table.find(\'th:first :checkbox\').length > 0);
if (checkbox)
{
// There is a checkbox in the first column, do not make it sortable
table.tablesorter( { headers: { 0: {sorter: false}}, widgets: [\'myZebra\', \'truncatedList\']} ); // sortable and zebra tables
}
else
{
// There is NO checkbox in the first column, all columns are considered sortable
table.tablesorter( { widgets: [\'myZebra\', \'truncatedList\']} ); // sortable and zebra tables
}
});
}
);
</script>';
');
}
if ($bAutoReload)
{
$sHtml .= '
<script language="javascript">
setInterval("ReloadBlock(\''.$sId.'\', \''.$this->m_sStyle.'\', \''.$sFilter.'\', \"'.$sExtraParams.'\")", '.$iReloadInterval.');
</script>';
$oPage->add_script('setInterval("ReloadBlock(\''.$sId.'\', \''.$this->m_sStyle.'\', \''.$sFilter.'\', \"'.$sExtraParams.'\")", '.$iReloadInterval.');');
}
return $sHtml;
}
@@ -331,9 +280,10 @@ class DisplayBlock
$oAppContext = new ApplicationContext();
$sClass = $this->m_oFilter->GetClass();
$aFilterCodes = array_keys(MetaModel::GetClassFilterDefs($sClass));
$aCallSpec = array($sClass, 'MapContextParam');
foreach($oAppContext->GetNames() as $sContextParam)
{
eval("\$sParamCode = $sClass::MapContextParam('$sContextParam');"); //Map context parameter to the value/filter code depending on the class
$sParamCode = call_user_func($aCallSpec, $sContextParam); //Map context parameter to the value/filter code depending on the class
if (!is_null($sParamCode))
{
$sParamValue = $oAppContext->GetCurrentValue($sContextParam, null);
@@ -345,7 +295,7 @@ class DisplayBlock
}
foreach($aFilterCodes as $sFilterCode)
{
$sExternalFilterValue = utils::ReadParam($sFilterCode, '');
$sExternalFilterValue = utils::ReadParam($sFilterCode, '', false, 'raw_data');
$condition = null;
if (isset($aExtraParams[$sFilterCode]))
{
@@ -361,11 +311,33 @@ class DisplayBlock
if (!is_null($condition))
{
$this->m_oFilter->AddCondition($sFilterCode, $condition); // Use the default 'loose' operator
$this->AddCondition($sFilterCode, $condition);
}
}
}
$this->m_oSet = new CMDBObjectSet($this->m_oFilter, array(), $aQueryParams);
$aOrderBy = array();
if (isset($aExtraParams['order_by']))
{
// Convert the string describing the order_by parameter into an array
// The syntax is +attCode1,-attCode2
// attCode1 => ascending, attCode2 => descending
$aTemp = explode(',', $aExtraParams['order_by']);
foreach($aTemp as $sTemp)
{
$aMatches = array();
if (preg_match('/^([+-])?(.+)$/', $sTemp, $aMatches))
{
$bAscending = true;
if ($aMatches[1] == '-')
{
$bAscending = false;
}
$aOrderBy[$aMatches[2]] = $bAscending;
}
}
}
$this->m_oSet = new CMDBObjectSet($this->m_oFilter, $aOrderBy, $aQueryParams);
}
switch($this->m_sStyle)
{
@@ -375,7 +347,12 @@ class DisplayBlock
$sGroupByField = $aExtraParams['group_by'];
$aGroupBy = array();
$sLabels = array();
while($oObj = $this->m_oSet->Fetch())
$iTotalCount = $this->m_oSet->Count();
$oTmpSet = clone $this->m_oSet;
// Speed up the load, load only the needed field to group on
$sAlias = $oTmpSet->GetFilter()->GetClassAlias();
$oTmpSet->OptimizeColumnLoad(array($sAlias => array($sGroupByField)));
while($oObj = $oTmpSet->Fetch())
{
if (isset($aExtraParams['group_by_expr']))
{
@@ -395,12 +372,14 @@ class DisplayBlock
foreach($aGroupBy as $sValue => $iCount)
{
$aData[] = array ( 'group' => $sLabels[$sValue],
'value' => "<a href=\"./UI.php?operation=search&dosearch=1&$sParams&filter=$sFilter&$sGroupByField=".urlencode($sValue)."\">$iCount</a>"); // TO DO: add the context information
'value' => "<a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=search&dosearch=1&$sParams&filter=$sFilter&$sGroupByField=".urlencode($sValue)."\">$iCount</a>"); // TO DO: add the context information
}
$aAttribs =array(
'group' => array('label' => MetaModel::GetLabel($this->m_oFilter->GetClass(), $sGroupByField), 'description' => ''),
'value' => array('label'=> Dict::S('UI:GroupBy:Count'), 'description' => Dict::S('UI:GroupBy:Count+'))
);
$sFormat = isset($aExtraParams['format']) ? $aExtraParams['format'] : 'UI:Pagination:HeaderNoSelection';
$sHtml .= $oPage->GetP(Dict::Format($sFormat, $iTotalCount));
$sHtml .= $oPage->GetTable($aAttribs, $aData);
}
else
@@ -574,7 +553,7 @@ class DisplayBlock
}
}
$sHtml .= $oPage->GetP("<a href=\"./UI.php?operation=new&class=$sClass&$sParams{$sDefault}\">".Dict::Format('UI:ClickToCreateNew', Metamodel::GetName($sClass))."</a>\n");
$sHtml .= $oPage->GetP("<a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=new&class=$sClass&$sParams{$sDefault}\">".Dict::Format('UI:ClickToCreateNew', Metamodel::GetName($sClass))."</a>\n");
}
}
}
@@ -610,7 +589,7 @@ class DisplayBlock
$sDefaults .= '&'.urlencode($sName).'='.urlencode($sValue);
}
}
$sHtml .= $oPage->GetP("<a href=\"../pages/UI.php?operation=modify_links&class=$sClass&sParams&link_attr=".$aExtraParams['link_attr']."&id=".$aExtraParams['object_id']."&target_class=$sTargetClass&addObjects=true$sDefaults\">".Dict::Format('UI:ClickToCreateNew', Metamodel::GetName($sClass))."</a>\n");
$sHtml .= $oPage->GetP("<a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=modify_links&class=$sClass&sParams&link_attr=".$aExtraParams['link_attr']."&id=".$aExtraParams['object_id']."&target_class=$sTargetClass&addObjects=true$sDefaults\">".Dict::Format('UI:ClickToCreateNew', Metamodel::GetName($sClass))."</a>\n");
}
}
}
@@ -635,7 +614,7 @@ class DisplayBlock
$sContextParamValue = $oAppContext->GetCurrentValue($sFilterCode, null);
if (!is_null($sContextParamValue) && ! empty($sContextParamValue) && MetaModel::IsValidFilterCode($sClass, $sFilterCode))
{
$this->m_oFilter->AddCondition($sFilterCode, $sContextParamValue); // Use the default 'loose' operator
$this->AddCondition($sFilterCode, $sContextParamValue);
}
}
$aQueryParams = array();
@@ -646,7 +625,7 @@ class DisplayBlock
$this->m_oSet = new CMDBObjectSet($this->m_oFilter, array(), $aQueryParams);
}
$iCount = $this->m_oSet->Count();
$sHyperlink = '../pages/UI.php?operation=search&'.$oAppContext->GetForLink().'&filter='.$this->m_oFilter->serialize();
$sHyperlink = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search&'.$oAppContext->GetForLink().'&filter='.$this->m_oFilter->serialize();
$sHtml .= '<p><a class="actions" href="'.$sHyperlink.'">';
$sHtml .= MetaModel::GetClassIcon($sClass, true, 'float;left;margin-right:10px;');
$sHtml .= MetaModel::GetName($sClass).': '.$iCount.'</a></p>';
@@ -654,9 +633,9 @@ class DisplayBlock
$sHtml .= '<p>';
if (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY))
{
$sHtml .= "<a href=\"../pages/UI.php?operation=new&class={$sClass}&$sParams\">".Dict::Format('UI:ClickToCreateNew', MetaModel::GetName($sClass))."</a><br/>\n";
$sHtml .= "<a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=new&class={$sClass}&$sParams\">".Dict::Format('UI:ClickToCreateNew', MetaModel::GetName($sClass))."</a><br/>\n";
}
$sHtml .= "<a href=\"../pages/UI.php?operation=search_form&class={$sClass}&$sParams\">".Dict::Format('UI:SearchFor_Class', MetaModel::GetName($sClass))."</a>\n";
$sHtml .= "<a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=search_form&class={$sClass}&$sParams\">".Dict::Format('UI:SearchFor_Class', MetaModel::GetName($sClass))."</a>\n";
$sHtml .= '</p>';
break;
@@ -677,7 +656,7 @@ class DisplayBlock
$sContextParamValue = $oAppContext->GetCurrentValue($sFilterCode, null);
if (!is_null($sContextParamValue) && ! empty($sContextParamValue) && MetaModel::IsValidFilterCode($sClass, $sFilterCode))
{
$this->m_oFilter->AddCondition($sFilterCode, $sContextParamValue); // Use the default 'loose' operator
$this->AddCondition($sFilterCode, $sContextParamValue);
}
}
$aQueryParams = array();
@@ -700,14 +679,14 @@ class DisplayBlock
$oFilter->AddCondition($sStateAttrCode, $sStateValue, '=');
$oSet = new DBObjectSet($oFilter);
$aCounts[$sStateValue] = $oSet->Count();
$aStateLabels[$sStateValue] = Dict::S("Class:".$oAttDef->GetHostClass()."/Attribute:$sStateAttrCode/Value:$sStateValue");
$aStateLabels[$sStateValue] = $oAttDef->GetValueLabel($sStateValue);
if ($aCounts[$sStateValue] == 0)
{
$aCounts[$sStateValue] = '-';
}
else
{
$sHyperlink = '../pages/UI.php?operation=search&'.$oAppContext->GetForLink().'&filter='.$oFilter->serialize();
$sHyperlink = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search&'.$oAppContext->GetForLink().'&filter='.$oFilter->serialize();
$aCounts[$sStateValue] = "<a href=\"$sHyperlink\">{$aCounts[$sStateValue]}</a>";
}
}
@@ -716,18 +695,11 @@ class DisplayBlock
$sHtml .= '<tr><td>'.implode('</td><td>', $aCounts).'</td></tr></table></div>';
// Title & summary
$iCount = $this->m_oSet->Count();
$sHyperlink = '../pages/UI.php?operation=search&'.$oAppContext->GetForLink().'&filter='.$this->m_oFilter->serialize();
$sHyperlink = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search&'.$oAppContext->GetForLink().'&filter='.$this->m_oFilter->serialize();
$sHtml .= '<h1>'.Dict::S(str_replace('_', ':', $sTitle)).'</h1>';
$sHtml .= '<a class="summary" href="'.$sHyperlink.'">'.Dict::Format(str_replace('_', ':', $sLabel), $iCount).'</a>';
break;
case 'bare_details':
while($oObj = $this->m_oSet->Fetch())
{
$sHtml .= $oObj->GetBareProperties($oPage);
}
break;
case 'csv':
$sHtml .= "<textarea style=\"width:95%;height:98%\">\n";
$sHtml .= cmdbAbstractObject::GetSetAsCSV($this->m_oSet);
@@ -764,6 +736,12 @@ EOF
case 'open_flash_chart':
static $iChartCounter = 0;
$oAppContext = new ApplicationContext();
$sContext = $oAppContext->GetForLink();
if (!empty($sContext))
{
$sContext = '&'.$sContext;
}
$sChartType = isset($aExtraParams['chart_type']) ? $aExtraParams['chart_type'] : 'pie';
$sTitle = isset($aExtraParams['chart_title']) ? $aExtraParams['chart_title'] : '';
$sGroupBy = isset($aExtraParams['group_by']) ? $aExtraParams['group_by'] : '';
@@ -772,13 +750,58 @@ EOF
$sHtml .= "<div id=\"my_chart_{$iChartCounter}\">If the chart does not display, <a href=\"http://get.adobe.com/flash/\" target=\"_blank\">install Flash</a></div>\n";
$oPage->add_script("function ofc_resize(left, width, top, height) { /* do nothing special */ }");
$oPage->add_ready_script("swfobject.embedSWF(\"../images/open-flash-chart.swf\", \"my_chart_{$iChartCounter}\", \"100%\", \"300\",\"9.0.0\", \"expressInstall.swf\",
{\"data-file\":\"".urlencode("../pages/ajax.render.php?operation=open_flash_chart&params[group_by]=$sGroupBy{$sGroupByExpr}&params[chart_type]=$sChartType&params[chart_title]=$sTitle&filter=".$sFilter)."\"}, {wmode: 'transparent'} );\n");
{\"data-file\":\"".urlencode(utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=open_flash_chart&params[group_by]=$sGroupBy{$sGroupByExpr}&params[chart_type]=$sChartType&params[chart_title]=$sTitle&params[currentId]=$sId&id=$sId&filter=".$sFilter)."\"}, {wmode: 'transparent'} );\n");
$iChartCounter++;
if (isset($aExtraParams['group_by']))
{
$sGroupByField = $aExtraParams['group_by'];
$aGroupBy = array();
while($oObj = $this->m_oSet->Fetch())
{
if (isset($aExtraParams['group_by_expr']))
{
eval("\$sValue = ".sprintf($aExtraParams['group_by_expr'], $oObj->Get($sGroupByField)).';');
}
else
{
$sValue = $oObj->Get($sGroupByField);
}
$aGroupBy[$sValue] = isset($aGroupBy[$sValue]) ? $aGroupBy[$sValue]+1 : 1;
}
$sFilter = urlencode($this->m_oFilter->serialize());
$aData = array();
$aLabels = array();
$idx = 0;
$aURLs = array();
foreach($aGroupBy as $sValue => $iValue)
{
$oDrillDownFilter = clone $this->m_oFilter;
$oDrillDownFilter->AddCondition($sGroupByField, $sValue, '=');
$aURLs[$idx] = $oDrillDownFilter->serialize();
$idx++;
}
$sURLList = '';
foreach($aURLs as $index => $sURL)
{
$sURLList .= "\taURLs[$index] = '".utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=search&format=html{$sContext}&filter=".addslashes($sURL)."';\n";
}
$oPage->add_script(
<<<EOF
function ofc_drill_down_{$sId}(index)
{
var aURLs = new Array();
{$sURLList}
window.location.href=aURLs[index];
}
EOF
);
}
break;
case 'open_flash_chart_ajax':
require_once(APPROOT.'/pages/php-ofc-library/open-flash-chart.php');
$sChartType = isset($aExtraParams['chart_type']) ? $aExtraParams['chart_type'] : 'pie';
$sId = utils::ReadParam('id', '');
$oChart = new open_flash_chart();
switch($sChartType)
@@ -805,12 +828,15 @@ EOF
$sFilter = urlencode($this->m_oFilter->serialize());
$aData = array();
$aLabels = array();
$maxValue = 0;
foreach($aGroupBy as $sValue => $iValue)
{
$aData[] = $iValue;
$oBarValue = new bar_value($iValue);
$oBarValue->on_click("ofc_drill_down_$sId");
$aData[] = $oBarValue;
if ($iValue > $maxValue) $maxValue = $iValue;
$aLabels[] = $sValue;
}
$maxValue = max($aData);
$oYAxis = new y_axis();
$aMagicValues = array(1,2,5,10);
$iMultiplier = 1;
@@ -869,7 +895,9 @@ EOF
$aData = array();
foreach($aGroupBy as $sValue => $iValue)
{
$aData[] = new pie_value($iValue, $sValue); //@@ BUG: not passed via ajax !!!
$PieValue = new pie_value($iValue, $sValue); //@@ BUG: not passed via ajax !!!
$PieValue->on_click("ofc_drill_down_$sId");
$aData[] = $PieValue;
}
@@ -894,6 +922,43 @@ EOF
}
return $sHtml;
}
/**
* Add a condition (restriction) to the current DBObjectSearch on which the display block is based
* taking into account the hierarchical keys for which the condition is based on the 'below' operator
*/
protected function AddCondition($sFilterCode, $condition)
{
$sClass = $this->m_oFilter->GetClass();
$bConditionAdded = false;
// If the condition is an external key with a class having a hierarchy, use a "below" criteria
if (MetaModel::IsValidAttCode($sClass, $sFilterCode))
{
$oAttDef = MetaModel::GetAttributeDef($sClass, $sFilterCode);
if ($oAttDef->IsExternalKey())
{
$sHierarchicalKeyCode = MetaModel::IsHierarchicalClass($oAttDef->GetTargetClass());
if ($sHierarchicalKeyCode !== false)
{
$oFilter = new DBObjectSearch($oAttDef->GetTargetClass());
$oFilter->AddCondition('id', $condition);
$oHKFilter = new DBObjectSearch($oAttDef->GetTargetClass());
$oHKFilter->AddCondition_PointingTo($oFilter, $sHierarchicalKeyCode, TREE_OPERATOR_BELOW); // Use the 'below' operator by default
$this->m_oFilter->AddCondition_PointingTo($oHKFilter, $sFilterCode);
$bConditionAdded = true;
}
}
}
// In all other cases, just add the condition directly
if (!$bConditionAdded)
{
$this->m_oFilter->AddCondition($sFilterCode, $condition); // Use the default 'loose' operator
}
}
}
/**
@@ -973,7 +1038,7 @@ class HistoryBlock extends DisplayBlock
$aValues = array();
foreach($aChanges as $aChange)
{
$aValues[] = array('date' => $aChange['date'], 'userinfo' => $aChange['userinfo'], 'log' => "<ul><li>".implode('</li><li>', $aChange['log'])."</li></ul>");
$aValues[] = array('date' => $aChange['date'], 'userinfo' => htmlentities($aChange['userinfo'], ENT_QUOTES, 'UTF-8'), 'log' => "<ul><li>".implode('</li><li>', $aChange['log'])."</li></ul>");
}
$sHtml .= $oPage->GetTable($aAttribs, $aValues);
return $sHtml;
@@ -996,11 +1061,17 @@ class MenuBlock extends DisplayBlock
$sHtml = '';
$oAppContext = new ApplicationContext();
$sContext = $oAppContext->GetForLink();
if (!empty($sContext))
{
$sContext = '&'.$sContext;
}
$sClass = $this->m_oFilter->GetClass();
$oSet = new CMDBObjectSet($this->m_oFilter);
$sFilter = $this->m_oFilter->serialize();
$sFilterDesc = $this->m_oFilter->ToOql();
$aActions = array();
$sUIPage = cmdbAbstractObject::ComputeUIPage($sClass);
$sUIPage = cmdbAbstractObject::ComputeStandardUIPage($sClass);
$sRootUrl = utils::GetAbsoluteUrlAppRoot();
// 1:n links, populate the target object as a default value when creating a new linked object
if (isset($aExtraParams['target_attr']))
{
@@ -1014,12 +1085,12 @@ class MenuBlock extends DisplayBlock
$sDefault.= "&default[$sKey]=$sValue";
}
}
$bIsCreationAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES);
switch($oSet->Count())
{
case 0:
// No object in the set, the only possible action is "new"
$bIsModifyAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES);
if ($bIsModifyAllowed) { $aActions[] = array ('label' => Dict::S('UI:Menu:New'), 'url' => "../page/$sUIPage?operation=new&class=$sClass&$sContext{$sDefault}"); }
if ($bIsCreationAllowed) { $aActions['UI:Menu:New'] = array ('label' => Dict::S('UI:Menu:New'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=new&class=$sClass{$sContext}{$sDefault}"); }
break;
case 1:
@@ -1032,23 +1103,22 @@ class MenuBlock extends DisplayBlock
// Just one object in the set, possible actions are "new / clone / modify and delete"
if (!isset($aExtraParams['link_attr']))
{
$sUrl = utils::GetAbsoluteUrl(false);
if ($bIsModifyAllowed) { $aActions[] = array ('label' => Dict::S('UI:Menu:Modify'), 'url' => "../pages/$sUIPage?operation=modify&class=$sClass&id=$id&$sContext#"); }
if ($bIsModifyAllowed) { $aActions[] = array ('label' => Dict::S('UI:Menu:New'), 'url' => "../pages/$sUIPage?operation=new&class=$sClass&$sContext{$sDefault}"); }
if ($bIsDeleteAllowed) { $aActions[] = array ('label' => Dict::S('UI:Menu:Delete'), 'url' => "../pages/$sUIPage?operation=delete&class=$sClass&id=$id&$sContext"); }
if ($bIsModifyAllowed) { $aActions['UI:Menu:Modify'] = array ('label' => Dict::S('UI:Menu:Modify'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=modify&class=$sClass&id=$id{$sContext}#"); }
if ($bIsCreationAllowed) { $aActions['UI:Menu:New'] = array ('label' => Dict::S('UI:Menu:New'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=new&class=$sClass{$sContext}{$sDefault}"); }
if ($bIsDeleteAllowed) { $aActions['UI:Menu:Delete'] = array ('label' => Dict::S('UI:Menu:Delete'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=delete&class=$sClass&id=$id{$sContext}"); }
// Transitions / Stimuli
$aTransitions = $oObj->EnumTransitions();
if (count($aTransitions))
{
$this->AddMenuSeparator($aActions);
$aStimuli = Metamodel::EnumStimuli($sClass);
$aStimuli = Metamodel::EnumStimuli(get_class($oObj));
foreach($aTransitions as $sStimulusCode => $aTransitionDef)
{
$iActionAllowed = (get_class($aStimuli[$sStimulusCode]) == 'StimulusUserAction') ? UserRights::IsStimulusAllowed($sClass, $sStimulusCode, $oSet) : UR_ALLOWED_NO;
switch($iActionAllowed)
{
case UR_ALLOWED_YES:
$aActions[] = array('label' => $aStimuli[$sStimulusCode]->GetLabel(), 'url' => "../pages/UI.php?operation=stimulus&stimulus=$sStimulusCode&class=$sClass&id=$id&$sContext");
$aActions[$sStimulusCode] = array('label' => $aStimuli[$sStimulusCode]->GetLabel(), 'url' => "{$sRootUrl}pages/UI.php?operation=stimulus&stimulus=$sStimulusCode&class=$sClass&id=$id{$sContext}");
break;
default:
@@ -1063,23 +1133,14 @@ class MenuBlock extends DisplayBlock
$this->AddMenuSeparator($aActions);
foreach($aRelations as $sRelationCode)
{
$aActions[] = array ('label' => MetaModel::GetRelationVerbUp($sRelationCode), 'url' => "../pages/$sUIPage?operation=swf_navigator&relation=$sRelationCode&class=$sClass&id=$id&$sContext");
$aActions[$sRelationCode] = array ('label' => MetaModel::GetRelationVerbUp($sRelationCode), 'url' => "{$sRootUrl}pages/$sUIPage?operation=swf_navigator&relation=$sRelationCode&class=$sClass&id=$id{$sContext}");
}
}
$this->AddMenuSeparator($aActions);
// Static menus: Email this page & CSV Export
$aActions[] = array ('label' => Dict::S('UI:Menu:EMail'), 'url' => "mailto:?subject=".$oObj->GetName()."&body=".urlencode("$sUrl?operation=details&class=$sClass&id=$id&$sContext"));
$aActions[] = array ('label' => Dict::S('UI:Menu:CSVExport'), 'url' => "../pages/$sUIPage?operation=search&filter=$sFilter&format=csv&$sContext");
}
else
{
// List of links, the only actions are 'Add...' and 'Manage...'
$id = $aExtraParams['object_id'];
$sTargetAttr = $aExtraParams['target_attr'];
$oAttDef = MetaModel::GetAttributeDef($sClass, $sTargetAttr);
$sTargetClass = $oAttDef->GetTargetClass();
if ($bIsModifyAllowed) { $aActions[] = array ('label' => Dict::S('UI:Menu:Add'), 'url' => "../pages/$sUIPage?operation=modify_links&class=$sClass&link_attr=".$aExtraParams['link_attr']."&target_class=$sTargetClass&id=$id&addObjects=true&$sContext"); }
if ($bIsModifyAllowed) { $aActions[] = array ('label' => Dict::S('UI:Menu:Manage'), 'url' => "../pages/$sUIPage?operation=modify_links&class=$sClass&link_attr=".$aExtraParams['link_attr']."&target_class=$sTargetClass&id=$id&sContext"); }
$sUrl = ApplicationContext::MakeObjectUrl($sClass, $id);
$aActions['UI:Menu:EMail'] = array ('label' => Dict::S('UI:Menu:EMail'), 'url' => "mailto:?subject=".urlencode($oObj->GetRawName())."&body=".urlencode($sUrl));
$aActions['UI:Menu:CSVExport'] = array ('label' => Dict::S('UI:Menu:CSVExport'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=search&filter=$sFilter&format=csv{$sContext}");
}
$this->AddMenuSeparator($aActions);
foreach (MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance)
@@ -1087,7 +1148,7 @@ class MenuBlock extends DisplayBlock
$oSet->Rewind();
foreach($oExtensionInstance->EnumAllowedActions($oSet) as $sLabel => $sUrl)
{
$aActions[] = array ('label' => $sLabel, 'url' => $sUrl);
$aActions[$sLabel] = array ('label' => $sLabel, 'url' => $sUrl);
}
}
break;
@@ -1105,21 +1166,23 @@ class MenuBlock extends DisplayBlock
$oAttDef = MetaModel::GetAttributeDef($sClass, $sTargetAttr);
$sTargetClass = $oAttDef->GetTargetClass();
$bIsDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, $oSet);
if ($bIsModifyAllowed) { $aActions[] = array ('label' => Dict::S('UI:Menu:Add'), 'url' => "../pages/$sUIPage?operation=modify_links&class=$sClass&link_attr=".$aExtraParams['link_attr']."&target_class=$sTargetClass&id=$id&addObjects=true&$sContext"); }
if ($bIsBulkModifyAllowed) { $aActions[] = array ('label' => Dict::S('UI:Menu:Manage'), 'url' => "../pages/$sUIPage?operation=modify_links&class=$sClass&link_attr=".$aExtraParams['link_attr']."&target_class=$sTargetClass&id=$id&sContext"); }
if ($bIsModifyAllowed) { $aActions['UI:Menu:Add'] = array ('label' => Dict::S('UI:Menu:Add'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=modify_links&class=$sClass&link_attr=".$aExtraParams['link_attr']."&target_class=$sTargetClass&id=$id&addObjects=true{$sContext}"); }
if ($bIsBulkModifyAllowed) { $aActions['UI:Menu:Manage'] = array ('label' => Dict::S('UI:Menu:Manage'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=modify_links&class=$sClass&link_attr=".$aExtraParams['link_attr']."&target_class=$sTargetClass&id=$id{$sContext}"); }
//if ($bIsBulkDeleteAllowed) { $aActions[] = array ('label' => 'Remove All...', 'url' => "#"); }
}
else
{
// many objects in the set, possible actions are: new / modify all / delete all
$sUrl = utils::GetAbsoluteUrl();
if ($bIsModifyAllowed) { $aActions[] = array ('label' => Dict::S('UI:Menu:New'), 'url' => "../pages/$sUIPage?operation=new&class=$sClass&$sContext{$sDefault}"); }
if ($bIsBulkModifyAllowed) { $aActions[] = array ('label' => Dict::S('UI:Menu:ModifyAll'), 'url' => "../pages/$sUIPage?operation=select_for_modify_all&class=$sClass&filter=$sFilter&sContext"); }
if ($bIsBulkDeleteAllowed) { $aActions[] = array ('label' => Dict::S('UI:Menu:BulkDelete'), 'url' => "../pages/$sUIPage?operation=select_for_deletion&filter=$sFilter&$sContext"); }
if ($bIsCreationAllowed) { $aActions['UI:Menu:New'] = array ('label' => Dict::S('UI:Menu:New'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=new&class=$sClass{$sContext}{$sDefault}"); }
if ($bIsBulkModifyAllowed) { $aActions['UI:Menu:ModifyAll'] = array ('label' => Dict::S('UI:Menu:ModifyAll'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=select_for_modify_all&class=$sClass&filter=$sFilter{$sContext}"); }
if ($bIsBulkDeleteAllowed) { $aActions['UI:Menu:BulkDelete'] = array ('label' => Dict::S('UI:Menu:BulkDelete'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=select_for_deletion&filter=$sFilter{$sContext}"); }
// Stimuli
$aStates = MetaModel::EnumStates($sClass);
if (count($aStates) > 0)
// Do not perform time consuming computations if there are too may objects in the list
$iLimit = MetaModel::GetConfig()->Get('complex_actions_limit');
if ((count($aStates) > 0) && (($iLimit == 0) || ($oSet->Count() < $iLimit)))
{
// Life cycle actions may be available... if all objects are in the same state
$oSet->Rewind();
@@ -1146,7 +1209,7 @@ class MenuBlock extends DisplayBlock
{
case UR_ALLOWED_YES:
case UR_ALLOWED_DEPENDS:
$aActions[] = array('label' => $aStimuli[$sStimulusCode]->GetLabel(), 'url' => "../pages/UI.php?operation=select_bulk_stimulus&stimulus=$sStimulusCode&state=$sState&class=$sClass&filter=$sFilter&$sContext");
$aActions[$sStimulusCode] = array('label' => $aStimuli[$sStimulusCode]->GetLabel(), 'url' => "{$sRootUrl}pages/UI.php?operation=select_bulk_stimulus&stimulus=$sStimulusCode&state=$sState&class=$sClass&filter=$sFilter{$sContext}");
break;
default:
@@ -1157,8 +1220,9 @@ class MenuBlock extends DisplayBlock
}
}
$this->AddMenuSeparator($aActions);
$aActions[] = array ('label' => Dict::S('UI:Menu:EMail'), 'url' => "mailto:?subject=".$oSet->GetFilter()->__DescribeHTML()."&body=".urlencode("$sUrl?operation=search&filter=$sFilter&$sContext"));
$aActions[] = array ('label' => Dict::S('UI:Menu:CSVExport'), 'url' => "../pages/$sUIPage?operation=search&filter=$sFilter&format=csv&$sContext");
$sUrl = utils::GetAbsoluteUrlAppRoot();
$aActions['UI:Menu:EMail'] = array ('label' => Dict::S('UI:Menu:EMail'), 'url' => "mailto:?subject=$sFilterDesc&body=".urlencode("{$sUrl}pages/$sUIPage?operation=search&filter=$sFilter{$sContext}"));
$aActions['UI:Menu:CSVExport'] = array ('label' => Dict::S('UI:Menu:CSVExport'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=search&filter=$sFilter&format=csv{$sContext}");
}
$this->AddMenuSeparator($aActions);
foreach (MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance)
@@ -1166,24 +1230,67 @@ class MenuBlock extends DisplayBlock
$oSet->Rewind();
foreach($oExtensionInstance->EnumAllowedActions($oSet) as $sLabel => $sUrl)
{
$aActions[] = array ('label' => $sLabel, 'url' => $sUrl);
$aActions[$sLabel] = array ('label' => $sLabel, 'url' => $sUrl);
}
}
}
$sHtml .= "<div class=\"itop_popup\"><ul>\n<li>".Dict::S('UI:Menu:Actions')."\n<ul>\n";
foreach ($aActions as $aAction)
$aFavoriteActions = array();
$aCallSpec = array($sClass, 'GetShortcutActions');
if (is_callable($aCallSpec))
{
$sClass = isset($aAction['class']) ? " class=\"{$aAction['class']}\"" : "";
if (empty($aAction['url']))
$aShortcutActions = call_user_func($aCallSpec, $sClass);
foreach ($aActions as $key => $aAction)
{
$sHtml .= "<li>{$aAction['label']}</li>\n";
if (in_array($key, $aShortcutActions))
{
$aFavoriteActions[] = $aAction;
unset($aActions[$key]);
}
}
}
else
{
$aShortcutActions = array();
}
if (count($aFavoriteActions) > 0)
{
$sHtml .= "<div class=\"itop_popup\"><ul>\n<li>".Dict::S('UI:Menu:OtherActions')."\n<ul>\n";
}
else
{
$sHtml .= "<div class=\"itop_popup\"><ul>\n<li>".Dict::S('UI:Menu:Actions')."\n<ul>\n";
}
$sPrevUrl = '';
foreach ($aActions as $key => $aAction)
{
if (in_array($key, $aShortcutActions))
{
$aFavoriteActions[] = $aAction;
}
else
{
$sHtml .= "<li><a href=\"{$aAction['url']}\"$sClass>{$aAction['label']}</a></li>\n";
$sClass = isset($aAction['class']) ? " class=\"{$aAction['class']}\"" : "";
if (empty($aAction['url']))
{
if ($sPrevUrl != '') // Don't output consecutively two separators...
{
$sHtml .= "<li>{$aAction['label']}</li>\n";
}
$sPrevUrl = '';
}
else
{
$sHtml .= "<li><a href=\"{$aAction['url']}\"$sClass>{$aAction['label']}</a></li>\n";
$sPrevUrl = $aAction['url'];
}
}
}
$sHtml .= "</ul>\n</li>\n</ul></div>\n";
$sHtml .= "</ul>\n</li>\n</ul></div>";
foreach(array_reverse($aFavoriteActions) as $aAction)
{
$sHtml .= "<div class=\"actions_button\"><a href='{$aAction['url']}'>{$aAction['label']}</a></div>";
}
static $bPopupScript = false;
if (!$bPopupScript)
{
@@ -1204,9 +1311,11 @@ class MenuBlock extends DisplayBlock
$sSeparator = '<hr class="menu-separator"/>';
if (count($aActions) > 0) // Make sure that the separator is not the first item in the menu
{
if ($aActions[count($aActions)-1]['label'] != $sSeparator) // Make sure there are no 2 consecutive separators
$aKeys = array_keys($aActions);
$sLastKey = array_pop($aKeys);
if ($aActions[$sLastKey]['label'] != $sSeparator) // Make sure there are no 2 consecutive separators
{
$aActions[] = array('label' => $sSeparator, 'url' => '');
$aActions['sep_'.(count($aActions)-1)] = array('label' => $sSeparator, 'url' => '');
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -48,7 +48,7 @@ class iTopWizardWebPage extends iTopWebPage
$sStyle = ($iIndex == $this->m_iCurrentStep) ? 'wizActiveStep' : 'wizStep';
$aSteps[] = "<div class=\"$sStyle\"><span>$sStepTitle</span></div>";
}
$sWizardHeader = "<div class=\"wizHeader\"><h1>{$this->s_title}</h1>\n".implode("<div class=\"wizSeparator\"><img align=\"bottom\" src=\"../images/wizArrow.gif\"></div>", $aSteps)."<br style=\"clear:both;\"/></div>\n";
$sWizardHeader = "<div class=\"wizHeader\"><h1>".htmlentities($this->s_title, ENT_QUOTES, 'UTF-8')."</h1>\n".implode("<div class=\"wizSeparator\"><img align=\"bottom\" src=\"../images/wizArrow.gif\"></div>", $aSteps)."<br style=\"clear:both;\"/></div>\n";
$this->s_content = "$sWizardHeader<div class=\"wizContainer\">".$this->s_content."</div>";
parent::output();
}

View File

@@ -27,11 +27,14 @@ require_once(APPROOT."/application/nicewebpage.class.inc.php");
/**
* Web page used for displaying the login form
*/
class LoginWebPage extends NiceWebPage
{
protected static $m_sLoginFailedMessage = '';
public function __construct()
{
parent::__construct("iTop Login");
parent::__construct(Dict::S("UI:iTopLogin"));
$this->add_style(<<<EOF
body {
background: #eee;
@@ -88,10 +91,21 @@ EOF
);
}
public static function SetLoginFailedMessage($sMessage)
{
self::$m_sLoginFailedMessage = $sMessage;
}
public function DisplayLoginForm($sLoginType, $bFailedLogin = false)
{
switch($sLoginType)
{
case 'cas':
utils::InitCASClient();
// force CAS authentication
phpCAS::forceAuthentication(); // Will redirect the user and exit since the user is not yet authenticated
break;
case 'basic':
case 'url':
$this->add_header('WWW-Authenticate: Basic realm="'.Dict::Format('UI:iTopVersion:Short', ITOP_VERSION));
@@ -103,8 +117,8 @@ EOF
case 'external':
case 'form':
default: // In case the settings get messed up...
$sAuthUser = utils::ReadParam('auth_user', '');
$sAuthPwd = utils::ReadParam('suggest_pwd', '');
$sAuthUser = utils::ReadParam('auth_user', '', true, 'raw_data');
$sAuthPwd = utils::ReadParam('suggest_pwd', '', true, 'raw_data');
$sVersionShort = Dict::Format('UI:iTopVersion:Short', ITOP_VERSION);
$this->add("<div id=\"login-logo\"><a href=\"http://www.combodo.com/itop\"><img title=\"$sVersionShort\" src=\"../images/itop-logo-external.png\"></a></div>\n");
@@ -112,7 +126,14 @@ EOF
$this->add("<h1>".Dict::S('UI:Login:Welcome')."</h1>\n");
if ($bFailedLogin)
{
$this->add("<p class=\"hilite\">".Dict::S('UI:Login:IncorrectLoginPassword')."</p>\n");
if (self::$m_sLoginFailedMessage != '')
{
$this->add("<p class=\"hilite\">".self::$m_sLoginFailedMessage."</p>\n");
}
else
{
$this->add("<p class=\"hilite\">".Dict::S('UI:Login:IncorrectLoginPassword')."</p>\n");
}
}
else
{
@@ -120,12 +141,13 @@ EOF
}
$this->add("<form method=\"post\">\n");
$this->add("<table width=\"100%\">\n");
$this->add("<tr><td style=\"text-align:right\"><label for=\"user\">".Dict::S('UI:Login:UserNamePrompt').":</label></td><td style=\"text-align:left\"><input id=\"user\" type=\"text\" name=\"auth_user\" value=\"$sAuthUser\" /></td></tr>\n");
$this->add("<tr><td style=\"text-align:right\"><label for=\"pwd\">".Dict::S('UI:Login:PasswordPrompt').":</label></td><td style=\"text-align:left\"><input id=\"pwd\" type=\"password\" name=\"auth_pwd\" value=\"$sAuthPwd\" /></td></tr>\n");
$this->add("<tr><td style=\"text-align:right\"><label for=\"user\">".Dict::S('UI:Login:UserNamePrompt').":</label></td><td style=\"text-align:left\"><input id=\"user\" type=\"text\" name=\"auth_user\" value=\"".htmlentities($sAuthUser, ENT_QUOTES, 'UTF-8')."\" /></td></tr>\n");
$this->add("<tr><td style=\"text-align:right\"><label for=\"pwd\">".Dict::S('UI:Login:PasswordPrompt').":</label></td><td style=\"text-align:left\"><input id=\"pwd\" type=\"password\" name=\"auth_pwd\" value=\"".htmlentities($sAuthPwd, ENT_QUOTES, 'UTF-8')."\" /></td></tr>\n");
$this->add("<tr><td colspan=\"2\" class=\"center v-spacer\"> <input type=\"submit\" value=\"".Dict::S('UI:Button:Login')."\" /></td></tr>\n");
$this->add("</table>\n");
$this->add("<input type=\"hidden\" name=\"loginop\" value=\"login\" />\n");
$this->add("</form>\n");
$this->add(Dict::S('UI:Login:About'));
$this->add("</div>\n");
break;
}
@@ -133,8 +155,7 @@ EOF
public function DisplayChangePwdForm($bFailedLogin = false)
{
$sAuthUser = utils::ReadParam('auth_user', '');
$sAuthPwd = utils::ReadParam('suggest_pwd', '');
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
$sVersionShort = Dict::Format('UI:iTopVersion:Short', ITOP_VERSION);
$sInconsistenPwdMsg = Dict::S('UI:Login:RetypePwdDoesNotMatch');
@@ -216,10 +237,8 @@ EOF
{
if (self::SecureConnectionRequired() && !self::IsConnectionSecure())
{
// Non secured URL... redirect to a secured one
$sUrl = Utils::GetAbsoluteUrl(true /* query string */, true /* force HTTPS */);
header("Location: $sUrl");
exit;
// Non secured URL... request for a secure connection
throw new Exception('Secure connection required!');
}
$aAllowedLoginTypes = MetaModel::GetConfig()->GetAllowedLoginTypes();
@@ -241,10 +260,22 @@ EOF
$sLoginType = $aAllowedLoginTypes[$index];
switch($sLoginType)
{
case 'cas':
utils::InitCASClient();
// check CAS authentication
if (phpCAS::isAuthenticated())
{
$sAuthUser = phpCAS::getUser();
$sAuthPwd = '';
$sLoginMode = 'cas';
$sAuthentication = 'external';
}
break;
case 'form':
// iTop standard mode: form based authentication
$sAuthUser = utils::ReadPostedParam('auth_user', '');
$sAuthPwd = utils::ReadPostedParam('auth_pwd', '');
$sAuthUser = utils::ReadPostedParam('auth_user', '', false, 'raw_data');
$sAuthPwd = utils::ReadPostedParam('auth_pwd', '', false, 'raw_data');
if ($sAuthUser != '')
{
$sLoginMode = 'form';
@@ -270,12 +301,10 @@ EOF
case 'external':
// Web server supplied authentication
$bExternalAuth = false;
$sExtAuthVar = MetaModel::GetConfig()->GetExternalAuthenticationVariable(); // In which variable is the info passed ?
$sEval = '$bExternalAuth = isset('.$sExtAuthVar.');';
eval($sEval);
if ($bExternalAuth)
{
eval('$sAuthUser = '.$sExtAuthVar.';'); // Retrieve the value
$sExtAuthVar = MetaModel::GetConfig()->GetExternalAuthenticationVariable(); // In which variable is the info passed ?
eval('$sAuthUser = isset('.$sExtAuthVar.') ? '.$sExtAuthVar.' : false;'); // Retrieve the value
if ($sAuthUser && (strlen($sAuthUser) > 0))
{
$sAuthPwd = ''; // No password in this case the web server already authentified the user...
$sLoginMode = 'external';
$sAuthentication = 'external';
@@ -284,10 +313,10 @@ EOF
case 'url':
// Credentials passed directly in the url
$sAuthUser = utils::ReadParam('auth_user', '');
if ($sAuthUser != '')
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
$sAuthPwd = utils::ReadParam('auth_pwd', null, false, 'raw_data');
if (($sAuthUser != '') && ($sAuthPwd != null))
{
$sAuthPwd = utils::ReadParam('auth_pwd', '');
$sLoginMode = 'url';
}
break;
@@ -314,8 +343,9 @@ EOF
}
else
{
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, $sAuthentication))
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, $sLoginMode, $sAuthentication))
{
//echo "Check Credentials returned false for user $sAuthUser!";
self::ResetSession();
$oPage = new LoginWebPage();
$oPage->DisplayLoginForm( $sLoginMode, true /* failed attempt */);
@@ -394,8 +424,8 @@ EOF
{
$sAuthUser = $_SESSION['auth_user'];
UserRights::Login($sAuthUser); // Set the user's language
$sOldPwd = utils::ReadPostedParam('old_pwd');
$sNewPwd = utils::ReadPostedParam('new_pwd');
$sOldPwd = utils::ReadPostedParam('old_pwd', '', false, 'raw_data');
$sNewPwd = utils::ReadPostedParam('new_pwd', '', false, 'raw_data');
if (UserRights::CanChangePassword() && ((!UserRights::CheckCredentials($sAuthUser, $sOldPwd)) || (!UserRights::ChangePassword($sOldPwd, $sNewPwd))))
{
$oPage = new LoginWebPage();
@@ -412,17 +442,16 @@ EOF
require_once(APPROOT.'/setup/setuppage.class.inc.php');
$oP = new SetupWebPage(Dict::S('UI:PageTitle:FatalError'));
$oP->add("<h1>".Dict::S('UI:Login:Error:AccessAdmin')."</h1>\n");
$oP->p("<a href=\"../pages/logoff.php\">".Dict::S('UI:LogOffMenu')."</a>");
$oP->p("<a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/logoff.php\">".Dict::S('UI:LogOffMenu')."</a>");
$oP->output();
exit;
}
elseif ( (!$bIsAllowedToPortalUsers) && (UserRights::IsPortalUser()))
{
// No rights to be here, redirect to the portal
header('Location: ../portal/index.php');
header('Location: '.utils::GetAbsoluteUrlAppRoot().'portal/index.php');
}
return $sMessage;
}
}
} // End of class
?>

View File

@@ -61,30 +61,73 @@ class ApplicationMenu
{
static $aRootMenus = array();
static $aMenusIndex = array();
static $sFavoriteSiloQuery = 'SELECT Organization';
/**
* Set the query used to limit the list of displayed organizations in the drop-down menu
* @param $sOQL string The OQL query returning a list of Organization objects
* @return none
*/
static public function SetFavoriteSiloQuery($sOQL)
{
self::$sFavoriteSiloQuery = $sOQL;
}
/**
* Get the query used to limit the list of displayed organizations in the drop-down menu
* @return string The OQL query returning a list of Organization objects
*/
static public function GetFavoriteSiloQuery()
{
return self::$sFavoriteSiloQuery;
}
/**
* Main function to add a menu entry into the application, can be called during the definition
* of the data model objects
*/
static public function InsertMenu(MenuNode $oMenuNode, $iParentIndex = -1, $fRank)
static public function InsertMenu(MenuNode $oMenuNode, $iParentIndex, $fRank)
{
$index = self::GetMenuIndexById($oMenuNode->GetMenuId());
if ($index == -1)
{
// The menu does not already exist, insert it
$index = count(self::$aMenusIndex);
self::$aMenusIndex[$index] = array( 'node' => $oMenuNode, 'children' => array());
if ($iParentIndex == -1)
{
$sParentId = '';
self::$aRootMenus[] = array ('rank' => $fRank, 'index' => $index);
}
else
{
$sParentId = self::$aMenusIndex[$iParentIndex]['node']->GetMenuId();
self::$aMenusIndex[$iParentIndex]['children'][] = array ('rank' => $fRank, 'index' => $index);
}
// Note: At the time when 'parent', 'rank' and 'source_file' have been added for the reflection API,
// they were not used to display the menus (redundant or unused)
//
$aBacktrace = debug_backtrace();
$sFile = $aBacktrace[2]["file"];
self::$aMenusIndex[$index] = array('node' => $oMenuNode, 'children' => array(), 'parent' => $sParentId, 'rank' => $fRank, 'source_file' => $sFile);
}
else
{
// the menu already exists, let's combine the conditions that make it visible
self::$aMenusIndex[$index]['node']->AddCondition($oMenuNode);
}
return $index;
}
/**
* Reflection API - Get menu entries
*/
static public function ReflectionMenuNodes()
{
return self::$aMenusIndex;
}
/**
* Entry point to display the whole menu into the web page, used by iTopWebPage
@@ -263,25 +306,30 @@ abstract class MenuNode
protected $sMenuId;
protected $index;
/**
* Properties reflecting how the node has been declared
*/
protected $aReflectionProperties;
/**
* Class of objects to check if the menu is enabled, null if none
*/
protected $m_sEnableClass;
protected $m_aEnableClasses;
/**
* User Rights Action code to check if the menu is enabled, null if none
*/
protected $m_iEnableAction;
protected $m_aEnableActions;
/**
* User Rights allowed results (actually a bitmask) to check if the menu is enabled, null if none
*/
protected $m_iEnableActionResults;
protected $m_aEnableActionResults;
/**
* Stimulus to check: if the user can 'apply' this stimulus, then she/he can see this menu
*/
protected $m_sEnableStimulus;
protected $m_aEnableStimuli;
/**
* Create a menu item, sets the condition to have it displayed and inserts it into the application's main menu
@@ -297,12 +345,25 @@ abstract class MenuNode
public function __construct($sMenuId, $iParentIndex = -1, $fRank = 0, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES, $sEnableStimulus = null)
{
$this->sMenuId = $sMenuId;
$this->m_sEnableClass = $sEnableClass;
$this->m_iEnableAction = $iActionCode;
$this->m_iEnableActionResults = $iAllowedResults;
$this->m_sEnableStimulus = $sEnableStimulus;
$this->aReflectionProperties = array();
if (strlen($sEnableClass) > 0)
{
$this->aReflectionProperties['enable_class'] = $sEnableClass;
$this->aReflectionProperties['enable_action'] = $iActionCode;
$this->aReflectionProperties['enable_permission'] = $iAllowedResults;
$this->aReflectionProperties['enable_stimulus'] = $sEnableStimulus;
}
$this->m_aEnableClasses = array($sEnableClass);
$this->m_aEnableActions = array($iActionCode);
$this->m_aEnableActionResults = array($iAllowedResults);
$this->m_aEnableStimuli = array($sEnableStimulus);
$this->index = ApplicationMenu::InsertMenu($this, $iParentIndex, $fRank);
}
public function ReflectionProperties()
{
return $this->aReflectionProperties;
}
public function GetMenuId()
{
@@ -327,41 +388,57 @@ abstract class MenuNode
public function GetHyperlink($aExtraParams)
{
$aExtraParams['c[menu]'] = $this->GetIndex();
return $this->AddParams('../pages/UI.php', $aExtraParams);
return $this->AddParams(utils::GetAbsoluteUrlAppRoot().'pages/UI.php', $aExtraParams);
}
/**
* Add a limiting display condition for the same menu node. The conditions will be combined with a AND
* @param $oMenuNode MenuNode Another definition of the same menu node, with potentially different access restriction
* @return void
*/
public function AddCondition(MenuNode $oMenuNode)
{
foreach($oMenuNode->m_aEnableClasses as $index => $sClass )
{
$this->m_aEnableClasses[] = $sClass;
$this->m_aEnableActions[] = $oMenuNode->m_aEnableActions[$index];
$this->m_aEnableActionResults[] = $oMenuNode->m_aEnableActionResults[$index];
$this->m_aEnableStimuli[] = $oMenuNode->m_aEnableStimuli[$index];
}
}
/**
* Tells whether the menu is enabled (i.e. displayed) for the current user
* @return bool True if enabled, false otherwise
*/
public function IsEnabled()
{
if ($this->m_sEnableClass != null)
foreach($this->m_aEnableClasses as $index => $sClass)
{
if (MetaModel::IsValidClass($this->m_sEnableClass))
if ($sClass != null)
{
if ($this->m_sEnableStimulus != null)
if (MetaModel::IsValidClass($sClass))
{
if (!UserRights::IsStimulusAllowed($this->m_sEnableClass, $this->m_sEnableStimulus))
if ($this->m_aEnableStimuli[$index] != null)
{
return false;
if (!UserRights::IsStimulusAllowed($sClass, $this->m_aEnableStimuli[$index]))
{
return false;
}
}
if ($this->m_aEnableActions[$index] != null)
{
$iResult = UserRights::IsActionAllowed($sClass, $this->m_aEnableActions[$index]);
if (!($iResult & $this->m_aEnableActionResults[$index]))
{
return false;
}
}
}
if ($this->m_iEnableAction != null)
else
{
$iResult = UserRights::IsActionAllowed($this->m_sEnableClass, $this->m_iEnableAction);
if (($iResult & $this->m_iEnableActionResults))
{
return true;
}
else
{
return false;
}
return false;
}
return true;
}
return false;
}
return true;
}
@@ -437,6 +514,7 @@ class TemplateMenuNode extends MenuNode
{
parent::__construct($sMenuId, $iParentIndex, $fRank, $sEnableClass, $iActionCode, $iAllowedResults, $sEnableStimulus);
$this->sTemplateFile = $sTemplateFile;
$this->aReflectionProperties['template_file'] = $sTemplateFile;
}
public function GetHyperlink($aExtraParams)
@@ -495,6 +573,8 @@ class OQLMenuNode extends MenuNode
$this->sOQL = $sOQL;
$this->bSearch = $bSearch;
$this->m_aParams = array();
$this->aReflectionProperties['oql'] = $sOQL;
$this->aReflectionProperties['do_search'] = $bSearch;
// Enhancement: we could set as the "enable" condition that the user has enough rights to "read" the objects
// of the class specified by the OQL...
}
@@ -506,6 +586,10 @@ class OQLMenuNode extends MenuNode
public function SetParameters($aParams)
{
$this->m_aParams = $aParams;
foreach($aParams as $sKey => $value)
{
$this->aReflectionProperties[$sKey] = $value;
}
}
public function RenderContent(WebPage $oPage, $aExtraParams = array())
@@ -572,6 +656,7 @@ class SearchMenuNode extends MenuNode
parent::__construct($sMenuId, $iParentIndex, $fRank, $sEnableClass, $iActionCode, $iAllowedResults, $sEnableStimulus);
$this->sPageTitle = "Menu:$sMenuId+";
$this->sClass = $sClass;
$this->aReflectionProperties['class'] = $sClass;
}
public function RenderContent(WebPage $oPage, $aExtraParams = array())
@@ -611,6 +696,7 @@ class WebPageMenuNode extends MenuNode
{
parent::__construct($sMenuId, $iParentIndex, $fRank, $sEnableClass, $iActionCode, $iAllowedResults, $sEnableStimulus);
$this->sHyperlink = $sHyperlink;
$this->aReflectionProperties['url'] = $sHyperlink;
}
public function GetHyperlink($aExtraParams)
@@ -648,11 +734,12 @@ class NewObjectMenuNode extends MenuNode
{
parent::__construct($sMenuId, $iParentIndex, $fRank);
$this->sClass = $sClass;
$this->aReflectionProperties['class'] = $sClass;
}
public function GetHyperlink($aExtraParams)
{
$sHyperlink = '../pages/UI.php?operation=new&class='.$this->sClass;
$sHyperlink = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=new&class='.$this->sClass;
$aExtraParams['c[menu]'] = $this->GetIndex();
return $this->AddParams($sHyperlink, $aExtraParams);
}

View File

@@ -26,6 +26,18 @@
require_once(APPROOT."/application/nicewebpage.class.inc.php");
require_once(APPROOT."/application/applicationcontext.class.inc.php");
require_once(APPROOT."/application/user.preferences.class.inc.php");
define('BUTTON_CANCEL', 1);
define('BUTTON_BACK', 2);
define('BUTTON_NEXT', 4);
define('BUTTON_FINISH', 8);
define('PARAM_ARROW_SEP', '_x_');
class TransactionException extends Exception
{
}
/**
* Web page with some associated CSS and scripts (jquery) for a fancier display
* of the Portal web page
@@ -35,16 +47,21 @@ class PortalWebPage extends NiceWebPage
/**
* Portal menu
*/
protected $m_sWelcomeMsg;
protected $m_aMenuButtons;
public function __construct($sTitle, $sAlternateStyleSheet = '')
{
$this->m_sWelcomeMsg = '';
$this->m_aMenuButtons = array();
parent::__construct($sTitle);
$this->add_header("Content-type: text/html; charset=utf-8");
$this->add_header("Cache-control: no-cache");
$this->add_linked_stylesheet("../css/jquery.treeview.css");
$this->add_linked_stylesheet("../css/jquery.autocomplete.css");
$sAbsURLAppRoot = addslashes(utils::GetAbsoluteUrlAppRoot()); // Pass it to Javascript scripts
$oAppContext = new ApplicationContext();
$sAppContext = addslashes($oAppContext->GetForLink());
if ($sAlternateStyleSheet != '')
{
$this->add_linked_stylesheet("../portal/$sAlternateStyleSheet/portal.css");
@@ -62,10 +79,12 @@ class PortalWebPage extends NiceWebPage
$this->add_linked_script("../js/jquery.popupmenu.js");
$this->add_linked_script("../js/date.js");
$this->add_linked_script("../js/jquery.tablesorter.min.js");
$this->add_linked_script("../js/jquery.tablesorter.pager.js");
$this->add_linked_script("../js/jquery.blockUI.js");
$this->add_linked_script("../js/utils.js");
$this->add_linked_script("../js/forms-json-utils.js");
$this->add_linked_script("../js/swfobject.js");
$this->add_linked_script("../js/jquery.qtip-1.0.min.js");
$this->add_ready_script(
<<<EOF
try
@@ -106,8 +125,6 @@ try
}
});
$("table.listResults").tableHover(); // hover tables
$(".listResults").tablesorter( { widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables
$(".date-pick").datepicker({
showOn: 'button',
buttonImage: '../images/calendar.png',
@@ -117,7 +134,7 @@ try
changeMonth: true,
changeYear: true
});
$('.resizable').resizable(); // Make resizable everything that claims to be resizable !
//$('.resizable').resizable(); // Make resizable everything that claims to be resizable !
$('.caselog_header').click( function () { $(this).toggleClass('open').next('.caselog_entry').toggle(); });
}
catch(err)
@@ -130,9 +147,18 @@ EOF
$this->add_script(
<<<EOF
function CheckSelection(sMessage)
function CheckSelection(sMessage, sInputId)
{
var bResult = ($('input:checked').length > 0);
var bResult;
if (sInputId.length > 0)
{
bResult = ($('input[name='+sInputId+']:checked').length > 0);
}
else
{
// First select found...
bResult = ($('input:checked').length > 0);
}
if (!bResult)
{
alert(sMessage);
@@ -140,19 +166,69 @@ EOF
return bResult;
}
function GoBack()
function GetAbsoluteUrlAppRoot()
{
var form = $('#request_form');
var step = $('input[name=step]');
return '$sAbsURLAppRoot';
}
function AddAppContext(sURL)
{
var sContext = '$sAppContext';
if (sContext.length > 0)
{
if (sURL.indexOf('?') == -1)
{
return sURL+'?'+sContext;
}
return sURL+'&'+sContext;
}
return sURL;
}
function GoBack(sFormId)
{
var form = $('#'+sFormId);
var step_back = $('input[name=step_back]');
form.unbind('submit'); // De-activate validation
step.val(step.val() -2); // To go Back one step: next step is x, current step is x-1, previous step is x-2
step_back.val(1);
form.submit(); // Go
}
function GoHome()
{
var form = $('FORM');
form.unbind('submit'); // De-activate validation
window.location.href = '?operation=';
return false;
}
function SetWizardNextStep(sStep)
{
var next_step = $('input[id=next_step]');
next_step.val(sStep);
}
EOF
);
// For Wizard helper to process the ajax replies
$this->add('<div id="ajax_content"></div>');
}
public function SetCurrentTab($sTabLabel = '')
{
}
/**
* Specify a welcome message (optional)
*/
public function SetWelcomeMessage($sMsg)
{
$this->m_sWelcomeMsg = $sMsg;
}
/**
* Add a button to the portal's main menu
@@ -161,17 +237,590 @@ EOF
{
$this->m_aMenuButtons[] = array('id' => $sId, 'label' => $sLabel, 'hyperlink' => $sHyperlink);
}
var $m_bEnableDisconnectButton = true;
public function EnableDisconnectButton($bEnable)
{
$this->m_bEnableDisconnectButton = $bEnable;
}
public function output()
{
$sMenu = '';
$this->AddMenuButton('logoff', 'Portal:Disconnect', '../pages/logoff.php'); // This menu is always present and is the last one
if ($this->m_bEnableDisconnectButton)
{
$this->AddMenuButton('logoff', 'Portal:Disconnect', utils::GetAbsoluteUrlAppRoot().'pages/logoff.php'); // This menu is always present and is the last one
}
foreach($this->m_aMenuButtons as $aMenuItem)
{
$sMenu .= "<a class=\"button\" id=\"{$aMenuItem['id']}\" href=\"{$aMenuItem['hyperlink']}\"><span>".Dict::S($aMenuItem['label'])."</span></a>";
}
$this->s_content = '<div id="portal"><div id="banner"><div id="logo"></div>'.$sMenu.'</div><div id="content">'.$this->s_content.'</div></div>';
$this->s_content = '<div id="portal"><div id="welcome">'.$this->m_sWelcomeMsg.'</div><div id="banner"><div id="logo"></div><div id="menu">'.$sMenu.'</div></div><div id="content">'.$this->s_content.'</div></div>';
parent::output();
}
/**
* Displays a list of objects, without any hyperlink (except for the object's details)
* @param DBObjectSet $oSet The set of objects to display
* @param Array $aZList The ZList (list of field codes) to use for the tabular display
* @param String $sEmptyListMessage Message displayed whenever the list is empty
* @return string The HTML text representing the list
*/
public function DisplaySet($oSet, $aZList, $sEmptyListMessage = '')
{
if ($oSet->Count() > 0)
{
$sClass = $oSet->GetClass();
if (is_subclass_of($sClass, 'cmdbAbstractObject'))
{
// Home-made and very limited display of an object set
$sUniqueId = $sClass.$this->GetUniqueId();
$this->add("<div id=\"$sUniqueId\">\n"); // The id here MUST be the same as currentId, otherwise the pagination will be broken
cmdbAbstractObject::DisplaySet($this, $oSet, array('currentId' => $sUniqueId, 'menu' => false, 'zlist' => false, 'extra_fields' => implode(',', $aZList)));
$this->add("</div>\n");
}
else
{
// Home-made and very limited display of an object set
$aAttribs = array();
$aValues = array();
$aAttribs['key'] = array('label' => MetaModel::GetName($sClass), 'description' => '');
foreach($aZList as $sAttCode)
{
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
$aAttribs[$sAttCode] = array('label' => $oAttDef->GetLabel(), 'description' => $oAttDef->GetDescription());
}
while($oObj = $oSet->Fetch())
{
$aRow = array();
$aRow['key'] = '<a href="./index.php?operation=details&class='.get_class($oObj).'&id='.$oObj->GetKey().'">'.$oObj->GetName().'</a>';
$sHilightClass = $oObj->GetHilightClass();
if ($sHilightClass != '')
{
$aRow['@class'] = $sHilightClass;
}
foreach($aZList as $sAttCode)
{
$aRow[$sAttCode] = $oObj->GetAsHTML($sAttCode);
}
$aValues[$oObj->GetKey()] = $aRow;
}
$this->table($aAttribs, $aValues);
}
}
elseif (strlen($sEmptyListMessage) > 0)
{
$this->add($sEmptyListMessage);
}
}
/**
* Display the attributes of an object (no title, no form)
* @param Object $oObj Any kind of object
* @param aAttList The list of attributes to display
* @return void
*/
public function DisplayObjectDetails($oObj, $aAttList)
{
$sClass = get_class($oObj);
$aDetails = array();
foreach($aAttList as $sAttCode)
{
$iFlags = $oObj->GetAttributeFlags($sAttCode);
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
if ( (!$oAttDef->IsLinkSet()) && (($iFlags & OPT_ATT_HIDDEN) == 0) )
{
// Don't display linked set and non-visible attributes (in this state)
$sDisplayValue = $oObj->GetAsHTML($sAttCode);
$aDetails[] = array('label' => '<span title="'.MetaModel::GetDescription($sClass, $sAttCode).'">'.MetaModel::GetLabel($sClass, $sAttCode).'</span>', 'value' => $sDisplayValue);
}
}
$this->details($aDetails);
}
/**
* DisplayObjectLinkset
* @param Object $oObj Any kind of object
* @param $sLinkSetAttCode The attribute code of the link set attribute to display
* @param $sRemoteAttCode The external key on the linked class, pointing to the remote objects
* @param $aZList The list of attribute of the remote object
* @param $sEmptyListMessage The message to display if the list is empty
* @return void
*/
public function DisplayObjectLinkset($oObj, $sLinkSetAttCode, $sRemoteAttCode, $aZList, $sEmptyListMessage = '', $oSearchRestriction = null)
{
if (empty($sEmptyListMessage))
{
$sEmptyListMessage = Dict::S('UI:Search:NoObjectFound');
}
$oLinkSet = $oObj->Get($sLinkSetAttCode);
if ($oLinkSet->Count() > 0)
{
$sClass = $oLinkSet->GetClass();
$oExtKeyToRemote = MetaModel::GetAttributeDef($sClass, $sRemoteAttCode);
$sRemoteClass = $oExtKeyToRemote->GetTargetClass();
if (is_null($oSearchRestriction))
{
$oObjSearch = new DBObjectSearch($sRemoteClass);
}
else
{
$oObjSearch = $oSearchRestriction;
}
$oObjSearch->AddCondition_ReferencedBy($oLinkSet->GetFilter(), $sRemoteAttCode);
$aExtraParams = array('menu' => false, 'zlist' => false, 'extra_fields' => implode(',', $aZList));
$oBlock = new DisplayBlock($oObjSearch, 'list', false);
$oBlock->Display($this, 1, $aExtraParams);
}
elseif (strlen($sEmptyListMessage) > 0)
{
$this->add($sEmptyListMessage);
}
}
protected function DisplaySearchField($sClass, $sAttSpec, $aExtraParams, $sPrefix, $sFieldName = null)
{
if (is_null($sFieldName))
{
$sFieldName = str_replace('->', PARAM_ARROW_SEP, $sAttSpec);
}
$iPos = strpos($sAttSpec, '->');
if ($iPos !== false)
{
$sAttCode = substr($sAttSpec, 0, $iPos);
$sSubSpec = substr($sAttSpec, $iPos + 2);
if (!MetaModel::IsValidAttCode($sClass, $sAttCode))
{
throw new Exception("Invalid attribute code '$sClass/$sAttCode' in search specification '$sAttSpec'");
}
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
if ($oAttDef->IsLinkSet())
{
$sTargetClass = $oAttDef->GetLinkedClass();
}
elseif ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE))
{
$sTargetClass = $oAttDef->GetTargetClass(EXTKEY_ABSOLUTE);
}
else
{
throw new Exception("Attribute specification '$sAttSpec', '$sAttCode' should be either a link set or an external key");
}
$this->DisplaySearchField($sTargetClass, $sSubSpec, $aExtraParams, $sPrefix, $sFieldName);
}
else
{
// $sAttSpec is an attribute code
//
$this->add('<span style="white-space: nowrap;padding:5px;display:inline-block;">');
$sFilterValue = '';
$sFilterValue = utils::ReadParam($sPrefix.$sFieldName, '', false, 'raw_data');
$sFilterOpCode = null; // Use the default 'loose' OpCode
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttSpec);
if ($oAttDef->IsExternalKey())
{
$sTargetClass = $oAttDef->GetTargetClass();
$oAllowedValues = new DBObjectSet(new DBObjectSearch($sTargetClass));
$iFieldSize = $oAttDef->GetMaxSize();
$iMaxComboLength = $oAttDef->GetMaximumComboLength();
$this->add("<label>".MetaModel::GetFilterLabel($sClass, $sAttSpec).":</label>&nbsp;");
//$oWidget = UIExtKeyWidget::DIsplayFromAttCode($sAttSpec, $sClass, $oAttDef->GetLabel(), $oAllowedValues, $sFilterValue, $sPrefix.$sFieldName, false, '', $sPrefix, '');
//$this->add($oWidget->Display($this, $aExtraParams, true /* bSearchMode */));
$aExtKeyParams = $aExtraParams;
$aExtKeyParams['iFieldSize'] = $oAttDef->GetMaxSize();
$aExtKeyParams['iMinChars'] = $oAttDef->GetMinAutoCompleteChars();
// DisplayFromAttCode($this, $sAttCode, $sClass, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName = '', $sFormPrefix = '', $aArgs, $bSearchMode = false)
$sHtml = UIExtKeyWidget::DisplayFromAttCode($this, $sAttSpec, $sClass, $oAttDef->GetLabel(), $oAllowedValues, $sFilterValue, $sPrefix.$sFieldName, false, $sPrefix.$sFieldName, $sPrefix, $aExtKeyParams, true);
$this->add($sHtml);
}
else
{
$aAllowedValues = MetaModel::GetAllowedValues_flt($sClass, $sAttSpec, $aExtraParams);
if (is_null($aAllowedValues))
{
// Any value is possible, display an input box
$this->add("<label>".MetaModel::GetFilterLabel($sClass, $sAttSpec).":</label>&nbsp;<input class=\"textSearch\" name=\"$sPrefix$sFieldName\" value=\"$sFilterValue\"/>\n");
}
else
{
//Enum field or external key, display a combo
$sValue = "<select name=\"$sPrefix$sFieldName\">\n";
$sValue .= "<option value=\"\">".Dict::S('UI:SearchValue:Any')."</option>\n";
foreach($aAllowedValues as $key => $value)
{
if ($sFilterValue == $key)
{
$sSelected = ' selected';
}
else
{
$sSelected = '';
}
$sValue .= "<option value=\"$key\"$sSelected>$value</option>\n";
}
$sValue .= "</select>\n";
$this->add("<label>".MetaModel::GetFilterLabel($sClass, $sAttSpec).":</label>&nbsp;$sValue\n");
}
}
unset($aExtraParams[$sFieldName]);
$this->add('</span> ');
$sTip = $oAttDef->GetHelpOnSmartSearch();
if (strlen($sTip) > 0)
{
$sTip = addslashes($sTip);
$sTip = str_replace(array("\n", "\r"), " ", $sTip);
// :input does represent in form visible input (INPUT, SELECT, TEXTAREA)
$this->add_ready_script("$(':input[name={$sPrefix}$sFieldName]').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );");
}
}
}
public function DisplaySearchForm($sClass, $aAttList, $aExtraParams, $sPrefix, $bClosed = true)
{
$sCSSClass = ($bClosed) ? 'DrawerClosed' : '';
$this->add("<div id=\"ds_$sPrefix\" class=\"SearchDrawer $sCSSClass\">\n");
$this->add_ready_script(
<<<EOF
$("#dh_$sPrefix").click( function() {
$("#ds_$sPrefix").slideToggle('normal', function() { $("#ds_$sPrefix").parent().resize(); } );
$("#dh_$sPrefix").toggleClass('open');
});
EOF
);
$this->add("<form id=\"search_$sClass\" action=\"\" method=\"post\">\n"); // Don't use $_SERVER['SCRIPT_NAME'] since the form may be called asynchronously (from ajax.php)
// $this->add("<h2>".Dict::Format('UI:SearchFor_Class_Objects', 'xxxxxx')."</h2>\n");
$this->add("<p>\n");
foreach($aAttList as $sAttSpec)
{
//$oAppContext->Reset($sAttSpec); // Make sure the same parameter will not be passed twice
$this->DisplaySearchField($sClass, $sAttSpec, $aExtraParams, $sPrefix);
}
$this->add("</p>\n");
$this->add("<p align=\"right\"><input type=\"submit\" value=\"".Dict::S('UI:Button:Search')."\"></p>\n");
foreach($aExtraParams as $sName => $sValue)
{
$this->add("<input type=\"hidden\" name=\"$sName\" value=\"$sValue\" />\n");
}
// $this->add($oAppContext->GetForForm());
$this->add("</form>\n");
$this->add("</div>\n");
$this->add("<div class=\"HRDrawer\"></div>\n");
$this->add("<div id=\"dh_$sPrefix\" class=\"DrawerHandle\">".Dict::S('UI:SearchToggle')."</div>\n");
}
/**
* Read parameters from the page
* Parameters that were absent from the page's parameters are not set in the resulting hash array
* @input string $sMethod Either get or post
* @return Hash Array of name => value corresponding to the parameters that were passed to the page
*/
public function ReadAllParams($sParamList, $sPrefix = 'attr_')
{
$aParams = explode(',', $sParamList);
$aValues = array();
foreach($aParams as $sName)
{
$sName = trim($sName);
$value = utils::ReadParam($sPrefix.$sName, null, false, 'raw_data');
if (!is_null($value))
{
$aValues[$sName] = $value;
}
}
return $aValues;
}
/**
* Outputs a list of parameters as hidden fields
* Example: attr_dummy[-123][id] = "blah"
* @param Hash $aParameters Array name => value for the parameters
* @param Array $aExclude The list of parameters that must not be handled this way (probably already in the visible part of the form)
* @return void
*/
protected function DumpHiddenParamsInternal($sName, $value)
{
if (is_array($value))
{
foreach($value as $sKey => $item)
{
$this->DumpHiddenParamsInternal($sName.'['.$sKey.']', $item);
}
}
else
{
$this->Add("<input type=\"hidden\" name=\"$sName\" value=\"$value\">");
}
}
/**
* Outputs a list of parameters as hidden field into the current page
* (must be called when inside a form)
* @param Hash $aParameters Array name => value for the parameters
* @param Array $aExclude The list of parameters that must not be handled this way (probably already in the visible part of the form)
* @return void
*/
public function DumpHiddenParams($aParameters, $aExclude = null, $sPrefix = 'attr_')
{
foreach($aParameters as $sAttCode => $value)
{
if (is_null($aExclude) || !in_array($sAttCode, $aExclude))
{
$this->DumpHiddenParamsInternal($sPrefix.$sAttCode, $value);
}
}
}
public function PostedParamsToFilter($sClass, $aAttList, $sPrefix)
{
$oFilter = new DBObjectSearch($sClass);
$iCountParams = 0;
foreach($aAttList as $sAttSpec)
{
$sFieldName = str_replace('->', PARAM_ARROW_SEP, $sAttSpec);
$value = utils::ReadPostedParam($sPrefix.$sFieldName, null, 'raw_data');
if (!is_null($value) && strlen($value) > 0)
{
$oFilter->AddConditionAdvanced($sAttSpec, $value);
$iCountParams++;
}
}
if ($iCountParams == 0)
{
return null;
}
else
{
return $oFilter;
}
}
/**
* Updates the object form POSTED arguments, and writes it into the DB (applies a stimuli if requested)
* @param DBObject $oObj The object to update
* $param array $aAttList If set, this will limit the list of updated attributes
* @return void
*/
public function DoUpdateObjectFromPostedForm(DBObject $oObj, $aAttList = null)
{
$sTransactionId = utils::ReadPostedParam('transaction_id', '');
if (!utils::IsTransactionValid($sTransactionId))
{
throw new TransactionException();
}
$sClass = get_class($oObj);
$sStimulus = trim(utils::ReadPostedParam('apply_stimulus', ''));
$sTargetState = '';
if (!empty($sStimulus))
{
// Compute the target state
$aTransitions = $oObj->EnumTransitions();
if (!isset($aTransitions[$sStimulus]))
{
throw new ApplicationException(Dict::Format('UI:Error:Invalid_Stimulus_On_Object_In_State', $sStimulus, $oObj->GetName(), $oObj->GetStateLabel()));
}
$sTargetState = $aTransitions[$sStimulus]['target_state'];
}
$oObj->UpdateObjectFromPostedForm('' /* form prefix */, $aAttList, $sTargetState);
// Optional: apply a stimulus
//
if (!empty($sStimulus))
{
if (!$oObj->ApplyStimulus($sStimulus))
{
throw new Exception("Cannot apply stimulus '$sStimulus' to {$oObj->GetName()}");
}
}
// Record the change
//
$oMyChange = MetaModel::NewObject("CMDBChange");
$oMyChange->Set("date", time());
$sUserString = CMDBChange::GetCurrentUserName();
$oMyChange->Set("userinfo", $sUserString);
$iChangeId = $oMyChange->DBInsert();
$oObj->DBUpdateTracked($oMyChange);
// Trigger ?
//
$aClasses = MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL);
$sClassList = implode(", ", CMDBSource::Quote($aClasses));
$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnPortalUpdate AS t WHERE t.target_class IN ($sClassList)"));
while ($oTrigger = $oSet->Fetch())
{
$oTrigger->DoActivate($oObj->ToArgs('this'));
}
$this->p("<h1>".Dict::Format('UI:Class_Object_Updated', MetaModel::GetName(get_class($oObj)), $oObj->GetName())."</h1>\n");
}
/**
* Find the object of the specified Class/ID.
* @param WebPage $oP The current page
* @return DBObject The found object, or throws an exception in case of failure
*/
public function FindObjectFromArgs($aAllowedClasses = null)
{
$sClass = utils::ReadParam('class', '', true, 'class');
$iId = utils::ReadParam('id', 0, true, 'integer');
if (empty($sClass))
{
throw new Exception("Missing argument 'class'");
}
if (!MetaModel::IsValidClass($sClass))
{
throw new Exception("Wrong value for argument 'class': $sClass");
}
if ($iId == 0)
{
throw new Exception("Missing argument 'id'");
}
if(!is_null($aAllowedClasses))
{
$bAllowed = false;
foreach($aAllowedClasses as $sParentClass)
{
if (MetaModel::IsParentClass($sParentClass, $sClass))
{
$bAllowed = true;
}
}
if (!$bAllowed)
{
throw new Exception("Class '$sClass not allowed in this implementation'");
}
}
$oObj = MetaModel::GetObject($sClass, $iId, false);
if (!is_object($oObj))
{
throw new Exception("Could not find the object $sClass/$iId");
}
return $oObj;
}
var $m_sWizardId = null;
public function WizardFormStart($sId = '', $sNextStep = null, $bAttachment = false, $sMethod = 'post')
{
$this->m_sWizardId = $sId;
// multipart... needed for file upload
$this->add("<form id=\"{$this->m_sWizardId}\" method=\"$sMethod\" enctype=\"multipart/form-data\">\n");
$aPreviousSteps = $this->GetWizardStepHistory();
if (utils::ReadParam('step_back', 0) == 1)
{
// Back into the past history
array_pop($aPreviousSteps);
}
else
{
// Moving forward
array_push($aPreviousSteps, utils::ReadParam('next_step'));
}
$sStepHistory = implode(',', $aPreviousSteps);
$this->add("<input type=\"hidden\" id=\"step_history\" name=\"step_history\" value=\"$sStepHistory\">");
if (!is_null($sNextStep))
{
$this->add("<input type=\"hidden\" id=\"next_step\" name=\"next_step\" value=\"$sNextStep\">");
}
$this->add("<input type=\"hidden\" id=\"step_back\" name=\"step_back\" value=\"0\">");
$sTransactionId = utils::GetNewTransactionId();
$this->SetTransactionId($sTransactionId);
$this->add("<input type=\"hidden\" id=\"transaction_id\" name=\"transaction_id\" value=\"$sTransactionId\">\n");
$this->add_ready_script("$(window).unload(function() { OnUnload('$sTransactionId') } );\n");
}
public function WizardFormButtons($iButtonFlags)
{
$aButtons = array();
if ($iButtonFlags & BUTTON_CANCEL)
{
$aButtons[] = "<input id=\"btn_cancel\" type=\"button\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"GoHome();\">";
}
if ($iButtonFlags & BUTTON_BACK)
{
$aButtons[] = "<input id=\"btn_back\" type=\"submit\" value=\"".Dict::S('UI:Button:Back')."\" onClick=\"GoBack('{$this->m_sWizardId}');\">";
}
if ($iButtonFlags & BUTTON_NEXT)
{
$aButtons[] = "<input id=\"btn_next\" type=\"submit\" value=\"".Dict::S('UI:Button:Next')."\">";
}
if ($iButtonFlags & BUTTON_FINISH)
{
$aButtons[] = "<input id=\"btn_finish\" type=\"submit\" value=\"".Dict::S('UI:Button:Finish')."\">";
}
$this->add('<div id="buttons">');
$this->add(implode('', $aButtons));
$this->add('</div>');
}
public function WizardFormEnd()
{
$this->add("</form>\n");
}
public function GetWizardStep()
{
if (utils::ReadParam('step_back', 0) == 1)
{
// Take the value into the history - one level above
$aPreviousSteps = $this->GetWizardStepHistory();
array_pop($aPreviousSteps);
return end($aPreviousSteps);
}
else
{
return utils::ReadParam('next_step');
}
}
protected function GetWizardStepHistory()
{
$sRawHistory = trim(utils::ReadParam('step_history', '', false, 'raw_data'));
if (strlen($sRawHistory) == 0)
{
return array();
}
else
{
return explode(',', $sRawHistory);
}
}
public function WizardCheckSelectionOnSubmit($sMessageIfNoSelection, $sInputName = '')
{
$this->add_ready_script(
<<<EOF
$('#{$this->m_sWizardId}').submit(function() {
return CheckSelection('$sMessageIfNoSelection', '$sInputName');
});
EOF
);
}
}
?>

View File

@@ -0,0 +1,107 @@
<?php
// Copyright (C) 2010 Combodo SARL
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; version 3 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
/**
* Persistent class Event and derived
* Application internal events
* There is also a file log
*
* @author Erwan Taloc <erwan.taloc@combodo.com>
* @author Romain Quetiez <romain.quetiez@combodo.com>
* @author Denis Flaven <denis.flaven@combodo.com>
* @license http://www.opensource.org/licenses/gpl-3.0.html LGPL
*/
abstract class Query extends cmdbAbstractObject
{
public static function Init()
{
$aParams = array
(
"category" => "core/cmdb,view_in_gui,application",
"key_type" => "autoincrement",
"name_attcode" => "name",
"state_attcode" => "",
"reconc_keys" => array(),
"db_table" => "priv_query",
"db_key_field" => "id",
"db_finalclass_field" => "realclass",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("fields", array("allowed_values"=>null, "sql"=>"fields", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
// Display lists
MetaModel::Init_SetZListItems('details', array('name', 'description', 'fields')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('description')); // Attributes to be displayed for a list
// Search criteria
// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
}
}
class QueryOQL extends Query
{
public static function Init()
{
$aParams = array
(
"category" => "core/cmdb,view_in_gui,application",
"key_type" => "autoincrement",
"name_attcode" => "name",
"state_attcode" => "",
"reconc_keys" => array(),
"db_table" => "priv_query_oql",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeOQL("oql", array("allowed_values"=>null, "sql"=>"oql", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
// Display lists
MetaModel::Init_SetZListItems('details', array('name', 'description', 'oql', 'fields')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('description')); // Attributes to be displayed for a list
// Search criteria
// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
}
function DisplayBareProperties(WebPage $oPage, $bEditMode = false, $sPrefix = '', $aExtraParams = array())
{
parent::DisplayBareProperties($oPage, $bEditMode, $sPrefix, $aExtraParams);
if (!$bEditMode)
{
$sUrl = utils::GetAbsoluteUrlAppRoot().'webservices/export.php?format=spreadsheet&login_mode=basic&query='.$this->GetKey();
$sOql = $this->Get('oql');
$oSearch = DBObjectSearch::FromOQL($sOql);
$aParameters = $oSearch->GetQueryParams();
foreach($aParameters as $sParam => $val)
{
$sUrl .= '&arg_'.$sParam.'=["'.$sParam.'"]';
}
$oPage->p(Dict::S('UI:Query:UrlForExcel').':<br/><textarea cols="80" rows="3" READONLY>'.$sUrl.'</textarea>');
}
}
}
?>

View File

@@ -0,0 +1,531 @@
<?php
// Copyright (C) 2010 Combodo SARL
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; version 3 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
/**
* SqlBlock - display tables or charts, given an SQL query - use cautiously!
*
*
* @author Erwan Taloc <erwan.taloc@combodo.com>
* @author Romain Quetiez <romain.quetiez@combodo.com>
* @author Denis Flaven <denis.flaven@combodo.com>
* @license http://www.opensource.org/licenses/gpl-3.0.html LGPL
*/
require_once(APPROOT.'/application/webpage.class.inc.php');
require_once(APPROOT.'/application/utils.inc.php');
require_once(APPROOT.'/pages/php-ofc-library/open-flash-chart.php');
/**
* Helper class to design optimized dashboards, based on an SQL query
*
*/
class SqlBlock
{
protected $m_sQuery;
protected $m_aColumns;
protected $m_sTitle;
protected $m_sType;
protected $m_aParams;
public function __construct($sQuery, $aColumns, $sTitle, $sType, $aParams = array())
{
$this->m_sQuery = $sQuery;
$this->m_aColumns = $aColumns;
$this->m_sTitle = $sTitle;
$this->m_sType = $sType;
$this->m_aParams = $aParams;
}
/**
* Constructs a SqlBlock object from an XML template
/*
*
* <sqlblock>
* <sql>SELECT date_format(start_date, '%d') AS Date, count(*) AS Count FROM ticket WHERE DATE_SUB(NOW(), INTERVAL 15 DAY) &lt; start_date AND finalclass = 'UserIssue' GROUP BY date_format(start_date, '%d') AND $CONDITION(param1, ticket.org_id)$</sql>
* <type>table</type>
* <title>UserRequest:Overview-Title</title>
* <parameter>
* <name>param1</name>
* <type>context</type>
* <mapping>org_id</mapping>
* </parameter>
* <column>
* <name>Date</name>
* <label>UserRequest:Overview-Date</label>
* <drilldown></drilldown>
* </column>
* <column>
* <name>Count</name>
* <label>UserRequest:Overview-Count</label>
* <drilldown>SELECT UserIssue WHERE date_format(start_date, '%d') = :Date</drilldown>
* </column>
* </sqlblock>
*
* Tags
* - sql: a (My)SQL query. Do not forget to use html entities (e.g. &lt; for <)
* - type: table (default), bars or pie. If bars or pie is selected only the two first columns are taken into account.
* - title: optional title, typed in clear or given as a dictionnary entry
* - parameter: specifies how to map the context parameters (namely org_id) to a given named parameter in the query.
* The expression $CONDITION(<param_name>, <sql_column_name>) will be automatically replaced by:
* either the string "1" if there is no restriction on the organisation in iTop
* or the string "(<sql_column_name>=<value_of_org_id>)" if there is a limitation to one organizations in iTop
* or the string "(<sql_column_name> IN (<values_of_org_id>))" if there is a limitation to a given set of organizations in iTop
* - column: specification of a column (not displayed if omitted)
* - column / name: name of the column in the SQL query (use aliases)
* - column / label: label, typed in clear or given as a dictionnary entry
* - column / drilldown: NOT IMPLEMENTED YET - OQL with parameters corresponding to column names (in the query)
*
* @param $sTemplate string The XML template
* @return DisplayBlock The DisplayBlock object, or null if the template is invalid
*/
public static function FromTemplate($sTemplate)
{
$oXml = simplexml_load_string('<root>'.$sTemplate.'</root>', 'SimpleXMLElement', LIBXML_NOCDATA);
if (false)
{
// Debug
echo "<pre>\n";
print_r($oXml);
echo "</pre>\n";
}
if (isset($oXml->title))
{
$sTitle = (string)$oXml->title;
}
if (isset($oXml->type))
{
$sType = (string)$oXml->type;
}
else
{
$sType = 'table';
}
if (!isset($oXml->sql))
{
throw new Exception('Missing tag "sql" in sqlblock');
}
$sQuery = (string)$oXml->sql;
$aColumns = array();
if (isset($oXml->column))
{
foreach ($oXml->column AS $oColumnData)
{
if (!isset($oColumnData->name))
{
throw new Exception("Missing tag 'name' in sqlblock/column");
}
$sName = (string) $oColumnData->name;
if (strlen($sName) == 0)
{
throw new Exception("Empty tag 'name' in sqlblock/column");
}
$aColumns[$sName] = array();
if (isset($oColumnData->label))
{
$sLabel = (string)$oColumnData->label;
if (strlen($sLabel) > 0)
{
$aColumns[$sName]['label'] = Dict::S($sLabel);
}
}
if (isset($oColumnData->drilldown))
{
$sDrillDown = (string)$oColumnData->drilldown;
if (strlen($sDrillDown) > 0)
{
$aColumns[$sName]['drilldown'] = $sDrillDown;
}
}
}
}
$aParams = array();
if (isset($oXml->parameter))
{
foreach ($oXml->parameter AS $oParamData)
{
if (!isset($oParamData->name))
{
throw new Exception("Missing tag 'name' for parameter in sqlblock/column");
}
$sName = (string) $oParamData->name;
if (strlen($sName) == 0)
{
throw new Exception("Empty tag 'name' for parameter in sqlblock/column");
}
if (!isset($oParamData->mapping))
{
throw new Exception("Missing tag 'mapping' for parameter in sqlblock/column");
}
$sMapping = (string) $oParamData->mapping;
if (strlen($sMapping) == 0)
{
throw new Exception("Empty tag 'mapping' for parameter in sqlblock/column");
}
if (isset($oParamData->type))
{
$sParamType = $oParamData->type;
}
else
{
$sParamType = 'context';
}
$aParams[$sName] = array('mapping' => $sMapping, 'type' => $sParamType);
}
}
return new SqlBlock($sQuery, $aColumns, $sTitle, $sType, $aParams);
}
/**
* Applies the defined parameters into the SQL query
* @return string the SQL query to execute
*/
public function BuildQuery()
{
$oAppContext = new ApplicationContext();
$sQuery = $this->m_sQuery;
$sQuery = str_replace('$DB_PREFIX$', MetaModel::GetConfig()->GetDBSubname(), $sQuery); // put the tables DB prefix (if any)
foreach($this->m_aParams as $sName => $aParam)
{
if ($aParam['type'] == 'context')
{
$sSearchPattern = '/\$CONDITION\('.$sName.',([^\)]+)\)\$/';
$value = $oAppContext->GetCurrentValue($aParam['mapping']);
if (empty($value))
{
$sSQLExpr = '(1)';
}
else
{
// Special case for managing the hierarchy of organizations
if (($aParam['mapping'] == 'org_id') && ( MetaModel::IsValidClass('Organization')))
{
$sHierarchicalKeyCode = MetaModel::IsHierarchicalClass('Organization');
if ($sHierarchicalKeyCode != false)
{
// organizations are in hierarchy... gather all the orgs below the given one...
$sOQL = "SELECT Organization AS node JOIN Organization AS root ON node.$sHierarchicalKeyCode BELOW root.id WHERE root.id = :value";
$oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), array(), array('value' => $value));
$aOrgIds = array();
while($oOrg = $oSet->Fetch())
{
$aOrgIds[]= $oOrg->GetKey();
}
$sSQLExpr = '($1 IN('.implode(',', $aOrgIds).'))';
}
else
{
$sSQLExpr = '($1 = '.CMDBSource::Quote($value).')';
}
}
else
{
$sSQLExpr = '($1 = '.CMDBSource::Quote($value).')';
}
}
$sQuery = preg_replace($sSearchPattern, $sSQLExpr, $sQuery);
}
}
return $sQuery;
}
public function RenderContent(WebPage $oPage, $aExtraParams = array())
{
if (empty($aExtraParams['currentId']))
{
$sId = 'sqlblock_'.$oPage->GetUniqueId(); // Works only if the page is not an Ajax one !
}
else
{
$sId = $aExtraParams['currentId'];
}
// $oPage->add($this->GetRenderContent($oPage, $aExtraParams, $sId));
$sQuery = $this->BuildQuery();
$res = CMDBSource::Query($sQuery);
$aQueryCols = CMDBSource::GetColumns($res);
// Prepare column definitions (check + give default values)
//
foreach($this->m_aColumns as $sName => $aColumnData)
{
if (!in_array($sName, $aQueryCols))
{
throw new Exception("Unknown column name '$sName' in sqlblock column");
}
if (!isset($aColumnData['label']))
{
$this->m_aColumns[$sName]['label'] = $sName;
}
if (isset($aColumnData['drilldown']) && !empty($aColumnData['drilldown']))
{
// Check if the OQL is valid
try
{
$this->m_aColumns[$sName]['filter'] = DBObjectSearch::FromOQL($aColumnData['drilldown']);
}
catch(OQLException $e)
{
unset($aColumnData['drilldown']);
}
}
}
if (strlen($this->m_sTitle) > 0)
{
$oPage->add("<h2>".Dict::S($this->m_sTitle)."</h2>\n");
}
switch ($this->m_sType)
{
case 'bars':
case 'pie':
$aColNames = array_keys($this->m_aColumns);
$sXColName = $aColNames[0];
$sYColName = $aColNames[1];
$aData = array();
$aRows = array();
while($aRow = CMDBSource::FetchArray($res))
{
$aData[$aRow[$sXColName]] = $aRow[$sYColName];
$aRows[$aRow[$sXColName]] = $aRow;
}
$this->RenderChart($oPage, $sId, $aData, $this->m_aColumns[$sYColName]['drilldown'], $aRows);
break;
default:
case 'table':
$oAppContext = new ApplicationContext();
$sContext = $oAppContext->GetForLink();
if (!empty($sContext))
{
$sContext = '&'.$sContext;
}
$aDisplayConfig = array();
foreach($this->m_aColumns as $sName => $aColumnData)
{
$aDisplayConfig[$sName] = array('label' => $aColumnData['label'], 'description' => '');
}
$aDisplayData = array();
while($aRow = CMDBSource::FetchArray($res))
{
$aSQLColNames = array_keys($aRow);
$aDisplayRow = array();
foreach($this->m_aColumns as $sName => $aColumnData)
{
if (isset($aColumnData['filter']))
{
$sFilter = $aColumnData['drilldown'];
$sClass = $aColumnData['filter']->GetClass();
$sFilter = str_replace('SELECT '.$sClass, '', $sFilter);
foreach($aSQLColNames as $sColName)
{
$sFilter = str_replace(':'.$sColName, "'".addslashes( $aRow[$sColName] )."'", $sFilter);
}
$sURL = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search_oql&search_form=0&oql_class='.$sClass.'&oql_clause='.urlencode($sFilter).'&format=html'.$sContext;
$aDisplayRow[$sName] = '<a href="'.$sURL.'">'.$aRow[$sName]."</a>";
}
else
{
$aDisplayRow[$sName] = $aRow[$sName];
}
}
$aDisplayData[] = $aDisplayRow;
}
$oPage->table($aDisplayConfig, $aDisplayData);
break;
}
}
public function GetRenderContent(WebPage $oPage, $aExtraParams = array(), $sId)
{
$sHtml = '';
return $sHtml;
}
protected function RenderChart($oPage, $sId, $aValues, $sDrillDown = '', $aRows = array())
{
// 1- Compute Open Flash Chart data
//
$aValueKeys = array();
$index = 0;
if ((count($aValues) > 0) && ($sDrillDown != ''))
{
$oFilter = DBObjectSearch::FromOQL($sDrillDown);
$sClass = $oFilter->GetClass();
$sOQLClause = str_replace('SELECT '.$sClass, '', $sDrillDown);
$aSQLColNames = array_keys(current($aRows)); // Read the list of columns from the current (i.e. first) element of the array
$oAppContext = new ApplicationContext();
$sURL = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search_oql&search_form=0&oql_class='.$sClass.'&format=html&'.$oAppContext->GetForLink().'&oql_clause=';
}
$aURLs = array();
foreach($aValues as $key => $value)
{
// Make sure that values are integers (so that max() will work....)
// and build an array of STRING with the keys (numeric keys are transformed into string by PHP :-(
$aValues[$key] = (int)$value;
$aValueKeys[] = (string)$key;
// Build the custom query for the 'drill down' on each element
if ($sDrillDown != '')
{
$sFilter = $sOQLClause;
foreach($aSQLColNames as $sColName)
{
$sFilter = str_replace(':'.$sColName, "'".addslashes( $aRows[$key][$sColName] )."'", $sFilter);
$aURLs[$index] = $sURL.urlencode($sFilter);
}
}
$index++;
}
$oChart = new open_flash_chart();
if ($this->m_sType == 'bars')
{
$oChartElement = new bar_glass();
if (count($aValues) > 0)
{
$maxValue = max($aValues);
}
else
{
$maxValue = 1;
}
$oYAxis = new y_axis();
$aMagicValues = array(1,2,5,10);
$iMultiplier = 1;
$index = 0;
$iTop = $aMagicValues[$index % count($aMagicValues)]*$iMultiplier;
while($maxValue > $iTop)
{
$index++;
$iTop = $aMagicValues[$index % count($aMagicValues)]*$iMultiplier;
if (($index % count($aMagicValues)) == 0)
{
$iMultiplier = $iMultiplier * 10;
}
}
//echo "oYAxis->set_range(0, $iTop, $iMultiplier);\n";
$oYAxis->set_range(0, $iTop, $iMultiplier);
$oChart->set_y_axis( $oYAxis );
$aBarValues = array();
foreach($aValues as $iValue)
{
$oBarValue = new bar_value($iValue);
$oBarValue->on_click("ofc_drilldown_{$sId}");
$aBarValues[] = $oBarValue;
}
$oChartElement->set_values($aBarValues);
//$oChartElement->set_values(array_values($aValues));
$oXAxis = new x_axis();
$oXLabels = new x_axis_labels();
// set them vertical
$oXLabels->set_vertical();
// set the label text
$oXLabels->set_labels($aValueKeys);
// Add the X Axis Labels to the X Axis
$oXAxis->set_labels( $oXLabels );
$oChart->set_x_axis( $oXAxis );
}
else
{
$oChartElement = new pie();
$oChartElement->set_start_angle( 35 );
$oChartElement->set_animate( true );
$oChartElement->set_tooltip( '#label# - #val# (#percent#)' );
$oChartElement->set_colours( array('#FF8A00', '#909980', '#2C2B33', '#CCC08D', '#596664') );
$aData = array();
foreach($aValues as $sValue => $iValue)
{
$oPieValue = new pie_value($iValue, $sValue); //@@ BUG: not passed via ajax !!!
$oPieValue->on_click("ofc_drilldown_{$sId}");
$aData[] = $oPieValue;
}
$oChartElement->set_values( $aData );
$oChart->x_axis = null;
}
// Title given in HTML
//$oTitle = new title($this->m_sTitle);
//$oChart->set_title($oTitle);
$oChart->set_bg_colour('#FFFFFF');
$oChart->add_element( $oChartElement );
$sData = $oChart->toPrettyString();
$sData = json_encode($sData);
// 2- Declare the Javascript function that will render the chart data\
//
$oPage->add_script(
<<< EOF
function ofc_get_data_{$sId}()
{
return $sData;
}
EOF
);
if (count($aURLs) > 0)
{
$sURLList = '';
foreach($aURLs as $index => $sURL)
{
$sURLList .= "\taURLs[$index] = '".addslashes($sURL)."';\n";
}
$oPage->add_script(
<<< EOF
function ofc_drilldown_{$sId}(index)
{
var aURLs = new Array();
{$sURLList}
var sURL = aURLs[index];
window.location.href = sURL; // Navigate !
}
EOF
);
}
// 3- Insert the Open Flash chart
//
$oPage->add("<div id=\"$sId\"><div>\n");
$oPage->add_ready_script(
<<<EOF
swfobject.embedSWF( "../images/open-flash-chart.swf",
"{$sId}",
"100%", "300","9.0.0",
"expressInstall.swf",
{"get-data":"ofc_get_data_{$sId}", "id":"{$sId}"},
{'wmode': 'transparent'}
);
EOF
);
}
}
?>

View File

@@ -35,7 +35,15 @@ class DisplayTemplate
public function __construct($sTemplate)
{
$this->m_aTags = array('itopblock', 'itopcheck', 'itoptabs', 'itoptab', 'itoptoggle', 'itopstring');
$this->m_aTags = array (
'itopblock',
'itopcheck',
'itoptabs',
'itoptab',
'itoptoggle',
'itopstring',
'sqlblock'
);
$this->m_sTemplate = $sTemplate;
}
@@ -203,6 +211,11 @@ class DisplayTemplate
$oPage->add(Dict::S($sContent));
break;
case 'sqlblock':
$oBlock = SqlBlock::FromTemplate($sContent);
$oBlock->RenderContent($oPage);
break;
case 'itopblock': // No longer used, handled by DisplayBlock::FromTemplate see above
$oPage->add("<!-- Application Error: should be handled by DisplayBlock::FromTemplate -->");
break;
@@ -227,7 +240,6 @@ class DisplayTemplate
<itopblock blockclass="HistoryBlock" type="toggle" encoding="text/oql">SELECT CMDBChangeOp WHERE objkey = $id$ AND objclass = \'$class$\'</itopblock>
</div>
<img src="../../images/connect_to_network.png" style="margin-top:-10px; margin-right:10px; float:right">
<itopblock blockclass="DisplayBlock" asynchronous="false" type="bare_details" encoding="text/oql">SELECT NetworkDevice AS d WHERE d.id = $id$</itopblock>
<itoptabs>
<itoptab name="Interfaces">
<itopblock blockclass="DisplayBlock" type="list" encoding="text/oql">SELECT Interface AS i WHERE i.device_id = $id$</itopblock>
@@ -248,6 +260,143 @@ class DisplayTemplate
}
}
//DisplayTemplate::UnitTest();
/**
* Special type of template for displaying the details of an object
* On top of the defaut 'blocks' managed by the parent class, the following placeholders
* are available in such a template:
* $attribute_code$ An attribute of the object (in edit mode this is the input for the attribute)
* $attribute_code->label()$ The label of an attribute
* $PlugIn:plugInClass->properties()$ The ouput of OnDisplayProperties of the specified plugInClass
*/
class ObjectDetailsTemplate extends DisplayTemplate
{
public function __construct($sTemplate, $oObj, $sFormPrefix = '')
{
parent::__construct($sTemplate);
$this->m_oObj = $oObj;
$this->m_sPrefix = $sFormPrefix;
}
public function Render(WebPage $oPage, $aParams = array(), $bEditMode = false)
{
$sStateAttCode = MetaModel :: GetStateAttributeCode(get_class($this->m_oObj));
$aTemplateFields = array();
preg_match_all('/\\$this->([a-z0-9_]+)\\$/', $this->m_sTemplate, $aMatches);
$aTemplateFields = $aMatches[1];
preg_match_all('/\\$this->field\\(([a-z0-9_]+)\\)\\$/', $this->m_sTemplate, $aMatches);
$aTemplateFields = array_merge($aTemplateFields, $aMatches[1]);
$aFieldsComments = (isset($aParams['fieldsComments'])) ? $aParams['fieldsComments'] : array();
$aFieldsMap = array();
$sClass = get_class($this->m_oObj);
// Renders the fields used in the template
foreach(MetaModel::ListAttributeDefs(get_class($this->m_oObj)) as $sAttCode => $oAttDef)
{
$aParams['this->label('.$sAttCode.')'] = $oAttDef->GetLabel();
$aParams['this->comments('.$sAttCode.')'] = isset($aFieldsComments[$sAttCode]) ? $aFieldsComments[$sAttCode] : '';
$iInputId = '2_'.$sAttCode; // TODO: generate a real/unique prefix...
if (in_array($sAttCode, $aTemplateFields))
{
if ($this->m_oObj->IsNew())
{
$iFlags = $this->m_oObj->GetInitialStateAttributeFlags($sAttCode);
}
else
{
$iFlags = $this->m_oObj->GetAttributeFlags($sAttCode);
}
if (($iFlags & OPT_ATT_MANDATORY) && $this->m_oObj->IsNew())
{
$iFlags = $iFlags & ~OPT_ATT_READONLY; // Mandatory fields cannot be read-only when creating an object
}
if ((!$oAttDef->IsWritable()) || ($sStateAttCode == $sAttCode))
{
$iFlags = $iFlags | OPT_ATT_READONLY;
}
if ($iFlags & OPT_ATT_HIDDEN)
{
$aParams['this->label('.$sAttCode.')'] = '';
$aParams['this->field('.$sAttCode.')'] = '';
$aParams['this->comments('.$sAttCode.')'] = '';
$aParams['this->'.$sAttCode] = '';
}
else
{
if ($bEditMode && ($iFlags & (OPT_ATT_READONLY|OPT_ATT_SLAVE)))
{
// Check if the attribute is not read-only because of a synchro...
$aReasons = array();
$sSynchroIcon = '';
if ($iFlags & OPT_ATT_SLAVE)
{
$iSynchroFlags = $this->m_oObj->GetSynchroReplicaFlags($sAttCode, $aReasons);
$sSynchroIcon = "&nbsp;<img id=\"synchro_$sInputId\" src=\"../images/transp-lock.png\" style=\"vertical-align:middle\"/>";
$sTip = '';
foreach($aReasons as $aRow)
{
$sTip .= "<p>Synchronized with {$aRow['name']} - {$aRow['description']}</p>";
}
$oPage->add_ready_script("$('#synchro_$iInputId').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );");
}
// Attribute is read-only
$sHTMLValue = "<span id=\"field_{$iInputId}\">".$this->m_oObj->GetAsHTML($sAttCode);
$sHTMLValue .= '<input type="hidden" id="'.$iInputId.'" name="attr_'.$sAttCode.'" value="'.htmlentities($this->m_oObj->Get($sAttCode), ENT_QUOTES, 'UTF-8').'"/></span>';
$aFieldsMap[$sAttCode] = $iInputId;
$aParams['this->comments('.$sAttCode.')'] = $sSynchroIcon;
}
if ($bEditMode && !($iFlags & OPT_ATT_READONLY)) //TODO: check the data synchro status...
{
$aParams['this->field('.$sAttCode.')'] = "<span id=\"field_{$iInputId}\">".$this->m_oObj->GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef,
$this->m_oObj->Get($sAttCode),
$this->m_oObj->GetEditValue($sAttCode),
$iInputId, // InputID
'',
$iFlags,
array('this' => $this->m_oObj) // aArgs
).'</span>';
$aFieldsMap[$sAttCode] = $iInputId;
}
else
{
$aParams['this->field('.$sAttCode.')'] = $this->m_oObj->GetAsHTML($sAttCode);
}
$aParams['this->'.$sAttCode] = "<table class=\"field\"><tr><td class=\"label\">".$aParams['this->label('.$sAttCode.')'].":</td><td>".$aParams['this->field('.$sAttCode.')']."</td><td>".$aParams['this->comments('.$sAttCode.')']."</td></tr></table>";
}
}
}
// Renders the PlugIns used in the template
preg_match_all('/\\$PlugIn:([A-Za-z0-9_]+)->properties\\(\\)\\$/', $this->m_sTemplate, $aMatches);
$aPlugInProperties = $aMatches[1];
foreach($aPlugInProperties as $sPlugInClass)
{
$oInstance = MetaModel::GetPlugins('iApplicationUIExtension', $sPlugInClass);
if ($oInstance != null) // Safety check...
{
$offset = $oPage->start_capture();
$oInstance->OnDisplayProperties($this->m_oObj, $oPage, $bEditMode);
$sContent = $oPage->end_capture($offset);
$aParams["PlugIn:{$sPlugInClass}->properties()"]= $sContent;
}
else
{
$aParams["PlugIn:{$sPlugInClass}->properties()"]= "Missing PlugIn: $sPlugInClass";
}
}
$offset = $oPage->start_capture();
parent::Render($oPage, $aParams);
$sContent = $oPage->end_capture($offset);
// Remove empty table rows in case some attributes are hidden...
$sContent = preg_replace('/<tr[^>]*>\s*(<td[^>]*>\s*<\\/td>)+\s*<\\/tr>/im', '', $sContent);
$oPage->add($sContent);
return $aFieldsMap;
}
}
//DisplayTemplate::UnitTest();
?>

View File

@@ -38,11 +38,11 @@ class privUITransaction
// Strictly speaking, the two lines below should be grouped together
// by a critical section
// sem_acquire($rSemIdentified);
$id = 1 + count($_SESSION['transactions']);
$id = str_replace(array('.', ' '), '', microtime()); //1 + count($_SESSION['transactions']);
$_SESSION['transactions'][$id] = true;
// sem_release($rSemIdentified);
return sprintf("%d", $id);
return (string)$id;
}
/**

View File

@@ -63,27 +63,36 @@ require_once(APPROOT.'/application/displayblock.class.inc.php');
class UIExtKeyWidget
{
protected static $iWidgetIndex = 0;
protected $sAttCode;
protected $sNameSuffix;
protected $iId;
protected $sTitle;
protected $sTargetClass;
protected $sAttCode;
protected $bSearchMode;
public function __construct($sAttCode, $sClass, $sTitle, $aAllowedValues, $value, $iInputId, $bMandatory, $sNameSuffix = '', $sFieldPrefix = '', $sFormPrefix = '')
//public function __construct($sAttCode, $sClass, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sNameSuffix = '', $sFieldPrefix = '', $sFormPrefix = '')
static public function DisplayFromAttCode($oPage, $sAttCode, $sClass, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName = '', $sFormPrefix = '', $aArgs, $bSearchMode = false)
{
self::$iWidgetIndex++;
$this->sAttCode = $sAttCode;
$this->sClass = $sClass;
$this->oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
$this->sNameSuffix = $sNameSuffix;
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
$sTargetClass = $oAttDef->GetTargetClass();
$iMaxComboLength = $oAttDef->GetMaximumComboLength();
$bAllowTargetCreation = $oAttDef->AllowTargetCreation();
if (!$bSearchMode)
{
$sDisplayStyle = $oAttDef->GetDisplayStyle();
}
else
{
$sDisplayStyle = 'select'; // In search mode, always use a drop-down list
}
$oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode, $bSearchMode);
return $oWidget->Display($oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName, $sFormPrefix, $aArgs, null, $sDisplayStyle);
}
public function __construct($sTargetClass, $iInputId, $sAttCode = '', $bSearchMode = false)
{
$this->sTargetClass = $sTargetClass;
$this->iId = $iInputId;
$this->aAllowedValues = $aAllowedValues;
$this->value = $value;
$this->sFieldPrefix = $sFieldPrefix;
$this->sTargetClass = $this->oAttDef->GetTargetClass();
$this->sTitle = $sTitle;
$this->sFormPrefix = $sFormPrefix;
$this->bMandatory = $bMandatory;
$this->sAttCode = $sAttCode;
$this->bSearchMode = $bSearchMode;
}
/**
@@ -92,121 +101,203 @@ class UIExtKeyWidget
* @param Hash $aArgs Extra context arguments
* @return string The HTML fragment to be inserted into the page
*/
public function Display(WebPage $oPage, $aArgs = array(), $bSearchMode = false)
public function Display(WebPage $oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName, $sFormPrefix = '', $aArgs = array(), $bSearchMode = null, $sDisplayStyle = 'select')
{
if (!is_null($bSearchMode))
{
$this->bSearchMode = $bSearchMode;
}
$sTitle = addslashes($sTitle);
$oPage->add_linked_script('../js/extkeywidget.js');
$oPage->add_linked_script('../js/forms-json-utils.js');
$bCreate = (!$bSearchMode) && (!MetaModel::IsAbstract($this->sTargetClass)) && (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_BULK_MODIFY) && $this->oAttDef->AllowTargetCreation());
$bCreate = (!$this->bSearchMode) && (!MetaModel::IsAbstract($this->sTargetClass)) && (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_BULK_MODIFY) && $bAllowTargetCreation);
$bExtensions = true;
$sMessage = Dict::S('UI:Message:EmptyList:UseSearchForm');
$sAttrFieldPrefix = ($bSearchMode) ? '' : 'attr_';
$sAttrFieldPrefix = ($this->bSearchMode) ? '' : 'attr_';
$sHTMLValue = "<span style=\"white-space:nowrap\">"; // no wrap
if($bSearchMode)
$sFilter = addslashes($oAllowedValues->GetFilter()->ToOQL());
if($this->bSearchMode)
{
$sWizHelper = 'null';
$sWizHelperJSON = "''";
$sJSSearchMode = 'true';
}
else
{
$sWizHelper = 'oWizardHelper'.$this->sFormPrefix;
}
if (count($this->aAllowedValues) < $this->oAttDef->GetMaximumComboLength())
{
// Few choices, use a normal 'select'
$sSelectMode = 'true';
$sHelpText = $this->oAttDef->GetHelpOnEdition();
// In case there are no valid values, the select will be empty, thus blocking the user from validating the form
$sHTMLValue = "<select title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$this->sFieldPrefix}{$this->sAttCode}{$this->sNameSuffix}\" id=\"$this->iId\">\n";
if ($bSearchMode)
if (isset($aArgs['wizHelper']))
{
$sHTMLValue .= "<option value=\"\">".Dict::S('UI:SearchValue:Any')."</option>\n";
$sWizHelper = $aArgs['wizHelper'];
}
else
{
$sHTMLValue .= "<option value=\"\">".Dict::S('UI:SelectOne')."</option>\n";
$sWizHelper = 'oWizardHelper'.$sFormPrefix;
}
foreach($this->aAllowedValues as $key => $display_value)
$sWizHelperJSON = $sWizHelper.'.UpdateWizardToJSON()';
$sJSSearchMode = 'false';
}
if (is_null($oAllowedValues))
{
throw new Exception('Implementation: null value for allowed values definition');
}
elseif ($oAllowedValues->Count() < $iMaxComboLength)
{
// Discrete list of values, use a SELECT or RADIO buttons depending on the config
switch($sDisplayStyle)
{
if ((count($this->aAllowedValues) == 1) && ($this->bMandatory == 'true') )
case 'radio':
case 'radio_horizontal':
case 'radio_vertical':
$sValidationField = "<span id=\"v_{$this->iId}\"></span>";
$sHTMLValue = '';
$bVertical = ($sDisplayStyle != 'radio_horizontal');
$bExtensions = false;
$oAllowedValues->Rewind();
$aAllowedValues = array();
while($oObj = $oAllowedValues->Fetch())
{
// When there is only once choice, select it by default
$sSelected = ' selected';
$aAllowedValues[$oObj->GetKey()] = $oObj->GetName();
}
$sHTMLValue = $oPage->GetRadioButtons($aAllowedValues, $value, $this->iId, "{$sAttrFieldPrefix}{$sFieldName}", $bMandatory, $bVertical, $sValidationField);
$aEventsList[] ='change';
break;
case 'select':
default:
$sSelectMode = 'true';
$sHelpText = ''; //$this->oAttDef->GetHelpOnEdition();
$sHTMLValue = "<select title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" id=\"$this->iId\">\n";
if ($this->bSearchMode)
{
$sDisplayValue = isset($aArgs['sDefaultValue']) ? $aArgs['sDefaultValue'] : Dict::S('UI:SearchValue:Any');
$sHTMLValue .= "<option value=\"\">$sDisplayValue</option>\n";
}
else
{
$sSelected = ($this->value == $key) ? ' selected' : '';
$sHTMLValue .= "<option value=\"\">".Dict::S('UI:SelectOne')."</option>\n";
}
$sHTMLValue .= "<option value=\"$key\"$sSelected>$display_value</option>\n";
}
$sHTMLValue .= "</select>\n";
$oPage->add_ready_script(
$oAllowedValues->Rewind();
while($oObj = $oAllowedValues->Fetch())
{
$key = $oObj->GetKey();
$display_value = $oObj->GetName();
if (($oAllowedValues->Count() == 1) && ($bMandatory == 'true') )
{
// When there is only once choice, select it by default
$sSelected = ' selected';
}
else
{
$sSelected = ($value == $key) ? ' selected' : '';
}
$sHTMLValue .= "<option value=\"$key\"$sSelected>$display_value</option>\n";
}
$sHTMLValue .= "</select>\n";
$oPage->add_ready_script(
<<<EOF
oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sClass}', '{$this->sAttCode}', '{$this->sNameSuffix}', $sSelectMode, $sWizHelper);
oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', true, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode);
oACWidget_{$this->iId}.emptyHtml = "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>$sMessage</p></div>";
$('#$this->iId').bind('update', function() { oACWidget_{$this->iId}.Update(); } );
$('#$this->iId').bind('change', function() { $(this).trigger('extkeychange') } );
EOF
);
);
} // Switch
}
else
{
// Too many choices, use an autocomplete
$sSelectMode = 'false';
if ($this->oAttDef->IsNull($this->value)) // Null values are displayed as ''
if (is_null($value) || ($value == 0)) // Null values are displayed as ''
{
$sDisplayValue = '';
$sDisplayValue = isset($aArgs['sDefaultValue']) ? $aArgs['sDefaultValue'] : '';
}
else
{
$sDisplayValue = $this->GetObjectName($this->value);
$sDisplayValue = $this->GetObjectName($value);
}
$sFormPrefix = $this->sFormPrefix;
$iMinChars = $this->oAttDef->GetMinAutoCompleteChars();
$iFieldSize = $this->oAttDef->GetMaxSize();
$iMinChars = isset($aArgs['iMinChars']) ? $aArgs['iMinChars'] : 3; //@@@ $this->oAttDef->GetMinAutoCompleteChars();
$iFieldSize = isset($aArgs['iFieldSize']) ? $aArgs['iFieldSize'] : 30; //@@@ $this->oAttDef->GetMaxSize();
// the input for the auto-complete
$sHTMLValue = "<input count=\"".count($this->aAllowedValues)."\" type=\"text\" id=\"label_$this->iId\" size=\"30\" maxlength=\"$iFieldSize\" value=\"$sDisplayValue\"/>&nbsp;";
$sHTMLValue .= "<a class=\"no-arrow\" href=\"javascript:oACWidget_{$this->iId}.Search();\"><img id=\"mini_search_{$this->iId}\" style=\"border:0;vertical-align:middle;\" src=\"../images/mini_search.gif\" /></a>&nbsp;";
$sHTMLValue = "<input count=\"".$oAllowedValues->Count()."\" type=\"text\" id=\"label_$this->iId\" size=\"$iFieldSize\" value=\"$sDisplayValue\"/>&nbsp;";
$sHTMLValue .= "<img id=\"mini_search_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_search.gif\" onClick=\"oACWidget_{$this->iId}.Search();\"/>&nbsp;";
// another hidden input to store & pass the object's Id
$sHTMLValue .= "<input type=\"hidden\" id=\"$this->iId\" name=\"{$sAttrFieldPrefix}{$this->sFieldPrefix}{$this->sAttCode}{$this->sNameSuffix}\" value=\"$this->value\" />\n";
$sHTMLValue .= "<input type=\"hidden\" id=\"$this->iId\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" value=\"$value\" />\n";
$JSSearchMode = $this->bSearchMode ? 'true' : 'false';
// Scripts to start the autocomplete and bind some events to it
$oPage->add_ready_script(
<<<EOF
oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sClass}', '{$this->sAttCode}', '{$this->sNameSuffix}', $sSelectMode, $sWizHelper);
oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', false, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode);
oACWidget_{$this->iId}.emptyHtml = "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>$sMessage</p></div>";
$('#label_$this->iId').autocomplete('./ajax.render.php', { scroll:true, minChars:{$iMinChars}, formatItem:formatItem, autoFill:false, matchContains:true, keyHolder:'#{$this->iId}', extraParams:{operation:'autocomplete', sclass:'{$this->sClass}',attCode:'{$this->sAttCode}'}});
$('#label_$this->iId').blur(function() { $(this).search(); } );
$('#label_$this->iId').autocomplete(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', { scroll:true, minChars:{$iMinChars}, autoFill:false, matchContains:true, mustMatch: true, keyHolder:'#{$this->iId}', extraParams:{operation:'ac_extkey', sTargetClass:'{$this->sTargetClass}',sFilter:'$sFilter',bSearchMode:$JSSearchMode, json: function() { return $sWizHelperJSON; } }});
$('#label_$this->iId').keyup(function() { if ($(this).val() == '') { $('#$this->iId').val(''); } } ); // Useful for search forms: empty value in the "label", means no value, immediatly !
$('#label_$this->iId').result( function(event, data, formatted) { OnAutoComplete('{$this->iId}', event, data, formatted); } );
$('#$this->iId').bind('update', function() { oACWidget_{$this->iId}.Update(); } );
if ($('#ac_dlg_{$this->iId}').length == 0)
{
$('body').append('<div id="ac_dlg_{$this->iId}"></div>');
}
EOF
);
//$oPage->add_at_the_end($this->GetSearchDialog($oPage)); // To prevent adding forms inside the main form
$oPage->add_at_the_end('<div id="ac_dlg_'.$this->iId.'"></div>'); // The place where to download the search dialog is outside of the main form (to prevent nested forms)
}
if ($bCreate)
if ($bExtensions && MetaModel::IsHierarchicalClass($this->sTargetClass) !== false)
{
$sHTMLValue .= "<a class=\"no-arrow\" href=\"javascript:oACWidget_{$this->iId}.CreateObject();\"><img id=\"mini_add_{$this->iId}\" style=\"border:0;vertical-align:middle;\" src=\"../images/mini_add.gif\" /></a>&nbsp;";
$oPage->add_at_the_end('<div id="ajax_'.$this->iId.'"></div>');
$sHTMLValue .= "<img id=\"mini_tree_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_tree.gif\" onClick=\"oACWidget_{$this->iId}.HKDisplay();\"/>&nbsp;";
$oPage->add_ready_script(
<<<EOF
if ($('#ac_tree_{$this->iId}').length == 0)
{
$('body').append('<div id="ac_tree_{$this->iId}"></div>');
}
EOF
);
}
if ($bCreate && $bExtensions)
{
$sHTMLValue .= "<img id=\"mini_add_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_add.gif\" onClick=\"oACWidget_{$this->iId}.CreateObject();\"/>&nbsp;";
$oPage->add_ready_script(
<<<EOF
if ($('#ajax_{$this->iId}').length == 0)
{
$('body').append('<div id="ajax_{$this->iId}"></div>');
}
EOF
);
}
if ($sDisplayStyle == 'select')
{
$sHTMLValue .= "<span id=\"v_{$this->iId}\"></span>";
}
$sHTMLValue .= "<span id=\"v_{$this->iId}\"></span>";
$sHTMLValue .= "</span>"; // end of no wrap
return $sHTMLValue;
}
public function GetSearchDialog(WebPage $oPage)
public function GetSearchDialog(WebPage $oPage, $sTitle, $oCurrObject = null)
{
$sHTML = '<div class="wizContainer" style="vertical-align:top;"><div id="dc_'.$this->iId.'">';
$oFilter = new DBObjectSearch($this->sTargetClass);
$oSet = new CMDBObjectSet($oFilter);
$oBlock = new DisplayBlock($oFilter, 'search', false);
if ( ($oCurrObject != null) && ($this->sAttCode != ''))
{
$oAttDef = MetaModel::GetAttributeDef(get_class($oCurrObject), $this->sAttCode);
$aParams = array('query_params' => array('this' => $oCurrObject));
$oSet = $oAttDef->GetAllowedValuesAsObjectSet($aParams);
$oFilter = $oSet->GetFilter();
}
else
{
$aParams = array();
$oFilter = new DBObjectSearch($this->sTargetClass);
}
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
$oBlock = new DisplayBlock($oFilter, 'search', false, $aParams);
$sHTML .= $oBlock->GetDisplay($oPage, $this->iId, array('open' => true, 'currentId' => $this->iId));
$sHTML .= "<form id=\"fr_{$this->iId}\" OnSubmit=\"return oACWidget_{$this->iId}.DoOk();\">\n";
$sHTML .= "<div id=\"dr_{$this->iId}\" style=\"vertical-align:top;background: #fff;height:100%;overflow:auto;padding:0;border:0;\">\n";
@@ -214,10 +305,11 @@ EOF
$sHTML .= "</div>\n";
$sHTML .= "<input type=\"button\" id=\"btn_cancel_{$this->iId}\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"$('#ac_dlg_{$this->iId}').dialog('close');\">&nbsp;&nbsp;";
$sHTML .= "<input type=\"button\" id=\"btn_ok_{$this->iId}\" value=\"".Dict::S('UI:Button:Ok')."\" onClick=\"oACWidget_{$this->iId}.DoOk();\">";
$sHTML .= "<input type=\"hidden\" id=\"count_{$this->iId}\" value=\"0\">";
$sHTML .= "</form>\n";
$sHTML .= '</div></div>';
$sDialogTitle = addslashes($this->sTitle);
$sDialogTitle = addslashes($sTitle);
$oPage->add_ready_script(
<<<EOF
$('#ac_dlg_{$this->iId}').dialog({ width: $(window).width()*0.8, height: $(window).height()*0.8, autoOpen: false, modal: true, title: '$sDialogTitle', resizeStop: oACWidget_{$this->iId}.UpdateSizes, close: oACWidget_{$this->iId}.OnClose });
@@ -231,25 +323,69 @@ EOF
/**
* Search for objects to be selected
* @param WebPage $oP The page used for the output (usually an AjaxWebPage)
* @param string $sRemoteClass Name of the "remote" class to perform the search on, must be a derived class of m_sRemoteClass
* @param string $sRemoteClass Name of the "remote" class to perform the search on, must be a derived class of sTargetClass
* @param Array $aAlreadyLinkedIds List of IDs of objects of "remote" class already linked, to be filtered out of the search
*/
public function SearchObjectsToSelect(WebPage $oP, $sTargetClass = '')
public function SearchObjectsToSelect(WebPage $oP, $sFilter, $sRemoteClass = '', $oObj = null)
{
if ($sTargetClass != '')
if (is_null($sFilter))
{
// assert(MetaModel::IsParentClass($this->m_sRemoteClass, $sRemoteClass));
$oFilter = new DBObjectSearch($sTargetClass);
throw new Exception('Implementation: null value for allowed values definition');
}
else
try
{
// No remote class specified use the one defined in the linkedset
$oFilter = new DBObjectSearch($this->sTargetClass);
if ($sRemoteClass != $this->sTargetClass)
{
//$oBaseClassFilter = DBObjectSearch::FromOQL($sFilter);
//$oFilter = new DBObjectSearch($sRemoteClass);
//$oFilter->AddConditionExpression($oBaseClassFilter->GetCriteria());
//$oFilter->TransferConditionExpression($oBaseClassFilter, array());
//$oFilter->TransferConditionExpression($oBaseClassFilter, array($this->sTargetClass => array('*' => $sRemoteClass.'_'.strtolower($this->sTargetClass))));
$sFilter = str_replace(" $this->sTargetClass ", " $sRemoteClass ", $sFilter);
$sFilter = str_replace("`$this->sTargetClass`", "`$sRemoteClass`", $sFilter);
$oFilter = DBObjectSearch::FromOQL($sFilter);
}
else
{
$oFilter = DBObjectSearch::FromOQL($sFilter);
}
//$oP->p($oFilter->ToOQL());
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
$oBlock = new DisplayBlock($oFilter, 'list', false, array('query_params' => array('this' => $oObj)));
$oBlock->Display($oP, $this->iId.'_results', array('this' => $oObj, 'cssCount'=> '#count_'.$this->iId, 'menu' => false, 'selection_mode' => true, 'selection_type' => 'single')); // Don't display the 'Actions' menu on the results
}
catch(MissingQueryArgument $e)
{
// When used in a search form the $this parameter may be missing, in this case return all possible values...
// TODO check if we can improve this behavior...
$sOQL = 'SELECT '.$sRemoteClass;
$oFilter = DBObjectSearch::FromOQL($sOQL);
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
//$oBlock = new DisplayBlock($oFilter, 'list', false);
//$oBlock->Display($oP, $this->iId.'_results', array('cssCount'=> '#count_'.$this->iId, 'menu' => false, 'selection_mode' => true, 'selection_type' => 'single')); // Don't display the 'Actions' menu on the results
}
}
/**
* Search for objects to be selected
* @param WebPage $oP The page used for the output (usually an AjaxWebPage)
* @param string $sFilter The OQL expression used to define/limit limit the scope of possible values
* @param DBObject $oObj The current object for the OQL context
* @param string $sContains The text of the autocomplete to filter the results
*/
public function AutoComplete(WebPage $oP, $sFilter, $oObj = null, $sContains)
{
if (is_null($sFilter))
{
throw new Exception('Implementation: null value for allowed values definition');
}
$oValuesSet = new ValueSetObjects($sFilter, 'friendlyname'); // Bypass GetName() to avoid the encoding by htmlentities
$oValuesSet->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
$aValues = $oValuesSet->GetValues(array('this' => $oObj), $sContains);
foreach($aValues as $sKey => $sFriendlyName)
{
$oP->add(trim($sFriendlyName)."\t".$sKey."\n");
}
$oFilter->AddCondition('id', array_keys($this->aAllowedValues), 'IN');
$oSet = new CMDBObjectSet($oFilter);
$oBlock = new DisplayBlock($oFilter, 'list', false);
$oBlock->Display($oP, $this->iId, array('menu' => false, 'selection_mode' => true, 'selection_type' => 'single', 'display_limit' => false)); // Don't display the 'Actions' menu on the results
}
/**
@@ -257,19 +393,56 @@ EOF
*/
public function GetObjectName($iObjId)
{
$oObj = MetaModel::GetObject($this->sTargetClass, $iObjId);
return $oObj->GetName();
$aModifierProps = array();
$aModifierProps['UserRightsGetSelectFilter']['bSearchMode'] = $this->bSearchMode;
$oObj = MetaModel::GetObject($this->sTargetClass, $iObjId, false, false, $aModifierProps);
if ($oObj)
{
return $oObj->GetName();
}
else
{
return '';
}
}
/**
* Get the form to create a new object of the 'target' class
*/
public function GetObjectCreationForm(WebPage $oPage)
public function GetObjectCreationForm(WebPage $oPage, $oCurrObject)
{
// Set all the default values in an object and clone this "default" object
$oNewObj = MetaModel::NewObject($this->sTargetClass);
// 1st - set context values
$oAppContext = new ApplicationContext();
$oAppContext->InitObjectFromContext($oNewObj);
// 2nd set the default values from the constraint on the external key... if any
if ( ($oCurrObject != null) && ($this->sAttCode != ''))
{
$oAttDef = MetaModel::GetAttributeDef(get_class($oCurrObject), $this->sAttCode);
$aParams = array('this' => $oCurrObject);
$oSet = $oAttDef->GetAllowedValuesAsObjectSet($aParams);
$aConsts = $oSet->ListConstantFields();
$sClassAlias = $oSet->GetFilter()->GetClassAlias();
if (isset($aConsts[$sClassAlias]))
{
foreach($aConsts[$sClassAlias] as $sAttCode => $value)
{
$oNewObj->Set($sAttCode, $value);
}
}
}
// 3rd - set values from the page argument 'default'
$oNewObj->UpdateObjectFromArg('default');
$sDialogTitle = addslashes($this->sTitle);
$oPage->add('<div id="ac_create_'.$this->iId.'"><div class="wizContainer" style="vertical-align:top;"><div id="dcr_'.$this->iId.'">');
$oPage->add("<h1>".MetaModel::GetClassIcon($this->sTargetClass)."&nbsp;".Dict::Format('UI:CreationTitle_Class', MetaModel::GetName($this->sTargetClass))."</h1>\n");
cmdbAbstractObject::DisplayCreationForm($oPage, $this->sTargetClass, null, array(), array('formPrefix' => $this->iId, 'noRelations' => true));
cmdbAbstractObject::DisplayCreationForm($oPage, $this->sTargetClass, $oNewObj, array(), array('formPrefix' => $this->iId, 'noRelations' => true));
$oPage->add('</div></div></div>');
// $oPage->add_ready_script("\$('#ac_create_$this->iId').dialog({ width: $(window).width()*0.8, height: 'auto', autoOpen: false, modal: true, title: '$sDialogTitle'});\n");
$oPage->add_ready_script("\$('#ac_create_$this->iId').dialog({ width: 'auto', height: 'auto', autoOpen: false, modal: true, title: '$sDialogTitle'});\n");
@@ -277,13 +450,54 @@ EOF
$oPage->add_ready_script("$('#dcr_{$this->iId} form').bind('submit.uilinksWizard', oACWidget_{$this->iId}.DoCreateObject);");
}
/**
* Display the hierarchy of the 'target' class
*/
public function DisplayHierarchy(WebPage $oPage, $sFilter, $currValue, $oObj)
{
$sDialogTitle = addslashes(Dict::Format('UI:HierarchyOf_Class', MetaModel::GetName($this->sTargetClass)));
$oPage->add('<div id="dlg_tree_'.$this->iId.'"><div class="wizContainer" style="vertical-align:top;"><div style="overflow:auto;background:#fff;margin-bottom:5px;" id="tree_'.$this->iId.'">');
$oPage->add('<table style="width:100%"><tr><td>');
if (is_null($sFilter))
{
throw new Exception('Implementation: null value for allowed values definition');
}
try
{
$oFilter = DBObjectSearch::FromOQL($sFilter);
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
$oSet = new DBObjectSet($oFilter, array(), array('this' => $oObj));
}
catch(MissingQueryArgument $e)
{
// When used in a search form the $this parameter may be missing, in this case return all possible values...
// TODO check if we can improve this behavior...
$sOQL = 'SELECT '.$this->m_sTargetClass;
$oFilter = DBObjectSearch::FromOQL($sOQL);
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
$oSet = new DBObjectSet($oFilter);
}
$sHKAttCode = MetaModel::IsHierarchicalClass($this->sTargetClass);
$this->DumpTree($oPage, $oSet, $sHKAttCode, $currValue);
$oPage->add('</td></tr></table>');
$oPage->add('</div>');
$oPage->add("<input type=\"button\" id=\"btn_cancel_{$this->iId}\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"$('#dlg_tree_{$this->iId}').dialog('close');\">&nbsp;&nbsp;");
$oPage->add("<input type=\"button\" id=\"btn_ok_{$this->iId}\" value=\"".Dict::S('UI:Button:Ok')."\" onClick=\"oACWidget_{$this->iId}.DoHKOk();\">");
$oPage->add('</div></div>');
$oPage->add_ready_script("\$('#tree_$this->iId ul').treeview();\n");
$oPage->add_ready_script("\$('#dlg_tree_$this->iId').dialog({ width: 'auto', height: 'auto', autoOpen: true, modal: true, title: '$sDialogTitle', resizeStop: oACWidget_{$this->iId}.OnHKResize, close: oACWidget_{$this->iId}.OnHKClose });\n");
}
/**
* Get the form to create a new object of the 'target' class
*/
public function DoCreateObject($oPage)
{
$oObj = MetaModel::NewObject($this->sTargetClass);
$aErrors = $oObj->UpdateObject($this->sFormPrefix.$this->iId);
$aErrors = $oObj->UpdateObjectFromPostedForm($this->iId);
if (count($aErrors) == 0)
{
$oMyChange = MetaModel::NewObject("CMDBChange");
@@ -299,5 +513,68 @@ EOF
return array('name' => implode(' ', $aErrors), 'id' => 0);
}
}
function DumpTree($oP, $oSet, $sParentAttCode, $currValue)
{
$aTree = array();
$aNodes = array();
while($oObj = $oSet->Fetch())
{
$iParentId = $oObj->Get($sParentAttCode);
if (!isset($aTree[$iParentId]))
{
$aTree[$iParentId] = array();
}
$aTree[$iParentId][$oObj->GetKey()] = $oObj->GetName();
$aNodes[$oObj->GetKey()] = $oObj;
}
$aParents = array_keys($aTree);
$aRoots = array();
foreach($aParents as $id)
{
if (!array_key_exists($id, $aNodes))
{
$aRoots[] = $id;
}
}
foreach($aRoots as $iRootId)
{
$this->DumpNodes($oP, $iRootId, $aTree, $aNodes, $currValue);
}
}
function DumpNodes($oP, $iRootId, $aTree, $aNodes, $currValue)
{
$bSelect = true;
$bMultiple = false;
$sSelect = '';
if (array_key_exists($iRootId, $aTree))
{
$aSortedRoots = $aTree[$iRootId];
asort($aSortedRoots);
$oP->add("<ul>\n");
foreach($aSortedRoots as $id => $sName)
{
if ($bSelect)
{
$sChecked = ($aNodes[$id]->GetKey() == $currValue) ? 'checked' : '';
if ($bMultiple)
{
$sSelect = '<input type="checkbox" value="'.$aNodes[$id]->GetKey().'" name="selectObject[]" '.$sChecked.'>&nbsp;';
}
else
{
$sSelect = '<input type="radio" value="'.$aNodes[$id]->GetKey().'" name="selectObject" '.$sChecked.'>&nbsp;';
}
}
$oP->add('<li>'.$sSelect.$aNodes[$id]->GetHyperlink());
$this->DumpNodes($oP, $id, $aTree, $aNodes, $currValue);
$oP->add("</li>\n");
}
$oP->add("</ul>\n");
}
}
}
?>

View File

@@ -34,6 +34,7 @@ class UILinksWidget
protected $m_iInputId;
protected $m_aAttributes;
protected $m_sExtKeyToRemote;
protected $m_sExtKeyToMe;
protected $m_sLinkedClass;
protected $m_sRemoteClass;
protected $m_bDuplicatesAllowed;
@@ -50,6 +51,7 @@ class UILinksWidget
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sAttCode);
$this->m_sLinkedClass = $oAttDef->GetLinkedClass();
$this->m_sExtKeyToRemote = $oAttDef->GetExtKeyToRemote();
$this->m_sExtKeyToMe = $oAttDef->GetExtKeyToMe();
$oLinkingAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $this->m_sExtKeyToRemote);
$this->m_sRemoteClass = $oLinkingAttDef->GetTargetClass();
$sExtKeyToMe = $oAttDef->GetExtKeyToMe();
@@ -60,8 +62,9 @@ class UILinksWidget
$this->m_aTableConfig = array();
$this->m_aTableConfig['form::checkbox'] = array( 'label' => "<input class=\"select_all\" type=\"checkbox\" value=\"1\" onClick=\"CheckAll('#linkedset_{$this->m_sAttCode}{$this->m_sNameSuffix} .selection', this.checked); oWidget".$this->m_iInputId.".OnSelectChange();\">", 'description' => Dict::S('UI:SelectAllToggle+'));
foreach(MetaModel::ListAttributeDefs($this->m_sLinkedClass) as $sAttCode=>$oAttDef)
foreach(MetaModel::FlattenZList(MetaModel::GetZListItems($this->m_sLinkedClass, 'list')) as $sAttCode)
{
$oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sAttCode);
if ($sStateAttCode == $sAttCode)
{
// State attribute is always hidden from the UI
@@ -97,43 +100,98 @@ class UILinksWidget
* @param Hash $aArgs Extra context arguments
* @return string The HTML fragment of the one-row form
*/
protected function GetFormRow(WebPage $oP, DBObject $oLinkedObj, $linkObjOrId = null, $aArgs = array() )
protected function GetFormRow(WebPage $oP, DBObject $oLinkedObj, $linkObjOrId = null, $aArgs = array(), $oCurrentObj )
{
$sPrefix = "$this->m_sAttCode{$this->m_sNameSuffix}";
$aRow = array();
$aFieldsMap = array();
if(is_object($linkObjOrId))
{
$key = $linkObjOrId->GetKey();
$iRemoteObjKey = $linkObjOrId->Get($this->m_sExtKeyToRemote);
$sPrefix .= "[$key][";
$sNameSuffix = "]"; // To make a tabular form
$aArgs['prefix'] = $sPrefix;
$aArgs['wizHelper'] = "oWizardHelper{$this->m_iInputId}{$key}";
$aArgs['this'] = $linkObjOrId;
$aRow['form::checkbox'] = "<input class=\"selection\" type=\"checkbox\" onClick=\"oWidget".$this->m_iInputId.".OnSelectChange();\" value=\"$key\">";
$aRow['form::checkbox'] .= "<input type=\"hidden\" name=\"attr_{$sPrefix}id{$sNameSuffix}\" value=\"$key\">";
foreach($this->m_aEditableFields as $sFieldCode)
{
$sFieldId = $this->m_iInputId.'_'.$sFieldCode.'['.$linkObjOrId->GetKey().']';
$sSafeId = str_replace(array('[',']','-'), '_', $sFieldId);
$sSafeId = self::MakeID($sFieldId);
$oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sFieldCode);
$aRow[$sFieldCode] = cmdbAbstractObject::GetFormElementForField($oP, $this->m_sLinkedClass, $sFieldCode, $oAttDef, $linkObjOrId->Get($sFieldCode), '' /* DisplayValue */, $sSafeId, $sNameSuffix, 0, $aArgs);
$aFieldsMap[$sFieldCode] = $sSafeId;
}
$sState = $linkObjOrId->GetState();
}
else
{
// form for creating a new record
$sPrefix .= "[$linkObjOrId][";
$iRemoteObjKey = -$linkObjOrId;
$oNewLinkObj = MetaModel::NewObject($this->m_sLinkedClass);
$oRemoteObj = MetaModel::GetObject($this->m_sRemoteClass, -$linkObjOrId);
$oNewLinkObj->Set($this->m_sExtKeyToRemote, $oRemoteObj); // Setting the extkey with the object alsoo fills the related external fields
$oNewLinkObj->Set($this->m_sExtKeyToMe, $oCurrentObj); // Setting the extkey with the object also fills the related external fields
$sNameSuffix = "]"; // To make a tabular form
$aArgs['prefix'] = $sPrefix;
$aArgs['wizHelper'] = "oWizardHelper{$this->m_iInputId}_".(-$linkObjOrId);
$aArgs['this'] = $oNewLinkObj;
$aRow['form::checkbox'] = "<input class=\"selection\" type=\"checkbox\" onClick=\"oWidget".$this->m_iInputId.".OnSelectChange();\" value=\"$linkObjOrId\">";
$aRow['form::checkbox'] .= "<input type=\"hidden\" name=\"attr_{$sPrefix}id{$sNameSuffix}\" value=\"\">";
foreach($this->m_aEditableFields as $sFieldCode)
{
$sFieldId = $this->m_iInputId.'_'.$sFieldCode.'['.$linkObjOrId.']';
$sSafeId = str_replace(array('[',']','-'), '_', $sFieldId);
$sSafeId = self::MakeID($sFieldId);
$oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sFieldCode);
$aRow[$sFieldCode] = cmdbAbstractObject::GetFormElementForField($oP, $this->m_sLinkedClass, $sFieldCode, $oAttDef, '' /* TO DO/ call GetDefaultValue($oObject->ToArgs()) */, '' /* DisplayValue */, $sSafeId /* id */, $sNameSuffix, 0, $aArgs);
$aFieldsMap[$sFieldCode] = $sSafeId;
}
$sState = '';
$oP->add_script(
<<<EOF
$(".date-pick").datepicker({
showOn: 'button',
buttonImage: '../images/calendar.png',
buttonImageOnly: true,
dateFormat: 'yy-mm-dd',
constrainInput: false,
changeMonth: true,
changeYear: true
});
$(".datetime-pick").datepicker({
showOn: 'button',
buttonImage: '../images/calendar.png',
buttonImageOnly: true,
dateFormat: 'yy-mm-dd 00:00:00',
constrainInput: false,
changeMonth: true,
changeYear: true
});
EOF
);
}
$sExtKeyToMeId = self::MakeID($sPrefix.$this->m_sExtKeyToMe);
$aFieldsMap[$this->m_sExtKeyToMe] = $sExtKeyToMeId;
$aRow['form::checkbox'] .= "<input type=\"hidden\" id=\"$sExtKeyToMeId\" value=\"".$oCurrentObj->GetKey()."\">";
$sExtKeyToRemoteId = self::MakeID($sPrefix.$this->m_sExtKeyToRemote);
$aFieldsMap[$this->m_sExtKeyToRemote] = $sExtKeyToRemoteId;
$aRow['form::checkbox'] .= "<input type=\"hidden\" id=\"$sExtKeyToRemoteId\" value=\"$iRemoteObjKey\">";
$iFieldsCount = count($aFieldsMap);
$sJsonFieldsMap = json_encode($aFieldsMap);
$oP->add_script(
<<<EOF
var {$aArgs['wizHelper']} = new WizardHelper('{$this->m_sLinkedClass}', '', '$sState');
{$aArgs['wizHelper']}.SetFieldsMap($sJsonFieldsMap);
{$aArgs['wizHelper']}.SetFieldsCount($iFieldsCount);
EOF
);
$aRow['static::key'] = $oLinkedObj->GetHyperLink();
foreach(MetaModel::GetZListItems($this->m_sRemoteClass, 'list') as $sFieldCode)
{
@@ -141,6 +199,11 @@ class UILinksWidget
}
return $aRow;
}
protected function MakeID($sName)
{
return str_replace(array('[', ']'), '_', $sName);
}
/**
* Display one row of the whole form
@@ -182,17 +245,17 @@ class UILinksWidget
// Content
$sHtml .= "</tbody>\n";
if (count($aData) == 0)
$sEmptyRowStyle = '';
if (count($aData) != 0)
{
$sHtml .= "<tr id=\"{$this->m_sAttCode}{$this->m_sNameSuffix}_empty_row\"><td colspan=\"".count($aConfig)."\" style=\"text-align:center;\">".Dict::S('UI:Message:EmptyList:UseAdd')."<input type=\"hidden\" name=\"attr_{$this->m_sAttCode}{$this->m_sNameSuffix}\" value=\"\"></td></td>";
$sEmptyRowStyle = 'style="display:none;"';
}
else
$sHtml .= "<tr $sEmptyRowStyle id=\"{$this->m_sAttCode}{$this->m_sNameSuffix}_empty_row\"><td colspan=\"".count($aConfig)."\" style=\"text-align:center;\">".Dict::S('UI:Message:EmptyList:UseAdd')."<input type=\"hidden\" name=\"attr_{$this->m_sAttCode}{$this->m_sNameSuffix}\" value=\"\"></td></td>";
foreach($aData as $iRowId => $aRow)
{
foreach($aData as $iRowId => $aRow)
{
$sHtml .= $this->DisplayFormRow($oP, $aConfig, $aRow, $iRowId);
}
}
$sHtml .= $this->DisplayFormRow($oP, $aConfig, $aRow, $iRowId);
}
$sHtml .= "</tbody>\n";
// Footer
@@ -207,9 +270,11 @@ class UILinksWidget
* @param WebPage $oP The web page used for all the output
* @param DBObjectSet The initial value of the linked set
* @param Hash $aArgs Extra context arguments
* @param string $sFormPrefix prefix of the fields in the current form
* @param DBObject $oCurrentObj the current object to which the linkset is related
* @return string The HTML fragment to be inserted into the page
*/
public function Display(WebPage $oPage, DBObjectSet $oValue, $aArgs = array())
public function Display(WebPage $oPage, DBObjectSet $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj)
{
$sHtmlValue = '';
$sTargetClass = self::GetTargetClass($this->m_sClass, $this->m_sAttCode);
@@ -219,23 +284,32 @@ class UILinksWidget
while($oCurrentLink = $oValue->Fetch())
{
$aRow = array();
$key = $oCurrentLink->GetKey();
$oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $oCurrentLink->Get($this->m_sExtKeyToRemote));
if ($oCurrentLink->IsNew())
{
$key = -$oLinkedObj->GetKey();
$aForm[$key] = $this->GetFormRow($oPage, $oLinkedObj, $key, $aArgs, $oCurrentObj);
}
else
{
$key = $oCurrentLink->GetKey();
$aForm[$key] = $this->GetFormRow($oPage, $oLinkedObj, $oCurrentLink, $aArgs, $oCurrentObj);
}
$aForm[$key] = $this->GetFormRow($oPage, $oLinkedObj, $oCurrentLink, $aArgs);
}
$sHtmlValue .= $this->DisplayFormTable($oPage, $this->m_aTableConfig, $aForm);
$sDuplicates = ($this->m_bDuplicatesAllowed) ? 'true' : 'false';
$sWizHelper = 'oWizardHelper'.$sFormPrefix;
$oPage->add_ready_script(<<<EOF
oWidget{$this->m_iInputId} = new LinksWidget('{$this->m_sAttCode}{$this->m_sNameSuffix}', '{$this->m_sClass}', '{$this->m_sAttCode}', '{$this->m_iInputId}', '{$this->m_sNameSuffix}', $sDuplicates);
oWidget{$this->m_iInputId} = new LinksWidget('{$this->m_sAttCode}{$this->m_sNameSuffix}', '{$this->m_sClass}', '{$this->m_sAttCode}', '{$this->m_iInputId}', '{$this->m_sNameSuffix}', $sDuplicates, $sWizHelper);
oWidget{$this->m_iInputId}.Init();
EOF
);
$sHtmlValue .= "<span style=\"float:left;\">&nbsp;&nbsp;&nbsp;<img src=\"../images/tv-item-last.gif\">&nbsp;&nbsp;<input id=\"{$this->m_sAttCode}{$this->m_sNameSuffix}_btnRemove\" type=\"button\" value=\"".Dict::S('UI:RemoveLinkedObjectsOf_Class')."\" onClick=\"oWidget{$this->m_iInputId}.RemoveSelected();\" >";
$sHtmlValue .= "&nbsp;&nbsp;&nbsp;<input id=\"{$this->m_sAttCode}{$this->m_sNameSuffix}_btnAdd\" type=\"button\" value=\"".Dict::Format('UI:AddLinkedObjectsOf_Class', MetaModel::GetName($this->m_sRemoteClass))."\" onClick=\"oWidget{$this->m_iInputId}.AddObjects();\"></span>\n";
$sHtmlValue .= "&nbsp;&nbsp;&nbsp;<input id=\"{$this->m_sAttCode}{$this->m_sNameSuffix}_btnAdd\" type=\"button\" value=\"".Dict::Format('UI:AddLinkedObjectsOf_Class', MetaModel::GetName($this->m_sRemoteClass))."\" onClick=\"oWidget{$this->m_iInputId}.AddObjects();\"><span id=\"{$this->m_sAttCode}{$this->m_sNameSuffix}_indicatorAdd\"></span></span>\n";
$sHtmlValue .= "<span style=\"clear:both;\"><p>&nbsp;</p></span>\n";
$sHtmlValue .= "</div>\n";
$oPage->add_at_the_end($this->GetObjectPickerDialog($oPage)); // To prevent adding forms inside the main form
$oPage->add_at_the_end("<div id=\"dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}\"></div>"); // To prevent adding forms inside the main form
return $sHtmlValue;
}
@@ -258,27 +332,26 @@ EOF
return $sTargetClass;
}
protected function GetObjectPickerDialog($oPage)
public function GetObjectPickerDialog($oPage, $oCurrentObj)
{
$sHtml = "<div id=\"dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}\">";
$sHtml .= "<div class=\"wizContainer\" style=\"vertical-align:top;\">\n";
$sHtml = "<div class=\"wizContainer\" style=\"vertical-align:top;\">\n";
$oFilter = new DBObjectSearch($this->m_sRemoteClass);
$oSet = new CMDBObjectSet($oFilter);
$this->SetSearchDefaultFromContext($oCurrentObj, $oFilter);
$oBlock = new DisplayBlock($oFilter, 'search', false);
$sHtml .= $oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}", array('open' => true));
$sHtml .= "<form id=\"ObjectsAddForm_{$this->m_sAttCode}{$this->m_sNameSuffix}\" OnSubmit=\"return oWidget{$this->m_iInputId}.DoAddObjects(this.id);\">\n";
$sHtml .= "<div id=\"SearchResultsToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}\" style=\"vertical-align:top;background: #fff;height:100%;overflow:auto;padding:0;border:0;\">\n";
$sHtml .= "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>".Dict::S('UI:Message:EmptyList:UseSearchForm')."</p></div>\n";
$sHtml .= "</div>\n";
$sHtml .= "<input type=\"button\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog('close');\">&nbsp;&nbsp;<input type=\"submit\" value=\"".Dict::S('UI:Button:Add')."\">";
$sHtml .= "<input type=\"hidden\" id=\"count_{$this->m_sAttCode}{$this->m_sNameSuffix}\" value=\"0\"/>";
$sHtml .= "<input type=\"button\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog('close');\">&nbsp;&nbsp;<input id=\"btn_ok_{$this->m_sAttCode}{$this->m_sNameSuffix}\" disabled=\"disabled\" type=\"submit\" value=\"".Dict::S('UI:Button:Add')."\">";
$sHtml .= "</div>\n";
$sHtml .= "</form>\n";
$sHtml .= "</div>\n";
$oPage->add($sHtml);
$oPage->add_ready_script("$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog({ width: $(window).width()*0.8, height: $(window).height()*0.8, autoOpen: false, modal: true, resizeStop: oWidget{$this->m_iInputId}.UpdateSizes });");
$oPage->add_ready_script("$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog('option', {title:'".addslashes(Dict::Format('UI:AddObjectsOf_Class_LinkedWith_Class', MetaModel::GetName($this->m_sLinkedClass), MetaModel::GetName($this->m_sClass)))."'});");
$oPage->add_ready_script("$('#SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix} form').bind('submit.uilinksWizard', oWidget{$this->m_iInputId}.SearchObjectsToAdd);");
$oPage->add_ready_script("$('#SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}').resize(oWidget{$this->m_iInputId}.UpdateSizes);");
return $sHtml;
}
/**
@@ -332,18 +405,19 @@ EOF
}
$oSet = new CMDBObjectSet($oFilter);
$oBlock = new DisplayBlock($oFilter, 'list', false);
$oBlock->Display($oP, 'ResultsToAdd', array('menu' => false, 'selection_mode' => true, 'display_limit' => false)); // Don't display the 'Actions' menu on the results
$oBlock->Display($oP, "ResultsToAdd_{$this->m_sAttCode}", array('menu' => false, 'cssCount'=> '#count_'.$this->m_sAttCode.$this->m_sNameSuffix , 'selection_mode' => true)); // Don't display the 'Actions' menu on the results
}
public function DoAddObjects(WebPage $oP, $aLinkedObjectIds = array())
public function DoAddObjects(WebPage $oP, $oFullSetFilter, $oCurrentObj)
{
$aTable = array();
$aLinkedObjectIds = utils::ReadMultipleSelection($oFullSetFilter);
foreach($aLinkedObjectIds as $iObjectId)
{
$oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $iObjectId);
if (is_object($oLinkedObj))
{
$aRow = $this->GetFormRow($oP, $oLinkedObj, -$iObjectId ); // Not yet created link get negative Ids
$aRow = $this->GetFormRow($oP, $oLinkedObj, -$iObjectId, array(), $oCurrentObj ); // Not yet created link get negative Ids
$oP->add($this->DisplayFormRow($oP, $this->m_aTableConfig, $aRow, -$iObjectId));
}
else
@@ -352,5 +426,47 @@ EOF
}
}
}
/**
* Initializes the default search parameters based on 1) a 'current' object and 2) the silos defined by the context
* @param DBObject $oSourceObj
* @param DBObjectSearch $oSearch
*/
protected function SetSearchDefaultFromContext($oSourceObj, &$oSearch)
{
$oAppContext = new ApplicationContext();
$sSrcClass = get_class($oSourceObj);
$sDestClass = $oSearch->GetClass();
foreach($oAppContext->GetNames() as $key)
{
// Find the value of the object corresponding to each 'context' parameter
$aCallSpec = array($sSrcClass, 'MapContextParam');
$sAttCode = '';
if (is_callable($aCallSpec))
{
$sAttCode = call_user_func($aCallSpec, $key); // Returns null when there is no mapping for this parameter
}
if (MetaModel::IsValidAttCode($sSrcClass, $sAttCode))
{
$oAttDef = MetaModel::GetAttributeDef($sSrcClass, $sAttCode);
$defaultValue = $oSourceObj->Get($sAttCode);
// Find the attcode for the same 'context' parameter in the destination class
// and sets its value as the default value for the search condition
$aCallSpec = array($sDestClass, 'MapContextParam');
$sAttCode = '';
if (is_callable($aCallSpec))
{
$sAttCode = call_user_func($aCallSpec, $key); // Returns null when there is no mapping for this parameter
}
if (MetaModel::IsValidAttCode($sDestClass, $sAttCode) && !empty($defaultValue))
{
$oSearch->AddCondition($sAttCode, $defaultValue);
}
}
}
}
}
?>

View File

@@ -52,13 +52,13 @@ class UIPasswordWidget
{
$sCode = $this->sAttCode.$this->sNameSuffix;
$iWidgetIndex = self::$iWidgetIndex;
$sPasswordValue = utils::ReadPostedParam("attr_$sCode", '*****');
$sConfirmPasswordValue = utils::ReadPostedParam("attr_{$sCode}_confirmed", '*****');
$sPasswordValue = utils::ReadPostedParam("attr_{$sCode}[value]", '*****', 'raw_data');
$sConfirmPasswordValue = utils::ReadPostedParam("attr_{$sCode}[confirm]", '*****', 'raw_data');
$sChangedValue = (($sPasswordValue != '*****') || ($sConfirmPasswordValue != '*****')) ? 1 : 0;
$sHtmlValue = '';
$sHtmlValue = '<input type="password" maxlength="255" name="attr_'.$sCode.'" id="'.$this->iId.'" value="'.htmlentities($sPasswordValue, ENT_QUOTES, 'UTF-8').'"/>&nbsp;<span class="form_validation" id="v_'.$this->iId.'"></span><br/>';
$sHtmlValue .= '<input type="password" maxlength="255" id="'.$this->iId.'_confirm" value="'.htmlentities($sConfirmPasswordValue, ENT_QUOTES, 'UTF-8').'" name="attr_'.$sCode.'_confirmed"/> '.Dict::S('UI:PasswordConfirm').' <input id="'.$this->iId.'_reset" type="button" value="'.Dict::S('UI:Button:ResetPassword').'" onClick="ResetPwd(\''.$this->iId.'\');">';
$sHtmlValue .= '<input type="hidden" id="'.$this->iId.'_changed" name="attr_'.$sCode.'_changed" value="'.$sChangedValue.'"/>';
$sHtmlValue = '<input type="password" maxlength="255" name="attr_'.$sCode.'[value]" id="'.$this->iId.'" value="'.htmlentities($sPasswordValue, ENT_QUOTES, 'UTF-8').'"/>&nbsp;<span class="form_validation" id="v_'.$this->iId.'"></span><br/>';
$sHtmlValue .= '<input type="password" maxlength="255" id="'.$this->iId.'_confirm" value="'.htmlentities($sConfirmPasswordValue, ENT_QUOTES, 'UTF-8').'" name="attr_'.$sCode.'[confirm]"/> '.Dict::S('UI:PasswordConfirm').' <input id="'.$this->iId.'_reset" type="button" value="'.Dict::S('UI:Button:ResetPassword').'" onClick="ResetPwd(\''.$this->iId.'\');">';
$sHtmlValue .= '<input type="hidden" id="'.$this->iId.'_changed" name="attr_'.$sCode.'[changed]" value="'.$sChangedValue.'"/>';
$oPage->add_ready_script("$('#$this->iId').bind('keyup change', function(evt) { return PasswordFieldChanged('$this->iId') } );"); // Bind to a custom event: validate
$oPage->add_ready_script("$('#$this->iId').bind('keyup change validate', function(evt, sFormId) { return ValidatePasswordField('$this->iId', sFormId) } );"); // Bind to a custom event: validate

View File

@@ -85,7 +85,7 @@ class UILinksWizard
$sTargetClass = $oAttDef->GetTargetClass();
$oTargetObj = MetaModel::GetObject($sTargetClass, $this->m_iObjectId);
$oP->set_title("iTop - ".MetaModel::GetName($this->m_sLinkedClass)." objects linked with ".MetaModel::GetName(get_class($oTargetObj)).": ".$oTargetObj->GetName());
$oP->set_title("iTop - ".MetaModel::GetName($this->m_sLinkedClass)." objects linked with ".MetaModel::GetName(get_class($oTargetObj)).": ".$oTargetObj->GetRawName());
$oP->add("<div class=\"wizContainer\">\n");
$oP->add("<form method=\"post\">\n");
$oP->add("<div class=\"page_header\">\n");
@@ -97,8 +97,7 @@ class UILinksWizard
$oP->add("<input type=\"hidden\" name=\"linking_attcode\" value=\"{$this->m_sLinkingAttCode}\">\n");
$oP->add("<h1>".Dict::Format('UI:ManageObjectsOf_Class_LinkedWith_Class_Instance', MetaModel::GetName($this->m_sLinkedClass), MetaModel::GetName(get_class($oTargetObj)), "<span class=\"hilite\">".$oTargetObj->GetHyperlink()."</span>")."</h1>\n");
$oP->add("</div>\n");
$oP->add("<script type=\"text/javascript\">\n");
$oP->add(
$oP->add_script(
<<<EOF
function OnSelectChange()
{
@@ -132,7 +131,7 @@ class UILinksWizard
function AddObjects()
{
// TO DO: compute the list of objects already linked with the current Object
$.post( '../pages/ajax.render.php', { 'operation': 'addObjects',
$.post( GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', { 'operation': 'addObjects',
'class': '{$this->m_sClass}',
'linkageAttr': '{$this->m_sLinkageAttr}',
'linkedClass': '{$this->m_sLinkedClass}',
@@ -175,7 +174,7 @@ class UILinksWizard
theMap['operation'] = 'searchObjectsToAdd';
// Run the query and display the results
$.post( '../pages/ajax.render.php', theMap,
$.post( GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', theMap,
function(data)
{
$('#SearchResultsToAdd').html(data);
@@ -223,7 +222,7 @@ class UILinksWizard
theMap['operation'] = 'doAddObjects';
// Run the query and display the results
$.post( '../pages/ajax.render.php', theMap,
$.post( GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', theMap,
function(data)
{
//console.log('Data: ' + data);
@@ -257,7 +256,6 @@ class UILinksWizard
}
EOF
);
$oP->Add("</script>\n");
$oP->add_ready_script("InitForm();");
$oFilter = new DBObjectSearch($this->m_sClass);
$oFilter->AddCondition($this->m_sLinkageAttr, $this->m_iObjectId, '=');
@@ -395,7 +393,7 @@ EOF
$oFilter = new DBObjectSearch($this->m_sLinkedClass);
$oSet = new CMDBObjectSet($oFilter);
$oBlock = new DisplayBlock($oFilter, 'list', false);
$oBlock->Display($oP, 'ResultsToAdd', array('menu' => false, 'selection_mode' => true, 'display_limit' => false)); // Don't display the 'Actions' menu on the results
$oBlock->Display($oP, 'ResultsToAdd', array('menu' => false, 'selection_mode' => true)); // Don't display the 'Actions' menu on the results
}
public function DoAddObjects(WebPage $oP, $aLinkedObjectIds = array())

View File

@@ -53,7 +53,7 @@ class UIWizard
{
if ($iStepIndex == 1) // one big form that contains everything, to make sure that the uploaded files are posted too
{
$this->m_oPage->add("<form method=\"post\" enctype=\"multipart/form-data\" action=\"../pages/UI.php\">\n");
$this->m_oPage->add("<form method=\"post\" enctype=\"multipart/form-data\" action=\"".utils::GetAbsoluteUrlAppRoot()."pages/UI.php\">\n");
}
$this->m_oPage->add("<div class=\"wizContainer\" id=\"wizStep$iStepIndex\" style=\"display:none;\">\n");
$this->m_oPage->add("<a name=\"step$iStepIndex\" />\n");
@@ -112,8 +112,7 @@ class UIWizard
<input type=\"button\" value=\"".Dict::S('UI:Button:Next')."\" onClick=\"GoToStep($iStepIndex, 1+$iStepIndex)\" />
<input type=\"button\" value=\"".Dict::S('UI:Button:Finish')."\" $sDisabled onClick=\"GoToStep($iStepIndex, 1+$nbSteps)\" />
</div>\n");
$this->m_oPage->add("
<script type=\"text/javascript\">
$this->m_oPage->add_script("
function OnEnterStep{$iStepIndex}()
{
oWizardHelper.ResetQuery();
@@ -123,7 +122,7 @@ $sJSHandlerCode
oWizardHelper.AjaxQueryServer();
}
</script>\n");
");
$this->m_oPage->add("</div>\n\n");
}
@@ -139,16 +138,15 @@ $sJSHandlerCode
$this->m_oPage->add("<input type=\"hidden\" name=\"operation\" value=\"wizard_apply_new\" />\n");
$this->m_oPage->add("<input type=\"hidden\" name=\"transaction_id\" value=\"".utils::GetNewTransactionId()."\" />\n");
$this->m_oPage->add("<input type=\"hidden\" id=\"wizard_json_obj\" name=\"json_obj\" value=\"\" />\n");
$this->m_oPage->add("<script type=\"text/javascript\">\n");
$this->m_oPage->add("function OnEnterStep$iStepIndex() {\n");
$sScript = "function OnEnterStep$iStepIndex() {\n";
foreach($aFieldsMap as $iInputId => $sAttCode)
{
$this->m_oPage->add("\toWizardHelper.UpdateCurrentValue('$sAttCode');\n");
$sScript .= "\toWizardHelper.UpdateCurrentValue('$sAttCode');\n";
}
$this->m_oPage->add("\toWizardHelper.Preview('object_preview');\n");
$this->m_oPage->add("\t$('#wizard_json_obj').val(oWizardHelper.ToJSON());\n");
$this->m_oPage->add("}\n");
$this->m_oPage->add("</script>\n");
$sScript .= "\toWizardHelper.Preview('object_preview');\n";
$sScript .= "\t$('#wizard_json_obj').val(oWizardHelper.ToJSON());\n";
$sScript .= "}\n";
$this->m_oPage->add_script($sScript);
$this->m_oPage->add("<div id=\"object_preview\">\n");
$this->m_oPage->add("</div>\n");
$this->m_oPage->add($oAppContext->GetForForm());

View File

@@ -27,6 +27,7 @@ require_once(APPROOT.'/core/config.class.inc.php');
require_once(APPROOT.'/application/transaction.class.inc.php');
define('ITOP_CONFIG_FILE', APPROOT.'/config-itop.php');
define('SERVER_NAME_PLACEHOLDER', '$SERVER_NAME$');
class FileUploadException extends Exception
{
@@ -41,9 +42,11 @@ class utils
{
private static $m_sConfigFile = ITOP_CONFIG_FILE;
private static $m_oConfig = null;
private static $m_bCASClient = false;
// Parameters loaded from a file, parameters of the page/command line still have precedence
private static $m_aParamsFromFile = null;
private static $m_aParamSource = array();
protected static function LoadParamFile($sParamFile)
{
@@ -80,13 +83,14 @@ class utils
$sParam = $aMatches[1];
$value = trim($aMatches[2]);
self::$m_aParamsFromFile[$sParam] = $value;
self::$m_aParamSource[$sParam] = $sParamFile;
}
}
}
public static function UseParamFile($sParamFileArgName = 'param_file', $bAllowCLI = true)
{
$sFileSpec = self::ReadParam($sParamFileArgName, '', $bAllowCLI);
$sFileSpec = self::ReadParam($sParamFileArgName, '', $bAllowCLI, 'raw_data');
foreach(explode(',', $sFileSpec) as $sFile)
{
$sFile = trim($sFile);
@@ -97,6 +101,25 @@ class utils
}
}
/**
* Return the source file from which the parameter has been found,
* usefull when it comes to pass user credential to a process executed
* in the background
* @param $sName Parameter name
* @return The file name if any, or null
*/
public static function GetParamSourceFile($sName)
{
if (array_key_exists($sName, self::$m_aParamSource))
{
return self::$m_aParamSource[$sName];
}
else
{
return null;
}
}
public static function IsModeCLI()
{
$sSAPIName = php_sapi_name();
@@ -112,7 +135,7 @@ class utils
}
public static function ReadParam($sName, $defaultValue = "", $bAllowCLI = false)
public static function ReadParam($sName, $defaultValue = "", $bAllowCLI = false, $sSanitizationFilter = 'parameter')
{
global $argv;
$retValue = $defaultValue;
@@ -139,29 +162,115 @@ class utils
}
}
}
return $retValue;
return self::Sanitize($retValue, $defaultValue, $sSanitizationFilter);
}
public static function ReadPostedParam($sName, $defaultValue = "")
public static function ReadPostedParam($sName, $defaultValue = '', $sSanitizationFilter = 'parameter')
{
return isset($_POST[$sName]) ? $_POST[$sName] : $defaultValue;
$retValue = isset($_POST[$sName]) ? $_POST[$sName] : $defaultValue;
return self::Sanitize($retValue, $defaultValue, $sSanitizationFilter);
}
public static function Sanitize($value, $defaultValue, $sSanitizationFilter)
{
if ($value === $defaultValue)
{
// Preserve the real default value (can be used to detect missing mandatory parameters)
$retValue = $value;
}
else
{
$retValue = self::Sanitize_Internal($value, $sSanitizationFilter);
if ($retValue === false)
{
$retValue = $defaultValue;
}
}
return $retValue;
}
protected static function Sanitize_Internal($value, $sSanitizationFilter)
{
switch($sSanitizationFilter)
{
case 'integer':
$retValue = filter_var($value, FILTER_SANITIZE_NUMBER_INT);
break;
case 'class':
$retValue = $value;
if (!MetaModel::IsValidClass($value))
{
$retValue = false;
}
break;
case 'string':
$retValue = filter_var($value, FILTER_SANITIZE_SPECIAL_CHARS);
break;
case 'parameter':
case 'field_name':
if (is_array($value))
{
$retValue = array();
foreach($value as $key => $val)
{
$retValue[$key] = self::Sanitize_Internal($val, $sSanitizationFilter); // recursively check arrays
if ($retValue[$key] === false)
{
$retValue = false;
break;
}
}
}
else
{
switch($sSanitizationFilter)
{
case 'parameter':
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options"=>array("regexp"=>'/^[ A-Za-z0-9_=-]*$/'))); // the '=' equal character is used in serialized filters
break;
case 'field_name':
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options"=>array("regexp"=>'/^[A-Za-z0-9_]+(->[A-Za-z0-9_]+)*$/'))); // att_code or att_code->name or AttCode->Name or AttCode->Key2->Name
break;
}
}
break;
break;
default:
case 'raw_data':
$retValue = $value;
// Do nothing
}
return $retValue;
}
/**
* Reads an uploaded file and turns it into an ormDocument object - Triggers an exception in case of error
* @param string $sName Name of the input used from uploading the file
* @param string $sIndex If Name is an array of posted files, then the index must be used to point out the file
* @return ormDocument The uploaded file (can be 'empty' if nothing was uploaded)
*/
public static function ReadPostedDocument($sName)
public static function ReadPostedDocument($sName, $sIndex = null)
{
$oDocument = new ormDocument(); // an empty document
if(isset($_FILES[$sName]))
{
switch($_FILES[$sName]['error'])
$aFileInfo = $_FILES[$sName];
$sError = is_null($sIndex) ? $aFileInfo['error'] : $aFileInfo['error'][$sIndex];
switch($sError)
{
case UPLOAD_ERR_OK:
$doc_content = file_get_contents($_FILES[$sName]['tmp_name']);
$sMimeType = $_FILES[$sName]['type'];
$sTmpName = is_null($sIndex) ? $aFileInfo['tmp_name'] : $aFileInfo['tmp_name'][$sIndex];
$sMimeType = is_null($sIndex) ? $aFileInfo['type'] : $aFileInfo['type'][$sIndex];
$sName = is_null($sIndex) ? $aFileInfo['name'] : $aFileInfo['name'][$sIndex];
$doc_content = file_get_contents($sTmpName);
if (function_exists('finfo_file'))
{
// as of PHP 5.3 the fileinfo extension is bundled within PHP
@@ -179,7 +288,7 @@ class utils
}
@finfo_close($rInfo);
}
$oDocument = new ormDocument($doc_content, $sMimeType, $_FILES[$sName]['name']);
$oDocument = new ormDocument($doc_content, $sMimeType, $sName);
break;
case UPLOAD_ERR_NO_FILE:
@@ -204,11 +313,12 @@ class utils
break;
case UPLOAD_ERR_EXTENSION:
throw new FileUploadException(Dict::Format('UI:Error:UploadStoppedByExtension_FileName', $_FILES[$sName]['name']));
$sName = is_null($sIndex) ? $aFileInfo['name'] : $aFileInfo['name'][$sIndex];
throw new FileUploadException(Dict::Format('UI:Error:UploadStoppedByExtension_FileName', $sName));
break;
default:
throw new FileUploadException(Dict::Format('UI:Error:UploadFailedUnknownCause_Code', $_FILES[$sName]['error']));
throw new FileUploadException(Dict::Format('UI:Error:UploadFailedUnknownCause_Code', $sError));
break;
}
@@ -216,6 +326,43 @@ class utils
return $oDocument;
}
/**
* Interprets the results posted by a normal or paginated list (in multiple selection mode)
* @param $oFullSetFilter DBObjectSearch The criteria defining the whole sets of objects being selected
* @return Array An arry of object IDs corresponding to the objects selected in the set
*/
public static function ReadMultipleSelection($oFullSetFilter)
{
$aSelectedObj = utils::ReadParam('selectObject', array());
$sSelectionMode = utils::ReadParam('selectionMode', '');
if ($sSelectionMode != '')
{
// Paginated selection
$aExceptions = utils::ReadParam('storedSelection', array());
if ($sSelectionMode == 'positive')
{
// Only the explicitely listed items are selected
$aSelectedObj = $aExceptions;
}
else
{
// All items of the set are selected, except the one explicitely listed
$aSelectedObj = array();
$oFullSet = new DBObjectSet($oFullSetFilter);
$sClassAlias = $oFullSetFilter->GetClassAlias();
$oFullSet->OptimizeColumnLoad(array($sClassAlias => array('friendlyname'))); // We really need only the IDs but it does not work since id is not a real field
while($oObj = $oFullSet->Fetch())
{
if (!in_array($oObj->GetKey(), $aExceptions))
{
$aSelectedObj[] = $oObj->GetKey();
}
}
}
}
return $aSelectedObj;
}
public static function GetNewTransactionId()
{
return privUITransaction::GetNewTransactionId();
@@ -264,39 +411,89 @@ class utils
return $iReturn;
}
/**
* Returns an absolute URL to the current page
* @param $bQueryString bool True to also get the query string, false otherwise
* @param $bForceHTTPS bool True to force HTTPS, false otherwise
* @return string The absolute URL to the current page
*/
static public function GetAbsoluteUrl($bQueryString = true, $bForceHTTPS = false)
/**
* Helper function to convert a string to a date, given a format specification. It replaces strtotime which does not allow for specifying a date in a french format (for instance)
* Example: StringToTime('01/05/11 12:03:45', '%d/%m/%y %H:%i:%s')
* @param string $sDate
* @param string $sFormat
* @return timestamp or false if the input format is not correct
*/
public static function StringToTime($sDate, $sFormat)
{
// Build an absolute URL to this page on this server/port
$sServerName = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '';
if (MetaModel::GetConfig()->GetSecureConnectionRequired() || MetaModel::GetConfig()->GetHttpsHyperlinks())
// Source: http://php.net/manual/fr/function.strftime.php
// (alternative: http://www.php.net/manual/fr/datetime.formats.date.php)
static $aDateTokens = null;
static $aDateRegexps = null;
if (is_null($aDateTokens))
{
// If a secure connection is required, or if the URL is requested to start with HTTPS
// then any URL must start with https !
$bForceHTTPS = true;
$aSpec = array(
'%d' =>'(?<day>[0-9]{2})',
'%m' => '(?<month>[0-9]{2})',
'%y' => '(?<year>[0-9]{2})',
'%Y' => '(?<year>[0-9]{4})',
'%H' => '(?<hour>[0-2][0-9])',
'%i' => '(?<minute>[0-5][0-9])',
'%s' => '(?<second>[0-5][0-9])',
);
$aDateTokens = array_keys($aSpec);
$aDateRegexps = array_values($aSpec);
}
if ($bForceHTTPS)
{
$sProtocol = 'https';
$sPort = '';
$sDateRegexp = str_replace($aDateTokens, $aDateRegexps, $sFormat);
if (preg_match('!^(?<head>)'.$sDateRegexp.'(?<tail>)$!', $sDate, $aMatches))
{
$sYear = isset($aMatches['year']) ? $aMatches['year'] : 0;
$sMonth = isset($aMatches['month']) ? $aMatches['month'] : 1;
$sDay = isset($aMatches['day']) ? $aMatches['day'] : 1;
$sHour = isset($aMatches['hour']) ? $aMatches['hour'] : 0;
$sMinute = isset($aMatches['minute']) ? $aMatches['minute'] : 0;
$sSecond = isset($aMatches['second']) ? $aMatches['second'] : 0;
return strtotime("$sYear-$sMonth-$sDay $sHour:$sMinute:$sSecond");
}
else
else
{
return false;
}
// http://www.spaweditor.com/scripts/regex/index.php
}
/**
* Returns the absolute URL to the server's root path
* @return string The absolute URL to the server's root, without the first slash
*/
static public function GetAbsoluteUrlAppRoot()
{
$sUrl = MetaModel::GetConfig()->Get('app_root_url');
if (strpos($sUrl, SERVER_NAME_PLACEHOLDER) > -1)
{
$sProtocol = (isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS']!="off")) ? 'https' : 'http';
$iPort = isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : 80;
if ($sProtocol == 'http')
if (isset($_SERVER['SERVER_NAME']))
{
$sPort = ($iPort == 80) ? '' : ':'.$iPort;
$sServerName = $_SERVER['SERVER_NAME'];
}
else
{
$sPort = ($iPort == 443) ? '' : ':'.$iPort;
// CLI mode ?
$sServerName = php_uname('n');
}
$sUrl = str_replace(SERVER_NAME_PLACEHOLDER, $sServerName, $sUrl);
}
return $sUrl;
}
static public function GetDefaultUrlAppRoot()
{
// Build an absolute URL to this page on this server/port
$sServerName = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '';
$sProtocol = (isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS']!="off")) ? 'https' : 'http';
$iPort = isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : 80;
if ($sProtocol == 'http')
{
$sPort = ($iPort == 80) ? '' : ':'.$iPort;
}
else
{
$sPort = ($iPort == 443) ? '' : ':'.$iPort;
}
// $_SERVER['REQUEST_URI'] is empty when running on IIS
// Let's use Ivan Tcholakov's fix (found on www.dokeos.com)
@@ -313,51 +510,53 @@ class utils
}
$_SERVER['REQUEST_URI'] = $sPath;
}
$sPath = $_SERVER['REQUEST_URI'];
if (!$bQueryString)
{
// remove all the parameters from the query string
$iQuestionMarkPos = strpos($sPath, '?');
if ($iQuestionMarkPos !== false)
{
$sPath = substr($sPath, 0, $iQuestionMarkPos);
}
}
$sUrl = "$sProtocol://{$sServerName}{$sPort}{$sPath}";
return $sUrl;
}
/**
* Returns the absolute URL PATH of the current page
* @param $bForceHTTPS bool True to force HTTPS, false otherwise
* @return string The absolute URL to the current page
*/
static public function GetAbsoluteUrlPath($bForceHTTPS = false)
{
$sAbsoluteUrl = self::GetAbsoluteUrl(false, $bForceHTTPS); // False => Don't get the query string
$sAbsoluteUrl = substr($sAbsoluteUrl, 0, 1+strrpos($sAbsoluteUrl, '/')); // remove the current page, keep just the path, up to the last /
return $sAbsoluteUrl;
}
$sPath = $_SERVER['REQUEST_URI'];
/**
* Returns the absolute URL to the server's root path
* @param $bForceHTTPS bool True to force HTTPS, false otherwise
* @return string The absolute URL to the server's root, without the first slash
*/
static public function GetAbsoluteUrlAppRoot($sCurrentRelativePath = '', $bForceHTTPS = false)
{
$sAbsoluteUrl = self::GetAbsoluteUrl(false, $bForceHTTPS); // False => Don't get the query string
// remove all the parameters from the query string
$iQuestionMarkPos = strpos($sPath, '?');
if ($iQuestionMarkPos !== false)
{
$sPath = substr($sPath, 0, $iQuestionMarkPos);
}
$sAbsoluteUrl = "$sProtocol://{$sServerName}{$sPort}{$sPath}";
$sCurrentScript = realpath($_SERVER['SCRIPT_FILENAME']);
$sCurrentScript = str_replace('\\', '/', $sCurrentScript); // canonical path
$sAppRoot = str_replace('\\', '/', APPROOT); // canonical path
$sCurrentRelativePath = str_replace($sAppRoot, '', $sCurrentScript);
$sAppRootPos = strpos($sAbsoluteUrl, $sCurrentRelativePath);
if ($sAppRootPos !== false)
{
$sAbsoluteUrl = substr($sAbsoluteUrl, 0, $sAppRootPos); // remove the current page and path
$sAppRootUrl = substr($sAbsoluteUrl, 0, $sAppRootPos); // remove the current page and path
}
else
{
throw new Exception("Failed to determine application root path $sAbsoluteUrl ($sCurrentRelativePath)");
// Second attempt without index.php at the end...
$sCurrentRelativePath = str_replace('index.php', '', $sCurrentRelativePath);
$sAppRootPos = strpos($sAbsoluteUrl, $sCurrentRelativePath);
if ($sAppRootPos !== false)
{
$sAppRootUrl = substr($sAbsoluteUrl, 0, $sAppRootPos); // remove the current page and path
}
else
{
// No luck...
throw new Exception("Failed to determine application root path $sAbsoluteUrl ($sCurrentRelativePath) APPROOT:'$sAppRoot'");
}
}
return $sAbsoluteUrl;
return $sAppRootUrl;
}
/**
* Returns the absolute URL to the modules root path
* Hardcoded here for compatibility with iTop 2.0 modules
* @return string The absolute URL to the modules
*/
static public function GetAbsoluteUrlModulesRoot()
{
$sUrl = self::GetAbsoluteUrlAppRoot().'modules/';
return $sUrl;
}
/**
@@ -369,7 +568,157 @@ class utils
*/
static function CanLogOff()
{
return (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'form');
$bResult = false;
if(isset($_SESSION['login_mode']))
{
$sLoginMode = $_SESSION['login_mode'];
switch($sLoginMode)
{
case 'external':
$bResult = false;
break;
case 'form':
case 'basic':
case 'url':
case 'cas':
default:
$bResult = true;
}
}
return $bResult;
}
/**
* Initializes the CAS client
*/
static function InitCASClient()
{
$sCASIncludePath = MetaModel::GetConfig()->Get('cas_include_path');
include_once($sCASIncludePath.'/CAS.php');
$bCASDebug = MetaModel::GetConfig()->Get('cas_debug');
if ($bCASDebug)
{
phpCAS::setDebug(APPROOT.'/error.log');
}
if (!self::$m_bCASClient)
{
// Initialize phpCAS
$sCASVersion = MetaModel::GetConfig()->Get('cas_version');
$sCASHost = MetaModel::GetConfig()->Get('cas_host');
$iCASPort = MetaModel::GetConfig()->Get('cas_port');
$sCASContext = MetaModel::GetConfig()->Get('cas_context');
phpCAS::client($sCASVersion, $sCASHost, $iCASPort, $sCASContext, false /* session already started */);
self::$m_bCASClient = true;
$sCASCACertPath = MetaModel::GetConfig()->Get('cas_server_ca_cert_path');
if (empty($sCASCACertPath))
{
// If no certificate authority is provided, do not attempt to validate
// the server's certificate
// THIS SETTING IS NOT RECOMMENDED FOR PRODUCTION.
// VALIDATING THE CAS SERVER IS CRUCIAL TO THE SECURITY OF THE CAS PROTOCOL!
phpCAS::setNoCasServerValidation();
}
else
{
phpCAS::setCasServerCACert($sCASCACertPath);
}
}
}
static function DebugBacktrace($iLimit = 5)
{
$aFullTrace = debug_backtrace();
$aLightTrace = array();
for($i=1; ($i<=$iLimit && $i < count($aFullTrace)); $i++) // Skip the last function call... which is the call to this function !
{
$aLightTrace[$i] = $aFullTrace[$i]['function'].'(), called from line '.$aFullTrace[$i]['line'].' in '.$aFullTrace[$i]['file'];
}
echo "<p><pre>".print_r($aLightTrace, true)."</pre></p>\n";
}
/**
* Execute the given iTop PHP script, passing it the current credentials
* Only CLI mode is supported, because of the need to hand the credentials over to the next process
* Throws an exception if the execution fails or could not be attempted (config issue)
* @param string $sScript Name and relative path to the file (relative to the iTop root dir)
* @param hash $aArguments Associative array of 'arg' => 'value'
* @return array(iCode, array(output lines))
*/
/**
*/
static function ExecITopScript($sScriptName, $aArguments)
{
$aDisabled = explode(', ', ini_get('disable_functions'));
if (in_array('exec', $aDisabled))
{
throw new Exception("The PHP exec() function has been disabled on this server");
}
$sPHPExec = trim(MetaModel::GetConfig()->Get('php_path'));
if (strlen($sPHPExec) == 0)
{
throw new Exception("The path to php must not be empty. Please set a value for 'php_path' in your configuration file.");
}
$sAuthUser = self::ReadParam('auth_user', '', 'raw_data');
$sAuthPwd = self::ReadParam('auth_pwd', '', 'raw_data');
$sParamFile = self::GetParamSourceFile('auth_user');
if (is_null($sParamFile))
{
$aArguments['auth_user'] = $sAuthUser;
$aArguments['auth_pwd'] = $sAuthPwd;
}
else
{
$aArguments['param_file'] = $sParamFile;
}
$aArgs = array();
foreach($aArguments as $sName => $value)
{
// Note: See comment from the 23-Apr-2004 03:30 in the PHP documentation
// It suggests to rely on pctnl_* function instead of using escapeshellargs
$aArgs[] = "--$sName=".escapeshellarg($value);
}
$sArgs = implode(' ', $aArgs);
$sScript = realpath(APPROOT.$sScriptName);
if (!file_exists($sScript))
{
throw new Exception("Could not find the script file '$sScriptName' from the directory '".APPROOT."'");
}
$sCommand = '"'.$sPHPExec.'" '.escapeshellarg($sScript).' -- '.$sArgs;
if (version_compare(phpversion(), '5.3.0', '<'))
{
if (substr(PHP_OS,0,3) == 'WIN')
{
// Under Windows, and for PHP 5.2.x, the whole command has to be quoted
// Cf PHP doc: http://php.net/manual/fr/function.exec.php, comment from the 27-Dec-2010
$sCommand = '"'.$sCommand.'"';
}
}
$sLastLine = exec($sCommand, $aOutput, $iRes);
if ($iRes == 1)
{
throw new Exception(Dict::S('Core:ExecProcess:Code1')." - ".$sCommand);
}
elseif ($iRes == 255)
{
$sErrors = implode("\n", $aOutput);
throw new Exception(Dict::S('Core:ExecProcess:Code255')." - ".$sCommand.":\n".$sErrors);
}
//$aOutput[] = $sCommand;
return array($iRes, $aOutput);
}
}
?>

View File

@@ -47,7 +47,12 @@ class WebPage
protected $a_base;
protected $iNextId;
protected $iTransactionId;
protected $sContentType;
protected $sContentDisposition;
protected $sContentFileName;
protected $s_sOutputFormat;
protected $a_OutputOptions;
public function __construct($s_title)
{
$this->s_title = $s_title;
@@ -61,6 +66,11 @@ class WebPage
$this->a_base = array( 'href' => '', 'target' => '');
$this->iNextId = 0;
$this->iTransactionId = 0;
$this->sContentType = '';
$this->sContentDisposition = '';
$this->sContentFileName = '';
$this->s_OutputFormat = utils::ReadParam('output_format', 'html');
$this->a_OutputOptions = array();
ob_start(); // Start capturing the output
}
@@ -90,11 +100,11 @@ class WebPage
}
/**
* Add any text or HTML fragment at the end of the body of the page
* Add any text or HTML fragment (identified by an ID) at the end of the body of the page
* This is useful to add hidden content, DIVs or FORMs that should not
* be embedded into each other.
*/
public function add_at_the_end($s_html)
public function add_at_the_end($s_html, $sId = '')
{
$this->s_deferred_content .= $s_html;
}
@@ -154,27 +164,33 @@ class WebPage
$sHtml .= "<tbody>\n";
foreach($aData as $aRow)
{
if (isset($aRow['@class'])) // Row specific class, for hilighting certain rows
{
$sHtml .= "<tr class=\"{$aRow['@class']}\">\n";
}
else
{
$sHtml .= "<tr>\n";
}
foreach($aConfig as $sName=>$aAttribs)
{
$aMatches = array();
$sClass = isset($aAttribs['class']) ? 'class="'.$aAttribs['class'].'"' : '';
$sValue = ($aRow[$sName] === '') ? '&nbsp;' : $aRow[$sName];
$sHtml .= "<td $sClass>$sValue</td>\n";
}
$sHtml .= "</tr>\n";
$sHtml .= $this->GetTableRow($aRow, $aConfig);
}
$sHtml .= "</tbody>\n";
$sHtml .= "</table>\n";
return $sHtml;
}
public function GetTableRow($aRow, $aConfig)
{
$sHtml = '';
if (isset($aRow['@class'])) // Row specific class, for hilighting certain rows
{
$sHtml .= "<tr class=\"{$aRow['@class']}\">";
}
else
{
$sHtml .= "<tr>";
}
foreach($aConfig as $sName=>$aAttribs)
{
$sClass = isset($aAttribs['class']) ? 'class="'.$aAttribs['class'].'"' : '';
$sValue = ($aRow[$sName] === '') ? '&nbsp;' : $aRow[$sName];
$sHtml .= "<td $sClass>$sValue</td>";
}
$sHtml .= "</tr>";
return $sHtml;
}
/**
* Add some Javascript to the header of the page
@@ -240,6 +256,28 @@ class WebPage
$this->add($this->GetDetails($aFields));
}
/**
* Records the current state of the 'html' part of the page output
* @return mixed The current state of the 'html' output
*/
public function start_capture()
{
return strlen($this->s_content);
}
/**
* Returns the part of the html output that occurred since the call to start_capture
* and removes this part from the current html output
* @param $offset mixed The value returned by start_capture
* @return string The part of the html output that was added since the call to start_capture
*/
public function end_capture($offset)
{
$sCaptured = substr($this->s_content, $offset);
$this->s_content = substr($this->s_content, 0, $offset);
return $sCaptured;
}
/**
* Build a special kind of TABLE useful for displaying the details of an object from a hash array of data
@@ -269,6 +307,53 @@ class WebPage
$sHtml .= "</table>\n";
return $sHtml;
}
/**
* Build a set of radio buttons suitable for editing a field/attribute of an object (including its validation)
* @param $aAllowedValues hash Array of value => display_value
* @param $value mixed Current value for the field/attribute
* @param $iId mixed Unique Id for the input control in the page
* @param $sFieldName string The name of the field, attr_<$sFieldName> will hold the value for the field
* @param $bMandatory bool Whether or not the field is mandatory
* @param $bVertical bool Disposition of the radio buttons vertical or horizontal
* @param $sValidationField string HTML fragment holding the validation field (exclamation icon...)
* @return string The HTML fragment corresponding to the radio buttons
*/
public function GetRadioButtons($aAllowedValues, $value, $iId, $sFieldName, $bMandatory, $bVertical, $sValidationField)
{
$idx = 0;
$sHTMLValue = '';
foreach($aAllowedValues as $key => $display_value)
{
if ((count($aAllowedValues) == 1) && ($bMandatory == 'true') )
{
// When there is only once choice, select it by default
$sSelected = ' checked';
}
else
{
$sSelected = ($value == $key) ? ' checked' : '';
}
$sHTMLValue .= "<input type=\"radio\" id=\"{$iId}_{$key}\" name=\"radio_$sFieldName\" onChange=\"$('#{$iId}').val(this.value).trigger('change');\" value=\"$key\"$sSelected><label class=\"radio\" for=\"{$iId}_{$key}\">&nbsp;$display_value</label>&nbsp;";
if ($bVertical)
{
if ($idx == 0)
{
// Validation icon at the end of the first line
$sHTMLValue .= "&nbsp;{$sValidationField}\n";
}
$sHTMLValue .= "<br>\n";
}
$idx++;
}
$sHTMLValue .= "<input type=\"hidden\" id=\"$iId\" name=\"$sFieldName\" value=\"$value\"/>";
if (!$bVertical)
{
// Validation icon at the end of the line
$sHTMLValue .= "&nbsp;{$sValidationField}\n";
}
return $sHTMLValue;
}
/**
* Outputs (via some echo) the complete HTML page by assembling all its elements
@@ -284,20 +369,20 @@ class WebPage
echo "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n";
echo "<html>\n";
echo "<head>\n";
echo "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n";
echo "<title>{$this->s_title}</title>\n";
echo "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n";
echo "<title>".htmlentities($this->s_title, ENT_QUOTES, 'UTF-8')."</title>\n";
echo $this->get_base_tag();
foreach($this->a_linked_scripts as $s_script)
{
// Make sure that the URL to the script contains the application's version number
// so that the new script do NOT get reloaded from the cache when the application is upgraded
if (strpos('?', $s_script) === false)
if (strpos($s_script, '?') === false)
{
$s_script .= "?version=".ITOP_VERSION;
$s_script .= "?itopversion=".ITOP_VERSION;
}
else
{
$s_script .= "&version=".ITOP_VERSION;
$s_script .= "&itopversion=".ITOP_VERSION;
}
echo "<script type=\"text/javascript\" src=\"$s_script\"></script>\n";
}
@@ -332,14 +417,18 @@ class WebPage
}
echo "</style>\n";
}
if (class_exists('MetaModel') && MetaModel::GetConfig())
{
echo "<link rel=\"shortcut icon\" href=\"".utils::GetAbsoluteUrlAppRoot()."images/favicon.ico\" />\n";
}
echo "</head>\n";
echo "<body>\n";
echo $this->s_content;
echo self::FilterXSS($this->s_content);
if (trim($s_captured_output) != "")
{
echo "<div class=\"raw_output\">$s_captured_output</div>\n";
echo "<div class=\"raw_output\">".self::FilterXSS($s_captured_output)."</div>\n";
}
echo '<div id="at_the_end">'.$this->s_deferred_content.'</div>';
echo '<div id="at_the_end">'.self::FilterXSS($this->s_deferred_content).'</div>';
echo "</body>\n";
echo "</html>\n";
}
@@ -384,7 +473,29 @@ class WebPage
{
return $this->iNextId++;
}
/**
* Set the content-type (mime type) for the page's content
* @param $sContentType string
* @return void
*/
public function SetContentType($sContentType)
{
$this->sContentType = $sContentType;
}
/**
* Set the content-disposition (mime type) for the page's content
* @param $sDisposition string The disposition: 'inline' or 'attachment'
* @param $sFileName string The original name of the file
* @return void
*/
public function SetContentDisposition($sDisposition, $sFileName)
{
$this->sContentDisposition = $sDisposition;
$this->sContentFileName = $sFileName;
}
/**
* Set the transactionId of the current form
* @param $iTransactionId integer
@@ -403,5 +514,72 @@ class WebPage
{
return $this->iTransactionId;
}
public static function FilterXSS($sHTML)
{
return str_ireplace('<script', '&lt;script', $sHTML);
}
/**
* What is the currently selected output format
* @return string The selected output format: html, pdf...
*/
public function GetOutputFormat()
{
return $this->s_OutputFormat;
}
/**
* Check whether the desired output format is possible or not
* @param string $sOutputFormat The desired output format: html, pdf...
* @return bool True if the format is Ok, false otherwise
*/
function IsOutputFormatAvailable($sOutputFormat)
{
$bResult = false;
switch($sOutputFormat)
{
case 'html':
$bResult = true; // Always supported
break;
case 'pdf':
$bResult = @is_readable(APPROOT.'lib/MPDF/mpdf.php');
break;
}
return $bResult;
}
/**
* Retrieves the value of a named output option for the given format
* @param string $sFormat The format: html or pdf
* @param string $sOptionName The name of the option
* @return mixed false if the option was never set or the options's value
*/
public function GetOutputOption($sFormat, $sOptionName)
{
if (isset($this->a_OutputOptions[$sFormat][$sOptionName]))
{
return $this->a_OutputOptions[$sFormat][$sOptionName];
}
return false;
}
/**
* Sets a named output option for the given format
* @param string $sFormat The format for which to set the option: html or pdf
* @param string $sOptionName the name of the option
* @param mixed $sValue The value of the option
*/
public function SetOutputOption($sFormat, $sOptionName, $sValue)
{
if (!isset($this->a_OutputOptions[$sFormat]))
{
$this->a_OutputOptions[$sFormat] = array($sOptionName => $sValue);
}
else
{
$this->a_OutputOptions[$sFormat][$sOptionName] = $sValue;
}
}
}
?>

View File

@@ -98,7 +98,7 @@ class WizardHelper
{
if ($bReadUploadedFiles)
{
$oDocument = utils::ReadPostedDocument('file_'.$sAttCode);
$oDocument = utils::ReadPostedDocument('attr_'.$sAttCode, 'fcontents');
$oObj->Set($sAttCode, $oDocument);
}
else
@@ -125,7 +125,7 @@ class WizardHelper
{
$oObj->Set(MetaModel::GetStateAttributeCode($this->m_aData['m_sClass']), $this->m_aData['m_sState']);
}
$oObj->DoComputeValues();
return $oObj;
}

View File

@@ -1,5 +1,6 @@
<?php
define('APPROOT', dirname(__FILE__).'/');
define('MODULESROOT', APPROOT.'modules/');
if (function_exists('microtime'))
{
$fItopStarted = microtime(true);

View File

@@ -42,7 +42,7 @@ abstract class Action extends cmdbAbstractObject
"key_type" => "autoincrement",
"name_attcode" => "name",
"state_attcode" => "",
"reconc_keys" => array(),
"reconc_keys" => array('name'),
"db_table" => "priv_action",
"db_key_field" => "id",
"db_finalclass_field" => "realclass",
@@ -106,7 +106,7 @@ abstract class ActionNotification extends Action
"key_type" => "autoincrement",
"name_attcode" => "name",
"state_attcode" => "",
"reconc_keys" => array(),
"reconc_keys" => array('name'),
"db_table" => "priv_action_notification",
"db_key_field" => "id",
"db_finalclass_field" => "",
@@ -139,7 +139,7 @@ class ActionEmail extends ActionNotification
"key_type" => "autoincrement",
"name_attcode" => "name",
"state_attcode" => "",
"reconc_keys" => array(),
"reconc_keys" => array('name'),
"db_table" => "priv_action_email",
"db_key_field" => "id",
"db_finalclass_field" => "",
@@ -183,6 +183,7 @@ class ActionEmail extends ActionNotification
try
{
$oSearch = DBObjectSearch::FromOQL($sOQL);
$oSearch->AllowAllData();
}
catch (OQLException $e)
{
@@ -274,26 +275,38 @@ class ActionEmail extends ActionNotification
protected function _DoExecute($oTrigger, $aContextArgs, &$oLog)
{
$this->m_iRecipients = 0;
$this->m_aMailErrors = array();
$bRes = false; // until we do succeed in sending the email
// Determine recicipients
//
$sTo = $this->FindRecipients('to', $aContextArgs);
$sCC = $this->FindRecipients('cc', $aContextArgs);
$sBCC = $this->FindRecipients('bcc', $aContextArgs);
$sFrom = $this->Get('from');
$sReplyTo = $this->Get('reply_to');
$sSubject = MetaModel::ApplyParams($this->Get('subject'), $aContextArgs);
$sBody = MetaModel::ApplyParams($this->Get('body'), $aContextArgs);
$sPreviousUrlMaker = ApplicationContext::SetUrlMakerClass();
$aHeaders = array();
try
{
$this->m_iRecipients = 0;
$this->m_aMailErrors = array();
$bRes = false; // until we do succeed in sending the email
// Determine recicipients
//
$sTo = $this->FindRecipients('to', $aContextArgs);
$sCC = $this->FindRecipients('cc', $aContextArgs);
$sBCC = $this->FindRecipients('bcc', $aContextArgs);
$sFrom = $this->Get('from');
$sReplyTo = $this->Get('reply_to');
$sSubject = MetaModel::ApplyParams($this->Get('subject'), $aContextArgs);
$sBody = MetaModel::ApplyParams($this->Get('body'), $aContextArgs);
$oObj = $aContextArgs['this->object()'];
$sMessageId = sprintf('<iTop_%s_%d_%f@%s.openitop.org>', get_class($oObj), $oObj->GetKey(), microtime(true /* get as float*/), MetaModel::GetConfig()->Get('session_name'));
$sReference = $sMessageId;
$aHeaders['Message-ID'] = $sMessageId;
}
catch(Exception $e)
{
ApplicationContext::SetUrlMakerClass($sPreviousUrlMaker);
throw $e;
}
ApplicationContext::SetUrlMakerClass($sPreviousUrlMaker);
$oObj = $aContextArgs['this->object()'];
$sServerIP = $_SERVER['SERVER_ADDR']; //gethostbyname(gethostname());
$sReference = '<iTop/'.get_class($oObj).'/'.$oObj->GetKey().'@'.$sServerIP.'>';
if (!is_null($oLog))
{
// Note: we have to secure this because those values are calculated
@@ -307,7 +320,7 @@ class ActionEmail extends ActionNotification
if (isset($sBody)) $oLog->Set('body', $sBody);
}
$oEmail = new EMail();
$oEmail = new EMail('', '', '', $aHeaders);
if ($this->IsBeingTested())
{

View File

@@ -140,7 +140,7 @@ class AsyncSendEmail extends AsyncTask
MetaModel::Init_AddAttribute(new AttributeText("to", array("allowed_values"=>null, "sql"=>"to", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("subject", array("allowed_values"=>null, "sql"=>"subject", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("body", array("allowed_values"=>null, "sql"=>"body", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeLongText("body", array("allowed_values"=>null, "sql"=>"body", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("header", array("allowed_values"=>null, "sql"=>"header", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
// Display lists

View File

@@ -64,7 +64,14 @@ define('DEL_MANUAL', 1);
* @package iTopORM
*/
define('DEL_AUTO', 2);
/**
* Fully silent delete... not yet implemented
*/
define('DEL_SILENT', 2);
/**
* For HierarchicalKeys only: move all the children up one level automatically
*/
define('DEL_MOVEUP', 3);
/**
@@ -109,6 +116,8 @@ abstract class AttributeDefinition
$this->m_aParams = $aParams;
$this->ConsistencyCheck();
}
// Left here for backward compatibility, deprecated in 2.0
public function OverloadParams($aParams)
{
foreach ($aParams as $sParam => $value)
@@ -123,6 +132,12 @@ abstract class AttributeDefinition
}
}
}
public function GetParams()
{
return $this->m_aParams;
}
public function SetHostClass($sHostClass)
{
$this->m_sHostClass = $sHostClass;
@@ -134,7 +149,7 @@ abstract class AttributeDefinition
// Note: I could factorize this code with the parameter management made for the AttributeDef class
// to be overloaded
static protected function ListExpectedParams()
static public function ListExpectedParams()
{
return array();
}
@@ -163,15 +178,56 @@ abstract class AttributeDefinition
return "";
// e.g: return array("Site", "infrid", "name");
}
public function GetFinalAttDef()
{
return $this;
}
public function IsDirectField() {return false;}
public function IsScalar() {return false;}
public function IsLinkSet() {return false;}
public function IsExternalKey($iType = EXTKEY_RELATIVE) {return false;}
public function IsHierarchicalKey() {return false;}
public function IsExternalField() {return false;}
public function IsWritable() {return false;}
public function IsNullAllowed() {return true;}
public function GetCode() {return $this->m_sCode;}
public function GetLabel() {return Dict::S('Class:'.$this->m_sHostClass.'/Attribute:'.$this->m_sCode, $this->m_sCode);}
public function GetLabel($sDefault = null)
{
// If no default value is specified, let's define the most relevant one for developping purposes
if (is_null($sDefault))
{
$sDefault = $this->m_sCode;
}
$sLabel = Dict::S('Class:'.$this->m_sHostClass.'/Attribute:'.$this->m_sCode, '');
if (strlen($sLabel) == 0)
{
// Nothing found: go higher in the hierarchy (if possible)
//
$sLabel = $sDefault;
$sParentClass = MetaModel::GetParentClass($this->m_sHostClass);
if ($sParentClass)
{
if (MetaModel::IsValidAttCode($sParentClass, $this->m_sCode))
{
$oAttDef = MetaModel::GetAttributeDef($sParentClass, $this->m_sCode);
$sLabel = $oAttDef->GetLabel($sDefault);
}
}
}
return $sLabel;
}
/**
* Get the label corresponding to the given value
* To be overloaded for localized enums
*/
public function GetValueLabel($sValue)
{
return GetAsHTML($sValue);
}
public function GetLabel_Obsolete()
{
// Written for compatibility with a data model written prior to version 0.9.1
@@ -184,8 +240,73 @@ abstract class AttributeDefinition
return $this->GetLabel();
}
}
public function GetDescription() {return Dict::S('Class:'.$this->m_sHostClass.'/Attribute:'.$this->m_sCode.'+', '');}
public function GetHelpOnEdition() {return Dict::S('Class:'.$this->m_sHostClass.'/Attribute:'.$this->m_sCode.'?', '');}
public function GetDescription($sDefault = null)
{
// If no default value is specified, let's define the most relevant one for developping purposes
if (is_null($sDefault))
{
$sDefault = '';
}
$sLabel = Dict::S('Class:'.$this->m_sHostClass.'/Attribute:'.$this->m_sCode.'+', '');
if (strlen($sLabel) == 0)
{
// Nothing found: go higher in the hierarchy (if possible)
//
$sLabel = $sDefault;
$sParentClass = MetaModel::GetParentClass($this->m_sHostClass);
if ($sParentClass)
{
if (MetaModel::IsValidAttCode($sParentClass, $this->m_sCode))
{
$oAttDef = MetaModel::GetAttributeDef($sParentClass, $this->m_sCode);
$sLabel = $oAttDef->GetDescription($sDefault);
}
}
}
return $sLabel;
}
public function GetHelpOnEdition($sDefault = null)
{
// If no default value is specified, let's define the most relevant one for developping purposes
if (is_null($sDefault))
{
$sDefault = '';
}
$sLabel = Dict::S('Class:'.$this->m_sHostClass.'/Attribute:'.$this->m_sCode.'?', '');
if (strlen($sLabel) == 0)
{
// Nothing found: go higher in the hierarchy (if possible)
//
$sLabel = $sDefault;
$sParentClass = MetaModel::GetParentClass($this->m_sHostClass);
if ($sParentClass)
{
if (MetaModel::IsValidAttCode($sParentClass, $this->m_sCode))
{
$oAttDef = MetaModel::GetAttributeDef($sParentClass, $this->m_sCode);
$sLabel = $oAttDef->GetHelpOnEdition($sDefault);
}
}
}
return $sLabel;
}
public function GetHelpOnSmartSearch()
{
$aParents = array_merge(array(get_class($this) => get_class($this)), class_parents($this));
foreach ($aParents as $sClass)
{
$sHelp = Dict::S("Core:$sClass?SmartSearch", '-missing-');
if ($sHelp != '-missing-')
{
return $sHelp;
}
}
return '';
}
public function GetDescription_Obsolete()
{
// Written for compatibility with a data model written prior to version 0.9.1
@@ -331,7 +452,7 @@ abstract class AttributeDefinition
*/
class AttributeLinkedSet extends AttributeDefinition
{
static protected function ListExpectedParams()
static public function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array("allowed_values", "depends_on", "linked_class", "ext_key_to_me", "count_min", "count_max"));
}
@@ -586,6 +707,28 @@ class AttributeLinkedSet extends AttributeDefinition
}
}
// Check (roughly) if such a link is valid
$aErrors = array();
foreach(MetaModel::ListAttributeDefs($sTargetClass) as $sAttCode => $oAttDef)
{
if ($oAttDef->IsExternalKey())
{
if (($oAttDef->GetTargetClass() == $this->GetHostClass()) || (is_subclass_of($this->GetHostClass(), $oAttDef->GetTargetClass())))
{
continue; // Don't check the key to self
}
}
if ($oAttDef->IsWritable() && $oAttDef->IsNull($oLink->Get($sAttCode)) && !$oAttDef->IsNullAllowed())
{
$aErrors[] = $sAttCode;
}
}
if (count($aErrors) > 0)
{
throw new CoreException("Missing value for mandatory attribute(s): ".implode(', ', $aErrors));
}
$aLinks[] = $oLink;
}
$oSet = DBObjectSet::FromArray($sTargetClass, $aLinks);
@@ -619,7 +762,7 @@ class AttributeLinkedSet extends AttributeDefinition
*/
class AttributeLinkedSetIndirect extends AttributeLinkedSet
{
static protected function ListExpectedParams()
static public function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array("ext_key_to_remote"));
}
@@ -636,7 +779,7 @@ class AttributeLinkedSetIndirect extends AttributeLinkedSet
*/
class AttributeDBFieldVoid extends AttributeDefinition
{
static protected function ListExpectedParams()
static public function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array("allowed_values", "depends_on", "sql"));
}
@@ -722,7 +865,7 @@ class AttributeDBFieldVoid extends AttributeDefinition
*/
class AttributeDBField extends AttributeDBFieldVoid
{
static protected function ListExpectedParams()
static public function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array("default_value", "is_null_allowed"));
}
@@ -737,7 +880,7 @@ class AttributeDBField extends AttributeDBFieldVoid
*/
class AttributeInteger extends AttributeDBField
{
static protected function ListExpectedParams()
static public function ListExpectedParams()
{
return parent::ListExpectedParams();
//return array_merge(parent::ListExpectedParams(), array());
@@ -833,7 +976,7 @@ class AttributeInteger extends AttributeDBField
*/
class AttributeDecimal extends AttributeDBField
{
static protected function ListExpectedParams()
static public function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array('digits', 'decimals' /* including precision */));
}
@@ -928,7 +1071,7 @@ class AttributeDecimal extends AttributeDBField
*/
class AttributeBoolean extends AttributeInteger
{
static protected function ListExpectedParams()
static public function ListExpectedParams()
{
return parent::ListExpectedParams();
//return array_merge(parent::ListExpectedParams(), array());
@@ -959,7 +1102,7 @@ class AttributeBoolean extends AttributeInteger
*/
class AttributeString extends AttributeDBField
{
static protected function ListExpectedParams()
static public function ListExpectedParams()
{
return parent::ListExpectedParams();
//return array_merge(parent::ListExpectedParams(), array());
@@ -1071,6 +1214,11 @@ class AttributeString extends AttributeDBField
$sEscaped = str_replace($sFrom, $sTo, (string)$sValue);
return $sTextQualifier.$sEscaped.$sTextQualifier;
}
public function GetDisplayStyle()
{
return $this->GetOptional('display_style', 'select');
}
}
/**
@@ -1080,7 +1228,7 @@ class AttributeString extends AttributeDBField
*/
class AttributeClass extends AttributeString
{
static protected function ListExpectedParams()
static public function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array("class_category", "more_values"));
}
@@ -1132,7 +1280,7 @@ class AttributeClass extends AttributeString
*/
class AttributeApplicationLanguage extends AttributeString
{
static protected function ListExpectedParams()
static public function ListExpectedParams()
{
return parent::ListExpectedParams();
}
@@ -1206,7 +1354,12 @@ class AttributeFinalClass extends AttributeString
{
return '=';
}
public function GetValueLabel($sValue)
{
if (empty($sValue)) return '';
return MetaModel::GetName($sValue);
}
}
@@ -1217,7 +1370,7 @@ class AttributeFinalClass extends AttributeString
*/
class AttributePassword extends AttributeString
{
static protected function ListExpectedParams()
static public function ListExpectedParams()
{
return parent::ListExpectedParams();
//return array_merge(parent::ListExpectedParams(), array());
@@ -1349,13 +1502,21 @@ class AttributeText extends AttributeString
static public function RenderWikiHtml($sText)
{
if (preg_match_all(WIKI_URL, $sText, $aAllMatches, PREG_SET_ORDER))
if (preg_match_all(WIKI_URL, $sText, $aAllMatches, PREG_SET_ORDER /* important !*/ |PREG_OFFSET_CAPTURE /* important ! */))
{
foreach($aAllMatches as $iPos => $aMatches)
$aUrls = array();
$i = count($aAllMatches);
// Replace the URLs by an actual hyperlink <a href="...">...</a>
// Let's do it backwards so that the initial positions are not modified by the replacement
// This works if the matches are captured: in the order they occur in the string AND
// with their offset (i.e. position) inside the string
while($i > 0)
{
$sUrl = $aMatches[0];
$sHLink = "<a href=\"$sUrl\">$sUrl</a>";
$sText = str_replace($sUrl, $sHLink, $sText);
$i--;
$sUrl = $aAllMatches[$i][0][0]; // String corresponding to the main pattern
$iPos = $aAllMatches[$i][0][1]; // Position of the main pattern
$sText = substr_replace($sText, "<a href=\"$sUrl\">$sUrl</a>", $iPos, strlen($sUrl));
}
}
if (preg_match_all(WIKI_OBJECT_REGEXP, $sText, $aAllMatches, PREG_SET_ORDER))
@@ -1380,7 +1541,7 @@ class AttributeText extends AttributeString
$sText = str_replace($aMatches[0], "<span class=\"wiki_broken_link\">$sClassLabel:$sName</span>", $sText);
// Later: propose a link to create a new object
// Anyhow... there is no easy way to suggest default values based on the given FRIENDLY name
//$sText = preg_replace('/\[\[(.+):(.+)\]\]/', '<a href="./UI.php?operation=new&class='.$sClass.'&default[att1]=xxx&default[att2]=yyy">'.$sName.'</a>', $sText);
//$sText = preg_replace('/\[\[(.+):(.+)\]\]/', '<a href="'.utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=new&class='.$sClass.'&default[att1]=xxx&default[att2]=yyy">'.$sName.'</a>', $sText);
}
}
}
@@ -1490,7 +1651,7 @@ class AttributeLongText extends AttributeText
*
* @package iTopORM
*/
class AttributeCaseLog extends AttributeText
class AttributeCaseLog extends AttributeLongText
{
public function GetNullValue()
{
@@ -1676,7 +1837,7 @@ class AttributeCaseLog extends AttributeText
*
* @package iTopORM
*/
class AttributeHTML extends AttributeText
class AttributeHTML extends AttributeLongText
{
public function GetEditClass() {return "HTML";}
@@ -1727,6 +1888,7 @@ class AttributeIPAddress extends AttributeString
*/
class AttributeOQL extends AttributeText
{
public function GetEditClass() {return "OQLExpression";}
}
/**
@@ -1770,7 +1932,7 @@ class AttributeTemplateHTML extends AttributeText
*/
class AttributeEnum extends AttributeString
{
static protected function ListExpectedParams()
static public function ListExpectedParams()
{
return parent::ListExpectedParams();
//return array_merge(parent::ListExpectedParams(), array());
@@ -1837,7 +1999,7 @@ class AttributeEnum extends AttributeString
return parent::GetBasicFilterSQLExpr($sOpCode, $value);
}
public function GetAsHTML($sValue, $oHostObject = null)
public function GetValueLabel($sValue)
{
if (is_null($sValue))
{
@@ -1846,17 +2008,61 @@ class AttributeEnum extends AttributeString
}
else
{
$sLabel = Dict::S('Class:'.$this->GetHostClass().'/Attribute:'.$this->GetCode().'/Value:'.$sValue, $sValue);
$sLabel = Dict::S('Class:'.$this->GetHostClass().'/Attribute:'.$this->GetCode().'/Value:'.$sValue, '');
if (strlen($sLabel) == 0)
{
$sLabel = $sValue;
$sParentClass = MetaModel::GetParentClass($this->m_sHostClass);
if ($sParentClass)
{
if (MetaModel::IsValidAttCode($sParentClass, $this->m_sCode))
{
$oAttDef = MetaModel::GetAttributeDef($sParentClass, $this->m_sCode);
$sLabel = $oAttDef->GetValueLabel($sValue);
}
}
}
}
$sDescription = Dict::S('Class:'.$this->GetHostClass().'/Attribute:'.$this->GetCode().'/Value:'.$sValue.'+', $sValue);
return $sLabel;
}
public function GetValueDescription($sValue)
{
if (is_null($sValue))
{
// Unless a specific label is defined for the null value of this enum, use a generic "undefined" label
$sDescription = Dict::S('Class:'.$this->GetHostClass().'/Attribute:'.$this->GetCode().'/Value:'.$sValue.'+', Dict::S('Enum:Undefined'));
}
else
{
$sDescription = Dict::S('Class:'.$this->GetHostClass().'/Attribute:'.$this->GetCode().'/Value:'.$sValue.'+', '');
if (strlen($sDescription) == 0)
{
$sParentClass = MetaModel::GetParentClass($this->m_sHostClass);
if ($sParentClass)
{
if (MetaModel::IsValidAttCode($sParentClass, $this->m_sCode))
{
$oAttDef = MetaModel::GetAttributeDef($sParentClass, $this->m_sCode);
$sDescription = $oAttDef->GetValueDescription($sValue);
}
}
}
}
return $sDescription;
}
public function GetAsHTML($sValue, $oHostObject = null)
{
$sLabel = $this->GetValueLabel($sValue);
$sDescription = $this->GetValueDescription($sValue);
// later, we could imagine a detailed description in the title
return "<span title=\"$sDescription\">".parent::GetAsHtml($sLabel)."</span>";
}
public function GetEditValue($sValue)
{
$sLabel = Dict::S('Class:'.$this->GetHostClass().'/Attribute:'.$this->GetCode().'/Value:'.$sValue, $sValue);
return $sLabel;
return $this->GetValueLabel($sValue);
}
public function GetAllowedValues($aArgs = array(), $sContains = '')
@@ -1866,7 +2072,7 @@ class AttributeEnum extends AttributeString
$aLocalizedValues = array();
foreach ($aRawValues as $sKey => $sValue)
{
$aLocalizedValues[$sKey] = Dict::S('Class:'.$this->GetHostClass().'/Attribute:'.$this->GetCode().'/Value:'.$sKey, $sKey);
$aLocalizedValues[$sKey] = $this->GetValueLabel($sKey);
}
return $aLocalizedValues;
}
@@ -1896,7 +2102,7 @@ class AttributeDateTime extends AttributeDBField
return "Y-m-d H:i:s";
}
static protected function ListExpectedParams()
static public function ListExpectedParams()
{
return parent::ListExpectedParams();
//return array_merge(parent::ListExpectedParams(), array());
@@ -1927,7 +2133,7 @@ class AttributeDateTime extends AttributeDBField
{
if (empty($default))
{
$default = date(self::GetDateFormat());
$default = date($this->GetDateFormat());
}
}
@@ -2110,6 +2316,7 @@ class AttributeDateTime extends AttributeDBField
default:
$oNewCondition = parent::GetSmartConditionExpression($sSearchText, $oField, $aParams);
}
return $oNewCondition;
@@ -2185,6 +2392,7 @@ class AttributeDuration extends AttributeInteger
static function SplitDuration($duration)
{
$duration = (int) $duration;
$days = floor($duration / 86400);
$hours = floor(($duration - (86400*$days)) / 3600);
$minutes = floor(($duration - (86400*$days + 3600*$hours)) / 60);
@@ -2206,7 +2414,7 @@ class AttributeDate extends AttributeDateTime
return "Y-m-d";
}
static protected function ListExpectedParams()
static public function ListExpectedParams()
{
return parent::ListExpectedParams();
//return array_merge(parent::ListExpectedParams(), array());
@@ -2233,17 +2441,20 @@ class AttributeDeadline extends AttributeDateTime
$sResult = '';
if ($value !== null)
{
$value = AttributeDateTime::GetAsUnixSeconds($value);
$difference = $value - time();
$iValue = AttributeDateTime::GetAsUnixSeconds($value);
$sDate = parent::GetAsHTML($value, $oHostObject);
$difference = $iValue - time();
if ($difference >= 0)
{
$sResult = self::FormatDuration($difference);
$sDifference = self::FormatDuration($difference);
}
else
{
$sResult = Dict::Format('UI:DeadlineMissedBy_duration', self::FormatDuration(-$difference));
$sDifference = Dict::Format('UI:DeadlineMissedBy_duration', self::FormatDuration(-$difference));
}
$sFormat = MetaModel::GetConfig()->Get('deadline_format', '$difference$');
$sResult = str_replace(array('$date$', '$difference$'), array($sDate, $sDifference), $sFormat);
}
return $sResult;
}
@@ -2289,7 +2500,7 @@ class AttributeDeadline extends AttributeDateTime
*/
class AttributeExternalKey extends AttributeDBFieldVoid
{
static protected function ListExpectedParams()
static public function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array("targetclass", "is_null_allowed", "on_target_delete"));
}
@@ -2305,6 +2516,7 @@ class AttributeExternalKey extends AttributeDBFieldVoid
public function GetTargetClass($iType = EXTKEY_RELATIVE) {return $this->Get("targetclass");}
public function GetKeyAttDef($iType = EXTKEY_RELATIVE){return $this;}
public function GetKeyAttCode() {return $this->GetCode();}
public function GetDisplayStyle() { return $this->GetOptional('display_style', 'select'); }
public function GetDefaultValue() {return 0;}
@@ -2339,6 +2551,7 @@ class AttributeExternalKey extends AttributeDBFieldVoid
public function GetAllowedValues($aArgs = array(), $sContains = '')
{
//throw new Exception("GetAllowedValues on ext key has been deprecated");
try
{
return parent::GetAllowedValues($aArgs, $sContains);
@@ -2351,6 +2564,13 @@ class AttributeExternalKey extends AttributeDBFieldVoid
}
}
public function GetAllowedValuesAsObjectSet($aArgs = array(), $sContains = '')
{
$oValSetDef = $this->GetValuesDef();
$oSet = $oValSetDef->ToObjectSet($aArgs, $sContains);
return $oSet;
}
public function GetDeletionPropagationOption()
{
return $this->Get("on_target_delete");
@@ -2390,6 +2610,130 @@ class AttributeExternalKey extends AttributeDBFieldVoid
}
}
/**
* Special kind of External Key to manage a hierarchy of objects
*/
class AttributeHierarchicalKey extends AttributeExternalKey
{
protected $m_sTargetClass;
static public function ListExpectedParams()
{
$aParams = parent::ListExpectedParams();
$idx = array_search('targetclass', $aParams);
unset($aParams[$idx]);
$idx = array_search('jointype', $aParams);
unset($aParams[$idx]);
return $aParams; // TODO: mettre les bons parametres ici !!
}
public function GetEditClass() {return "ExtKey";}
public function RequiresIndex()
{
return true;
}
/*
* The target class is the class for which the attribute has been defined first
*/
public function SetHostClass($sHostClass)
{
if (!isset($this->m_sTargetClass))
{
$this->m_sTargetClass = $sHostClass;
}
parent::SetHostClass($sHostClass);
}
public function IsHierarchicalKey() {return true;}
public function GetTargetClass($iType = EXTKEY_RELATIVE) {return $this->m_sTargetClass;}
public function GetKeyAttDef($iType = EXTKEY_RELATIVE){return $this;}
public function GetKeyAttCode() {return $this->GetCode();}
public function GetBasicFilterOperators()
{
return parent::GetBasicFilterOperators();
}
public function GetBasicFilterLooseOperator()
{
return parent::GetBasicFilterLooseOperator();
}
public function GetSQLColumns()
{
$aColumns = array();
$aColumns[$this->GetCode()] = 'INT(11)';
$aColumns[$this->GetSQLLeft()] = 'INT(11)';
$aColumns[$this->GetSQLRight()] = 'INT(11)';
return $aColumns;
}
public function GetSQLRight()
{
return $this->GetCode().'_right';
}
public function GetSQLLeft()
{
return $this->GetCode().'_left';
}
public function GetSQLValues($value)
{
if (!is_array($value))
{
$aValues[$this->GetCode()] = $value;
}
else
{
$aValues = array();
$aValues[$this->GetCode()] = $value[$this->GetCode()];
$aValues[$this->GetSQLRight()] = $value[$this->GetSQLRight()];
$aValues[$this->GetSQLLeft()] = $value[$this->GetSQLLeft()];
}
return $aValues;
}
public function GetAllowedValues($aArgs = array(), $sContains = '')
{
if (array_key_exists('this', $aArgs))
{
// Hierarchical keys have one more constraint: the "parent value" cannot be
// "under" themselves
$iRootId = $aArgs['this']->GetKey();
if ($iRootId > 0) // ignore objects that do no exist in the database...
{
$oValSetDef = $this->GetValuesDef();
$sClass = $this->m_sTargetClass;
$oFilter = DBObjectSearch::FromOQL("SELECT $sClass AS node JOIN $sClass AS root ON node.".$this->GetCode()." NOT BELOW root.id WHERE root.id = $iRootId");
$oValSetDef->AddCondition($oFilter);
}
}
else
{
return parent::GetAllowedValues($aArgs, $sContains);
}
}
public function GetAllowedValuesAsObjectSet($aArgs = array(), $sContains = '')
{
$oValSetDef = $this->GetValuesDef();
if (array_key_exists('this', $aArgs))
{
// Hierarchical keys have one more constraint: the "parent value" cannot be
// "under" themselves
$iRootId = $aArgs['this']->GetKey();
if ($iRootId > 0) // ignore objects that do no exist in the database...
{
$aValuesSetDef = $this->GetValuesDef();
$sClass = $this->m_sTargetClass;
$oFilter = DBObjectSearch::FromOQL("SELECT $sClass AS node JOIN $sClass AS root ON node.".$this->GetCode()." NOT BELOW root.id WHERE root.id = $iRootId");
$oValSetDef->AddCondition($oFilter);
}
}
$oSet = $oValSetDef->ToObjectSet($aArgs, $sContains);
return $oSet;
}
}
/**
* An attribute which corresponds to an external key (direct or indirect)
*
@@ -2397,12 +2741,19 @@ class AttributeExternalKey extends AttributeDBFieldVoid
*/
class AttributeExternalField extends AttributeDefinition
{
static protected function ListExpectedParams()
static public function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array("extkey_attcode", "target_attcode"));
}
public function GetEditClass() {return "ExtField";}
public function GetFinalAttDef()
{
$oExtAttDef = $this->GetExtAttDef();
return $oExtAttDef->GetFinalAttDef();
}
protected function GetSQLCol()
{
// throw new CoreException("external attribute: does it make any sense to request its type ?");
@@ -2422,23 +2773,35 @@ class AttributeExternalField extends AttributeDefinition
}
}
public function GetLabel()
public function GetLabel($sDefault = null)
{
$oRemoteAtt = $this->GetExtAttDef();
$sDefault = $oRemoteAtt->GetLabel();
return Dict::S('Class:'.$this->m_sHostClass.'/Attribute:'.$this->m_sCode, $sDefault);
$sLabel = parent::GetLabel('');
if (strlen($sLabel) == 0)
{
$oRemoteAtt = $this->GetExtAttDef();
$sLabel = $oRemoteAtt->GetLabel($this->m_sCode);
}
return $sLabel;
}
public function GetDescription()
public function GetDescription($sDefault = null)
{
$oRemoteAtt = $this->GetExtAttDef();
$sDefault = $oRemoteAtt->GetDescription();
return Dict::S('Class:'.$this->m_sHostClass.'/Attribute:'.$this->m_sCode.'+', $sDefault);
$sLabel = parent::GetDescription('');
if (strlen($sLabel) == 0)
{
$oRemoteAtt = $this->GetExtAttDef();
$sLabel = $oRemoteAtt->GetDescription('');
}
return $sLabel;
}
public function GetHelpOnEdition()
public function GetHelpOnEdition($sDefault = null)
{
$oRemoteAtt = $this->GetExtAttDef();
$sDefault = $oRemoteAtt->GetHelpOnEdition();
return Dict::S('Class:'.$this->m_sHostClass.'/Attribute:'.$this->m_sCode.'?', $sDefault);
$sLabel = parent::GetHelpOnEdition('');
if (strlen($sLabel) == 0)
{
$oRemoteAtt = $this->GetExtAttDef();
$sLabel = $oRemoteAtt->GetHelpOnEdition('');
}
return $sLabel;
}
public function IsExternalKey($iType = EXTKEY_RELATIVE)
@@ -2495,8 +2858,8 @@ class AttributeExternalField extends AttributeDefinition
public function GetExtAttDef()
{
$oKeyAttDef = $this->GetKeyAttDef();
$oExtAttDef = MetaModel::GetAttributeDef($oKeyAttDef->Get("targetclass"), $this->Get("target_attcode"));
if (!is_object($oExtAttDef)) throw new CoreException("Invalid external field ".$this->GetCode()." in class ".$this->GetHostClass().". The class ".$oKeyAttDef->Get("targetclass")." has no attribute ".$this->Get("target_attcode"));
$oExtAttDef = MetaModel::GetAttributeDef($oKeyAttDef->GetTargetClass(), $this->Get("target_attcode"));
if (!is_object($oExtAttDef)) throw new CoreException("Invalid external field ".$this->GetCode()." in class ".$this->GetHostClass().". The class ".$oKeyAttDef->GetTargetClass()." has no attribute ".$this->Get("target_attcode"));
return $oExtAttDef;
}
@@ -2605,7 +2968,7 @@ class AttributeExternalField extends AttributeDefinition
*/
class AttributeURL extends AttributeString
{
static protected function ListExpectedParams()
static public function ListExpectedParams()
{
//return parent::ListExpectedParams();
return array_merge(parent::ListExpectedParams(), array("target"));
@@ -2639,7 +3002,7 @@ class AttributeURL extends AttributeString
*/
class AttributeBlob extends AttributeDefinition
{
static protected function ListExpectedParams()
static public function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array("depends_on"));
}
@@ -2786,7 +3149,7 @@ class AttributeBlob extends AttributeDefinition
*/
class AttributeOneWayPassword extends AttributeDefinition
{
static protected function ListExpectedParams()
static public function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array("depends_on"));
}
@@ -2936,16 +3299,26 @@ class AttributeOneWayPassword extends AttributeDefinition
}
// Indexed array having two dimensions
class AttributeTable extends AttributeText
class AttributeTable extends AttributeDBField
{
public function GetEditClass() {return "Text";}
protected function GetSQLCol() {return "TEXT";}
public function GetEditClass() {return "Table";}
protected function GetSQLCol() {return "LONGTEXT";}
public function GetMaxSize()
{
return null;
}
public function GetNullValue()
{
return array();
}
public function IsNull($proposedValue)
{
return (count($proposedValue) == 0);
}
// Facilitate things: allow the user to Set the value from a string
public function MakeRealValue($proposedValue, $oHostObj)
{
@@ -3008,13 +3381,39 @@ class AttributeTable extends AttributeText
$sRes .= "</TABLE>";
return $sRes;
}
public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null)
{
// Not implemented
return '';
}
public function GetAsXML($value, $oHostObject = null)
{
if (count($value) == 0)
{
return "";
}
$sRes = "";
foreach($value as $iRow => $aRawData)
{
$sRes .= "<row>";
foreach ($aRawData as $iCol => $cell)
{
$sCell = Str::pure2xml((string)$cell);
$sRes .= "<cell icol=\"$iCol\">$sCell</cell>";
}
$sRes .= "</row>";
}
return $sRes;
}
}
// The PHP value is a hash array, it is stored as a TEXT column
class AttributePropertySet extends AttributeTable
{
public function GetEditClass() {return "Text";}
protected function GetSQLCol() {return "TEXT";}
public function GetEditClass() {return "PropertySet";}
// Facilitate things: allow the user to Set the value from a string
public function MakeRealValue($proposedValue, $oHostObj)
@@ -3041,6 +3440,10 @@ class AttributePropertySet extends AttributeTable
$sRes .= "<TBODY>";
foreach($value as $sProperty => $sValue)
{
if ($sProperty == 'auth_pwd')
{
$sValue = '*****';
}
$sRes .= "<TR>";
$sCell = str_replace("\n", "<br>\n", Str::pure2html((string)$sValue));
$sRes .= "<TD class=\"label\">$sProperty</TD><TD>$sCell</TD>";
@@ -3050,6 +3453,53 @@ class AttributePropertySet extends AttributeTable
$sRes .= "</TABLE>";
return $sRes;
}
public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null)
{
if (count($value) == 0)
{
return "";
}
$aRes = array();
foreach($value as $sProperty => $sValue)
{
if ($sProperty == 'auth_pwd')
{
$sValue = '*****';
}
$sFrom = array(',', '=');
$sTo = array('\,', '\=');
$aRes[] = $sProperty.'='.str_replace($sFrom, $sTo, (string)$sValue);
}
$sRaw = implode(',', $aRes);
$sFrom = array("\r\n", $sTextQualifier);
$sTo = array("\n", $sTextQualifier.$sTextQualifier);
$sEscaped = str_replace($sFrom, $sTo, $sRaw);
return $sTextQualifier.$sEscaped.$sTextQualifier;
}
public function GetAsXML($value, $oHostObject = null)
{
if (count($value) == 0)
{
return "";
}
$sRes = "";
foreach($value as $sProperty => $sValue)
{
if ($sProperty == 'auth_pwd')
{
$sValue = '*****';
}
$sRes .= "<property id=\"$sProperty\">";
$sRes .= Str::pure2xml((string)$sValue);
$sRes .= "</property>";
}
return $sRes;
}
}
/**
@@ -3059,7 +3509,7 @@ class AttributePropertySet extends AttributeTable
*/
class AttributeComputedFieldVoid extends AttributeDefinition
{
static protected function ListExpectedParams()
static public function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array());
}
@@ -3152,6 +3602,43 @@ class AttributeFriendlyName extends AttributeComputedFieldVoid
public function GetKeyAttCode() {return $this->Get("extkey_attcode");}
public function GetLabel($sDefault = null)
{
$sLabel = parent::GetLabel('');
if (strlen($sLabel) == 0)
{
$sKeyAttCode = $this->Get("extkey_attcode");
if ($sKeyAttCode == 'id')
{
return Dict::S('Core:FriendlyName-Label');
}
else
{
$oExtKeyAttDef = MetaModel::GetAttributeDef($this->GetHostClass(), $sKeyAttCode);
$sLabel = $oExtKeyAttDef->GetLabel($this->m_sCode);
}
}
return $sLabel;
}
public function GetDescription($sDefault = null)
{
$sLabel = parent::GetDescription('');
if (strlen($sLabel) == 0)
{
$sKeyAttCode = $this->Get("extkey_attcode");
if ($sKeyAttCode == 'id')
{
return Dict::S('Core:FriendlyName-Description');
}
else
{
$oExtKeyAttDef = MetaModel::GetAttributeDef($this->GetHostClass(), $sKeyAttCode);
$sLabel = $oExtKeyAttDef->GetDescription('');
}
}
return $sLabel;
}
// n/a, the friendly name is made of a complex expression (see GetNameSpec)
protected function GetSQLCol() {return "";}

View File

@@ -252,8 +252,9 @@ class BulkChange
protected $m_aReconcilKeys; // attcode (attcode = 'id' for the pkey)
protected $m_sSynchroScope; // OQL - if specified, then the missing items will be reported
protected $m_aOnDisappear; // array of attcode => value, values to be set when an object gets out of scope (ignored if no scope has been defined)
protected $m_sDateFormat; // Date format specification, see utils::StringToTime()
public function __construct($sClass, $aData, $aAttList, $aExtKeys, $aReconcilKeys, $sSynchroScope = null, $aOnDisappear = null)
public function __construct($sClass, $aData, $aAttList, $aExtKeys, $aReconcilKeys, $sSynchroScope = null, $aOnDisappear = null, $sDateFormat = null)
{
$this->m_sClass = $sClass;
$this->m_aData = $aData;
@@ -262,6 +263,7 @@ class BulkChange
$this->m_aExtKeys = $aExtKeys;
$this->m_sSynchroScope = $sSynchroScope;
$this->m_aOnDisappear = $aOnDisappear;
$this->m_sDateFormat = $sDateFormat;
}
protected $m_bReportHtml = false;
@@ -399,7 +401,13 @@ class BulkChange
if ($sAttCode == 'id') continue;
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
if ($oAttDef->IsLinkSet() && $oAttDef->IsIndirect())
$aReasons = array();
$iFlags = $oTargetObj->GetAttributeFlags($sAttCode, $aReasons);
if ( (($iFlags & OPT_ATT_READONLY) == OPT_ATT_READONLY) && ( $oTargetObj->Get($sAttCode) != $aRowData[$iCol]) )
{
$aErrors[$sAttCode] = "the attribute '$sAttCode' is read-only and cannot be modified (current value: ".$oTargetObj->Get($sAttCode).", proposed value: {$aRowData[$iCol]}).";
}
else if ($oAttDef->IsLinkSet() && $oAttDef->IsIndirect())
{
try
{
@@ -412,7 +420,7 @@ class BulkChange
}
}
else
{
{
$res = $oTargetObj->CheckValue($sAttCode, $aRowData[$iCol]);
if ($res === true)
{
@@ -702,6 +710,35 @@ class BulkChange
exit;
}
$aResult = array();
if (!is_null($this->m_sDateFormat) && (strlen($this->m_sDateFormat) > 0))
{
// Translate dates from the source data
//
foreach ($this->m_aAttList as $sAttCode => $iCol)
{
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
if ($oAttDef instanceof AttributeDateTime)
{
foreach($this->m_aData as $iRow => $aRowData)
{
$sNewDate = utils::StringToTime($this->m_aData[$iRow][$iCol], $this->m_sDateFormat);
if ($sNewDate !== false)
{
// Todo - improve the reporting
$this->m_aData[$iRow][$iCol] = $sNewDate;
}
else
{
// Leave the cell unchanged
$aResult[$iRow]["__STATUS__"]= new RowStatus_Issue("wrong date format");
$aResult[$iRow][$sAttCode] = new CellStatus_Issue(null, $this->m_aData[$iRow][$iCol], 'Wrong date format');
}
}
}
}
}
// Compute the results
//
@@ -709,9 +746,13 @@ class BulkChange
{
$aVisited = array();
}
$aResult = array();
foreach($this->m_aData as $iRow => $aRowData)
{
if (isset($aResult[$iRow]["__STATUS__"]))
{
// An issue at the earlier steps - skip the rest
continue;
}
$oReconciliationFilter = new CMDBSearchFilter($this->m_sClass);
$bSkipQuery = false;
foreach($this->m_aReconcilKeys as $sAttCode)
@@ -798,8 +839,28 @@ class BulkChange
$aResult[$iRow]["finalclass"]= 'n/a';
}
}
// Whatever happened, do report the reconciliation values
}
if (!is_null($this->m_sSynchroScope))
{
// Compute the delta between the scope and visited objects
$oScopeSearch = DBObjectSearch::FromOQL($this->m_sSynchroScope);
$oScopeSet = new DBObjectSet($oScopeSearch);
while ($oObj = $oScopeSet->Fetch())
{
$iObj = $oObj->GetKey();
if (!in_array($iObj, $aVisited))
{
$iRow++;
$this->UpdateMissingObject($aResult, $iRow, $oObj, $oChange);
}
}
}
// Fill in the blanks - the result matrix is expected to be 100% complete
//
foreach($this->m_aData as $iRow => $aRowData)
{
foreach($this->m_aAttList as $iCol)
{
if (!array_key_exists($iCol, $aResult[$iRow]))
@@ -824,22 +885,6 @@ class BulkChange
}
}
if (!is_null($this->m_sSynchroScope))
{
// Compute the delta between the scope and visited objects
$oScopeSearch = DBObjectSearch::FromOQL($this->m_sSynchroScope);
$oScopeSet = new DBObjectSet($oScopeSearch);
while ($oObj = $oScopeSet->Fetch())
{
$iObj = $oObj->GetKey();
if (!in_array($iObj, $aVisited))
{
$iRow++;
$this->UpdateMissingObject($aResult, $iRow, $oObj, $oChange);
}
}
}
return $aResult;
}
@@ -960,7 +1005,7 @@ EOF
<<<EOF
function OnTruncatedHistoryToggle(bShowAll)
{
$.get('../pages/ajax.render.php?{$sAppContext}', {operation: 'displayCSVHistory', showall: bShowAll}, function(data)
$.get(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?{$sAppContext}', {operation: 'displayCSVHistory', showall: bShowAll}, function(data)
{
$('#$sAjaxDivId').html(data);
var table = $('#$sAjaxDivId .listResults');
@@ -1032,10 +1077,19 @@ EOF
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
if ($oAttDef->IsExternalKey())
{
$oOldTarget = MetaModel::GetObject($oAttDef->GetTargetClass(), $oOperation->Get('oldvalue'));
$oNewTarget = MetaModel::GetObject($oAttDef->GetTargetClass(), $oOperation->Get('newvalue'));
$sOldValue = $oOldTarget->GetHyperlink();
$sNewValue = $oNewTarget->GetHyperlink();
$sOldValue = Dict::S('UI:UndefinedObject');
if ($oOperation->Get('oldvalue') != 0)
{
$oOldTarget = MetaModel::GetObject($oAttDef->GetTargetClass(), $oOperation->Get('oldvalue'));
$sOldValue = $oOldTarget->GetHyperlink();
}
$sNewValue = Dict::S('UI:UndefinedObject');
if ($oOperation->Get('newvalue') != 0)
{
$oNewTarget = MetaModel::GetObject($oAttDef->GetTargetClass(), $oOperation->Get('newvalue'));
$sNewValue = $oNewTarget->GetHyperlink();
}
}
else
{

View File

@@ -212,11 +212,27 @@ class CMDBChangeOpSetAttributeScalar extends CMDBChangeOpSetAttribute
$oMonoObjectSet = new DBObjectSet($oTargetSearch);
if (UserRights::IsActionAllowedOnAttribute($this->Get('objclass'), $this->Get('attcode'), UR_ACTION_READ, $oMonoObjectSet) == UR_ALLOWED_YES)
{
if (!MetaModel::IsValidAttCode($this->Get('objclass'), $this->Get('attcode'))) return ''; // Protects against renamed attributes...
$oAttDef = MetaModel::GetAttributeDef($this->Get('objclass'), $this->Get('attcode'));
$sAttName = $oAttDef->GetLabel();
$sNewValue = $this->Get('newvalue');
$sOldValue = $this->Get('oldvalue');
if ( (($oAttDef->GetType() == 'String') || ($oAttDef->GetType() == 'Text')) &&
if ($oAttDef instanceof AttributeEnum)
{
// translate the enum values
$sOldValue = $oAttDef->GetAsHTML($sOldValue);
$sNewValue = $oAttDef->GetAsHTML($sNewValue);
if (strlen($sOldValue) == 0)
{
$sResult = Dict::Format('Change:AttName_SetTo', $sAttName, $sNewValue);
}
else
{
$sResult = Dict::Format('Change:AttName_SetTo_NewValue_PreviousValue_OldValue', $sAttName, $sNewValue, $sOldValue);
}
}
elseif ( (($oAttDef->GetType() == 'String') || ($oAttDef->GetType() == 'Text')) &&
(strlen($sNewValue) > strlen($sOldValue)) )
{
// Check if some text was not appended to the field
@@ -554,4 +570,41 @@ class CMDBChangeOpSetAttributeCaseLog extends CMDBChangeOpSetAttribute
}
}
/**
* Record an action made by a plug-in
*
* @package iTopORM
*/
class CMDBChangeOpPlugin extends CMDBChangeOp
{
public static function Init()
{
$aParams = array
(
"category" => "core/cmdb",
"key_type" => "",
"name_attcode" => "change",
"state_attcode" => "",
"reconc_keys" => array(),
"db_table" => "priv_changeop_plugin",
"db_key_field" => "id",
"db_finalclass_field" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>'', "is_null_allowed"=>false, "depends_on"=>array())));
/* May be used later when implementing an extension mechanism that will allow the plug-ins to store some extra information and still degrades gracefully when the plug-in is desinstalled
MetaModel::Init_AddAttribute(new AttributeString("extension_class", array("allowed_values"=>null, "sql"=>"extension_class", "default_value"=>'', "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeInteger("extension_id", array("allowed_values"=>null, "sql"=>"extension_id", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array())));
*/
MetaModel::Init_InheritAttributes();
}
/**
* Describe (as a text string) the modifications corresponding to this change
*/
public function GetDescription()
{
return $this->Get('description');
}
}
?>

View File

@@ -76,6 +76,7 @@ require_once('cmdbchangeop.class.inc.php');
// Romain: temporary moved into application.inc.php (see explanations there)
//require_once('event.class.inc.php');
require_once('templatestring.class.inc.php');
require_once('csvparser.class.inc.php');
require_once('bulkchange.class.inc.php');
@@ -92,6 +93,23 @@ abstract class CMDBObject extends DBObject
protected static $m_oCurrChange = null;
public static function SetCurrentChange(CMDBChange $oChange)
{
self::$m_oCurrChange = $oChange;
}
//
// Todo: simplify the APIs and do not pass the current change as an argument anymore
// SetCurrentChange to be invoked in very few cases (UI.php, CSV import, Data synchro)
// GetCurrentChange to be called ONCE (!) by CMDBChangeOp::OnInsert ($this->Set('change', ..GetCurrentChange())
// GetCurrentChange to create a default change if not already done in the current context
//
public static function GetCurrentChange()
{
return self::$m_oCurrChange;
}
private function RecordObjCreation(CMDBChange $oChange)
{
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpCreate");
@@ -279,9 +297,10 @@ abstract class CMDBObject extends DBObject
{
$this->CheckUserRights($bSkipStrongSecurity, UR_ACTION_MODIFY);
$oPreviousChange = self::$m_oCurrChange;
self::$m_oCurrChange = $oChange;
$ret = $this->DBInsertTracked_Internal();
self::$m_oCurrChange = null;
self::$m_oCurrChange = $oPreviousChange;
return $ret;
}
@@ -289,9 +308,10 @@ abstract class CMDBObject extends DBObject
{
$this->CheckUserRights($bSkipStrongSecurity, UR_ACTION_MODIFY);
$oPreviousChange = self::$m_oCurrChange;
self::$m_oCurrChange = $oChange;
$ret = $this->DBInsertTracked_Internal(true);
self::$m_oCurrChange = null;
self::$m_oCurrChange = $oPreviousChange;
return $ret;
}
@@ -320,9 +340,10 @@ abstract class CMDBObject extends DBObject
public function DBCloneTracked(CMDBChange $oChange, $newKey = null)
{
$oPreviousChange = self::$m_oCurrChange;
self::$m_oCurrChange = $oChange;
$this->DBCloneTracked_Internal($newKey);
self::$m_oCurrChange = null;
self::$m_oCurrChange = $oPreviousChange;
}
protected function DBCloneTracked_Internal($newKey = null)
@@ -347,9 +368,10 @@ abstract class CMDBObject extends DBObject
{
$this->CheckUserRights($bSkipStrongSecurity, UR_ACTION_MODIFY);
$oPreviousChange = self::$m_oCurrChange;
self::$m_oCurrChange = $oChange;
$this->DBUpdateTracked_Internal();
self::$m_oCurrChange = null;
self::$m_oCurrChange = $oPreviousChange;
}
protected function DBUpdateTracked_Internal()
@@ -382,9 +404,10 @@ abstract class CMDBObject extends DBObject
{
$this->CheckUserRights($bSkipStrongSecurity, UR_ACTION_DELETE);
$oPreviousChange = self::$m_oCurrChange;
self::$m_oCurrChange = $oChange;
$this->DBDeleteTracked_Internal($oDeletionPlan);
self::$m_oCurrChange = null;
self::$m_oCurrChange = $oPreviousChange;
}
protected function DBDeleteTracked_Internal(&$oDeletionPlan = null)
@@ -406,9 +429,10 @@ abstract class CMDBObject extends DBObject
public static function BulkDeleteTracked(CMDBChange $oChange, DBObjectSearch $oFilter)
{
$oPreviousChange = self::$m_oCurrChange;
self::$m_oCurrChange = $oChange;
$this->BulkDeleteTracked_Internal($oFilter);
self::$m_oCurrChange = null;
self::$m_oCurrChange = $oPreviousChange;
}
protected static function BulkDeleteTracked_Internal(DBObjectSearch $oFilter)
@@ -445,9 +469,10 @@ abstract class CMDBObject extends DBObject
public static function BulkUpdateTracked(CMDBChange $oChange, DBObjectSearch $oFilter, array $aValues)
{
$oPreviousChange = self::$m_oCurrChange;
self::$m_oCurrChange = $oChange;
$this->BulkUpdateTracked_Internal($oFilter, $aValues);
self::$m_oCurrChange = null;
self::$m_oCurrChange = $oPreviousChange;
}
protected static function BulkUpdateTracked_Internal(DBObjectSearch $oFilter, array $aValues)
@@ -498,24 +523,22 @@ class CMDBObjectSet extends DBObjectSet
static public function FromScratch($sClass)
{
$oFilter = new CMDBSearchFilter($sClass);
$oRetSet = new CMDBObjectSet($oFilter); // THE ONLY DIFF IS HERE
// NOTE: THIS DOES NOT WORK IF m_bLoaded is private...
// BUT IT THAT CASE YOU DO NOT GET ANY ERROR !!!!!
$oFilter->AddConditionExpression(new FalseExpression());
$oRetSet = new self($oFilter);
// NOTE: THIS DOES NOT WORK IF m_bLoaded is private in the base class (and you will not get any error message)
$oRetSet->m_bLoaded = true; // no DB load
return $oRetSet;
}
// create an object set ex nihilo
// input = array of objects
static public function FromArray($sClass, $aObjects)
{
$oFilter = new CMDBSearchFilter($sClass);
$oRetSet = new CMDBObjectSet($oFilter); // THE ONLY DIFF IS HERE
// NOTE: THIS DOES NOT WORK IF m_bLoaded is private...
// BUT IT THAT CASE YOU DO NOT GET ANY ERROR !!!!!
$oRetSet->m_bLoaded = true; // no DB load
$oRetSet->AddObjectArray($aObjects);
$oRetSet = self::FromScratch($sClass);
$oRetSet->AddObjectArray($aObjects, $sClass);
return $oRetSet;
}
static public function FromArrayAssoc($aClasses, $aObjects)
{
// In a perfect world, we should create a complete tree of DBObjectSearch,

View File

@@ -58,7 +58,6 @@ define ('DEFAULT_MAX_DISPLAY_LIMIT', 15);
define ('DEFAULT_STANDARD_RELOAD_INTERVAL', 5*60);
define ('DEFAULT_FAST_RELOAD_INTERVAL', 1*60);
define ('DEFAULT_SECURE_CONNECTION_REQUIRED', false);
define ('DEFAULT_HTTPS_HYPERLINKS', false);
define ('DEFAULT_ALLOWED_LOGIN_TYPES', 'form|basic|external');
define ('DEFAULT_EXT_AUTH_VARIABLE', '$_SERVER[\'REMOTE_USER\']');
define ('DEFAULT_ENCRYPTION_KEY', '@iT0pEncr1pti0n!'); // We'll use a random value, later...
@@ -85,6 +84,14 @@ class Config
// New way to store the settings !
//
protected $m_aSettings = array(
'app_root_url' => array(
'type' => 'string',
'description' => 'Root URL used for navigating within the application, or from an email to the application (you can put $SERVER_NAME$ as a placeholder for the server\'s name)',
'default' => '',
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'skip_check_to_write' => array(
'type' => 'bool',
'description' => 'Disable data format and integrity checks to boost up data load (insert or update)',
@@ -117,6 +124,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'php_path' => array(
'type' => 'string',
'description' => 'Path to the php executable in CLI mode',
'default' => 'php',
'value' => 'php',
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'session_name' => array(
'type' => 'string',
'description' => 'The name of the cookie used to store the PHP session id',
@@ -182,6 +197,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'csv_import_history_display' => array(
'type' => 'bool',
'description' => 'Display the history tab in the import wizard',
'default' => true,
'value' => true,
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'access_mode' => array(
'type' => 'integer',
'description' => 'Combination of flags (ACCESS_USER_WRITE | ACCESS_ADMIN_WRITE, or ACCESS_FULL)',
@@ -304,12 +327,169 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'cas_include_path' => array(
'type' => 'string',
'description' => 'The path where to find the phpCAS library',
// examples... not used (nor 'description')
'default' => '/usr/share/php',
'value' => '/usr/share/php',
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'cas_version' => array(
'type' => 'string',
'description' => 'The CAS protocol version to use: "1.0" (CAS v1), "2.0" (CAS v2) or "S1" (SAML V1) )',
// examples... not used (nor 'description')
'default' => '2.0',
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'cas_host' => array(
'type' => 'string',
'description' => 'The name of the CAS host',
// examples... not used (nor 'description')
'default' => '',
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'cas_port' => array(
'type' => 'integer',
'description' => 'The port used by the CAS server',
// examples... not used (nor 'description')
'default' => 443,
'value' => 443,
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'cas_context' => array(
'type' => 'string',
'description' => 'The CAS context',
// examples... not used (nor 'description')
'default' => '',
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'cas_server_ca_cert_path' => array(
'type' => 'string',
'description' => 'The path where to find the certificate of the CA for validating the certificate of the CAS server',
// examples... not used (nor 'description')
'default' => '',
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'cas_logout_redirect_service' => array(
'type' => 'string',
'description' => 'The redirect service (URL) to use when logging-out with CAS',
// examples... not used (nor 'description')
'default' => '',
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'cas_memberof' => array(
'type' => 'string',
'description' => 'A semicolon separated list of group names that the user must be member of (works only with SAML - e.g. cas_version=> "S1")',
// examples... not used (nor 'description')
'default' => '',
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'cas_user_synchro' => array(
'type' => 'bool',
'description' => 'Whether or not to synchronize users with CAS/LDAP',
// examples... not used (nor 'description')
'default' => 0,
'value' => 0,
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'cas_update_profiles' => array(
'type' => 'bool',
'description' => 'Whether or not to update the profiles of an existing user from the CAS information',
// examples... not used (nor 'description')
'default' => 0,
'value' => 0,
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'cas_profile_pattern' => array(
'type' => 'string',
'description' => 'A regular expression pattern to extract the name of the iTop profile from the name of an LDAP/CAS group',
// examples... not used (nor 'description')
'default' => '/^cn=([^,]+),/',
'value' => '/^cn=([^,]+),/',
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'cas_default_profiles' => array(
'type' => 'string',
'description' => 'A semi-colon separated list of iTop Profiles to use when creating a new user if no profile is retrieved from CAS',
// examples... not used (nor 'description')
'default' => 'Portal user',
'value' => 'Portal user',
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'cas_debug' => array(
'type' => 'bool',
'description' => 'Activate the CAS debug',
// examples... not used (nor 'description')
'default' => false,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'deadline_format' => array(
'type' => 'string',
'description' => 'The format used for displaying "deadline" attributes: any string with the following placeholders: $date$, $difference$',
// examples... $date$ ($deadline$)
'default' => '$difference$',
'value' => '$difference$',
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'buttons_position' => array(
'type' => 'string',
'description' => 'Position of the forms buttons: bottom | top | both',
// examples... not used
'default' => 'both',
'value' => 'both',
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'shortcut_actions' => array(
'type' => 'string',
'description' => 'Actions that are available as direct buttons next to the "Actions" menu',
// examples... not used
'default' => 'UI:Menu:Modify,UI:Menu:New',
'value' => 'UI:Menu:Modify',
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'complex_actions_limit' => array(
'type' => 'integer',
'description' => 'Display the "actions" menu items that require long computation only if the list of objects is contains less objects than this number (0 means no limit)',
// examples... not used
'default' => 50,
'value' => 50,
'source_of_value' => '',
'show_in_conf_sample' => false,
),
);
public function IsProperty($sPropCode)
{
return (array_key_exists($sPropCode, $this->m_aSettings));
}
public function GetDescription($sPropCode)
{
return $this->m_aSettings[$sPropCode];
}
public function Set($sPropCode, $value, $sSourceDesc = 'unknown')
{
@@ -388,13 +568,6 @@ class Config
*/
protected $m_bSecureConnectionRequired;
/**
* @var boolean Forces iTop to output hyperlinks starting with https:// even
* if the current page is not using https. This can be useful when
* the application runs behind a SSL gateway
*/
protected $m_bHttpsHyperlinks;
/**
* @var string Langage code, default if the user language is undefined
*/
@@ -432,6 +605,7 @@ class Config
'application/menunode.class.inc.php',
'application/user.preferences.class.inc.php',
'application/audit.rule.class.inc.php',
'application/query.class.inc.php',
// Romain - That's dirty, because those classes are in fact part of the core
// but I needed those classes to be derived from cmdbAbstractObject
// (to be managed via the GUI) and this class in not really known from
@@ -450,29 +624,8 @@ class Config
// Default AddOn, always present can be moved to an official iTop Module later if needed
'user rights' => 'addons/userrights/userrightsprofile.class.inc.php',
);
$this->m_aDictionaries = array(
// Default dictionaries, always present can be moved to an official iTop Module later if needed
'dictionaries/dictionary.itop.core.php',
'dictionaries/dictionary.itop.ui.php', // Support for English
'dictionaries/fr.dictionary.itop.ui.php', // Support for French
'dictionaries/fr.dictionary.itop.core.php', // Support for French
'dictionaries/es_cr.dictionary.itop.ui.php', // Support for Spanish (from Costa Rica)
'dictionaries/es_cr.dictionary.itop.core.php', // Support for Spanish (from Costa Rica)
'dictionaries/de.dictionary.itop.ui.php', // Support for German
'dictionaries/de.dictionary.itop.core.php', // Support for German
'dictionaries/pt_br.dictionary.itop.ui.php', // Support for Brazilian Portuguese
'dictionaries/pt_br.dictionary.itop.core.php', // Support for Brazilian Portuguese
'dictionaries/ru.dictionary.itop.ui.php', // Support for Russian
'dictionaries/ru.dictionary.itop.core.php', // Support for Russian
'dictionaries/tr.dictionary.itop.ui.php', // Support for Turkish
'dictionaries/tr.dictionary.itop.core.php', // Support for Turkish
'dictionaries/zh.dictionary.itop.ui.php', // Support for Chinese
'dictionaries/zh.dictionary.itop.core.php', // Support for Chinese
'dictionaries/it.dictionary.itop.ui.php', // Support for Italian
'dictionaries/it.dictionary.itop.core.php', // Support for Italian
'dictionaries/hu.dictionary.itop.ui.php', // Support for Hungarian
'dictionaries/hu.dictionary.itop.core.php', // Support for Hungarian
);
$this->m_aDictionaries = self::ScanDictionariesDir();
foreach($this->m_aSettings as $sPropCode => $aSettingInfo)
{
$this->m_aSettings[$sPropCode]['value'] = $aSettingInfo['default'];
@@ -496,7 +649,6 @@ class Config
$this->m_iStandardReloadInterval = DEFAULT_STANDARD_RELOAD_INTERVAL;
$this->m_iFastReloadInterval = DEFAULT_FAST_RELOAD_INTERVAL;
$this->m_bSecureConnectionRequired = DEFAULT_SECURE_CONNECTION_REQUIRED;
$this->m_bHttpsHyperlinks = DEFAULT_HTTPS_HYPERLINKS;
$this->m_sDefaultLanguage = 'EN US';
$this->m_sAllowedLoginTypes = DEFAULT_ALLOWED_LOGIN_TYPES;
$this->m_sExtAuthVariable = DEFAULT_EXT_AUTH_VARIABLE;
@@ -510,6 +662,18 @@ class Config
$this->Load($sConfigFile);
$this->Verify();
}
// Application root url: set a default value, then normalize it
$sAppRootUrl = trim($this->Get('app_root_url'));
if (strlen($sAppRootUrl) == 0)
{
$sAppRootUrl = utils::GetDefaultUrlAppRoot();
}
if (substr($sAppRootUrl, -1, 1) != '/')
{
$sAppRootUrl .= '/';
}
$this->Set('app_root_url', $sAppRootUrl);
}
protected function CheckFile($sPurpose, $sFileName)
@@ -518,6 +682,10 @@ class Config
{
throw new ConfigException("Could not find $sPurpose file", array('file' => $sFileName));
}
if (!is_readable($sFileName))
{
throw new ConfigException("Could not read $sPurpose file (the file exists but cannot be read). Do you have the rights to access this file?", array('file' => $sFileName));
}
}
protected function Load($sConfigFile)
@@ -618,7 +786,6 @@ class Config
$this->m_iStandardReloadInterval = isset($MySettings['standard_reload_interval']) ? trim($MySettings['standard_reload_interval']) : DEFAULT_STANDARD_RELOAD_INTERVAL;
$this->m_iFastReloadInterval = isset($MySettings['fast_reload_interval']) ? trim($MySettings['fast_reload_interval']) : DEFAULT_FAST_RELOAD_INTERVAL;
$this->m_bSecureConnectionRequired = isset($MySettings['secure_connection_required']) ? (bool) trim($MySettings['secure_connection_required']) : DEFAULT_SECURE_CONNECTION_REQUIRED;
$this->m_bHttpsHyperlinks = isset($MySettings['https_hyperlinks']) ? (bool) trim($MySettings['https_hyperlinks']) : DEFAULT_HTTPS_HYPERLINKS;
$this->m_aModuleSettings = isset($MyModuleSettings) ? $MyModuleSettings : array();
@@ -794,11 +961,6 @@ class Config
return $this->m_bSecureConnectionRequired;
}
public function GetHttpsHyperlinks()
{
return $this->m_bHttpsHyperlinks;
}
public function GetDefaultLanguage()
{
return $this->m_sDefaultLanguage;
@@ -904,11 +1066,6 @@ class Config
$this->m_bSecureConnectionRequired = $bSecureConnectionRequired;
}
public function SetHttpsHyperlinks($bHttpsHyperlinks)
{
$this->m_bHttpsHyperlinks = $bHttpsHyperlinks;
}
public function SetDefaultLanguage($sLanguageCode)
{
$this->m_sDefaultLanguage = $sLanguageCode;
@@ -974,7 +1131,6 @@ class Config
$aSettings['standard_reload_interval'] = $this->m_iStandardReloadInterval;
$aSettings['fast_reload_interval'] = $this->m_iFastReloadInterval;
$aSettings['secure_connection_required'] = $this->m_bSecureConnectionRequired;
$aSettings['https_hyperlinks'] = $this->m_bHttpsHyperlinks;
$aSettings['default_language'] = $this->m_sDefaultLanguage;
$aSettings['allowed_login_types'] = $this->m_sAllowedLoginTypes;
$aSettings['encryption_key'] = $this->m_sEncryptionKey;
@@ -1069,7 +1225,6 @@ class Config
fwrite($hFile, "\t'standard_reload_interval' => {$this->m_iStandardReloadInterval},\n");
fwrite($hFile, "\t'fast_reload_interval' => {$this->m_iFastReloadInterval},\n");
fwrite($hFile, "\t'secure_connection_required' => ".($this->m_bSecureConnectionRequired ? 'true' : 'false').",\n");
fwrite($hFile, "\t'https_hyperlinks' => ".($this->m_bHttpsHyperlinks ? 'true' : 'false').",\n");
fwrite($hFile, "\t'default_language' => '{$this->m_sDefaultLanguage}',\n");
fwrite($hFile, "\t'allowed_login_types' => '{$this->m_sAllowedLoginTypes}',\n");
fwrite($hFile, "\t'encryption_key' => '{$this->m_sEncryptionKey}',\n");
@@ -1094,7 +1249,7 @@ class Config
fwrite($hFile, "\n/**\n");
fwrite($hFile, " *\n");
fwrite($hFile, " * Data model modules to be loaded. Names should be specified as absolute paths\n");
fwrite($hFile, " * Data model modules to be loaded. Names are specified as relative paths\n");
fwrite($hFile, " *\n");
fwrite($hFile, " */\n");
fwrite($hFile, "\$MyModules = array(\n");
@@ -1137,5 +1292,25 @@ class Config
throw new ConfigException("Could not write to configuration file", array('file' => $sFileName));
}
}
protected static function ScanDictionariesDir()
{
$aResult = array();
// Populate automatically the list of dictionary files
$sDir = APPROOT.'/dictionaries';
if ($hDir = @opendir($sDir))
{
while (($sFile = readdir($hDir)) !== false)
{
$aMatches = array();
if (preg_match("/^([^\.]+\.)?dictionary\.itop\.(ui|core)\.php$/i", $sFile, $aMatches)) // Dictionary files named like [<Lang>.]dictionary.[core|ui].php are loaded automatically
{
$aResult[] = 'dictionaries/'.$sFile;
}
}
closedir($hDir);
}
return $aResult;
}
}
?>

View File

@@ -62,15 +62,15 @@ abstract class DBObject
protected $m_oMasterReplicaSet = null; // Set of SynchroReplica related to this object
// Use the MetaModel::NewObject to build an object (do we have to force it?)
public function __construct($aRow = null, $sClassAlias = '', $aExtendedDataSpec = null)
public function __construct($aRow = null, $sClassAlias = '', $aAttToLoad = null, $aExtendedDataSpec = null)
{
if (!empty($aRow))
{
$this->FromRow($aRow, $sClassAlias, $aExtendedDataSpec);
$this->FromRow($aRow, $sClassAlias, $aAttToLoad, $aExtendedDataSpec);
$this->m_bFullyLoaded = $this->IsFullyLoaded();
return;
}
// Creation of brand new object
// Creation of a brand new object
//
$this->m_iKey = self::GetNextTempId(get_class($this));
@@ -82,8 +82,8 @@ abstract class DBObject
$this->m_aOrigValues[$sAttCode] = null;
if ($oAttDef->IsExternalField())
{
// This field has to be read from the DB
$this->m_aLoadedAtt[$sAttCode] = false;
// This field has to be read from the DB
// Leave the flag unset (optimization)
}
else
{
@@ -154,7 +154,7 @@ abstract class DBObject
return true;
}
protected function Reload()
public function Reload()
{
assert($this->m_bIsInDB);
$aRow = MetaModel::MakeSingleRow(get_class($this), $this->m_iKey, false/*, $this->m_bAllowAllData*/);
@@ -194,7 +194,7 @@ abstract class DBObject
$this->m_bFullyLoaded = true;
}
protected function FromRow($aRow, $sClassAlias = '', $aExtendedDataSpec = null)
protected function FromRow($aRow, $sClassAlias = '', $aAttToLoad = null, $aExtendedDataSpec = null)
{
if (strlen($sClassAlias) == 0)
{
@@ -235,11 +235,17 @@ abstract class DBObject
// Build the object from an array of "attCode"=>"value")
//
$bFullyLoaded = true; // ... set to false if any attribute is not found
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef)
if (is_null($aAttToLoad) || !array_key_exists($sClassAlias, $aAttToLoad))
{
$aAttList = MetaModel::ListAttributeDefs(get_class($this));
}
else
{
$aAttList = $aAttToLoad[$sClassAlias];
}
foreach($aAttList as $sAttCode=>$oAttDef)
{
// Say something, whatever the type of attribute
$this->m_aLoadedAtt[$sAttCode] = false;
// Skip links (could not be loaded by the mean of this query)
if ($oAttDef->IsLinkSet()) continue;
@@ -296,30 +302,53 @@ abstract class DBObject
$this->Reload();
}
if ($oAttDef->IsExternalKey() && is_object($value))
if ($oAttDef->IsExternalKey())
{
// Setting an external key with a whole object (instead of just an ID)
// let's initialize also the external fields that depend on it
// (useful when building objects in memory and not from a query)
if ( (get_class($value) != $oAttDef->GetTargetClass()) && (!is_subclass_of($value, $oAttDef->GetTargetClass())))
if (is_object($value))
{
throw new CoreUnexpectedValue("Trying to set the value of '$sAttCode', to an object of class '".get_class($value)."', whereas it's an ExtKey to '".$oAttDef->GetTargetClass()."'. Ignored");
// Setting an external key with a whole object (instead of just an ID)
// let's initialize also the external fields that depend on it
// (useful when building objects in memory and not from a query)
if ( (get_class($value) != $oAttDef->GetTargetClass()) && (!is_subclass_of($value, $oAttDef->GetTargetClass())))
{
throw new CoreUnexpectedValue("Trying to set the value of '$sAttCode', to an object of class '".get_class($value)."', whereas it's an ExtKey to '".$oAttDef->GetTargetClass()."'. Ignored");
}
else
{
$this->m_aCurrValues[$sAttCode] = $value->GetKey();
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sCode => $oDef)
{
if ($oDef->IsExternalField() && ($oDef->GetKeyAttCode() == $sAttCode))
{
$this->m_aCurrValues[$sCode] = $value->Get($oDef->GetExtAttCode());
}
}
$this->m_aCurrValues[$sAttCode.'_friendlyname'] = $value->GetName();
}
}
else
else if ($this->m_aCurrValues[$sAttCode] != $value)
{
// The object has changed, reset caches
$this->m_bCheckStatus = null;
$this->m_aAsArgs = null;
$this->m_aCurrValues[$sAttCode] = $value->GetKey();
// If the external key changed, invalidate all the external fields (and friendly name) related to this external key
$this->m_aCurrValues[$sAttCode] = $value;
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sCode => $oDef)
{
if ($oDef->IsExternalField() && ($oDef->GetKeyAttCode() == $sAttCode))
{
$this->m_aCurrValues[$sCode] = $value->Get($oDef->GetExtAttCode());
unset($this->m_aLoadedAtt[$sCode]);
$this->m_aCurrValues[$sCode] = null;
}
}
$this->m_aCurrValues[$sAttCode.'_friendlyname'] = null;
unset($this->m_aLoadedAtt[$sAttCode.'_friendlyname']);
}
// The object has changed, reset caches
$this->m_bCheckStatus = null;
$this->m_aAsArgs = null;
// Make sure we do not reload it anymore... before saving it
$this->RegisterAsDirty();
return;
}
if(!$oAttDef->IsScalar() && !is_object($value))
@@ -361,15 +390,93 @@ abstract class DBObject
}
public function Get($sAttCode)
{
if (($iPos = strpos($sAttCode, '->')) === false)
{
return $this->GetStrict($sAttCode);
}
else
{
$sExtKeyAttCode = substr($sAttCode, 0, $iPos);
$sRemoteAttCode = substr($sAttCode, $iPos + 2);
if (!MetaModel::IsValidAttCode(get_class($this), $sExtKeyAttCode))
{
throw new CoreException("Unknown external key '$sExtKeyAttCode' for the class ".get_class($this));
}
$oKeyAttDef = MetaModel::GetAttributeDef(get_class($this), $sExtKeyAttCode);
$sRemoteClass = $oKeyAttDef->GetTargetClass();
$oRemoteObj = MetaModel::GetObject($sRemoteClass, $this->GetStrict($sExtKeyAttCode), false);
if (is_null($oRemoteObj))
{
return '';
}
else
{
return $oRemoteObj->Get($sRemoteAttCode);
}
}
}
public function GetStrict($sAttCode)
{
if (!array_key_exists($sAttCode, MetaModel::ListAttributeDefs(get_class($this))))
{
throw new CoreException("Unknown attribute code '$sAttCode' for the class ".get_class($this));
}
if ($this->m_bIsInDB && !$this->m_aLoadedAtt[$sAttCode] && !$this->m_bDirty)
if ($this->m_bIsInDB && !isset($this->m_aLoadedAtt[$sAttCode]))
{
// #@# non-scalar attributes.... handle that differently
$this->Reload();
if (!$this->m_bDirty)
{
$this->Reload();
}
else
{
// If the missing attribute is an external fields (or a friendlyname), try to selectively reload it
// from the value of its external key... and reload the related external fields & friendlyname as well
$sTargetClass = '';
$iCurrKey = 0;
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
if ($oAttDef->IsExternalField())
{
$sKeyAttCode = $oAttDef->GetKeyAttCode();
$iCurrKey = $this->m_aCurrValues[$oAttDef->GetKeyAttCode()];
$sTargetClass= $oAttDef->GetTargetClass();
}
else if ($oAttDef instanceof AttributeFriendlyName)
{
$sKeyAttCode = $oAttDef->GetKeyAttCode();
$oKeyAttDef = MetaModel::GetAttributeDef(get_class($this), $sKeyAttCode);
$iCurrKey = $this->m_aCurrValues[$oAttDef->GetKeyAttCode()];
$sTargetClass = $oKeyAttDef->GetTargetClass();
}
if (($sTargetClass != '') && ($iCurrKey != 0))
{
$oTargetObj = MetaModel::GetObject($sTargetClass, $iCurrKey, false);
if (is_object($oTargetObj))
{
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sCode => $oDef)
{
if ($oDef->IsExternalField() && ($oDef->GetKeyAttCode() == $sKeyAttCode))
{
$this->m_aLoadedAtt[$sCode] = true;
$this->m_aCurrValues[$sCode] = $oTargetObj->Get($oDef->GetExtAttCode());
}
}
if ($oAttDef instanceof AttributeFriendlyName)
{
$this->m_aLoadedAtt[$sAttCode] = true;
$this->m_aCurrValues[$sAttCode] = $oTargetObj->GetName();
}
else
{
$this->m_aLoadedAtt[$sKeyAttCode.'_friendlyname'] = true;
$this->m_aCurrValues[$sKeyAttCode.'_friendlyname'] = $oTargetObj->GetName();
}
}
}
}
}
$value = $this->m_aCurrValues[$sAttCode];
if ($value instanceof DBObjectSet)
@@ -454,7 +561,7 @@ abstract class DBObject
}
// That's a standard attribute (might be an ext field or a direct field, etc.)
return $oAtt->GetAsHTML($this->Get($sAttCode));
return $oAtt->GetAsHTML($this->Get($sAttCode), $this);
}
public function GetEditValue($sAttCode)
@@ -522,44 +629,46 @@ abstract class DBObject
return $oAtt->GetAsCSV($this->GetOriginal($sAttCode), $sSeparator, $sTextQualifier, $this);
}
protected static function MakeHyperLink($sObjClass, $sObjKey, $sLabel = '')
protected static function MakeHyperLink($sObjClass, $sObjKey, $sLabel = '', $sUrlMakerClass = null, $bWithNavigationContext = true)
{
if ($sObjKey <= 0) return '<em>'.Dict::S('UI:UndefinedObject').'</em>'; // Objects built in memory have negative IDs
$oAppContext = new ApplicationContext();
$sPage = self::ComputeUIPage($sObjClass);
$sAbsoluteUrl = utils::GetAbsoluteUrlPath();
// Safety net
//
if (empty($sLabel))
{
// If the object if not issued from a query but constructed programmatically
// the label may be empty. In this case run a query to get the object's friendly name
$oTmpObj = MetaModel::GetObject($sObjClass, $sObjKey);
$sLabel = $oTmpObj->GetName();
$oTmpObj = MetaModel::GetObject($sObjClass, $sObjKey, false);
if (is_object($oTmpObj))
{
$sLabel = $oTmpObj->GetName();
}
else
{
// May happen in case the target object is not in the list of allowed values for this attribute
$sLabel = "<em>$sObjClass::$sObjKey</em>";
}
//$sLabel = MetaModel::GetName($sObjClass)." #$sObjKey";
}
$sHint = MetaModel::GetName($sObjClass)."::$sObjKey";
return "<a href=\"{$sAbsoluteUrl}{$sPage}?operation=details&class=$sObjClass&id=$sObjKey&".$oAppContext->GetForLink()."\" title=\"$sHint\">$sLabel</a>";
$sUrl = ApplicationContext::MakeObjectUrl($sObjClass, $sObjKey, $sUrlMakerClass, $bWithNavigationContext);
if (strlen($sUrl) > 0)
{
return "<a href=\"$sUrl\" title=\"$sHint\">$sLabel</a>";
}
else
{
return $sLabel;
}
}
public function GetHyperlink()
public function GetHyperlink($sUrlMakerClass = null, $bWithNavigationContext = true)
{
if ($this->IsNew()) return '<em>'.Dict::S('UI:UndefinedObject').'</em>'; // Objects built in memory have negative IDs
$oAppContext = new ApplicationContext();
$sPage = $this->GetUIPage();
$sAbsoluteUrl = utils::GetAbsoluteUrlPath();
$sObjClass = get_class($this);
$sObjKey = $this->GetKey();
$sLabel = $this->GetName();
$sHint = MetaModel::GetName($sObjClass)."::$sObjKey";
return "<a href=\"{$sAbsoluteUrl}{$sPage}?operation=details&class=$sObjClass&id=$sObjKey&".$oAppContext->GetForLink()."\" title=\"$sHint\">$sLabel</a>";
return self::MakeHyperLink(get_class($this), $this->GetKey(), $this->GetName(), $sUrlMakerClass, $bWithNavigationContext);
}
public static function ComputeUIPage($sClass)
public static function ComputeStandardUIPage($sClass)
{
static $aUIPagesCache = array(); // Cache to store the php page used to display each class of object
if (!isset($aUIPagesCache[$sClass]))
@@ -614,25 +723,25 @@ abstract class DBObject
return MetaModel::GetClassIcon(get_class($this), $bImgTag);
}
/**
* Gets the name of an object in a safe manner for displaying inside a web page
* @return string
*/
public function GetName()
{
$aNameSpec = MetaModel::GetNameSpec(get_class($this));
$sFormat = $aNameSpec[0];
$aAttributes = $aNameSpec[1];
return htmlentities($this->GetRawName(), ENT_QUOTES, 'UTF-8');
}
$aValues = array();
foreach ($aAttributes as $sAttCode)
{
if (empty($sAttCode))
{
$aValues[] = $this->m_iKey;
}
else
{
$aValues[] = $this->Get($sAttCode);
}
}
return vsprintf($sFormat, $aValues);
/**
* Gets the raw name of an object, this is not safe for displaying inside a web page
* since the " < > characters are not escaped and the name may contain some XSS script
* instructions.
* Use this function only for internal computations or for an output to a non-HTML destination
* @return string
*/
public function GetRawName()
{
return $this->Get('friendlyname');
}
public function GetState()
@@ -678,22 +787,48 @@ abstract class DBObject
/**
* Returns the set of flags (OPT_ATT_HIDDEN, OPT_ATT_READONLY, OPT_ATT_MANDATORY...)
* for the given attribute in the current state of the object
* @param string $sAttCode The code of the attribute
* @param $sAttCode string $sAttCode The code of the attribute
* @param $aReasons array To store the reasons why the attribute is read-only (info about the synchro replicas)
* @param $sTargetState string The target state in which to evalutate the flags, if empty the current state will be used
* @return integer Flags: the binary combination of the flags applicable to this attribute
*/
public function GetAttributeFlags($sAttCode, &$aReasons = array())
public function GetAttributeFlags($sAttCode, &$aReasons = array(), $sTargetState = '')
{
$iFlags = 0; // By default (if no life cycle) no flag at all
$sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this));
if (!empty($sStateAttCode))
{
$iFlags = MetaModel::GetAttributeFlags(get_class($this), $this->Get($sStateAttCode), $sAttCode);
if ($sTargetState != '')
{
$iFlags = MetaModel::GetAttributeFlags(get_class($this), $sTargetState, $sAttCode);
}
else
{
$iFlags = MetaModel::GetAttributeFlags(get_class($this), $this->Get($sStateAttCode), $sAttCode);
}
}
$aReasons = array();
$iSynchroFlags = $this->GetSynchroReplicaFlags($sAttCode, $aReasons);
return $iFlags | $iSynchroFlags; // Combine both sets of flags
}
/**
* Returns the set of flags (OPT_ATT_HIDDEN, OPT_ATT_READONLY, OPT_ATT_MANDATORY...)
* for the given attribute for the current state of the object considered as an INITIAL state
* @param string $sAttCode The code of the attribute
* @return integer Flags: the binary combination of the flags applicable to this attribute
*/
public function GetInitialStateAttributeFlags($sAttCode, &$aReasons = array())
{
$iFlags = 0;
$sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this));
if (!empty($sStateAttCode))
{
$iFlags = MetaModel::GetInitialStateAttributeFlags(get_class($this), $this->Get($sStateAttCode), $sAttCode);
}
return $iFlags; // No need to care about the synchro flags since we'll be creating a new object anyway
}
// check if the given (or current) value is suitable for the attribute
// return true if successfull
// return the error desciption otherwise
@@ -735,6 +870,14 @@ abstract class DBObject
return "Target object not found ($sTargetClass::$toCheck)";
}
}
if ($oAtt->IsHierarchicalKey())
{
// This check cannot be deactivated since otherwise the user may break things by a CSV import of a bulk modify
if ($toCheck == $this->GetKey())
{
return "An object can not be its own parent in a hierarchy (".$oAtt->Getlabel()." = $toCheck)";
}
}
}
elseif ($oAtt->IsScalar())
{
@@ -862,7 +1005,7 @@ abstract class DBObject
$oDeletionPlan->AddToDelete($oReplica, DEL_SILENT);
if ($oDataSource->GetKey() == SynchroDataSource::GetCurrentTaskId())
if ($oDataSource->GetKey() == SynchroExecution::GetCurrentTaskId())
{
// The current task has the right to delete the object
continue;
@@ -911,30 +1054,35 @@ abstract class DBObject
$aDelta = array();
foreach ($aProposal as $sAtt => $proposedValue)
{
if (!array_key_exists($sAtt, $this->m_aOrigValues))
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAtt);
// Ignore external fields and friendly names that change only as a consequence of modifying another field
if ((!$oAttDef->IsExternalField() && !($oAttDef instanceof AttributeFriendlyName)))
{
// The value was not set
$aDelta[$sAtt] = $proposedValue;
}
elseif(is_object($proposedValue))
{
$oLinkAttDef = MetaModel::GetAttributeDef(get_class($this), $sAtt);
// The value is an object, the comparison is not strict
if (!$oLinkAttDef->Equals($proposedValue, $this->m_aOrigValues[$sAtt]))
if (!array_key_exists($sAtt, $this->m_aOrigValues))
{
// The value was not set
$aDelta[$sAtt] = $proposedValue;
}
}
else
{
// The value is a scalar, the comparison must be 100% strict
if($this->m_aOrigValues[$sAtt] !== $proposedValue)
{
//echo "$sAtt:<pre>\n";
//var_dump($this->m_aOrigValues[$sAtt]);
//var_dump($proposedValue);
//echo "</pre>\n";
$aDelta[$sAtt] = $proposedValue;
elseif(is_object($proposedValue))
{
$oLinkAttDef = MetaModel::GetAttributeDef(get_class($this), $sAtt);
// The value is an object, the comparison is not strict
if (!$oLinkAttDef->Equals($proposedValue, $this->m_aOrigValues[$sAtt]))
{
$aDelta[$sAtt] = $proposedValue;
}
}
else
{
// The value is a scalar, the comparison must be 100% strict
if($this->m_aOrigValues[$sAtt] !== $proposedValue)
{
//echo "$sAtt:<pre>\n";
//var_dump($this->m_aOrigValues[$sAtt]);
//var_dump($proposedValue);
//echo "</pre>\n";
$aDelta[$sAtt] = $proposedValue;
}
}
}
}
@@ -1083,6 +1231,8 @@ abstract class DBObject
$aValuesToWrite[] = CMDBSource::Quote($this->m_iKey);
}
$aHierarchicalKeys = array();
foreach(MetaModel::ListAttributeDefs($sTableClass) as $sAttCode=>$oAttDef)
{
// Skip this attribute if not defined in this table
@@ -1093,6 +1243,10 @@ abstract class DBObject
$aFieldsToWrite[] = "`$sColumn`";
$aValuesToWrite[] = CMDBSource::Quote($sValue);
}
if ($oAttDef->IsHierarchicalKey())
{
$aHierarchicalKeys[$sAttCode] = $oAttDef;
}
}
if (count($aValuesToWrite) == 0) return false;
@@ -1115,6 +1269,17 @@ abstract class DBObject
}
else
{
if (count($aHierarchicalKeys) > 0)
{
foreach($aHierarchicalKeys as $sAttCode => $oAttDef)
{
$aValues = MetaModel::HKInsertChildUnder($this->m_aCurrValues[$sAttCode], $oAttDef, $sTable);
$aFieldsToWrite[] = '`'.$oAttDef->GetSQLRight().'`';
$aValuesToWrite[] = $aValues[$oAttDef->GetSQLRight()];
$aFieldsToWrite[] = '`'.$oAttDef->GetSQLLeft().'`';
$aValuesToWrite[] = $aValues[$oAttDef->GetSQLLeft()];
}
}
$sInsertSQL = "INSERT INTO `$sTable` (".join(",", $aFieldsToWrite).") VALUES (".join(", ", $aValuesToWrite).")";
$iNewKey = CMDBSource::InsertInto($sInsertSQL);
}
@@ -1194,13 +1359,11 @@ abstract class DBObject
// Activate any existing trigger
$sClass = get_class($this);
$oSet = new DBObjectSet(new DBObjectSearch('TriggerOnObjectCreate'));
$sClassList = implode("', '", MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL));
$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnObjectCreate AS t WHERE t.target_class IN ('$sClassList')"));
while ($oTrigger = $oSet->Fetch())
{
if (MetaModel::IsParentClass($oTrigger->Get('target_class'), $sClass))
{
$oTrigger->DoActivate($this->ToArgs('this'));
}
$oTrigger->DoActivate($this->ToArgs('this'));
}
return $this->m_iKey;
@@ -1218,6 +1381,11 @@ abstract class DBObject
return $this->DBInsert();
}
public function DBInsertTrackedNoReload(CMDBChange $oVoid)
{
return $this->DBInsertNoReload();
}
// Creates a copy of the current object into the database
// Returns the id of the newly created object
public function DBClone($iNewKey = null)
@@ -1266,22 +1434,68 @@ abstract class DBObject
}
$bHasANewExternalKeyValue = false;
$aHierarchicalKeys = array();
foreach($aChanges as $sAttCode => $valuecurr)
{
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
if ($oAttDef->IsExternalKey()) $bHasANewExternalKeyValue = true;
if (!$oAttDef->IsDirectField()) unset($aChanges[$sAttCode]);
if ($oAttDef->IsHierarchicalKey())
{
$aHierarchicalKeys[$sAttCode] = $oAttDef;
}
}
// Update scalar attributes
if (count($aChanges) != 0)
if (!MetaModel::DBIsReadOnly())
{
$oFilter = new DBObjectSearch(get_class($this));
$oFilter->AddCondition('id', $this->m_iKey, '=');
$sSQL = MetaModel::MakeUpdateQuery($oFilter, $aChanges);
if (!MetaModel::DBIsReadOnly())
// Update the left & right indexes for each hierarchical key
foreach($aHierarchicalKeys as $sAttCode => $oAttDef)
{
$sTable = $sTable = MetaModel::DBGetTable(get_class($this), $sAttCode);
$sSQL = "SELECT `".$oAttDef->GetSQLRight()."` AS `right`, `".$oAttDef->GetSQLLeft()."` AS `left` FROM `$sTable` WHERE id=".$this->GetKey();
$aRes = CMDBSource::QueryToArray($sSQL);
$iMyLeft = $aRes[0]['left'];
$iMyRight = $aRes[0]['right'];
$iDelta =$iMyRight - $iMyLeft + 1;
MetaModel::HKTemporaryCutBranch($iMyLeft, $iMyRight, $oAttDef, $sTable);
if ($aChanges[$sAttCode] == 0)
{
// No new parent, insert completely at the right of the tree
$sSQL = "SELECT max(`".$oAttDef->GetSQLRight()."`) AS max FROM `$sTable`";
$aRes = CMDBSource::QueryToArray($sSQL);
if (count($aRes) == 0)
{
$iNewLeft = 1;
}
else
{
$iNewLeft = $aRes[0]['max']+1;
}
}
else
{
// Insert at the right of the specified parent
$sSQL = "SELECT `".$oAttDef->GetSQLRight()."` FROM `$sTable` WHERE id=".((int)$aChanges[$sAttCode]);
$iNewLeft = CMDBSource::QueryToScalar($sSQL);
}
MetaModel::HKReplugBranch($iNewLeft, $iNewLeft + $iDelta - 1, $oAttDef, $sTable);
$aHKChanges = array();
$aHKChanges[$sAttCode] = $aChanges[$sAttCode];
$aHKChanges[$oAttDef->GetSQLLeft()] = $iNewLeft;
$aHKChanges[$oAttDef->GetSQLRight()] = $iNewLeft + $iDelta - 1;
$aChanges[$sAttCode] = $aHKChanges; // the 3 values will be stored by MakeUpdateQuery below
}
// Update scalar attributes
if (count($aChanges) != 0)
{
$oFilter = new DBObjectSearch(get_class($this));
$oFilter->AddCondition('id', $this->m_iKey, '=');
$sSQL = MetaModel::MakeUpdateQuery($oFilter, $aChanges);
CMDBSource::Query($sSQL);
}
}
@@ -1337,6 +1551,34 @@ abstract class DBObject
if (!MetaModel::DBIsReadOnly())
{
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode => $oAttDef)
{
if ($oAttDef->IsHierarchicalKey())
{
// Update the left & right indexes for each hierarchical key
$sTable = $sTable = MetaModel::DBGetTable(get_class($this), $sAttCode);
$sSQL = "SELECT `".$oAttDef->GetSQLRight()."` AS `right`, `".$oAttDef->GetSQLLeft()."` AS `left` FROM `$sTable` WHERE id=".CMDBSource::Quote($this->m_iKey);
$aRes = CMDBSource::QueryToArray($sSQL);
$iMyLeft = $aRes[0]['left'];
$iMyRight = $aRes[0]['right'];
$iDelta =$iMyRight - $iMyLeft + 1;
MetaModel::HKTemporaryCutBranch($iMyLeft, $iMyRight, $oAttDef, $sTable);
// No new parent for now, insert completely at the right of the tree
$sSQL = "SELECT max(`".$oAttDef->GetSQLRight()."`) AS max FROM `$sTable`";
$aRes = CMDBSource::QueryToArray($sSQL);
if (count($aRes) == 0)
{
$iNewLeft = 1;
}
else
{
$iNewLeft = $aRes[0]['max']+1;
}
MetaModel::HKReplugBranch($iNewLeft, $iNewLeft + $iDelta - 1, $oAttDef, $sTable);
}
}
foreach(MetaModel::EnumParentClasses(get_class($this), ENUM_PARENT_CLASSES_ALL) as $sParentClass)
{
$this->DBDeleteSingleTable($sParentClass);
@@ -1372,7 +1614,14 @@ abstract class DBObject
foreach ($aToDelete as $iId => $aData)
{
$oToDelete = $aData['to_delete'];
$oToDelete->DBDeleteSingleObject();
// The deletion based on a deletion plan should not be done for each oject if the deletion plan is common (Trac #457)
// because for each object we would try to update all the preceding ones... that are already deleted
// A better approach would be to change the API to apply the DBDelete on the deletion plan itself... just once
// As a temporary fix: delete only the objects that are still to be deleted...
if ($oToDelete->m_bIsInDB)
{
$oToDelete->DBDeleteSingleObject();
}
}
}
@@ -1383,7 +1632,7 @@ abstract class DBObject
$oToUpdate = $aData['to_reset'];
foreach ($aData['attributes'] as $sRemoteExtKey => $aRemoteAttDef)
{
$oToUpdate->Set($sRemoteExtKey, 0);
$oToUpdate->Set($sRemoteExtKey, $aData['values'][$sRemoteExtKey]);
$oToUpdate->DBUpdate();
}
}
@@ -1442,19 +1691,22 @@ abstract class DBObject
if (!$bRet) $bSuccess = false;
}
// Change state triggers...
$sClass = get_class($this);
$sClassList = implode("', '", MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL));
$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnStateLeave AS t WHERE t.target_class IN ('$sClassList') AND t.state='$sPreviousState'"));
while ($oTrigger = $oSet->Fetch())
if ($bSuccess)
{
$oTrigger->DoActivate($this->ToArgs('this'));
}
$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnStateEnter AS t WHERE t.target_class IN ('$sClassList') AND t.state='$sNewState'"));
while ($oTrigger = $oSet->Fetch())
{
$oTrigger->DoActivate($this->ToArgs('this'));
// Change state triggers...
$sClass = get_class($this);
$sClassList = implode("', '", MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL));
$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnStateLeave AS t WHERE t.target_class IN ('$sClassList') AND t.state='$sPreviousState'"));
while ($oTrigger = $oSet->Fetch())
{
$oTrigger->DoActivate($this->ToArgs('this'));
}
$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnStateEnter AS t WHERE t.target_class IN ('$sClassList') AND t.state='$sNewState'"));
while ($oTrigger = $oSet->Fetch())
{
$oTrigger->DoActivate($this->ToArgs('this'));
}
}
return $bSuccess;
@@ -1474,10 +1726,8 @@ abstract class DBObject
$aScalarArgs[$sArgName] = $this->GetKey();
$aScalarArgs[$sArgName.'->id'] = $this->GetKey();
$aScalarArgs[$sArgName.'->object()'] = $this;
$aScalarArgs[$sArgName.'->hyperlink()'] = $this->GetHyperlink();
// #@# Prototype for a user portal - to be dehardcoded later
$sToPortal = utils::GetAbsoluteUrlPath().'../portal/index.php?operation=details&id='.$this->GetKey();
$aScalarArgs[$sArgName.'->hyperlink(portal)'] = '<a href="'.$sToPortal.'">'.$this->GetName().'</a>';
$aScalarArgs[$sArgName.'->hyperlink()'] = $this->GetHyperlink('iTopStandardURLMaker', false);
$aScalarArgs[$sArgName.'->hyperlink(portal)'] = $this->GetHyperlink('PortalURLMaker', false);
$aScalarArgs[$sArgName.'->name()'] = $this->GetName();
$sClass = get_class($this);
@@ -1489,7 +1739,14 @@ abstract class DBObject
// #@# Note: This has been proven to be quite slow, this can slow down bulk load
$sAsHtml = $this->GetAsHtml($sAttCode);
$aScalarArgs[$sArgName.'->html('.$sAttCode.')'] = $sAsHtml;
$aScalarArgs[$sArgName.'->label('.$sAttCode.')'] = strip_tags($sAsHtml);
$aScalarArgs[$sArgName.'->label('.$sAttCode.')'] = $this->GetEditValue($sAttCode); // "Nice" display value, but without HTML tags and entities
}
// Do something for case logs... quick N' dirty...
if ($aScalarArgs[$sArgName.'->'.$sAttCode] instanceof ormCaseLog)
{
$oCaseLog = $aScalarArgs[$sArgName.'->'.$sAttCode];
$aScalarArgs[$sArgName.'->'.$sAttCode] = $oCaseLog->GetText();
$aScalarArgs[$sArgName.'->head('.$sAttCode.')'] = $oCaseLog->GetLatestEntry();
}
}
$this->m_aAsArgs = $aScalarArgs;
@@ -1639,7 +1896,16 @@ abstract class DBObject
if ($oAttDef->IsNullAllowed())
{
// Optional external key, list to reset
$oDeletionPlan->AddToUpdate($oDependentObj, $oAttDef);
if (($iDeletePropagationOption == DEL_MOVEUP) && ($oAttDef->IsHierarchicalKey()))
{
// Move the child up one level i.e. set the same parent as the current object
$iParentId = $this->Get($oAttDef->GetCode());
$oDeletionPlan->AddToUpdate($oDependentObj, $oAttDef, $iParentId);
}
else
{
$oDeletionPlan->AddToUpdate($oDependentObj, $oAttDef);
}
}
else
{
@@ -1679,7 +1945,7 @@ abstract class DBObject
$oSet = $this->GetMasterReplica();
while($aData = $oSet->FetchAssoc())
{
if ($aData['datasource']->GetKey() == SynchroDataSource::GetCurrentTaskId())
if ($aData['datasource']->GetKey() == SynchroExecution::GetCurrentTaskId())
{
// Ignore the current task (check to write => ok)
continue;

View File

@@ -22,6 +22,16 @@
* @author Denis Flaven <denis.flaven@combodo.com>
* @license http://www.opensource.org/licenses/gpl-3.0.html LGPL
*/
define('TREE_OPERATOR_EQUALS', 0);
define('TREE_OPERATOR_BELOW', 1);
define('TREE_OPERATOR_BELOW_STRICT', 2);
define('TREE_OPERATOR_NOT_BELOW', 3);
define('TREE_OPERATOR_NOT_BELOW_STRICT', 4);
define('TREE_OPERATOR_ABOVE', 5);
define('TREE_OPERATOR_ABOVE_STRICT', 6);
define('TREE_OPERATOR_NOT_ABOVE', 7);
define('TREE_OPERATOR_NOT_ABOVE_STRICT', 8);
class DBObjectSearch
{
@@ -33,6 +43,7 @@ class DBObjectSearch
private $m_aPointingTo;
private $m_aReferencedBy;
private $m_aRelatedTo;
private $m_bDataFiltered;
// By default, some information may be hidden to the current user
// But it may happen that we need to disable that feature
@@ -53,12 +64,29 @@ class DBObjectSearch
$this->m_aPointingTo = array();
$this->m_aReferencedBy = array();
$this->m_aRelatedTo = array();
$this->m_bDataFiltered = false;
$this->m_aParentConditions = array();
$this->m_aModifierProperties = array();
}
public function AllowAllData() {$this->m_bAllowAllData = true;}
public function IsAllDataAllowed() {return $this->m_bAllowAllData;}
public function IsDataFiltered() {return $this->m_bDataFiltered; }
public function SetDataFiltered() {$this->m_bDataFiltered = true;}
public function GetClassName($sAlias)
{
if (array_key_exists($sAlias, $this->m_aClasses))
{
return $this->m_aClasses[$sAlias];
}
else
{
throw new CoreException("Invalid class alias '$sAlias'");
}
}
public function GetClassName($sAlias) {return $this->m_aClasses[$sAlias];}
public function GetJoinedClasses() {return $this->m_aClasses;}
public function GetClass()
@@ -100,6 +128,23 @@ class DBObjectSearch
}
public function SetModifierProperty($sPluginClass, $sProperty, $value)
{
$this->m_aModifierProperties[$sPluginClass][$sProperty] = $value;
}
public function GetModifierProperties($sPluginClass)
{
if (array_key_exists($sPluginClass, $this->m_aModifierProperties))
{
return $this->m_aModifierProperties[$sPluginClass];
}
else
{
return array();
}
}
public function IsAny()
{
// #@# todo - if (!$this->m_oSearchCondition->IsTrue()) return false;
@@ -107,6 +152,7 @@ class DBObjectSearch
if (count($this->m_aPointingTo) > 0) return false;
if (count($this->m_aReferencedBy) > 0) return false;
if (count($this->m_aRelatedTo) > 0) return false;
if (count($this->m_aParentConditions) > 0) return false;
return true;
}
@@ -115,13 +161,55 @@ class DBObjectSearch
// To replace __Describe
}
public function DescribeConditionPointTo($sExtKeyAttCode)
public function DescribeConditionPointTo($sExtKeyAttCode, $aPointingTo)
{
if (!isset($this->m_aPointingTo[$sExtKeyAttCode])) return "";
$oFilter = $this->m_aPointingTo[$sExtKeyAttCode];
if ($oFilter->IsAny()) return "";
$oAtt = MetaModel::GetAttributeDef($this->GetClass(), $sExtKeyAttCode);
return $oAtt->GetLabel()." having ({$oFilter->DescribeConditions()})";
if (empty($aPointingTo)) return "";
foreach($aPointingTo as $iOperatorCode => $oFilter)
{
if ($oFilter->IsAny()) break;
$oAtt = MetaModel::GetAttributeDef($this->GetClass(), $sExtKeyAttCode);
$sOperator = '';
switch($iOperatorCode)
{
case TREE_OPERATOR_EQUALS:
$sOperator = 'having';
break;
case TREE_OPERATOR_BELOW:
$sOperator = 'below';
break;
case TREE_OPERATOR_BELOW_STRICT:
$sOperator = 'strictly below';
break;
case TREE_OPERATOR_NOT_BELOW:
$sOperator = 'not below';
break;
case TREE_OPERATOR_NOT_BELOW_STRICT:
$sOperator = 'strictly not below';
break;
case TREE_OPERATOR_ABOVE:
$sOperator = 'above';
break;
case TREE_OPERATOR_ABOVE_STRICT:
$sOperator = 'strictly above';
break;
case TREE_OPERATOR_NOT_ABOVE:
$sOperator = 'not above';
break;
case TREE_OPERATOR_NOT_ABOVE_STRICT:
$sOperator = 'strictly not above';
break;
}
$aDescription[] = $oAtt->GetLabel()."$sOperator ({$oFilter->DescribeConditions()})";
}
return implode(' and ', $aDescription);
}
public function DescribeConditionRefBy($sForeignClass, $sForeignExtKeyAttCode)
@@ -141,6 +229,7 @@ class DBObjectSearch
return "related ($sRelCode... peut mieux faire !, $iMaxDepth dig depth) to a {$oFilter->GetClass()} ({$oFilter->DescribeConditions()})";
}
public function DescribeConditions()
{
$aConditions = array();
@@ -159,10 +248,9 @@ class DBObjectSearch
$aConditions[] = $this->RenderCondition();
$aCondPoint = array();
foreach($this->m_aPointingTo as $sExtKeyAttCode=>$oFilter)
foreach($this->m_aPointingTo as $sExtKeyAttCode => $aPointingTo)
{
if ($oFilter->IsAny()) continue;
$aCondPoint[] = $this->DescribeConditionPointTo($sExtKeyAttCode);
$aCondPoint[] = $this->DescribeConditionPointTo($sExtKeyAttCode, $aPointingTo);
}
if (count($aCondPoint) > 0)
{
@@ -187,6 +275,11 @@ class DBObjectSearch
$aConditions[] = implode(" and ", $aCondReferred);
}
foreach ($this->m_aParentConditions as $aRelInfo)
{
$aCondReferred[] = $this->DescribeConditionParent($aRelInfo);
}
return implode(" and ", $aConditions);
}
@@ -209,18 +302,75 @@ class DBObjectSearch
protected function TransferConditionExpression($oFilter, $aTranslation)
{
// Prevent collisions in the parameter names by renaming them if needed
foreach($this->m_aParams as $sParam => $value)
{
if (array_key_exists($sParam, $oFilter->m_aParams) && ($value != $oFilter->m_aParams[$sParam]))
{
// Generate a new and unique name for the collinding parameter
$index = 1;
while(array_key_exists($sParam.$index, $oFilter->m_aParams))
{
$index++;
}
$secondValue = $oFilter->m_aParams[$sParam];
$oFilter->RenameParam($sParam, $sParam.$index);
unset($oFilter->m_aParams[$sParam]);
$oFilter->m_aParams[$sParam.$index] = $secondValue;
}
}
//echo "<p>TransferConditionExpression:<br/>";
//echo "Adding Conditions:<br/><pre>oFilter:\n".print_r($oFilter, true)."\naTranslation:\n".print_r($aTranslation, true)."</pre>\n";
//echo "</p>";
$oTranslated = $oFilter->GetCriteria()->Translate($aTranslation, false, false /* leave unresolved fields */);
//echo "Adding Conditions (translated):<br/><pre>".print_r($oTranslated, true)."</pre>\n";
$this->AddConditionExpression($oTranslated);
// #@# what about collisions in parameter names ???
$this->m_aParams = array_merge($this->m_aParams, $oFilter->m_aParams);
}
protected function RenameParam($sOldName, $sNewName)
{
$this->m_oSearchCondition->RenameParam($sOldName, $sNewName);
foreach($this->m_aRelatedTo as $aRelatedTo)
{
$aRelatedTo['flt']->RenameParam($sOldName, $sNewName);
}
foreach($this->m_aPointingTo as $sExtKeyAttCode=>$aPointingTo)
{
foreach($aPointingTo as $iOperatorCode => $aFilter)
{
foreach($aFilter as $sAlias => $oExtFilter)
{
$oExtFilter->RenameParam($sOldName, $sNewName);
}
}
}
foreach($this->m_aReferencedBy as $sForeignClass => $aReferences)
{
foreach($aReferences as $sForeignExtKeyAttCode => $oForeignFilter)
{
$oForeignFilter->RenameParam($sOldName, $sNewName);
}
}
foreach($this->m_aParentConditions as $aParent)
{
$aParent['expression']->RenameParam($sOldName, $sNewName);
}
}
public function ResetCondition()
{
$this->m_oSearchCondition = new TrueExpression();
$this->m_aParentConditions = array();
// ? is that usefull/enough, do I need to rebuild the list after the subqueries ?
}
public function MergeConditionExpression($oExpression)
{
$this->m_oSearchCondition = $this->m_oSearchCondition->LogOr($oExpression);
}
public function AddConditionExpression($oExpression)
{
$this->m_oSearchCondition = $this->m_oSearchCondition->LogAnd($oExpression);
@@ -320,30 +470,113 @@ class DBObjectSearch
$this->AddConditionExpression($oNewCondition);
}
/**
* Specify a condition on external keys or link sets
* @param sAttSpec Can be either an attribute code or extkey->[sAttSpec] or linkset->[sAttSpec] and so on, recursively
* Example: infra_list->ci_id->location_id->country
* @param value The value to match
* @return void
*/
public function AddConditionAdvanced($sAttSpec, $value)
{
$sClass = $this->GetClass();
$iPos = strpos($sAttSpec, '->');
if ($iPos !== false)
{
$sAttCode = substr($sAttSpec, 0, $iPos);
$sSubSpec = substr($sAttSpec, $iPos + 2);
if (!MetaModel::IsValidAttCode($sClass, $sAttCode))
{
throw new Exception("Invalid attribute code '$sClass/$sAttCode' in condition specification '$sAttSpec'");
}
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
if ($oAttDef->IsLinkSet())
{
$sTargetClass = $oAttDef->GetLinkedClass();
$sExtKeyToMe = $oAttDef->GetExtKeyToMe();
$oNewFilter = new DBObjectSearch($sTargetClass);
$oNewFilter->AddConditionAdvanced($sSubSpec, $value);
$this->AddCondition_ReferencedBy($oNewFilter, $sExtKeyToMe);
}
elseif ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE))
{
$sTargetClass = $oAttDef->GetTargetClass(EXTKEY_ABSOLUTE);
$oNewFilter = new DBObjectSearch($sTargetClass);
$oNewFilter->AddConditionAdvanced($sSubSpec, $value);
$this->AddCondition_PointingTo($oNewFilter, $sAttCode);
}
else
{
throw new Exception("Attribute specification '$sAttSpec', '$sAttCode' should be either a link set or an external key");
}
}
else
{
// $sAttSpec is an attribute code
//
$this->AddCondition($sAttSpec, $value);
}
}
public function AddCondition_FullText($sFullText)
{
$this->m_aFullText[] = $sFullText;
}
protected function AddToNameSpace(&$aClassAliases, &$aAliasTranslation)
public function AddCondition_Parent($sAttCode, $iOperatorCode, $oExpression)
{
$sOrigAlias = $this->GetClassAlias();
if (array_key_exists($sOrigAlias, $aClassAliases))
$oAttDef = MetaModel::GetAttributeDef($this->GetClass(), $sAttCode);
if (!$oAttDef instanceof AttributeHierarchicalKey)
{
$sNewAlias = MetaModel::GenerateUniqueAlias($aClassAliases, $sOrigAlias, $this->GetClass());
$this->m_aSelectedClasses[$sNewAlias] = $this->GetClass();
unset($this->m_aSelectedClasses[$sOrigAlias]);
// Translate the condition expression with the new alias
$aAliasTranslation[$sOrigAlias]['*'] = $sNewAlias;
throw new Exception("AddCondition_Parent can only be used on hierarchical keys. '$sAttCode' is not a hierarchical key.");
}
// add the alias into the filter aliases list
$aClassAliases[$this->GetClassAlias()] = $this->GetClass();
foreach($this->m_aPointingTo as $sExtKeyAttCode=>$oFilter)
$this->m_aParentConditions[] = array(
'attCode' => $sAttCode,
'operator' => $iOperatorCode,
'expression' => $oExpression,
);
}
protected function AddToNameSpace(&$aClassAliases, &$aAliasTranslation, $bTranslateMainAlias = true)
{
if ($bTranslateMainAlias)
{
$oFilter->AddToNameSpace($aClassAliases, $aAliasTranslation);
$sOrigAlias = $this->GetClassAlias();
if (array_key_exists($sOrigAlias, $aClassAliases))
{
$sNewAlias = MetaModel::GenerateUniqueAlias($aClassAliases, $sOrigAlias, $this->GetClass());
//echo "<p>Generating a new alias for $sOrigAlias (already used). It is now: $sNewAlias</p>\n";
$this->m_aSelectedClasses[$sNewAlias] = $this->GetClass();
unset($this->m_aSelectedClasses[$sOrigAlias]);
$this->m_aClasses[$sNewAlias] = $this->GetClass();
unset($this->m_aClasses[$sOrigAlias]);
// Translate the condition expression with the new alias
$aAliasTranslation[$sOrigAlias]['*'] = $sNewAlias;
}
//echo "<p>Adding the alias ".$this->GetClass()." as ".$this->GetClassAlias()."</p>\n";
// add the alias into the filter aliases list
$aClassAliases[$this->GetClassAlias()] = $this->GetClass();
}
foreach($this->m_aPointingTo as $sExtKeyAttCode=>$aPointingTo)
{
foreach($aPointingTo as $iOperatorCode => $aFilter)
{
foreach($aFilter as $sAlias => $oFilter)
{
$oFilter->AddToNameSpace($aClassAliases, $aAliasTranslation);
}
}
}
foreach($this->m_aReferencedBy as $sForeignClass=>$aReferences)
@@ -355,16 +588,17 @@ class DBObjectSearch
}
}
public function AddCondition_PointingTo(DBObjectSearch $oFilter, $sExtKeyAttCode)
public function AddCondition_PointingTo(DBObjectSearch $oFilter, $sExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS)
{
$aAliasTranslation = array();
$res = $this->AddCondition_PointingTo_InNameSpace($oFilter, $sExtKeyAttCode, $this->m_aClasses, $aAliasTranslation);
$res = $this->AddCondition_PointingTo_InNameSpace($oFilter, $sExtKeyAttCode, $this->m_aClasses, $aAliasTranslation, $iOperatorCode);
$this->TransferConditionExpression($oFilter, $aAliasTranslation);
return $res;
}
protected function AddCondition_PointingTo_InNameSpace(DBObjectSearch $oFilter, $sExtKeyAttCode, &$aClassAliases, &$aAliasTranslation)
protected function AddCondition_PointingTo_InNameSpace(DBObjectSearch $oFilter, $sExtKeyAttCode, &$aClassAliases, &$aAliasTranslation, $iOperatorCode)
{
//echo "<p style=\"color:green\">Calling: AddCondition_PointingTo_InNameSpace([<pre>".print_r($aClassAliases, true)."</pre></br>], [<pre>".print_r($aAliasTranslation, true)."</pre>]);</p>";
if (!MetaModel::IsValidKeyAttCode($this->GetClass(), $sExtKeyAttCode))
{
throw new CoreWarning("The attribute code '$sExtKeyAttCode' is not an external key of the class '{$this->GetClass()}' - the condition will be ignored");
@@ -374,20 +608,41 @@ class DBObjectSearch
{
throw new CoreException("The specified filter (pointing to {$oFilter->GetClass()}) is not compatible with the key '{$this->GetClass()}::$sExtKeyAttCode', which is pointing to {$oAttExtKey->GetTargetClass()}");
}
if(($iOperatorCode != TREE_OPERATOR_EQUALS) && !($oAttExtKey instanceof AttributeHierarchicalKey))
{
throw new CoreException("The specified tree operator $isOperatorCode is not applicable to the key '{$this->GetClass()}::$sExtKeyAttCode', which is not a HierarchicalKey");
}
$bSamePointingTo = false;
if (array_key_exists($sExtKeyAttCode, $this->m_aPointingTo))
{
$this->m_aPointingTo[$sExtKeyAttCode]->MergeWith_InNamespace($oFilter, $aClassAliases, $aAliasTranslation);
if (array_key_exists($iOperatorCode, $this->m_aPointingTo[$sExtKeyAttCode]))
{
if (array_key_exists($oFilter->GetClassAlias(), $this->m_aPointingTo[$sExtKeyAttCode][$iOperatorCode]))
{
//echo "<p style=\"color:red\">[".__LINE__."]this->m_aPointingTo[$sExtKeyAttCode][$iOperatorCode][".$oFilter->GetFirstJoinedClassAlias()."]:<pre>\n".print_r($this->m_aPointingTo[$sExtKeyAttCode][$iOperatorCode], true)."</pre>;</p>";
$bSamePointingTo = true;
}
}
}
//echo "<p style=\"color:red\">[".__LINE__."]Calling: AddToNameSpace([".implode(',', $aClassAliases)."], [".implode(',', $aAliasTranslation)."]);</p>";
if ($bSamePointingTo)
{
//echo "<p style=\"color:red\">[".__LINE__."]AddPointingTo: Merging filters for [$sExtKeyAttCode][$iOperatorCode][".$oFilter->GetClassAlias()."]</p>";
// Same ext key, alias and same operator, merge the filters together
// $sAlias = $oFilter->GetClassAlias();
//echo "<p style=\"color:red\">[".__LINE__."]before: AddToNameSpace(aClassAliases[<pre>\n".print_r($aClassAliases, true)."</pre>], aAliasTranslation[<pre>\n".print_r($aAliasTranslation, true)."</pre>]);</p>";
$oFilter->AddToNamespace($aClassAliases, $aAliasTranslation, true /* Don't translate the main alias */);
//echo "<p style=\"color:blue\">[".__LINE__."]after: AddToNameSpace(aClassAliases[<pre>\n".print_r($aClassAliases, true)."</pre>], aAliasTranslation[<pre>\n".print_r($aAliasTranslation, true)."</pre>]);</p>";
// $this->m_aPointingTo[$sExtKeyAttCode][$iOperatorCode][$sAlias]->MergeWith($oFilter, $aClassAliases, $aAliasTranslation);
$this->m_aPointingTo[$sExtKeyAttCode][$iOperatorCode][$oFilter->GetClassAlias()] = $oFilter;
}
else
{
//echo "<p style=\"color:red\">[".__LINE__."]AddPointingTo: Adding a new PointingTo filter for [$sExtKeyAttCode][$iOperatorCode][".$oFilter->GetClassAlias()."]</p>";
$oFilter->AddToNamespace($aClassAliases, $aAliasTranslation);
// #@# The condition expression found in that filter should not be used - could be another kind of structure like a join spec tree !!!!
// $oNewFilter = clone $oFilter;
// $oNewFilter->ResetCondition();
$this->m_aPointingTo[$sExtKeyAttCode] = $oFilter;
$this->m_aPointingTo[$sExtKeyAttCode][$iOperatorCode][$oFilter->GetClassAlias()] = $oFilter;
}
}
@@ -431,6 +686,7 @@ class DBObjectSearch
public function AddCondition_LinkedTo(DBObjectSearch $oLinkFilter, $sExtKeyAttCodeToMe, $sExtKeyAttCodeTarget, DBObjectSearch $oFilterTarget)
{
$oLinkFilterFinal = clone $oLinkFilter;
// todo : new function prototype
$oLinkFilterFinal->AddCondition_PointingTo($sExtKeyAttCodeToMe);
$this->AddCondition_ReferencedBy($oLinkFilterFinal, $sExtKeyAttCodeToMe);
@@ -463,9 +719,15 @@ class DBObjectSearch
$this->m_aFullText = array_merge($this->m_aFullText, $oFilter->m_aFullText);
$this->m_aRelatedTo = array_merge($this->m_aRelatedTo, $oFilter->m_aRelatedTo);
foreach($oFilter->m_aPointingTo as $sExtKeyAttCode=>$oExtFilter)
foreach($oFilter->m_aPointingTo as $sExtKeyAttCode=>$aPointingTo)
{
$this->AddCondition_PointingTo_InNamespace($oExtFilter, $sExtKeyAttCode, $aClassAliases, $aAliasTranslation);
foreach($aPointingTo as $iOperatorCode => $aFilter)
{
foreach($aFilter as $sAlias => $oExtFilter)
{
$this->AddCondition_PointingTo_InNamespace($oExtFilter, $sExtKeyAttCode, $aClassAliases, $aAliasTranslation, $iOperatorCode);
}
}
}
foreach($oFilter->m_aReferencedBy as $sForeignClass => $aReferences)
{
@@ -484,7 +746,7 @@ class DBObjectSearch
{
return $this->m_aPointingTo;
}
if (!array_key_exists($sKeyAttCode, $this->m_aPointingTo)) return null;
if (!array_key_exists($sKeyAttCode, $this->m_aPointingTo)) return array();
return $this->m_aPointingTo[$sKeyAttCode];
}
public function GetCriteria_ReferencedBy($sRemoteClass = "", $sForeignExtKeyAttCode = "")
@@ -505,20 +767,47 @@ class DBObjectSearch
{
return $this->m_aRelatedTo;
}
public function SetInternalParams($aParams)
{
return $this->m_aParams = $aParams;
}
public function GetInternalParams()
{
return $this->m_aParams;
}
public function GetQueryParams()
{
$aParams = array();
$this->m_oSearchCondition->Render($aParams, true);
return $aParams;
}
public function ListConstantFields()
{
return $this->m_oSearchCondition->ListConstantFields();
}
public function RenderCondition()
{
return $this->m_oSearchCondition->Render($this->m_aParams, false);
}
/**
* Turn the parameters (:xxx) into scalar values in order to easily
* serialize a search
*/
public function ApplyParameters($aArgs)
{
return $this->m_oSearchCondition->ApplyParameters(array_merge($this->m_aParams, $aArgs));
}
public function serialize($bDevelopParams = false, $aContextParams = null)
{
$sOql = $this->ToOql($bDevelopParams, $aContextParams);
return base64_encode(serialize(array($sOql, $this->m_aParams)));
return base64_encode(serialize(array($sOql, $this->m_aParams, $this->m_aModifierProperties)));
}
static public function unserialize($sValue)
@@ -529,7 +818,9 @@ class DBObjectSearch
// We've tried to use gzcompress/gzuncompress, but for some specific queries
// it was not working at all (See Trac #193)
// gzuncompress was issuing a warning "data error" and the return object was null
return self::FromOQL($sOql, $aParams);
$oRetFilter = self::FromOQL($sOql, $aParams);
$oRetFilter->m_aModifierProperties = $aData[2];
return $oRetFilter;
}
// SImple BUt Structured Query Languag - SubuSQL
@@ -650,10 +941,55 @@ class DBObjectSearch
protected function ToOQL_Joins()
{
$sRes = '';
foreach($this->m_aPointingTo as $sExtKey=>$oFilter)
foreach($this->m_aPointingTo as $sExtKey => $aPointingTo)
{
$sRes .= ' JOIN '.$oFilter->GetClass().' AS '.$oFilter->GetClassAlias().' ON '.$this->GetClassAlias().'.'.$sExtKey.' = '.$oFilter->GetClassAlias().'.id';
$sRes .= $oFilter->ToOQL_Joins();
foreach($aPointingTo as $iOperatorCode => $aFilter)
{
foreach($aFilter as $sAlias => $oFilter)
{
switch($iOperatorCode)
{
case TREE_OPERATOR_EQUALS:
$sOperator = ' = ';
break;
case TREE_OPERATOR_BELOW:
$sOperator = ' BELOW ';
break;
case TREE_OPERATOR_BELOW_STRICT:
$sOperator = ' BELOW STRICT ';
break;
case TREE_OPERATOR_NOT_BELOW:
$sOperator = ' NOT BELOW ';
break;
case TREE_OPERATOR_NOT_BELOW_STRICT:
$sOperator = ' NOT BELOW STRICT ';
break;
case TREE_OPERATOR_ABOVE:
$sOperator = ' ABOVE ';
break;
case TREE_OPERATOR_ABOVE_STRICT:
$sOperator = ' ABOVE STRICT ';
break;
case TREE_OPERATOR_NOT_ABOVE:
$sOperator = ' NOT ABOVE ';
break;
case TREE_OPERATOR_NOT_ABOVE_STRICT:
$sOperator = ' NOT ABOVE STRICT ';
break;
}
$sRes .= ' JOIN '.$oFilter->GetClass().' AS '.$oFilter->GetClassAlias().' ON '.$this->GetClassAlias().'.'.$sExtKey.$sOperator.$oFilter->GetClassAlias().'.id';
$sRes .= $oFilter->ToOQL_Joins();
}
}
}
foreach($this->m_aReferencedBy as $sForeignClass=>$aReferences)
{
@@ -866,7 +1202,38 @@ class DBObjectSearch
}
else
{
$aJoinItems[$sFromClass]->AddCondition_PointingTo($aJoinItems[$sToClass], $sExtKeyAttCode);
$sOperator = $oJoinSpec->GetOperator();
switch($sOperator)
{
case '=':
$iOperatorCode = TREE_OPERATOR_EQUALS;
break;
case 'BELOW':
$iOperatorCode = TREE_OPERATOR_BELOW;
break;
case 'BELOW_STRICT':
$iOperatorCode = TREE_OPERATOR_BELOW_STRICT;
break;
case 'NOT_BELOW':
$iOperatorCode = TREE_OPERATOR_NOT_BELOW;
break;
case 'NOT_BELOW_STRICT':
$iOperatorCode = TREE_OPERATOR_NOT_BELOW_STRICT;
break;
case 'ABOVE':
$iOperatorCode = TREE_OPERATOR_ABOVE;
break;
case 'ABOVE_STRICT':
$iOperatorCode = TREE_OPERATOR_ABOVE_STRICT;
break;
case 'NOT_ABOVE':
$iOperatorCode = TREE_OPERATOR_NOT_ABOVE;
break;
case 'NOT_ABOVE_STRICT':
$iOperatorCode = TREE_OPERATOR_NOT_ABOVE_STRICT;
break;
}
$aJoinItems[$sFromClass]->AddCondition_PointingTo($aJoinItems[$sToClass], $sExtKeyAttCode, $iOperatorCode);
}
}
}

View File

@@ -32,6 +32,7 @@
class DBObjectSet
{
private $m_oFilter;
private $m_aAddedIds; // Ids of objects added (discrete lists)
private $m_aOrderBy;
public $m_bLoaded;
private $m_aData;
@@ -41,12 +42,15 @@ class DBObjectSet
public function __construct(DBObjectSearch $oFilter, $aOrderBy = array(), $aArgs = array(), $aExtendedDataSpec = null, $iLimitCount = 0, $iLimitStart = 0)
{
$this->m_oFilter = $oFilter;
$this->m_aAddedIds = array();
$this->m_aOrderBy = $aOrderBy;
$this->m_aArgs = $aArgs;
$this->m_aAttToLoad = null;
$this->m_aExtendedDataSpec = $aExtendedDataSpec;
$this->m_iLimitCount = $iLimitCount;
$this->m_iLimitStart = $iLimitStart;
$this->m_iCount = null; // null if unknown yet
$this->m_bLoaded = false; // true when the filter has been used OR the set is built step by step (AddObject...)
$this->m_aData = array(); // array of (row => array of (classalias) => object/null)
$this->m_aId2Row = array(); // array of (pkey => index in m_aData)
@@ -77,6 +81,46 @@ class DBObjectSet
return $sRet;
}
public function OptimizeColumnLoad($aAttToLoad)
{
if (is_null($aAttToLoad))
{
$this->m_aAttToLoad = null;
}
else
{
// Complete the attribute list with the attribute codes
$aAttToLoadWithAttDef = array();
foreach($aAttToLoad as $sClassAlias => $aAttList)
{
$aSelectedClasses = $this->m_oFilter->GetSelectedClasses();
$sClass = $aSelectedClasses[$sClassAlias];
foreach($aAttList as $sAttToLoad)
{
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttToLoad);
$aAttToLoadWithAttDef[$sClassAlias][$sAttToLoad] = $oAttDef;
if ($oAttDef->IsExternalKey())
{
// Add the external key friendly name anytime
$oFriendlyNameAttDef = MetaModel::GetAttributeDef($sClass, $sAttToLoad.'_friendlyname');
$aAttToLoadWithAttDef[$sClassAlias][$sAttToLoad.'_friendlyname'] = $oFriendlyNameAttDef;
}
}
// Add the friendly name anytime
$oFriendlyNameAttDef = MetaModel::GetAttributeDef($sClass, 'friendlyname');
$aAttToLoadWithAttDef[$sClassAlias]['friendlyname'] = $oFriendlyNameAttDef;
// Make sure that the final class is requested anytime, whatever the specification (needed for object construction!)
if (!MetaModel::IsStandaloneClass($sClass) && !array_key_exists('finalclass', $aAttList))
{
$aAttToLoadWithAttDef[$sClassAlias]['finalclass'] = MetaModel::GetAttributeDef($sClass, 'finalclass');
}
}
$this->m_aAttToLoad = $aAttToLoadWithAttDef;
}
}
static public function FromObject($oObject)
{
$oRetSet = self::FromScratch(get_class($oObject));
@@ -86,7 +130,8 @@ class DBObjectSet
static public function FromScratch($sClass)
{
$oFilter = new CMDBSearchFilter($sClass);
$oFilter = new DBObjectSearch($sClass);
$oFilter->AddConditionExpression(new FalseExpression());
$oRetSet = new self($oFilter);
$oRetSet->m_bLoaded = true; // no DB load
return $oRetSet;
@@ -96,9 +141,7 @@ class DBObjectSet
// input = array of objects
static public function FromArray($sClass, $aObjects)
{
$oFilter = new CMDBSearchFilter($sClass);
$oRetSet = new self($oFilter);
$oRetSet->m_bLoaded = true; // no DB load
$oRetSet = self::FromScratch($sClass);
$oRetSet->AddObjectArray($aObjects, $sClass);
return $oRetSet;
}
@@ -226,8 +269,20 @@ class DBObjectSet
public function GetFilter()
{
// #@# This is false as soon as the set has been manipulated (AddObject...)
return $this->m_oFilter;
if (count($this->m_aAddedIds) == 0)
{
return $this->m_oFilter;
}
else
{
$oFilter = clone $this->m_oFilter;
$oIdListExpr = ListExpression::FromScalars(array_keys($this->m_aAddedIds));
$oIdExpr = new FieldExpression('id', $oFilter->GetClassAlias());
$oIdInList = new BinaryExpression($oIdExpr, 'IN', $oIdListExpr);
$oFilter->MergeConditionExpression($oIdInList);
return $oFilter;
}
}
public function GetClass()
@@ -245,6 +300,11 @@ class DBObjectSet
return MetaModel::GetRootClass($this->GetClass());
}
public function GetArgs()
{
return $this->m_aArgs;
}
public function SetLimit($iLimitCount, $iLimitStart = 0)
{
$this->m_iLimitCount = $iLimitCount;
@@ -269,11 +329,11 @@ class DBObjectSet
if ($this->m_iLimitCount > 0)
{
$sSQL = MetaModel::MakeSelectQuery($this->m_oFilter, $this->m_aOrderBy, $this->m_aArgs, $this->m_aExtendedDataSpec, $this->m_iLimitCount, $this->m_iLimitStart);
$sSQL = MetaModel::MakeSelectQuery($this->m_oFilter, $this->m_aOrderBy, $this->m_aArgs, $this->m_aAttToLoad, $this->m_aExtendedDataSpec, $this->m_iLimitCount, $this->m_iLimitStart);
}
else
{
$sSQL = MetaModel::MakeSelectQuery($this->m_oFilter, $this->m_aOrderBy, $this->m_aArgs, $this->m_aExtendedDataSpec);
$sSQL = MetaModel::MakeSelectQuery($this->m_oFilter, $this->m_aOrderBy, $this->m_aArgs, $this->m_aAttToLoad, $this->m_aExtendedDataSpec);
}
$resQuery = CMDBSource::Query($sSQL);
if (!$resQuery) return;
@@ -290,11 +350,12 @@ class DBObjectSet
}
else
{
$oObject = MetaModel::GetObjectByRow($sClass, $aRow, $sClassAlias, $this->m_aExtendedDataSpec);
$oObject = MetaModel::GetObjectByRow($sClass, $aRow, $sClassAlias, $this->m_aAttToLoad, $this->m_aExtendedDataSpec);
}
$aObjects[$sClassAlias] = $oObject;
}
$this->AddObjectExtended($aObjects);
$this->AddObjectExtended($aObjects, true /* internal load */);
}
CMDBSource::FreeResult($resQuery);
}
@@ -307,13 +368,17 @@ class DBObjectSet
}
else
{
$sSQL = MetaModel::MakeSelectQuery($this->m_oFilter, $this->m_aOrderBy, $this->m_aArgs, null, 0, 0, true);
$resQuery = CMDBSource::Query($sSQL);
if (!$resQuery) return 0;
$aRow = CMDBSource::FetchArray($resQuery);
CMDBSource::FreeResult($resQuery);
return $aRow['COUNT'];
if (is_null($this->m_iCount))
{
$sSQL = MetaModel::MakeSelectQuery($this->m_oFilter, $this->m_aOrderBy, $this->m_aArgs, null, null, 0, 0, true);
$resQuery = CMDBSource::Query($sSQL);
if (!$resQuery) return 0;
$aRow = CMDBSource::FetchArray($resQuery);
CMDBSource::FreeResult($resQuery);
$this->m_iCount = $aRow['COUNT'];
}
return $this->m_iCount;
}
}
@@ -353,7 +418,10 @@ class DBObjectSet
public function Rewind()
{
$this->Seek(0);
if ($this->m_bLoaded)
{
$this->Seek(0);
}
}
public function Seek($iRow)
@@ -378,10 +446,11 @@ class DBObjectSet
if (!is_null($oObject))
{
$this->m_aId2Row[$sClassAlias][$oObject->GetKey()] = $iNextPos;
$this->m_aAddedIds[$oObject->GetKey()] = true;
}
}
protected function AddObjectExtended($aObjectArray)
protected function AddObjectExtended($aObjectArray, $bInternalLoad = false)
{
if (!$this->m_bLoaded) $this->Load();
@@ -393,6 +462,10 @@ class DBObjectSet
if (!is_null($oObject))
{
$this->m_aId2Row[$sClassAlias][$oObject->GetKey()] = $iNextPos;
if (!$bInternalLoad)
{
$this->m_aAddedIds[$oObject->GetKey()] = true;
}
}
}
}
@@ -614,6 +687,56 @@ class DBObjectSet
$this->Rewind();
return $oCommonObj;
}
/**
* List the constant fields (and their value) in the given query
* @return Hash [Alias][AttCode] => value
*/
public function ListConstantFields()
{
$aScalarArgs = $this->ExpandArgs();
$aConst = $this->m_oFilter->ListConstantFields();
foreach($aConst as $sClassAlias => $aVals)
{
foreach($aVals as $sCode => $oExpr)
{
if ($oExpr instanceof ScalarExpression)
{
$aConst[$sClassAlias][$sCode] = $oExpr->GetValue();
}
else //Variable
{
$aConst[$sClassAlias][$sCode] = $aScalarArgs[$oExpr->GetName()];
}
}
}
return $aConst;
}
protected function ExpandArgs()
{
$aScalarArgs = $this->m_oFilter->GetInternalParams();
foreach($this->m_aArgs as $sArgName => $value)
{
if (MetaModel::IsValidObject($value))
{
$aScalarArgs = array_merge($aScalarArgs, $value->ToArgs($sArgName));
}
else
{
$aScalarArgs[$sArgName] = (string) $value;
}
}
$aScalarArgs['current_contact_id'] = UserRights::GetContactId();
return $aScalarArgs;
}
public function ApplyParameters()
{
$aScalarArgs = $this->ExpandArgs();
$this->m_oFilter->ApplyParameters($aScalarArgs);
}
}
/**

View File

@@ -109,11 +109,11 @@ class DeletionPlan
{
$this->m_iToUpdate++;
$oObject = $aData['to_reset'];
$oObject = $aData['to_reset'];
$aExtKeyLabels = array();
foreach ($aData['attributes'] as $sRemoteExtKey => $aRemoteAttDef)
{
$oObject->Set($sRemoteExtKey, 0);
$oObject->Set($sRemoteExtKey, $aData['values'][$sRemoteExtKey]);
$aExtKeyLabels[] = $aRemoteAttDef->GetLabel();
}
$this->m_aToUpdate[$sClass][$iId]['attributes_list'] = implode(', ', $aExtKeyLabels);
@@ -264,7 +264,7 @@ class DeletionPlan
}
}
public function AddToUpdate($oObject, $oAttDef)
public function AddToUpdate($oObject, $oAttDef, $value = 0)
{
$sClass = get_class($oObject);
$iId = $oObject->GetKey();
@@ -281,6 +281,7 @@ class DeletionPlan
);
}
$this->m_aToUpdate[$sClass][$iId]['attributes'][$oAttDef->GetCode()] = $oAttDef;
$this->m_aToUpdate[$sClass][$iId]['values'][$oAttDef->GetCode()] = $value;
}
}
}

View File

@@ -97,7 +97,7 @@ class Dict
}
public static function GetCurrentLanguage()
public static function GetUserLanguage()
{
if (self::$m_sCurrentLanguage == null) // May happen when no user is logged in (i.e login screen, non authentifed page)
{
@@ -124,12 +124,12 @@ class Dict
{
// Attempt to find the string in the user language
//
if (!array_key_exists(self::GetCurrentLanguage(), self::$m_aData))
if (!array_key_exists(self::GetUserLanguage(), self::$m_aData))
{
// It may happen, when something happens before the dictionnaries get loaded
return $sStringCode;
}
$aCurrentDictionary = self::$m_aData[self::GetCurrentLanguage()];
$aCurrentDictionary = self::$m_aData[self::GetUserLanguage()];
if (array_key_exists($sStringCode, $aCurrentDictionary))
{
return $aCurrentDictionary[$sStringCode];
@@ -220,6 +220,20 @@ class Dict
}
}
/**
* Clone a string in every language (if it exists in that language)
*/
public static function CloneString($sSourceCode, $sDestCode)
{
foreach(self::$m_aLanguages as $sLanguageCode => $foo)
{
if (isset(self::$m_aData[$sLanguageCode][$sSourceCode]))
{
self::$m_aData[$sLanguageCode][$sDestCode] = self::$m_aData[$sLanguageCode][$sSourceCode];
}
}
}
public static function MakeStats($sLanguageCode, $sLanguageRef = 'EN US')
{
$aMissing = array(); // Strings missing for the target language

View File

@@ -35,6 +35,7 @@ class EMail
protected $m_sSubject;
protected $m_sTo;
protected $m_aHeaders; // array of key=>value
protected $m_aAttachments;
public function __construct($sTo = '', $sSubject = '', $sBody = '', $aHeaders = array())
{
@@ -42,6 +43,7 @@ class EMail
$this->m_sSubject = $sSubject;
$this->m_sBody = $sBody;
$this->m_aHeaders = $aHeaders;
$this->m_aAttachments = array();
}
// Errors management : not that simple because we need that function to be
@@ -73,8 +75,14 @@ class EMail
{
$sHeaders = 'MIME-Version: 1.0' . "\r\n";
// ! the case is important for MS-Outlook
$sHeaders .= 'Content-Type: text/html; charset=UTF-8' . "\r\n";
$sHeaders .= 'Content-Transfer-Encoding: 8bit' . "\r\n";
if (!array_key_exists('Content-Type', $this->m_aHeaders))
{
$sHeaders .= 'Content-Type: text/html; charset=UTF-8' . "\r\n";
}
if (!array_key_exists('Content-Transfer-Encoding', $this->m_aHeaders))
{
$sHeaders .= 'Content-Transfer-Encoding: 8bit' . "\r\n";
}
foreach ($this->m_aHeaders as $sKey => $sValue)
{
$sHeaders .= "$sKey: $sValue\r\n";
@@ -86,8 +94,8 @@ class EMail
set_error_handler(array($this, 'mail_error_handler'));
$bRes = mail
(
$this->m_sTo,
$this->m_sSubject,
str_replace(array("\n", "\r"), ' ', $this->m_sTo), // Prevent header injection
$this->EncodeHeaderField($this->m_sSubject), // Prevent header injection & MIME Encode charsets
$this->m_sBody,
$sHeaders
);
@@ -110,6 +118,7 @@ class EMail
public function Send(&$aIssues, $bForceSynchronous = false, $oLog = null)
{
$this->BuildMessage(); // assemble the attachments into the header/body structure
if ($bForceSynchronous)
{
return $this->SendSynchronous($aIssues, $oLog);
@@ -128,13 +137,18 @@ class EMail
}
}
protected function AddToHeader($sKey, $sValue)
public function AddToHeader($sKey, $sValue)
{
if (strlen($sValue) > 0)
{
$this->m_aHeaders[$sKey] = $sValue;
}
}
public function SetMessageId($sId)
{
$this->AddToHeader('Message-ID', $sId);
}
public function SetReferences($sReferences)
{
@@ -181,6 +195,48 @@ class EMail
$this->AddToHeader('Reply-To', $sAddress);
}
public function AddAttachment($data, $sFileName, $sMimeType)
{
$this->m_aAttachments[] = array('data' => $data, 'filename' => $sFileName, 'mimeType' => $sMimeType);
}
/**
* Takes care of the attachments (if any) to build the header/body of the message before storing or sending it
*/
protected function BuildMessage()
{
if (count($this->m_aAttachments) == 0) return; // Nothing to do if there are no attachments
$sDelimiter = '== iTopEmailPart---'.md5(date('r', time()))." ==";
$sContentType = isset($this->m_aHeaders['Content-Type']) ? $this->m_aHeaders['Content-Type'] : 'text/html; charset="UTF-8"';
$sContentHeader = "Content-Type: $sContentType\r\n";
$this->m_aHeaders['Content-Type'] = "multipart/mixed; boundary=\"{$sDelimiter}\"";
$aAttachments = array();
foreach($this->m_aAttachments as $aAttach)
{
$sAttachmentHeader = "Content-Type: {$aAttach['mimeType']};\r\n Name=\"{$aAttach['filename']}\"\r\n";
$sAttachmentHeader .= "Content-Transfer-Encoding: base64\r\nContent-Disposition: attachment;\r\n filename=\"{$aAttach['filename']}\"\r\n";
$sAttachmentHeader .= "\r\n";
$sAttachment = chunk_split(base64_encode($aAttach['data']));
$aAttachments[] = $sAttachmentHeader.$sAttachment."\r\n";
}
$this->m_sBody = "This is a multi-part message in MIME format.\r\n--".$sDelimiter."\r\n".$sContentHeader."\r\n".$this->m_sBody."\r\n--".$sDelimiter."\r\n";
$this->m_sBody .= implode("--".$sDelimiter."\r\n", $aAttachments);
$this->m_sBody .= "--".$sDelimiter."--";
}
/**
* MIME encode the content of a header field according to RFC2047
* @param string $sFieldContent the content of the header to encode
* @return string The encoded string
*/
protected function EncodeHeaderField($sFieldContent)
{
$sTemp = str_replace(array("\n", "\r"), ' ', $sFieldContent);
$sTemp = iconv_mime_encode('Tagada', $sTemp, array('scheme' => 'Q', 'input-charset' => 'UTF-8', 'output-charset' => 'UTF-8'));
return preg_replace('/^Tagada: /', '', $sTemp);
}
}
?>

View File

@@ -90,7 +90,7 @@ class Event extends DBObject implements iDisplay
public static function GetUIPage()
{
return '../pages/UI.php';
return 'UI.php';
}
function DisplayDetails(WebPage $oPage, $bEditMode = false)
@@ -103,7 +103,7 @@ class Event extends DBObject implements iDisplay
$this->DisplayBareProperties($oPage, $bEditMode);
}
function DisplayBareProperties(WebPage $oPage, $bEditMode = false)
function DisplayBareProperties(WebPage $oPage, $bEditMode = false, $sPrefix = '', $aExtraParams = array())
{
if ($bEditMode) return; // Not editable
@@ -174,7 +174,7 @@ class EventNotificationEmail extends EventNotification
MetaModel::Init_AddAttribute(new AttributeText("bcc", array("allowed_values"=>null, "sql"=>"bcc", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("from", array("allowed_values"=>null, "sql"=>"from", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("subject", array("allowed_values"=>null, "sql"=>"subject", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("body", array("allowed_values"=>null, "sql"=>"body", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeHTML("body", array("allowed_values"=>null, "sql"=>"body", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
// Display lists
MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'message', 'trigger_id', 'action_id', 'object_id', 'to', 'cc', 'bcc', 'from', 'subject', 'body')); // Attributes to be displayed for the complete details
@@ -340,14 +340,22 @@ class EventLoginUsage extends Event
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeExternalKey("user_id", array("targetclass"=>"User", "jointype"=> "", "allowed_values"=>null, "sql"=>"user_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeExternalField("contact_name", array("allowed_values"=>null, "extkey_attcode"=>"user_id", "target_attcode"=>"contactid", "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeExternalField("contact_email", array("allowed_values"=>null, "extkey_attcode"=>"user_id", "target_attcode"=>"email", "is_null_allowed"=>true, "depends_on"=>array())));
$aZList = array('date', 'user_id');
if (MetaModel::IsValidAttCode('Contact', 'name'))
{
MetaModel::Init_AddAttribute(new AttributeExternalField("contact_name", array("allowed_values"=>null, "extkey_attcode"=>"user_id", "target_attcode"=>"contactid", "is_null_allowed"=>true, "depends_on"=>array())));
$aZList[] = 'contact_name';
}
if (MetaModel::IsValidAttCode('Contact', 'email'))
{
MetaModel::Init_AddAttribute(new AttributeExternalField("contact_email", array("allowed_values"=>null, "extkey_attcode"=>"user_id", "target_attcode"=>"email", "is_null_allowed"=>true, "depends_on"=>array())));
$aZList[] = 'contact_email';
}
// Display lists
MetaModel::Init_SetZListItems('details', array('date', 'user_id', 'contact_name', 'contact_email', 'userinfo', 'message')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('date', 'user_id', 'contact_name', 'contact_email', 'userinfo')); // Attributes to be displayed for a list
MetaModel::Init_SetZListItems('details', array_merge($aZList, array('userinfo', 'message'))); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array_merge($aZList, array('userinfo'))); // Attributes to be displayed for a list
// Search criteria
MetaModel::Init_SetZListItems('standard_search', array('date', 'user_id', 'contact_name', 'contact_email')); // Criteria of the std search form
MetaModel::Init_SetZListItems('standard_search', $aZList); // Criteria of the std search form
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
}
}

View File

@@ -36,11 +36,16 @@ abstract class Expression
// recursive rendering (aArgs used as input by default, or used as output if bRetrofitParams set to True
abstract public function Render(&$aArgs = null, $bRetrofitParams = false);
abstract public function ApplyParameters($aArgs);
// recursively builds an array of class => fieldname
abstract public function ListRequiredFields();
abstract public function IsTrue();
// recursively builds an array of [classAlias][fieldName] => value
abstract public function ListConstantFields();
public function RequiresField($sClass, $sFieldName)
{
// #@# todo - optimize : this is called quite often when building a single query !
@@ -84,6 +89,8 @@ abstract class Expression
{
return new BinaryExpression($this, 'OR', $oExpr);
}
abstract public function RenameParam($sOldName, $sNewName);
}
class SQLExpression extends Expression
@@ -106,6 +113,10 @@ class SQLExpression extends Expression
return $this->m_sSQL;
}
public function ApplyParameters($aArgs)
{
}
public function GetUnresolvedFields($sAlias, &$aUnresolved)
{
}
@@ -119,6 +130,16 @@ class SQLExpression extends Expression
{
return array();
}
public function ListConstantFields()
{
return array();
}
public function RenameParam($sOldName, $sNewName)
{
// Do nothing, since there is nothing to rename
}
}
@@ -157,11 +178,11 @@ class BinaryExpression extends Expression
// return true if we are certain that it will be true
if ($this->m_sOperator == 'AND')
{
if ($this->m_oLeftExpr->IsTrue() && $this->m_oLeftExpr->IsTrue()) return true;
if ($this->m_oLeftExpr->IsTrue() && $this->m_oRightExpr->IsTrue()) return true;
}
return false;
}
public function GetLeftExpr()
{
return $this->m_oLeftExpr;
@@ -185,7 +206,27 @@ class BinaryExpression extends Expression
$sRight = $this->GetRightExpr()->Render($aArgs, $bRetrofitParams);
return "($sLeft $sOperator $sRight)";
}
public function ApplyParameters($aArgs)
{
if ($this->m_oLeftExpr instanceof VariableExpression)
{
$this->m_oLeftExpr = $this->m_oLeftExpr->GetAsScalar($aArgs);
}
else //if ($this->m_oLeftExpr instanceof Expression)
{
$this->m_oLeftExpr->ApplyParameters($aArgs);
}
if ($this->m_oRightExpr instanceof VariableExpression)
{
$this->m_oRightExpr = $this->m_oRightExpr->GetAsScalar($aArgs);
}
else //if ($this->m_oRightExpr instanceof Expression)
{
$this->m_oRightExpr->ApplyParameters($aArgs);
}
}
public function GetUnresolvedFields($sAlias, &$aUnresolved)
{
$this->GetLeftExpr()->GetUnresolvedFields($sAlias, $aUnresolved);
@@ -205,6 +246,50 @@ class BinaryExpression extends Expression
$aRight = $this->GetRightExpr()->ListRequiredFields();
return array_merge($aLeft, $aRight);
}
/**
* List all constant expression of the form <field> = <scalar> or <field> = :<variable>
* Could be extended to support <field> = <function><constant_expression>
*/
public function ListConstantFields()
{
$aResult = array();
if ($this->m_sOperator == '=')
{
if (($this->m_oLeftExpr instanceof FieldExpression) && ($this->m_oRightExpr instanceof ScalarExpression))
{
$aResult[$this->m_oLeftExpr->GetParent()][$this->m_oLeftExpr->GetName()] = $this->m_oRightExpr;
}
else if (($this->m_oRightExpr instanceof FieldExpression) && ($this->m_oLeftExpr instanceof ScalarExpression))
{
$aResult[$this->m_oRightExpr->GetParent()][$this->m_oRightExpr->GetName()] = $this->m_oLeftExpr;
}
else if (($this->m_oLeftExpr instanceof FieldExpression) && ($this->m_oRightExpr instanceof VariableExpression))
{
$aResult[$this->m_oLeftExpr->GetParent()][$this->m_oLeftExpr->GetName()] = $this->m_oRightExpr;
}
else if (($this->m_oRightExpr instanceof FieldExpression) && ($this->m_oLeftExpr instanceof VariableExpression))
{
$aResult[$this->m_oRightExpr->GetParent()][$this->m_oRightExpr->GetName()] = $this->m_oLeftExpr;
}
else
{
$aResult = array_merge($this->m_oRightExpr->ListConstantFields(), $this->m_oLeftExpr->ListConstantFields()) ;
}
}
else
{
$aResult = array_merge($this->m_oRightExpr->ListConstantFields(), $this->m_oLeftExpr->ListConstantFields()) ;
}
return $aResult;
}
public function RenameParam($sOldName, $sNewName)
{
$this->GetLeftExpr()->RenameParam($sOldName, $sNewName);
$this->GetRightExpr()->RenameParam($sOldName, $sNewName);
}
}
@@ -231,18 +316,13 @@ class UnaryExpression extends Expression
// recursive rendering
public function Render(&$aArgs = null, $bRetrofitParams = false)
{
if ($bRetrofitParams)
{
$iParamIndex = count($aArgs) + 1; // 1-based indexation
$aArgs['param'.$iParamIndex] = $this->m_value;
return ':param'.$iParamIndex;
}
else
{
return CMDBSource::Quote($this->m_value);
}
return CMDBSource::Quote($this->m_value);
}
public function ApplyParameters($aArgs)
{
}
public function GetUnresolvedFields($sAlias, &$aUnresolved)
{
}
@@ -256,6 +336,17 @@ class UnaryExpression extends Expression
{
return array();
}
public function ListConstantFields()
{
return array();
}
public function RenameParam($sOldName, $sNewName)
{
// Do nothing
// really ? what about :param{$iParamIndex} ??
}
}
class ScalarExpression extends UnaryExpression
@@ -421,7 +512,7 @@ class VariableExpression extends UnaryExpression
}
elseif ($bRetrofitParams)
{
//$aArgs[$this->m_sName] = null;
$aArgs[$this->m_sName] = null;
return ':'.$this->m_sName;
}
else
@@ -429,6 +520,28 @@ class VariableExpression extends UnaryExpression
throw new MissingQueryArgument('Missing query argument', array('expecting'=>$this->m_sName, 'available'=>$aArgs));
}
}
public function RenameParam($sOldName, $sNewName)
{
if ($this->m_sName == $sOldName)
{
$this->m_sName = $sNewName;
}
}
public function GetAsScalar($aArgs)
{
$value = '';
if (array_key_exists($this->m_sName, $aArgs))
{
$value = $aArgs[$this->m_sName];
}
else
{
throw new MissingQueryArgument('Missing query argument', array('expecting'=>$this->m_sName, 'available'=>array_keys($aArgs)));
}
return new ScalarExpression($value);
}
}
// Temporary, until we implement functions and expression casting!
@@ -474,6 +587,22 @@ class ListExpression extends Expression
return '('.implode(', ', $aRes).')';
}
public function ApplyParameters($aArgs)
{
$aRes = array();
foreach ($this->m_aExpressions as $idx => $oExpr)
{
if ($oExpr instanceof VariableExpression)
{
$this->m_aExpressions[$idx] = $oExpr->GetAsScalar();
}
else
{
$oExpr->ApplyParameters($aArgs);
}
}
}
public function GetUnresolvedFields($sAlias, &$aUnresolved)
{
foreach ($this->m_aExpressions as $oExpr)
@@ -501,6 +630,25 @@ class ListExpression extends Expression
}
return $aRes;
}
public function ListConstantFields()
{
$aRes = array();
foreach ($this->m_aExpressions as $oExpr)
{
$aRes = array_merge($aRes, $oExpr->ListConstantFields());
}
return $aRes;
}
public function RenameParam($sOldName, $sNewName)
{
$aRes = array();
foreach ($this->m_aExpressions as $key => $oExpr)
{
$this->m_aExpressions[$key] = $oExpr->RenameParam($sOldName, $sNewName);
}
}
}
@@ -542,6 +690,22 @@ class FunctionExpression extends Expression
return $this->m_sVerb.'('.implode(', ', $aRes).')';
}
public function ApplyParameters($aArgs)
{
$aRes = array();
foreach ($this->m_aArgs as $idx => $oExpr)
{
if ($oExpr instanceof VariableExpression)
{
$this->m_aArgs[$idx] = $oExpr->GetAsScalar($aArgs);
}
else
{
$oExpr->ApplyParameters($aArgs);
}
}
}
public function GetUnresolvedFields($sAlias, &$aUnresolved)
{
foreach ($this->m_aArgs as $oExpr)
@@ -569,6 +733,24 @@ class FunctionExpression extends Expression
}
return $aRes;
}
public function ListConstantFields()
{
$aRes = array();
foreach ($this->m_aArgs as $oExpr)
{
$aRes = array_merge($aRes, $oExpr->ListConstantFields());
}
return $aRes;
}
public function RenameParam($sOldName, $sNewName)
{
foreach ($this->m_aArgs as $key => $oExpr)
{
$this->m_aArgs[$key] = $oExpr->RenameParam($sOldName, $sNewName);
}
}
}
class IntervalExpression extends Expression
@@ -604,6 +786,18 @@ class IntervalExpression extends Expression
return 'INTERVAL '.$this->m_oValue->Render($aArgs, $bRetrofitParams).' '.$this->m_sUnit;
}
public function ApplyParameters($aArgs)
{
if ($this->m_oValue instanceof VariableExpression)
{
$this->m_oValue = $this->m_oValue->GetAsScalar($aArgs);
}
else
{
$this->m_oValue->ApplyParameters($aArgs);
}
}
public function GetUnresolvedFields($sAlias, &$aUnresolved)
{
$this->m_oValue->GetUnresolvedFields($sAlias, $aUnresolved);
@@ -618,6 +812,16 @@ class IntervalExpression extends Expression
{
return array();
}
public function ListConstantFields()
{
return array();
}
public function RenameParam($sOldName, $sNewName)
{
$this->m_oValue->RenameParam($sOldName, $sNewName);
}
}
class CharConcatExpression extends Expression
@@ -653,6 +857,22 @@ class CharConcatExpression extends Expression
return "CAST(CONCAT(".implode(', ', $aRes).") AS CHAR)";
}
public function ApplyParameters($aArgs)
{
$aRes = array();
foreach ($this->m_aExpressions as $idx => $oExpr)
{
if ($oExpr instanceof VariableExpression)
{
$this->m_aExpressions[$idx] = $oExpr->GetAsScalar();
}
else
{
$this->m_aExpressions->ApplyParameters($aArgs);
}
}
}
public function GetUnresolvedFields($sAlias, &$aUnresolved)
{
foreach ($this->m_aExpressions as $oExpr)
@@ -680,6 +900,24 @@ class CharConcatExpression extends Expression
}
return $aRes;
}
public function ListConstantFields()
{
$aRes = array();
foreach ($this->m_aExpressions as $oExpr)
{
$aRes = array_merge($aRes, $oExpr->ListConstantFields());
}
return $aRes;
}
public function RenameParam($sOldName, $sNewName)
{
foreach ($this->m_aExpressions as $key => $oExpr)
{
$this->m_aExpressions[$key] = $oExpr->RenameParam($sOldName, $sNewName);
}
}
}
@@ -715,10 +953,10 @@ class QueryBuilderExpressions
protected $m_aSelectExpr;
protected $m_aJoinFields;
public function __construct($aSelect, $oCondition)
public function __construct($oCondition)
{
$this->m_oConditionExpr = $oCondition;
$this->m_aSelectExpr = $aSelect;
$this->m_aSelectExpr = array();
$this->m_aJoinFields = array();
}
@@ -778,6 +1016,19 @@ class QueryBuilderExpressions
$this->m_aJoinFields[$index] = $oExpression->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved);
}
}
public function RenameParam($sOldName, $sNewName)
{
$this->m_oConditionExpr->RenameParam($sOldName, $sNewName);
foreach($this->m_aSelectExpr as $sColAlias => $oExpr)
{
$this->m_aSelectExpr[$sColAlias] = $oExpr->RenameParam($sOldName, $sNewName);
}
foreach($this->m_aJoinFields as $index => $oExpression)
{
$this->m_aJoinFields[$index] = $oExpression->RenameParam($sOldName, $sNewName);
}
}
}
?>

View File

@@ -50,6 +50,7 @@ abstract class FilterDefinition
$this->ConsistencyCheck();
}
// Left here for backward compatibility, deprecated in 2.0
public function OverloadParams($aParams)
{
foreach ($aParams as $sParam => $value)

View File

@@ -191,6 +191,16 @@ class ExecutionKPI
return $output[1] * 1024;
}
}
static public function memory_get_peak_usage($bRealUsage = false)
{
if (function_exists('memory_get_peak_usage'))
{
return memory_get_peak_usage($bRealUsage);
}
// PHP > 5.2.1 - this verb depends on a compilation option
return 0;
}
}
class ApplicationStartupKPI extends ExecutionKPI

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,31 @@
<?php
// Copyright (C) 2010 Combodo SARL
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; version 3 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
/**
* Any extension to hook the initialization of the metamodel
*
* @author Erwan Taloc <erwan.taloc@combodo.com>
* @author Romain Quetiez <romain.quetiez@combodo.com>
* @author Denis Flaven <denis.flaven@combodo.com>
* @license http://www.opensource.org/licenses/gpl-3.0.html LGPL
*/
interface iOnClassInitialization
{
public function OnAfterClassInitialization($sClass);
}
?>

4
core/oql/build.bash Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/bash
php /usr/share/php/PHP/LexerGenerator/cli.php oql-lexer.plex
php /usr/share/php/PHP/ParserGenerator/cli.php oql-parser.y

View File

@@ -3,5 +3,6 @@
# since they are used solely for constructing other files during the build process
#
build.cmd
build.bash
oql-lexer.plex
oql-parser.y
oql-parser.y

View File

@@ -104,155 +104,163 @@ class OQLLexerRaw
if ($this->count >= strlen($this->data)) {
return false; // end of input
}
do {
$rules = array(
'/^[ \t\n\r]+/',
'/^SELECT/',
'/^FROM/',
'/^AS/',
'/^WHERE/',
'/^JOIN/',
'/^ON/',
'/^\//',
'/^\\*/',
'/^\\+/',
'/^-/',
'/^AND/',
'/^OR/',
'/^,/',
'/^\\(/',
'/^\\)/',
'/^REGEXP/',
'/^=/',
'/^!=/',
'/^>/',
'/^</',
'/^>=/',
'/^<=/',
'/^LIKE/',
'/^NOT LIKE/',
'/^IN/',
'/^NOT IN/',
'/^INTERVAL/',
'/^IF/',
'/^ELT/',
'/^COALESCE/',
'/^ISNULL/',
'/^CONCAT/',
'/^SUBSTR/',
'/^TRIM/',
'/^DATE/',
'/^DATE_FORMAT/',
'/^CURRENT_DATE/',
'/^NOW/',
'/^TIME/',
'/^TO_DAYS/',
'/^FROM_DAYS/',
'/^YEAR/',
'/^MONTH/',
'/^DAY/',
'/^HOUR/',
'/^MINUTE/',
'/^SECOND/',
'/^DATE_ADD/',
'/^DATE_SUB/',
'/^ROUND/',
'/^FLOOR/',
'/^INET_ATON/',
'/^INET_NTOA/',
'/^[0-9]+|0x[0-9a-fA-F]+/',
'/^\"([^\\\\\"]|\\\\\"|\\\\\\\\)*\"|'.chr(94).chr(39).'([^\\\\'.chr(39).']|\\\\'.chr(39).'|\\\\\\\\)*'.chr(39).'/',
'/^([_a-zA-Z][_a-zA-Z0-9]*|`[^`]+`)/',
'/^:([_a-zA-Z][_a-zA-Z0-9]*->[_a-zA-Z][_a-zA-Z0-9]*|[_a-zA-Z][_a-zA-Z0-9]*)/',
'/^\\./',
);
$match = false;
foreach ($rules as $index => $rule) {
if (preg_match($rule, substr($this->data, $this->count), $yymatches)) {
if ($match) {
if (strlen($yymatches[0]) > strlen($match[0][0])) {
$match = array($yymatches, $index); // matches, token
}
} else {
$match = array($yymatches, $index);
}
}
}
if (!$match) {
throw new Exception('Unexpected input at line' . $this->line .
': ' . $this->data[$this->count]);
}
$this->token = $match[1];
$this->value = $match[0][0];
$yysubmatches = $match[0];
array_shift($yysubmatches);
if (!$yysubmatches) {
$yysubmatches = array();
}
$r = $this->{'yy_r1_' . $this->token}($yysubmatches);
if ($r === null) {
$this->count += strlen($this->value);
$this->line += substr_count($this->value, "\n");
// accept this token
return true;
} elseif ($r === true) {
// we have changed state
// process this token in the new state
return $this->yylex();
} elseif ($r === false) {
$this->count += strlen($this->value);
$this->line += substr_count($this->value, "\n");
if ($this->count >= strlen($this->data)) {
return false; // end of input
}
// skip this token
continue;
} else {
$yy_yymore_patterns = array_slice($rules, $this->token, true);
// yymore is needed
do {
if (!isset($yy_yymore_patterns[$this->token])) {
throw new Exception('cannot do yymore for the last token');
}
$match = false;
foreach ($yy_yymore_patterns[$this->token] as $index => $rule) {
if (preg_match('/' . $rule . '/',
substr($this->data, $this->count), $yymatches)) {
$yymatches = array_filter($yymatches, 'strlen'); // remove empty sub-patterns
if ($match) {
if (strlen($yymatches[0]) > strlen($match[0][0])) {
$match = array($yymatches, $index); // matches, token
}
} else {
$match = array($yymatches, $index);
}
}
}
if (!$match) {
throw new Exception('Unexpected input at line' . $this->line .
': ' . $this->data[$this->count]);
}
$this->token = $match[1];
$this->value = $match[0][0];
$yysubmatches = $match[0];
array_shift($yysubmatches);
if (!$yysubmatches) {
$yysubmatches = array();
}
$this->line = substr_count($this->value, "\n");
$r = $this->{'yy_r1_' . $this->token}();
} while ($r !== null || !$r);
if ($r === true) {
// we have changed state
// process this token in the new state
return $this->yylex();
} else {
// accept
$this->count += strlen($this->value);
$this->line += substr_count($this->value, "\n");
return true;
}
}
do {
$rules = array(
'/\G[ \t\n\r]+/ ',
'/\GSELECT/ ',
'/\GFROM/ ',
'/\GAS/ ',
'/\GWHERE/ ',
'/\GJOIN/ ',
'/\GON/ ',
'/\G\// ',
'/\G\\*/ ',
'/\G\\+/ ',
'/\G-/ ',
'/\GAND/ ',
'/\GOR/ ',
'/\G,/ ',
'/\G\\(/ ',
'/\G\\)/ ',
'/\GREGEXP/ ',
'/\G=/ ',
'/\G!=/ ',
'/\G>/ ',
'/\G</ ',
'/\G>=/ ',
'/\G<=/ ',
'/\GLIKE/ ',
'/\GNOT LIKE/ ',
'/\GIN/ ',
'/\GNOT IN/ ',
'/\GINTERVAL/ ',
'/\GIF/ ',
'/\GELT/ ',
'/\GCOALESCE/ ',
'/\GISNULL/ ',
'/\GCONCAT/ ',
'/\GSUBSTR/ ',
'/\GTRIM/ ',
'/\GDATE/ ',
'/\GDATE_FORMAT/ ',
'/\GCURRENT_DATE/ ',
'/\GNOW/ ',
'/\GTIME/ ',
'/\GTO_DAYS/ ',
'/\GFROM_DAYS/ ',
'/\GYEAR/ ',
'/\GMONTH/ ',
'/\GDAY/ ',
'/\GHOUR/ ',
'/\GMINUTE/ ',
'/\GSECOND/ ',
'/\GDATE_ADD/ ',
'/\GDATE_SUB/ ',
'/\GROUND/ ',
'/\GFLOOR/ ',
'/\GINET_ATON/ ',
'/\GINET_NTOA/ ',
'/\GBELOW/ ',
'/\GBELOW STRICT/ ',
'/\GNOT BELOW/ ',
'/\GNOT BELOW STRICT/ ',
'/\GABOVE/ ',
'/\GABOVE STRICT/ ',
'/\GNOT ABOVE/ ',
'/\GNOT ABOVE STRICT/ ',
'/\G(0x[0-9a-fA-F]+|[0-9]+)/ ',
'/\G\"([^\\\\\"]|\\\\\"|\\\\\\\\)*\"|'.chr(94).chr(39).'([^\\\\'.chr(39).']|\\\\'.chr(39).'|\\\\\\\\)*'.chr(39).'/ ',
'/\G([_a-zA-Z][_a-zA-Z0-9]*|`[^`]+`)/ ',
'/\G:([_a-zA-Z][_a-zA-Z0-9]*->[_a-zA-Z][_a-zA-Z0-9]*|[_a-zA-Z][_a-zA-Z0-9]*)/ ',
'/\G\\./ ',
);
$match = false;
foreach ($rules as $index => $rule) {
if (preg_match($rule, substr($this->data, $this->count), $yymatches)) {
if ($match) {
if (strlen($yymatches[0]) > strlen($match[0][0])) {
$match = array($yymatches, $index); // matches, token
}
} else {
$match = array($yymatches, $index);
}
}
}
if (!$match) {
throw new Exception('Unexpected input at line ' . $this->line .
': ' . $this->data[$this->count]);
}
$this->token = $match[1];
$this->value = $match[0][0];
$yysubmatches = $match[0];
array_shift($yysubmatches);
if (!$yysubmatches) {
$yysubmatches = array();
}
$r = $this->{'yy_r1_' . $this->token}($yysubmatches);
if ($r === null) {
$this->count += strlen($this->value);
$this->line += substr_count($this->value, "\n");
// accept this token
return true;
} elseif ($r === true) {
// we have changed state
// process this token in the new state
return $this->yylex();
} elseif ($r === false) {
$this->count += strlen($this->value);
$this->line += substr_count($this->value, "\n");
if ($this->count >= strlen($this->data)) {
return false; // end of input
}
// skip this token
continue;
} else {
$yy_yymore_patterns = array_slice($rules, $this->token, true);
// yymore is needed
do {
if (!isset($yy_yymore_patterns[$this->token])) {
throw new Exception('cannot do yymore for the last token');
}
$match = false;
foreach ($yy_yymore_patterns[$this->token] as $index => $rule) {
if (preg_match('/' . $rule . '/',
$this->data, $yymatches, null, $this->count)) {
$yymatches = array_filter($yymatches, 'strlen'); // remove empty sub-patterns
if ($match) {
if (strlen($yymatches[0]) > strlen($match[0][0])) {
$match = array($yymatches, $index); // matches, token
}
} else {
$match = array($yymatches, $index);
}
}
}
if (!$match) {
throw new Exception('Unexpected input at line ' . $this->line .
': ' . $this->data[$this->count]);
}
$this->token = $match[1];
$this->value = $match[0][0];
$yysubmatches = $match[0];
array_shift($yysubmatches);
if (!$yysubmatches) {
$yysubmatches = array();
}
$this->line = substr_count($this->value, "\n");
$r = $this->{'yy_r1_' . $this->token}();
} while ($r !== null || !$r);
if ($r === true) {
// we have changed state
// process this token in the new state
return $this->yylex();
} else {
// accept
$this->count += strlen($this->value);
$this->line += substr_count($this->value, "\n");
return true;
}
}
} while (true);
} // end function
@@ -530,24 +538,64 @@ class OQLLexerRaw
function yy_r1_54($yy_subpatterns)
{
$this->token = OQLParser::NUMVAL;
$this->token = OQLParser::BELOW;
}
function yy_r1_55($yy_subpatterns)
{
$this->token = OQLParser::STRVAL;
$this->token = OQLParser::BELOW_STRICT;
}
function yy_r1_56($yy_subpatterns)
{
$this->token = OQLParser::NAME;
$this->token = OQLParser::NOT_BELOW;
}
function yy_r1_57($yy_subpatterns)
{
$this->token = OQLParser::VARNAME;
$this->token = OQLParser::NOT_BELOW_STRICT;
}
function yy_r1_58($yy_subpatterns)
{
$this->token = OQLParser::ABOVE;
}
function yy_r1_59($yy_subpatterns)
{
$this->token = OQLParser::ABOVE_STRICT;
}
function yy_r1_60($yy_subpatterns)
{
$this->token = OQLParser::NOT_ABOVE;
}
function yy_r1_61($yy_subpatterns)
{
$this->token = OQLParser::NOT_ABOVE_STRICT;
}
function yy_r1_62($yy_subpatterns)
{
$this->token = OQLParser::NUMVAL;
}
function yy_r1_63($yy_subpatterns)
{
$this->token = OQLParser::STRVAL;
}
function yy_r1_64($yy_subpatterns)
{
$this->token = OQLParser::NAME;
}
function yy_r1_65($yy_subpatterns)
{
$this->token = OQLParser::VARNAME;
}
function yy_r1_66($yy_subpatterns)
{
$this->token = OQLParser::DOT;

View File

@@ -132,7 +132,31 @@ f_round = "ROUND"
f_floor = "FLOOR"
f_inet_aton = "INET_ATON"
f_inet_ntoa = "INET_NTOA"
numval = /[0-9]+|0x[0-9a-fA-F]+/
below = "BELOW"
below_strict = "BELOW STRICT"
not_below = "NOT BELOW"
not_below_strict = "NOT BELOW STRICT"
above = "ABOVE"
above_strict = "ABOVE STRICT"
not_above = "NOT ABOVE"
not_above_strict = "NOT ABOVE STRICT"
//
// WARNING: there seems to be a bug in the Lexer about matching the longest pattern
// when there are alternates in the regexp.
//
// For instance:
// numval = /[0-9]+|0x[0-9a-fA-F]+/
// Does not work: SELECT Toto WHERE name = 'Text0xCTest' => Fails because 0xC is recongnized as a numval (inside the string) instead of a strval !!
//
// Inserting a ^ after the alternate (see comment at the top of this file) does not work either
// numval = /[0-9]+|'.chr(94).'0x[0-9a-fA-F]+/
// SELECT Toto WHERE name = 'Text0xCTest' => works but
// SELECT Toto WHERE id = 0xC => does not work, 'xC' is found as a name (apparently 0 is recognized as a numval and the remaining is a name !)
//
// numval = /([0-9]+|0x[0-9a-fA-F]+)/
// Does not work either, the hexadecimal numbers are not matched properly
// The following seems to work...
numval = /(0x[0-9a-fA-F]+|[0-9]+)/
strval = /"([^\\"]|\\"|\\\\)*"|'.chr(94).chr(39).'([^\\'.chr(39).']|\\'.chr(39).'|\\\\)*'.chr(39).'/
name = /([_a-zA-Z][_a-zA-Z0-9]*|`[^`]+`)/
varname = /:([_a-zA-Z][_a-zA-Z0-9]*->[_a-zA-Z][_a-zA-Z0-9]*|[_a-zA-Z][_a-zA-Z0-9]*)/
@@ -302,6 +326,30 @@ f_inet_aton {
f_inet_ntoa {
$this->token = OQLParser::F_INET_NTOA;
}
below {
$this->token = OQLParser::BELOW;
}
below_strict {
$this->token = OQLParser::BELOW_STRICT;
}
not_below {
$this->token = OQLParser::NOT_BELOW;
}
not_below_strict {
$this->token = OQLParser::NOT_BELOW_STRICT;
}
above {
$this->token = OQLParser::ABOVE;
}
above_strict {
$this->token = OQLParser::ABOVE_STRICT;
}
not_above {
$this->token = OQLParser::NOT_ABOVE;
}
not_above_strict {
$this->token = OQLParser::NOT_ABOVE_STRICT;
}
numval {
$this->token = OQLParser::NUMVAL;
}

File diff suppressed because it is too large Load Diff

View File

@@ -78,6 +78,14 @@ join_item(A) ::= JOIN class_name(X) ON join_condition(C).
}
join_condition(A) ::= field_id(X) EQ field_id(Y). { A = new BinaryOqlExpression(X, '=', Y); }
join_condition(A) ::= field_id(X) BELOW field_id(Y). { A = new BinaryOqlExpression(X, 'BELOW', Y); }
join_condition(A) ::= field_id(X) BELOW_STRICT field_id(Y). { A = new BinaryOqlExpression(X, 'BELOW_STRICT', Y); }
join_condition(A) ::= field_id(X) NOT_BELOW field_id(Y). { A = new BinaryOqlExpression(X, 'NOT_BELOW', Y); }
join_condition(A) ::= field_id(X) NOT_BELOW_STRICT field_id(Y). { A = new BinaryOqlExpression(X, 'NOT_BELOW_STRICT', Y); }
join_condition(A) ::= field_id(X) ABOVE field_id(Y). { A = new BinaryOqlExpression(X, 'ABOVE', Y); }
join_condition(A) ::= field_id(X) ABOVE_STRICT field_id(Y). { A = new BinaryOqlExpression(X, 'ABOVE_STRICT', Y); }
join_condition(A) ::= field_id(X) NOT_ABOVE field_id(Y). { A = new BinaryOqlExpression(X, 'NOT_ABOVE', Y); }
join_condition(A) ::= field_id(X) NOT_ABOVE_STRICT field_id(Y). { A = new BinaryOqlExpression(X, 'NOT_ABOVE_STRICT', Y); }
condition(A) ::= expression_prio4(X). { A = X; }

View File

@@ -59,6 +59,7 @@ class OqlJoinSpec
protected $m_oClassAlias;
protected $m_oLeftField;
protected $m_oRightField;
protected $m_sOperator;
protected $m_oNextJoinspec;
@@ -68,6 +69,8 @@ class OqlJoinSpec
$this->m_oClassAlias = $oClassAlias;
$this->m_oLeftField = $oExpression->GetLeftExpr();
$this->m_oRightField = $oExpression->GetRightExpr();
$this->m_oRightField = $oExpression->GetRightExpr();
$this->m_sOperator = $oExpression->GetOperator();
}
public function GetClass()
@@ -96,6 +99,10 @@ class OqlJoinSpec
{
return $this->m_oRightField;
}
public function GetOperator()
{
return $this->m_sOperator;
}
}
class BinaryOqlExpression extends BinaryExpression

View File

@@ -79,7 +79,29 @@ class ormCaseLog {
$iPos += $this->m_aIndex[$index]['text_length'];
$sEntry = '<div class="caselog_header'.$sOpen.'">';
$sEntry .= sprintf(Dict::S('UI:CaseLog:Header_Date_UserName'), $this->m_aIndex[$index]['date']->format(Dict::S('UI:CaseLog:DateFormat')), $this->m_aIndex[$index]['user_name']);
// Workaround: PHP < 5.3 cannot unserialize correctly DateTime objects,
// therefore we have changed the format. To preserve the compatibility with existing
// installations of iTop, both format are allowed:
// the 'date' item is either a DateTime object, or a unix timestamp
if (is_int($this->m_aIndex[$index]['date']))
{
// Unix timestamp
$sDate = date(Dict::S('UI:CaseLog:DateFormat'), $this->m_aIndex[$index]['date']);
}
elseif (is_object($this->m_aIndex[$index]['date']))
{
if (version_compare(phpversion(), '5.3.0', '>='))
{
// DateTime
$sDate = $this->m_aIndex[$index]['date']->format(Dict::S('UI:CaseLog:DateFormat'));
}
else
{
// No Warning... but the date is unknown
$sDate = '';
}
}
$sEntry .= sprintf(Dict::S('UI:CaseLog:Header_Date_UserName'), $sDate, $this->m_aIndex[$index]['user_name']);
$sEntry .= '</div>';
$sEntry .= '<div class="caselog_entry"'.$sDisplay.'>';
$sEntry .= $sTextEntry;
@@ -131,17 +153,26 @@ class ormCaseLog {
* Add a new entry to the log and updates the internal index
* @param $sText string The text of the new entry
*/
public function AddLogEntry($sText)
public function AddLogEntry($sText, $sOnBehalfOf = '')
{
$sDate = date(Dict::S('UI:CaseLog:DateFormat'));
$sSeparator = sprintf(CASELOG_SEPARATOR, $sDate, UserRights::GetUserFriendlyName(), UserRights::GetUserId());
if ($sOnBehalfOf == '')
{
$sOnBehalfOf = UserRights::GetUserFriendlyName();
$iUserId = UserRights::GetUserId();
}
else
{
$iUserId = null;
}
$sSeparator = sprintf(CASELOG_SEPARATOR, $sDate, $sOnBehalfOf, $iUserId);
$iSepLength = strlen($sSeparator);
$iTextlength = strlen($sText);
$this->m_sLog = $sSeparator.$sText.$this->m_sLog; // Latest entry printed first
$this->m_aIndex[] = array(
'user_name' => UserRights::GetUserFriendlyName(),
'user_id' => UserRights::GetUserId(),
'date' => new DateTime(),
'user_name' => $sOnBehalfOf,
'user_id' => $iUserId,
'date' => time(),
'text_length' => $iTextlength,
'separator_length' => $iSepLength,
);
@@ -153,8 +184,7 @@ class ormCaseLog {
*/
public function GetLatestEntry()
{
$iLast = count($this->m_aIndex) - 1;
$aLastEntry = $this->m_aIndex[$iLast];
$aLastEntry = end($this->m_aIndex);
$sRes = substr($this->m_sLog, $aLastEntry['separator_length'], $aLastEntry['text_length']);
return $sRes;
}
@@ -165,7 +195,8 @@ class ormCaseLog {
*/
public function GetLatestEntryIndex()
{
$iLast = count($this->m_aIndex) - 1;
$aKeys = array_keys($this->m_aIndex);
$iLast = end($aKeys); // Strict standards: the parameter passed to 'end' must be a variable since it is passed by reference
return $iLast;
}
}

View File

@@ -105,7 +105,7 @@ class ormDocument
*/
public function GetDisplayLink($sClass, $Id, $sAttCode)
{
return "<a href=\"../pages/ajax.render.php?operation=display_document&class=$sClass&id=$Id&field=$sAttCode\" target=\"_blank\" >".$this->GetFileName()."</a>\n";
return "<a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=display_document&class=$sClass&id=$Id&field=$sAttCode\" target=\"_blank\" >".$this->GetFileName()."</a>\n";
}
/**
@@ -114,7 +114,7 @@ class ormDocument
*/
public function GetDownloadLink($sClass, $Id, $sAttCode)
{
return "<a href=\"../pages/ajax.render.php?operation=download_document&class=$sClass&id=$Id&field=$sAttCode\">".$this->GetFileName()."</a>\n";
return "<a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=download_document&class=$sClass&id=$Id&field=$sAttCode\">".$this->GetFileName()."</a>\n";
}
}
?>

View File

@@ -0,0 +1,74 @@
<?php
// Copyright (C) 2010 Combodo SARL
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; version 3 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
/**
* Associated with the metamodel -> MakeQuery/MakeQuerySingleTable
*
* @author Erwan Taloc <erwan.taloc@combodo.com>
* @author Romain Quetiez <romain.quetiez@combodo.com>
* @author Denis Flaven <denis.flaven@combodo.com>
* @license http://www.opensource.org/licenses/gpl-3.0.html LGPL
*/
class QueryBuilderContext
{
protected $m_oRootFilter;
protected $m_aClassAliases;
protected $m_aTableAliases;
protected $m_aModifierProperties;
public $m_oQBExpressions;
public function __construct($oFilter, $aModifierProperties)
{
$this->m_oRootFilter = $oFilter;
$this->m_oQBExpressions = new QueryBuilderExpressions($oFilter->GetCriteria());
$this->m_aClassAliases = $oFilter->GetJoinedClasses();
$this->m_aTableAliases = array();
$this->m_aModifierProperties = $aModifierProperties;
}
public function GetRootFilter()
{
return $this->m_oRootFilter;
}
public function GenerateTableAlias($sNewName, $sRealName)
{
return MetaModel::GenerateUniqueAlias($this->m_aTableAliases, $sNewName, $sRealName);
}
public function GenerateClassAlias($sNewName, $sRealName)
{
return MetaModel::GenerateUniqueAlias($this->m_aClassAliases, $sNewName, $sRealName);
}
public function GetModifierProperties($sPluginClass)
{
if (array_key_exists($sPluginClass, $this->m_aModifierProperties))
{
return $this->m_aModifierProperties[$sPluginClass];
}
else
{
return array();
}
}
}
?>

View File

@@ -0,0 +1,33 @@
<?php
// Copyright (C) 2010 Combodo SARL
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; version 3 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
/**
* Interface iQueryModifier
* Defines the API to tweak queries (e.g. translate data on the fly)
*
* @author Erwan Taloc <erwan.taloc@combodo.com>
* @author Romain Quetiez <romain.quetiez@combodo.com>
* @author Denis Flaven <denis.flaven@combodo.com>
* @license http://www.opensource.org/licenses/gpl-3.0.html LGPL
*/
interface iQueryModifier
{
public function __construct();
public function GetFieldExpression(QueryBuilderContext &$oBuild, $sClass, $sAttCode, $sColId, Expression $oFieldSQLExp, SQLQuery &$oSelect);
}
?>

View File

@@ -68,6 +68,11 @@ class SQLQuery
$this->m_oSelectedIdField = $oSelectedIdField;
}
public function GetTableAlias()
{
return $this->m_sTableAlias;
}
public function SetSourceOQL($sOQL)
{
$this->m_SourceOQL = $sOQL;
@@ -101,11 +106,20 @@ class SQLQuery
{
$sJoinType = $aJoinInfo["jointype"];
$oSQLQuery = $aJoinInfo["select"];
$sLeftField = $aJoinInfo["leftfield"];
$sRightField = $aJoinInfo["rightfield"];
$sRightTableAlias = $aJoinInfo["righttablealias"];
if (isset($aJoinInfo["on_expression"]))
{
$sOnCondition = $aJoinInfo["on_expression"]->Render();
echo "<li>Join '$sJoinType', $sLeftField, $sRightTableAlias.$sRightField".$oSQLQuery->DisplayHtml()."</li>\n";
echo "<li>Join '$sJoinType', ON ($sOnCondition)".$oSQLQuery->DisplayHtml()."</li>\n";
}
else
{
$sLeftField = $aJoinInfo["leftfield"];
$sRightField = $aJoinInfo["rightfield"];
$sRightTableAlias = $aJoinInfo["righttablealias"];
echo "<li>Join '$sJoinType', $sLeftField, $sRightTableAlias.$sRightField".$oSQLQuery->DisplayHtml()."</li>\n";
}
}
echo "</ul>";
}
@@ -163,7 +177,6 @@ class SQLQuery
// {
// throw new CoreException("Unknown field '$sRightField' in table '".$sRightTable."'");
// }
$this->m_aJoinSelects[] = array(
"jointype" => $sJoinType,
"select" => $oSQLQuery,
@@ -172,14 +185,49 @@ class SQLQuery
"righttablealias" => $sRightTableAlias
);
}
public function AddInnerJoin($oSQLQuery, $sLeftField, $sRightField, $sRigthtTable = '')
public function AddInnerJoin($oSQLQuery, $sLeftField, $sRightField, $sRightTable = '')
{
$this->AddJoin("inner", $oSQLQuery, $sLeftField, $sRightField, $sRigthtTable);
$this->AddJoin("inner", $oSQLQuery, $sLeftField, $sRightField, $sRightTable);
}
public function AddInnerJoinTree($oSQLQuery, $sLeftFieldLeft, $sLeftFieldRight, $sRightFieldLeft, $sRightFieldRight, $sRightTableAlias = '', $iOperatorCode = TREE_OPERATOR_BELOW)
{
assert((get_class($oSQLQuery) == __CLASS__) || is_subclass_of($oSQLQuery, __CLASS__));
if (empty($sRightTableAlias))
{
$sRightTableAlias = $oSQLQuery->m_sTableAlias;
}
$this->m_aJoinSelects[] = array(
"jointype" => 'inner_tree',
"select" => $oSQLQuery,
"leftfield" => $sLeftFieldLeft,
"rightfield" => $sLeftFieldRight,
"rightfield_left" => $sRightFieldLeft,
"rightfield_right" => $sRightFieldRight,
"righttablealias" => $sRightTableAlias,
"tree_operator" => $iOperatorCode);
}
public function AddLeftJoin($oSQLQuery, $sLeftField, $sRightField)
{
return $this->AddJoin("left", $oSQLQuery, $sLeftField, $sRightField);
}
public function AddInnerJoinEx(SQLQuery $oSQLQuery, Expression $oOnExpression)
{
$this->m_aJoinSelects[] = array(
"jointype" => 'inner',
"select" => $oSQLQuery,
"on_expression" => $oOnExpression
);
}
public function AddLeftJoinEx(SQLQuery $oSQLQuery, Expression $oOnExpression)
{
$this->m_aJoinSelects[] = array(
"jointype" => 'left',
"select" => $oSQLQuery,
"on_expression" => $oOnExpression
);
}
// Interface, build the SQL query
public function RenderDelete($aArgs = array())
@@ -227,7 +275,6 @@ class SQLQuery
$aSetValues = array();
$aSelectedIdFields = array();
$this->privRender($aFrom, $aFields, $oCondition, $aDelTables, $aSetValues, $aSelectedIdFields);
$sFrom = self::ClauseFrom($aFrom);
$sValues = self::ClauseValues($aSetValues);
$sWhere = self::ClauseWhere($oCondition, $aArgs);
@@ -315,6 +362,7 @@ class SQLQuery
$sFrom .= " ".self::ClauseFrom($aJoinInfo["subfrom"]);
break;
case "inner":
case "inner_tree":
$sFrom .= " INNER JOIN (`".$aJoinInfo["tablename"]."` AS `$sTableAlias`";
$sFrom .= " ".self::ClauseFrom($aJoinInfo["subfrom"]);
$sFrom .= ") ON ".$aJoinInfo["joincondition"];
@@ -368,12 +416,12 @@ class SQLQuery
// Purpose: prepare the query data, once for all
private function privRender(&$aFrom, &$aFields, &$oCondition, &$aDelTables, &$aSetValues, &$aSelectedIdFields)
{
$sTableAlias = $this->privRenderSingleTable($aFrom, $aFields, $aDelTables, $aSetValues, $aSelectedIdFields);
$sTableAlias = $this->privRenderSingleTable($aFrom, $aFields, $aDelTables, $aSetValues, $aSelectedIdFields, '', array('jointype' => 'first'));
$oCondition = $this->m_oConditionExpr;
return $sTableAlias;
}
private function privRenderSingleTable(&$aFrom, &$aFields, &$aDelTables, &$aSetValues, &$aSelectedIdFields, $sJoinType = 'first', $sCallerAlias = '', $sLeftField = '', $sRightField = '', $sRightTableAlias = '')
private function privRenderSingleTable(&$aFrom, &$aFields, &$aDelTables, &$aSetValues, &$aSelectedIdFields, $sCallerAlias = '', $aJoinData)
{
$aActualTableFields = CMDBSource::GetTableFieldsList($this->m_sTable);
@@ -381,20 +429,72 @@ class SQLQuery
// Handle the various kinds of join (or first table in the list)
//
if (empty($sRightTableAlias))
if (empty($aJoinData['righttablealias']))
{
$sRightTableAlias = $this->m_sTableAlias;
}
$sJoinCond = "`$sCallerAlias`.`$sLeftField` = `$sRightTableAlias`.`$sRightField`";
switch ($sJoinType)
else
{
$sRightTableAlias = $aJoinData['righttablealias'];
}
switch ($aJoinData['jointype'])
{
case "first":
$aFrom[$this->m_sTableAlias] = array("jointype"=>"first", "tablename"=>$this->m_sTable, "joincondition"=>"");
break;
case "inner":
case "left":
// table or tablealias ???
$aFrom[$this->m_sTableAlias] = array("jointype"=>$sJoinType, "tablename"=>$this->m_sTable, "joincondition"=>"$sJoinCond");
if (isset($aJoinData["on_expression"]))
{
$sJoinCond = $aJoinData["on_expression"]->Render();
}
else
{
$sJoinCond = "`$sCallerAlias`.`{$aJoinData['leftfield']}` = `$sRightTableAlias`.`{$aJoinData['rightfield']}`";
}
$aFrom[$this->m_sTableAlias] = array("jointype"=>$aJoinData['jointype'], "tablename"=>$this->m_sTable, "joincondition"=>"$sJoinCond");
break;
case "inner_tree":
$sNodeLeft = "`$sCallerAlias`.`{$aJoinData['leftfield']}`";
$sNodeRight = "`$sCallerAlias`.`{$aJoinData['rightfield']}`";
$sRootLeft = "`$sRightTableAlias`.`{$aJoinData['rightfield_left']}`";
$sRootRight = "`$sRightTableAlias`.`{$aJoinData['rightfield_right']}`";
switch($aJoinData['tree_operator'])
{
case TREE_OPERATOR_BELOW:
$sJoinCond = "$sNodeLeft >= $sRootLeft AND $sNodeLeft <= $sRootRight";
break;
case TREE_OPERATOR_BELOW_STRICT:
$sJoinCond = "$sNodeLeft > $sRootLeft AND $sNodeLeft < $sRootRight";
break;
case TREE_OPERATOR_NOT_BELOW: // Complementary of 'BELOW'
$sJoinCond = "$sNodeLeft < $sRootLeft OR $sNodeLeft > $sRootRight";
break;
case TREE_OPERATOR_NOT_BELOW_STRICT: // Complementary of BELOW_STRICT
$sJoinCond = "$sNodeLeft <= $sRootLeft OR $sNodeLeft >= $sRootRight";
break;
case TREE_OPERATOR_ABOVE:
$sJoinCond = "$sNodeLeft <= $sRootLeft AND $sNodeRight >= $sRootRight";
break;
case TREE_OPERATOR_ABOVE_STRICT:
$sJoinCond = "$sNodeLeft < $sRootLeft AND $sNodeRight > $sRootRight";
break;
case TREE_OPERATOR_NOT_ABOVE: // Complementary of 'ABOVE'
$sJoinCond = "$sNodeLeft > $sRootLeft OR $sNodeRight < $sRootRight";
break;
case TREE_OPERATOR_NOT_ABOVE_STRICT: // Complementary of ABOVE_STRICT
$sJoinCond = "$sNodeLeft >= $sRootLeft OR $sNodeRight <= $sRootRight";
break;
}
$aFrom[$this->m_sTableAlias] = array("jointype"=>$aJoinData['jointype'], "tablename"=>$this->m_sTable, "joincondition"=>"$sJoinCond");
break;
}
@@ -409,6 +509,7 @@ class SQLQuery
{
$aDelTables[] = "`{$this->m_sTableAlias}`";
}
//echo "<p>in privRenderSingleTable this->m_aValues<pre>".print_r($this->m_aValues, true)."</pre></p>\n";
foreach($this->m_aValues as $sFieldName=>$value)
{
$aSetValues["`{$this->m_sTableAlias}`.`$sFieldName`"] = $value; // quoted further!
@@ -424,13 +525,9 @@ class SQLQuery
$aTempFrom = array(); // temporary subset of 'from' specs, to be grouped in the final query
foreach ($this->m_aJoinSelects as $aJoinData)
{
$sJoinType = $aJoinData["jointype"];
$oRightSelect = $aJoinData["select"];
$sLeftField = $aJoinData["leftfield"];
$sRightField = $aJoinData["rightfield"];
$sRightTableAlias = $aJoinData["righttablealias"];
$sJoinTableAlias = $oRightSelect->privRenderSingleTable($aTempFrom, $aFields, $aDelTables, $aSetValues, $aSelectedIdFields, $sJoinType, $this->m_sTableAlias, $sLeftField, $sRightField, $sRightTableAlias);
$sJoinTableAlias = $oRightSelect->privRenderSingleTable($aTempFrom, $aFields, $aDelTables, $aSetValues, $aSelectedIdFields, $this->m_sTableAlias, $aJoinData);
}
$aFrom[$this->m_sTableAlias]['subfrom'] = $aTempFrom;

View File

@@ -0,0 +1,177 @@
<?php
// Copyright (C) 2010 Combodo SARL
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; version 3 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
/**
* Simple helper class to interpret and transform a template string
*
* Usage:
* $oString = new TemplateString("Blah $this->friendlyname$ is in location $this->location_id->name$ ('$this->location_id->org_id->name$)");
* echo $oString->Render(array('this' => $oContact));
* @author Erwan Taloc <erwan.taloc@combodo.com>
* @author Romain Quetiez <romain.quetiez@combodo.com>
* @author Denis Flaven <denis.flaven@combodo.com>
* @license http://www.opensource.org/licenses/gpl-3.0.html LGPL
*/
/**
* Helper class
*/
class TemplateStringPlaceholder
{
public $sToken;
public $sAttCode;
public $sFunction;
public $sParamName;
public $bIsValid;
public function __construct($sToken)
{
$this->sToken = $sToken;
$this->sAttcode = '';
$this->sFunction = '';
$this->sParamName = '';
$this->bIsValid = false; // Validity may be false in general, but it can work anyway (thanks to specialization) when rendering
}
}
/**
* Class TemplateString
*/
class TemplateString
{
protected $m_sRaw;
protected $m_aPlaceholders;
public function __construct($sRaw)
{
$this->m_sRaw = $sRaw;
$this->m_aPlaceholders = null;
}
/**
* Split the string into placholders
* @param Hash $aParamTypes Class of the expected parameters: hash array of '<param_id>' => '<class_name>'
* @return void
*/
protected function Analyze($aParamTypes = array())
{
if (!is_null($this->m_aPlaceholders)) return;
$this->m_aPlaceholders = array();
if (preg_match_all('/\\$([a-z0-9_]+(->[a-z0-9_]+)*)\\$/', $this->m_sRaw, $aMatches))
{
foreach($aMatches[1] as $sPlaceholder)
{
$oPlaceholder = new TemplateStringPlaceholder($sPlaceholder);
$oPlaceholder->bIsValid = false;
foreach ($aParamTypes as $sParamName => $sClass)
{
$sParamPrefix = $sParamName.'->';
if (substr($sPlaceholder, 0, strlen($sParamPrefix)) == $sParamPrefix)
{
// Todo - detect functions (label...)
$oPlaceholder->sFunction = '';
$oPlaceholder->sParamName = $sParamName;
$sAttcode = substr($sPlaceholder, strlen($sParamPrefix));
$oPlaceholder->sAttcode = $sAttcode;
$oPlaceholder->bIsValid = MetaModel::IsValidAttCode($sClass, $sAttcode, true /* extended */);
}
}
$this->m_aPlaceholders[] = $oPlaceholder;
}
}
}
/**
* Return the placeholders (for reporting purposes)
* @return void
*/
public function GetPlaceholders()
{
return $this->m_aPlaceholders;
}
/**
* Check the format when possible
* @param Hash $aParamTypes Class of the expected parameters: hash array of '<param_id>' => '<class_name>'
* @return void
*/
public function IsValid($aParamTypes = array())
{
$this->Analyze($aParamTypes);
foreach($this->m_aPlaceholders as $oPlaceholder)
{
if (!$oPlaceholder->bIsValid)
{
if (count($aParamTypes) == 0)
{
return false;
}
if (array_key_exists($oPlaceholder->sParamName, $aParamTypes))
{
return false;
}
}
}
return true;
}
/**
* Apply the given parameters to replace the placeholders
* @param Hash $aParamValues Value of the expected parameters: hash array of '<param_id>' => '<value>'
* @return void
*/
public function Render($aParamValues = array())
{
$aParamTypes = array();
foreach($aParamValues as $sParamName => $value)
{
$aParamTypes[$sParamName] = get_class($value);
}
$this->Analyze($aParamTypes);
$aSearch = array();
$aReplace = array();
foreach($this->m_aPlaceholders as $oPlaceholder)
{
if (array_key_exists($oPlaceholder->sParamName, $aParamValues))
{
$oRef = $aParamValues[$oPlaceholder->sParamName];
try
{
$value = $oRef->Get($oPlaceholder->sAttcode);
$aSearch[] = '$'.$oPlaceholder->sToken.'$';
$aReplace[] = $value;
$oPlaceholder->bIsValid = true;
}
catch(Exception $e)
{
$oPlaceholder->bIsValid = false;
}
}
else
{
$oPlaceholder->bIsValid = false;
}
}
return str_replace($aSearch, $aReplace, $this->m_sRaw);
}
}
?>

View File

@@ -41,7 +41,7 @@ abstract class Trigger extends cmdbAbstractObject
"key_type" => "autoincrement",
"name_attcode" => "description",
"state_attcode" => "",
"reconc_keys" => array(),
"reconc_keys" => array('description'),
"db_table" => "priv_trigger",
"db_key_field" => "id",
"db_finalclass_field" => "realclass",
@@ -87,7 +87,7 @@ abstract class TriggerOnObject extends Trigger
"key_type" => "autoincrement",
"name_attcode" => "description",
"state_attcode" => "",
"reconc_keys" => array(),
"reconc_keys" => array('description'),
"db_table" => "priv_trigger_onobject",
"db_key_field" => "id",
"db_finalclass_field" => "",
@@ -105,6 +105,34 @@ abstract class TriggerOnObject extends Trigger
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
}
}
/**
* To trigger notifications when a ticket is updated from the portal
*/
class TriggerOnPortalUpdate extends TriggerOnObject
{
public static function Init()
{
$aParams = array
(
"category" => "core/cmdb,bizmodel",
"key_type" => "autoincrement",
"name_attcode" => "description",
"state_attcode" => "",
"reconc_keys" => array('description'),
"db_table" => "priv_trigger_onportalupdate",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
// Display lists
MetaModel::Init_SetZListItems('details', array('description', 'target_class', 'action_list')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('finalclass', 'target_class', 'description')); // Attributes to be displayed for a list
// Search criteria
}
}
abstract class TriggerOnStateChange extends TriggerOnObject
{
@@ -116,7 +144,7 @@ abstract class TriggerOnStateChange extends TriggerOnObject
"key_type" => "autoincrement",
"name_attcode" => "description",
"state_attcode" => "",
"reconc_keys" => array(),
"reconc_keys" => array('description'),
"db_table" => "priv_trigger_onstatechange",
"db_key_field" => "id",
"db_finalclass_field" => "",
@@ -145,7 +173,7 @@ class TriggerOnStateEnter extends TriggerOnStateChange
"key_type" => "autoincrement",
"name_attcode" => "description",
"state_attcode" => "",
"reconc_keys" => array(),
"reconc_keys" => array('description'),
"db_table" => "priv_trigger_onstateenter",
"db_key_field" => "id",
"db_finalclass_field" => "",
@@ -173,7 +201,7 @@ class TriggerOnStateLeave extends TriggerOnStateChange
"key_type" => "autoincrement",
"name_attcode" => "description",
"state_attcode" => "",
"reconc_keys" => array(),
"reconc_keys" => array('description'),
"db_table" => "priv_trigger_onstateleave",
"db_key_field" => "id",
"db_finalclass_field" => "",
@@ -201,7 +229,7 @@ class TriggerOnObjectCreate extends TriggerOnObject
"key_type" => "autoincrement",
"name_attcode" => "description",
"state_attcode" => "",
"reconc_keys" => array(),
"reconc_keys" => array('description'),
"db_table" => "priv_trigger_onobjcreate",
"db_key_field" => "id",
"db_finalclass_field" => "",
@@ -229,7 +257,7 @@ class lnkTriggerAction extends cmdbAbstractObject
"key_type" => "autoincrement",
"name_attcode" => "",
"state_attcode" => "",
"reconc_keys" => array(""),
"reconc_keys" => array('action_id', 'trigger_id'),
"db_table" => "priv_link_action_trigger",
"db_key_field" => "link_id",
"db_finalclass_field" => "",

View File

@@ -55,7 +55,7 @@ abstract class UserRightsAddOnAPI
abstract public function Init(); // loads data (possible optimizations)
// Used to build select queries showing only objects visible for the given user
abstract public function GetSelectFilter($sLogin, $sClass); // returns a filter object
abstract public function GetSelectFilter($sLogin, $sClass, $aSettings = array()); // returns a filter object
abstract public function IsActionAllowed($oUser, $sClass, $iActionCode, /*dbObjectSet*/ $oInstanceSet = null);
abstract public function IsStimulusAllowed($oUser, $sClass, $sStimulusCode, /*dbObjectSet*/ $oInstanceSet = null);
@@ -295,6 +295,35 @@ abstract class UserInternal extends User
}
}
/**
* Self register extension
*
* @package iTopORM
*/
interface iSelfRegister
{
/**
* Called when no user is found in iTop for the corresponding 'name'. This method
* can create/synchronize the User in iTop with an external source (such as AD/LDAP) on the fly
* @param string $sName The typed-in user name
* @param string $sPassword The typed-in password
* @param string $sLoginMode The login method used (cas|form|basic|url)
* @param string $sAuthentication The authentication method used (any|internal|external)
* @return bool true if the user is a valid one, false otherwise
*/
public static function CheckCredentialsAndCreateUser($sName, $sPassword, $sLoginMode, $sAuthentication);
/**
* Called after the user has been authenticated and found in iTop. This method can
* Update the user's definition on the fly (profiles...) to keep it in sync with an external source
* @param User $oUser The user to update/synchronize
* @param string $sLoginMode The login mode used (cas|form|basic|url)
* @param string $sAuthentication The authentication method used
* @return void
*/
public static function UpdateUser(User $oUser, $sLoginMode, $sAuthentication);
}
/**
* User management core API
*
@@ -305,6 +334,7 @@ class UserRights
protected static $m_oAddOn;
protected static $m_oUser;
protected static $m_oRealUser;
protected static $m_sSelfRegisterAddOn = null;
public static function SelectModule($sModuleName)
{
@@ -324,6 +354,15 @@ class UserRights
self::$m_oRealUser = null;
}
public static function SelectSelfRegister($sModuleName)
{
if (!class_exists($sModuleName))
{
throw new CoreException("Could not select the class, '$sModuleName' for self register, is not a valid class name");
}
self::$m_sSelfRegisterAddOn = $sModuleName;
}
public static function GetModuleInstance()
{
return self::$m_oAddOn;
@@ -361,21 +400,38 @@ class UserRights
return true;
}
public static function CheckCredentials($sName, $sPassword, $sAuthentication = 'any')
public static function CheckCredentials($sName, $sPassword, $sLoginMode = 'form', $sAuthentication = 'any')
{
$oUser = self::FindUser($sName, $sAuthentication);
if (is_null($oUser))
{
return false;
return self::CheckCredentialsAndCreateUser($sName, $sPassword, $sLoginMode, $sAuthentication);
}
if (!$oUser->CheckCredentials($sPassword))
{
return false;
}
self::UpdateUser($oUser, $sLoginMode, $sAuthentication);
return true;
}
public static function CheckCredentialsAndCreateUser($sName, $sPassword, $sLoginMode, $sAuthentication)
{
if (self::$m_sSelfRegisterAddOn != null)
{
return call_user_func(array(self::$m_sSelfRegisterAddOn, 'CheckCredentialsAndCreateUser'), $sName, $sPassword, $sLoginMode, $sAuthentication);
}
}
public static function UpdateUser($oUser, $sLoginMode, $sAuthentication)
{
if (self::$m_sSelfRegisterAddOn != null)
{
call_user_func(array(self::$m_sSelfRegisterAddOn, 'UpdateUser'), $oUser, $sLoginMode, $sAuthentication);
}
}
public static function TrustWebServerContext()
{
if (!is_null(self::$m_oUser))
@@ -405,18 +461,6 @@ class UserRights
}
}
public static function CanLogOff()
{
if (!is_null(self::$m_oUser))
{
return self::$m_oUser->CanLogOff();
}
else
{
return false;
}
}
public static function ChangePassword($sOldPassword, $sNewPassword, $sName = '')
{
if (empty($sName))
@@ -603,18 +647,16 @@ class UserRights
return true;
}
public static function GetSelectFilter($sClass)
public static function GetSelectFilter($sClass, $aSettings = array())
{
// When initializing, we need to let everything pass trough
if (!self::CheckLogin()) return true;
if (self::IsAdministrator()) return true;
// Portal users actions are limited by the portal page...
if (self::IsPortalUser()) return true;
if (MetaModel::HasCategory($sClass, 'bizmodel'))
{
return self::$m_oAddOn->GetSelectFilter(self::$m_oUser, $sClass);
return self::$m_oAddOn->GetSelectFilter(self::$m_oUser, $sClass, $aSettings);
}
else
{
@@ -639,10 +681,6 @@ class UserRights
if (MetaModel::HasCategory($sClass, 'bizmodel'))
{
// #@# Temporary?????
// The read access is controlled in MetaModel::MakeSelectQuery()
if ($iActionCode == UR_ACTION_READ) return true;
if (is_null($oUser))
{
$oUser = self::$m_oUser;
@@ -863,7 +901,7 @@ class ActionChecker
{
$sClass = $this->oFilter->GetClass();
$oSet = new DBObjectSet($this->oFilter);
$iActionAllowed = UserRights::IsActionAllowed($sClass, $oSet, $this->iActionCode);
$iActionAllowed = UserRights::IsActionAllowed($sClass, $this->iActionCode, $oSet);
if ($iActionAllowed == UR_ALLOWED_DEPENDS)
{
// Check for each object if the action is allowed or not
@@ -919,6 +957,8 @@ class StimulusChecker extends ActionChecker
public function IsAllowed()
{
$sClass = $this->oFilter->GetClass();
if (MetaModel::IsAbstract($sClass)) return UR_ALLOWED_NO; // Safeguard, not implemented if the base class of the set is abstract !
$oSet = new DBObjectSet($this->oFilter);
$iActionAllowed = UserRights::IsStimulusAllowed($sClass, $this->iActionCode, $oSet);
if ($iActionAllowed == UR_ALLOWED_NO)
@@ -945,7 +985,7 @@ class StimulusChecker extends ActionChecker
// of IsActionAllowed does not perform a 'per instance' check, we could
// skip this second validation phase and assume it would return UR_ALLOWED_YES
$oObjSet = DBObjectSet::FromArray($sClass, array($oObj));
if (UserRights::IsActionAllowed($sClass, $this->iActionCode, $oObjSet) == UR_ALLOWED_NO)
if (!UserRights::IsStimulusAllowed($sClass, $this->iActionCode, $oObjSet))
{
$this->aAllowedIDs[$oObj->GetKey()] = false;
}
@@ -981,4 +1021,339 @@ class StimulusChecker extends ActionChecker
return $this->iState;
}
}
/**
* Self-register extension to allow the automatic creation & update of CAS users
*
* @package iTopORM
*
*/
class CAS_SelfRegister implements iSelfRegister
{
/**
* Called when no user is found in iTop for the corresponding 'name'. This method
* can create/synchronize the User in iTop with an external source (such as AD/LDAP) on the fly
* @param string $sName The CAS authenticated user name
* @param string $sPassword Ignored
* @param string $sLoginMode The login mode used (cas|form|basic|url)
* @param string $sAuthentication The authentication method used
* @return bool true if the user is a valid one, false otherwise
*/
public static function CheckCredentialsAndCreateUser($sName, $sPassword, $sLoginMode, $sAuthentication)
{
$bOk = true;
if ($sLoginMode != 'cas') return false; // Must be authenticated via CAS
$sCASMemberships = MetaModel::GetConfig()->Get('cas_memberof');
$bFound = false;
if (!empty($sCASMemberships))
{
if (phpCAS::hasAttribute('memberOf'))
{
// A list of groups is specified, the user must a be member of (at least) one of them to pass
$aCASMemberships = array();
$aTmp = explode(';', $sCASMemberships);
setlocale(LC_ALL, "en_US.utf8"); // !!! WARNING: this is needed to have the iconv //TRANSLIT working fine below !!!
foreach($aTmp as $sGroupName)
{
$aCASMemberships[] = trim(iconv('UTF-8', 'ASCII//TRANSLIT', $sGroupName)); // Just in case remove accents and spaces...
}
$aMemberOf = phpCAS::getAttribute('memberOf');
if (!is_array($aMemberOf)) $aMemberOf = array($aMemberOf); // Just one entry, turn it into an array
$aFilteredGroupNames = array();
foreach($aMemberOf as $sGroupName)
{
phpCAS::log("Info: user if a member of the group: ".$sGroupName);
$sGroupName = trim(iconv('UTF-8', 'ASCII//TRANSLIT', $sGroupName)); // Remove accents and spaces as well
$aFilteredGroupNames[] = $sGroupName;
$bIsMember = false;
foreach($aCASMemberships as $sCASPattern)
{
if (self::IsPattern($sCASPattern))
{
if (preg_match($sCASPattern, $sGroupName))
{
$bIsMember = true;
break;
}
}
else if ($sPattern == $sGroupName)
{
$bIsMember = true;
break;
}
}
if ($bIsMember)
{
$bCASUserSynchro = MetaModel::GetConfig()->Get('cas_user_synchro');
if ($bCASUserSynchro)
{
// If needed create a new user for this email/profile
phpCAS::log('Info: cas_user_synchro is ON');
$bOk = self::CreateCASUser(phpCAS::getUser(), $aMemberOf);
if($bOk)
{
$bFound = true;
}
else
{
phpCAS::log("User ".phpCAS::getUser()." cannot be created in iTop. Logging off...");
}
}
else
{
phpCAS::log('Info: cas_user_synchro is OFF');
$bFound = true;
}
break;
}
}
if($bOk && !$bFound)
{
phpCAS::log("User ".phpCAS::getUser().", none of his/her groups (".implode('; ', $aFilteredGroupNames).") match any of the required groups: ".implode('; ', $aCASMemberships));
}
}
else
{
// Too bad, the user is not part of any of the group => not allowed
phpCAS::log("No 'memberOf' attribute found for user ".phpCAS::getUser().". Are you using the SAML protocol (S1) ?");
}
}
else
{
// No membership required, anybody will pass
$bFound = true;
}
if (!$bFound)
{
// The user is not part of the allowed groups, => log out
$sUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php';
$sCASLogoutUrl = MetaModel::GetConfig()->Get('cas_logout_redirect_service');
if (empty($sCASLogoutUrl))
{
$sCASLogoutUrl = $sUrl;
}
phpCAS::logoutWithRedirectService($sCASLogoutUrl); // Redirects to the CAS logout page
// Will never return !
}
return $bFound;
}
/**
* Called after the user has been authenticated and found in iTop. This method can
* Update the user's definition (profiles...) on the fly to keep it in sync with an external source
* @param User $oUser The user to update/synchronize
* @param string $sLoginMode The login mode used (cas|form|basic|url)
* @param string $sAuthentication The authentication method used
* @return void
*/
public static function UpdateUser(User $oUser, $sLoginMode, $sAuthentication)
{
$bCASUpdateProfiles = MetaModel::GetConfig()->Get('cas_update_profiles');
if (($sLoginMode == 'cas') && $bCASUpdateProfiles && (phpCAS::hasAttribute('memberOf')))
{
$aMemberOf = phpCAS::getAttribute('memberOf');
if (!is_array($aMemberOf)) $aMemberOf = array($aMemberOf); // Just one entry, turn it into an array
return self::SetProfilesFromCAS($oUser, $aMemberOf);
}
// No groups defined in CAS or not CAS at all: do nothing...
return true;
}
/**
* Helper method to create a CAS based user
* @param string $sEmail
* @param array $aGroups
* @return bool true on success, false otherwise
*/
protected static function CreateCASUser($sEmail, $aGroups)
{
if (!MetaModel::IsValidClass('URP_Profiles'))
{
phpCAS::log("URP_Profiles is not a valid class. Automatic creation of Users is not supported in this context, sorry.");
return false;
}
$oUser = MetaModel::GetObjectByName('UserExternal', $sEmail, false);
if ($oUser == null)
{
// Create the user, link it to a contact
phpCAS::log("Info: the user '$sEmail' does not exist. A new UserExternal will be created.");
$oSearch = new DBObjectSearch('Person');
$oSearch->AddCondition('email', $sEmail);
$oSet = new DBObjectSet($oSearch);
$iContactId = 0;
switch($oSet->Count())
{
case 0:
phpCAS::log("Error: found no contact with the email: '$sEmail'. Cannot create the user in iTop.");
return false;
case 1:
$oContact = $oSet->Fetch();
$iContactId = $oContact->GetKey();
phpCAS::log("Info: Found 1 contact '".$oContact->GetName()."' (id=$iContactId) corresponding to the email '$sEmail'.");
break;
default:
phpCAS::log("Error: ".$oSet->Count()." contacts have the same email: '$sEmail'. Cannot create a user for this email.");
return false;
}
$oUser = new UserExternal();
$oUser->Set('login', $sEmail);
$oUser->Set('contactid', $iContactId);
$oUser->Set('language', MetaModel::GetConfig()->GetDefaultLanguage());
}
else
{
phpCAS::log("Info: the user '$sEmail' already exists (id=".$oUser->GetKey().").");
}
// Now synchronize the profiles
if (!self::SetProfilesFromCAS($oUser, $aGroups))
{
return false;
}
else
{
if ($oUser->IsNew() || $oUser->IsModified())
{
$oMyChange = MetaModel::NewObject("CMDBChange");
$oMyChange->Set("date", time());
$oMyChange->Set("userinfo", 'CAS/LDAP Synchro');
$oMyChange->DBInsert();
if ($oUser->IsNew())
{
$oUser->DBInsertTracked($oMyChange);
}
else
{
$oUser->DBUpdateTracked($oMyChange);
}
}
return true;
}
}
protected static function SetProfilesFromCAS($oUser, $aGroups)
{
if (!MetaModel::IsValidClass('URP_Profiles'))
{
phpCAS::log("URP_Profiles is not a valid class. Automatic creation of Users is not supported in this context, sorry.");
return false;
}
// read all the existing profiles
$oProfilesSearch = new DBObjectSearch('URP_Profiles');
$oProfilesSet = new DBObjectSet($oProfilesSearch);
$aAllProfiles = array();
while($oProfile = $oProfilesSet->Fetch())
{
$aAllProfiles[strtolower($oProfile->GetName())] = $oProfile->GetKey();
}
// Translate the CAS/LDAP group names into iTop profile names
$aProfiles = array();
$sPattern = MetaModel::GetConfig()->Get('cas_profile_pattern');
foreach($aGroups as $sGroupName)
{
if (preg_match($sPattern, $sGroupName, $aMatches))
{
if (array_key_exists(strtolower($aMatches[1]), $aAllProfiles))
{
$aProfiles[] = $aAllProfiles[strtolower($aMatches[1])];
phpCAS::log("Info: Adding the profile '{$aMatches[1]}' from CAS.");
}
else
{
phpCAS::log("Warning: {$aMatches[1]} is not a valid iTop profile (extracted from group name: '$sGroupName'). Ignored.");
}
}
else
{
phpCAS::log("Info: The CAS group '$sGroupName' does not seem to match an iTop pattern. Ignored.");
}
}
if (count($aProfiles) == 0)
{
phpCAS::log("Info: The user '".$oUser->GetName()."' has no profiles retrieved from CAS. Default profile(s) will be used.");
// Second attempt: check if there is/are valid default profile(s)
$sCASDefaultProfiles = MetaModel::GetConfig()->Get('cas_default_profiles');
$aCASDefaultProfiles = explode(';', $sCASDefaultProfiles);
foreach($aCASDefaultProfiles as $sDefaultProfileName)
{
if (array_key_exists(strtolower($sDefaultProfileName), $aAllProfiles))
{
$aProfiles[] = $aAllProfiles[strtolower($sDefaultProfileName)];
phpCAS::log("Info: Adding the default profile '".$aAllProfiles[strtolower($sDefaultProfileName)]."' from CAS.");
}
else
{
phpCAS::log("Warning: the default profile {$sDefaultProfileName} is not a valid iTop profile. Ignored.");
}
}
if (count($aProfiles) == 0)
{
phpCAS::log("Error: The user '".$oUser->GetName()."' has no profiles in iTop, and therefore cannot be created.");
return false;
}
}
// Now synchronize the profiles
$oProfilesSet = DBObjectSet::FromScratch('URP_UserProfile');
foreach($aProfiles as $iProfileId)
{
$oLink = new URP_UserProfile();
$oLink->Set('profileid', $iProfileId);
$oLink->Set('reason', 'CAS/LDAP Synchro');
$oProfilesSet->AddObject($oLink);
}
$oUser->Set('profile_list', $oProfilesSet);
phpCAS::log("Info: the user '".$oUser->GetName()."' (id=".$oUser->GetKey().") now has the following profiles: '".implode("', '", $aProfiles)."'.");
if ($oUser->IsModified())
{
$oMyChange = MetaModel::NewObject("CMDBChange");
$oMyChange->Set("date", time());
$oMyChange->Set("userinfo", 'CAS/LDAP Synchro');
$oMyChange->DBInsert();
if ($oUser->IsNew())
{
$oUser->DBInsertTracked($oMyChange);
}
else
{
$oUser->DBUpdateTracked($oMyChange);
}
}
return true;
}
/**
* Helper function to check if the supplied string is a litteral string or a regular expression pattern
* @param string $sCASPattern
* @return bool True if it's a regular expression pattern, false otherwise
*/
protected static function IsPattern($sCASPattern)
{
if ((substr($sCASPattern, 0, 1) == '/') && (substr($sCASPattern, -1) == '/'))
{
// the string is enclosed by slashes, let's assume it's a pattern
return true;
}
else
{
return false;
}
}
}
// By default enable the 'CAS_SelfRegister' defined above
UserRights::SelectSelfRegister('CAS_SelfRegister');
?>

View File

@@ -60,10 +60,12 @@ abstract class ValueSetDefinition
}
if (strlen($sContains) == 0)
{
// No filtering
$aRet = $this->m_aValues;
}
else
{
// Filter on results containing the needle <sContain>
$aRet = array();
foreach ($this->m_aValues as $sKey=>$sValue)
{
@@ -73,6 +75,7 @@ abstract class ValueSetDefinition
}
}
}
// Sort on the display value
asort($aRet);
return $aRet;
}
@@ -88,21 +91,76 @@ abstract class ValueSetDefinition
*/
class ValueSetObjects extends ValueSetDefinition
{
protected $m_sContains;
protected $m_sFilterExpr; // in OQL
protected $m_sValueAttCode;
protected $m_aOrderBy;
protected $m_aExtraConditions;
private $m_bAllowAllData;
private $m_aModifierProperties;
public function __construct($sFilterExp, $sValueAttCode = '', $aOrderBy = array(), $bAllowAllData = false)
public function __construct($sFilterExp, $sValueAttCode = '', $aOrderBy = array(), $bAllowAllData = false, $aModifierProperties = array())
{
$this->m_sContains = '';
$this->m_sFilterExpr = $sFilterExp;
$this->m_sValueAttCode = $sValueAttCode;
$this->m_aOrderBy = $aOrderBy;
$this->m_bAllowAllData = $bAllowAllData;
$this->m_aModifierProperties = $aModifierProperties;
$this->m_aExtraConditions = array();
}
protected function LoadValues($aArgs)
public function SetModifierProperty($sPluginClass, $sProperty, $value)
{
$this->m_aModifierProperties[$sPluginClass][$sProperty] = $value;
}
public function AddCondition(DBObjectSearch $oFilter)
{
$this->m_aExtraConditions[] = $oFilter;
}
public function ToObjectSet($aArgs = array(), $sContains = '')
{
if ($this->m_bAllowAllData)
{
$oFilter = DBObjectSearch::FromOQL_AllData($this->m_sFilterExpr);
}
else
{
$oFilter = DBObjectSearch::FromOQL($this->m_sFilterExpr);
}
foreach($this->m_aExtraConditions as $oExtraFilter)
{
$oFilter->MergeWith($oExtraFilter);
}
foreach($this->m_aModifierProperties as $sPluginClass => $aProperties)
{
foreach ($aProperties as $sProperty => $value)
{
$oFilter->SetModifierProperty($sPluginClass, $sProperty, $value);
}
}
return new DBObjectSet($oFilter, $this->m_aOrderBy, $aArgs);
}
public function GetValues($aArgs, $sContains = '')
{
if (!$this->m_bIsLoaded || ($sContains != $this->m_sContains))
{
$this->LoadValues($aArgs, $sContains);
$this->m_bIsLoaded = true;
}
// The results are already filtered and sorted (on friendly name)
$aRet = $this->m_aValues;
return $aRet;
}
protected function LoadValues($aArgs, $sContains = '')
{
$this->m_sContains = $sContains;
$this->m_aValues = array();
if ($this->m_bAllowAllData)
@@ -114,6 +172,22 @@ class ValueSetObjects extends ValueSetDefinition
$oFilter = DBObjectSearch::FromOQL($this->m_sFilterExpr);
}
if (!$oFilter) return false;
foreach($this->m_aExtraConditions as $oExtraFilter)
{
$oFilter->MergeWith($oExtraFilter);
}
foreach($this->m_aModifierProperties as $sPluginClass => $aProperties)
{
foreach ($aProperties as $sProperty => $value)
{
$oFilter->SetModifierProperty($sPluginClass, $sProperty, $value);
}
}
$oValueExpr = new ScalarExpression('%'.$sContains.'%');
$oNameExpr = new FieldExpression('friendlyname', $oFilter->GetClassAlias());
$oNewCondition = new BinaryExpression($oNameExpr, 'LIKE', $oValueExpr);
$oFilter->AddConditionExpression($oNewCondition);
$oObjects = new DBObjectSet($oFilter, $this->m_aOrderBy, $aArgs);
while ($oObject = $oObjects->Fetch())
@@ -134,6 +208,11 @@ class ValueSetObjects extends ValueSetDefinition
{
return 'Filter: '.$this->m_sFilterExpr;
}
public function GetFilterExpression()
{
return $this->m_sFilterExpr;
}
}
@@ -198,7 +277,7 @@ class ValueSetRelatedObjectsFromLinkSet extends ValueSetDefinition
}
// #@# or AddObjectArray($aObjects) ?
$oSetToCreate = DBObjectSet::FromArray($this->m_sTargetLinkClass, $aLinksToCreate);
$this->m_aValues[$oObject->GetKey()] = $oObject->GetAsHTML($oObject->GetName());
$this->m_aValues[$oObject->GetKey()] = $oObject->GetName();
}
return true;
@@ -225,6 +304,13 @@ class ValueSetEnum extends ValueSetDefinition
$this->m_values = $Values;
}
// Helper to export the datat model
public function GetValueList()
{
$this->LoadValues($aArgs = array());
return $this->m_aValues;
}
protected function LoadValues($aArgs)
{
if (is_array($this->m_values))
@@ -250,6 +336,34 @@ class ValueSetEnum extends ValueSetDefinition
}
}
/**
* Fixed set values, defined as a range: 0..59 (with an optional increment)
*
* @package iTopORM
*/
class ValueSetRange extends ValueSetDefinition
{
protected $m_iStart;
protected $m_iEnd;
public function __construct($iStart, $iEnd, $iStep = 1)
{
$this->m_iStart = $iStart;
$this->m_iEnd = $iEnd;
$this->m_iStep = $iStep;
}
protected function LoadValues($aArgs)
{
$iValue = $this->m_iStart;
for($iValue = $this->m_iStart; $iValue <= $this->m_iEnd; $iValue += $this->m_iStep)
{
$this->m_aValues[$iValue] = $iValue;
}
return true;
}
}
/**
* Data model classes

View File

@@ -2,7 +2,6 @@
body {
font-family: Tahoma, Verdana, Arial, Helvetica;
font-size: 10pt;
background-color: #fff;
color:#000000;
margin: 0; /* Remove body margin/padding */
padding: 0;
@@ -60,6 +59,7 @@ table.listContainer {
padding: 0;
margin:0;
width: 97%;
clear: both;
}
tr.containerHeader, tr.containerHeader td {
@@ -176,6 +176,10 @@ legend {
-webkit-border-radius: 6px;
border-radius: 6px;
}
.ui-widget-content td legend a, .ui-widget-content td legend a:hover, .ui-widget-content td legend a:visited {
color: #fff;
}
.ui-widget-content td a, p a, p a:visited, td a, td a:visited {
text-decoration:none;
color: #1C94C4;
@@ -402,8 +406,10 @@ div.itop_popup {
div.itop_popup > ul {
height:19px;
line-height: 17px;
vertical-align: middle;
display:block;
width:70px; /* Nasty work-around for IE... en attendant mieux */
nowidth:70px; /* Nasty work-around for IE... en attendant mieux */
padding-left: 5px;
background: url(../images/actions_left.png) no-repeat top left;
cursor: pointer;
@@ -414,7 +420,7 @@ div.itop_popup > ul > li {
list-style: none;
font-size: 11px;
font-family: Tahoma,sans-serif;
height: 19px;
height: 17px;
padding-right: 16px;
padding-left: 4px;
background: url(../images/actions_right.png) no-repeat top right transparent;
@@ -1048,4 +1054,61 @@ fieldset table.details>tbody>tr>td {
}
.ac_dlg_loading {
background: white url('../images/indicator.gif') right center no-repeat;
}
table.pagination {
display:inline-block;
}
table.pagination tr td {
padding: 3px;
}
.pager {
float:left;
}
.pager p {
margin-top: 0;
margin-bottom: 0;
}
.pager td span {
min-width: 20px;
display:inline-block;
text-align: center;
cursor: pointer;
}
.pager td span.curr_page {
color: #fff;
background: #999;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
border-radius: 4px;
}
img.prev, img.first, img.next, img.last {
cursor: pointer;
}
div.actions_button {
float:right;
background: url("../images/actions_left.png") no-repeat scroll left top transparent;
padding-left: 5px;
margin-top: 13px;
margin-right: 10px;
height:17px;
vertical-align: middle;
}
div.actions_button a, .actions_button a:hover, .actions_button a:visited {
background:url(../images/actions_bkg.png) no-repeat scroll right top transparent;
color:#fff;
padding-right: 8px;
cursor:pointer;
font-family: Tahoma,sans-serif;
font-size: 11px;
font-weight: bold;
padding-left: 4px;
text-decoration: none;
height:17px;
line-height: 17px;
display: block;
}
select#org_id {
max-width: 90%;
}

15
css/print.css Normal file
View File

@@ -0,0 +1,15 @@
@CHARSET "UTF-8";
#left-pane { display: none; }
span.ui-layout-resizer { display: none; }
#header-logo { display: none; }
#logo { display: none; }
div.header-menu { display:none; }
div.footer { display:none; }
#top-bar { display: none; }
#menu { display: none; }
div.actions_button { display:none; }
div.itop_popup { display:none; }
div.HRDrawer { display:none; }
div.DrawerHandle { display:none; }
a.tab { display:none; }
div.itop-tab { border: #ccc 1px solid; margin-top: 1em; padding-bottom:1em; }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -86,9 +86,36 @@ Dict::Add('EN US', 'English', 'English', array(
'Core:AttributeDateTime' => 'Date/time',
'Core:AttributeDateTime+' => 'Date and time (year-month-day hh:mm:ss)',
'Core:AttributeDateTime?SmartSearch' => '
<p>
Date format:<br/>
<b>yyyy-mm-dd hh:mm:ss</b><br/>
Example: 2011-07-19 18:40:00
</p>
<p>
Operators:<br/>
<b>&gt;</b><em>date</em><br/>
<b>&lt;</b><em>date</em><br/>
<b>[</b><em>date</em>,<em>date</em><b>]</b>
</p>
<p>
If the time is omitted, it defaults to 00:00:00
</p>',
'Core:AttributeDate' => 'Date',
'Core:AttributeDate+' => 'Date (year-month-day)',
'Core:AttributeDate?SmartSearch' => '
<p>
Date format:<br/>
<b>yyyy-mm-dd</b><br/>
Example: 2011-07-19
</p>
<p>
Operators:<br/>
<b>&gt;</b><em>date</em><br/>
<b>&lt;</b><em>date</em><br/>
<b>[</b><em>date</em>,<em>date</em><b>]</b>
</p>',
'Core:AttributeDeadline' => 'Deadline',
'Core:AttributeDeadline+' => 'Date, displayed relatively to the current time',
@@ -113,6 +140,12 @@ Dict::Add('EN US', 'English', 'English', array(
'Core:AttributePropertySet' => 'Properties',
'Core:AttributePropertySet+' => 'List of untyped properties (name and value)',
'Core:AttributeFriendlyName' => 'Friendly name',
'Core:AttributeFriendlyName+' => 'Attribute created automatically ; the friendly name is computed after several attributes',
'Core:FriendlyName-Label' => 'Friendly name',
'Core:FriendlyName-Description' => 'Friendly name',
));
@@ -436,6 +469,15 @@ Dict::Add('EN US', 'English', 'English', array(
'Class:TriggerOnObject/Attribute:target_class+' => '',
));
//
// Class: TriggerOnPortalUpdate
//
Dict::Add('EN US', 'English', 'English', array(
'Class:TriggerOnPortalUpdate' => 'Trigger (when updated from the portal)',
'Class:TriggerOnPortalUpdate+' => 'Trigger on a end-user\'s update from the portal',
));
//
// Class: TriggerOnStateChange
//
@@ -528,6 +570,8 @@ Dict::Add('EN US', 'English', 'English', array(
'Class:SynchroDataSource/Attribute:delete_policy_update+' => 'Syntax: field_name:value; ...',
'Class:SynchroDataSource/Attribute:delete_policy_retention' => 'Retention Duration',
'Class:SynchroDataSource/Attribute:delete_policy_retention+' => 'How much time an obsolete object is kept before being deleted',
'Class:SynchroDataSource/Attribute:database_table_name' => 'Data table',
'Class:SynchroDataSource/Attribute:database_table_name+' => 'Name of the table to store the synchronization data. If left empty, a default name will be computed.',
'SynchroDataSource:Description' => 'Description',
'SynchroDataSource:Reconciliation' => 'Search &amp; reconciliation',
'SynchroDataSource:Deletion' => 'Deletion rules',
@@ -551,7 +595,7 @@ Dict::Add('EN US', 'English', 'English', array(
'Core:Synchro:History' => 'Synchronization History',
'Core:Synchro:NeverRun' => 'This synchro was never run. No log yet.',
'Core:Synchro:SynchroEndedOn_Date' => 'The latest synchronization ended on %1$s.',
'Core:Synchro:SynchroRunningStartedOn_Date' => 'The synchronization started on $1$s is still running...',
'Core:Synchro:SynchroRunningStartedOn_Date' => 'The synchronization started on %1$s is still running...',
'Menu:DataSources' => 'Synchronization Data Sources',
'Menu:DataSources+' => 'All Synchronization Data Sources',
'Core:Synchro:label_repl_ignored' => 'Ignored (%1$s)',
@@ -576,6 +620,7 @@ Dict::Add('EN US', 'English', 'English', array(
'Class:SynchroDataSource/Error:AtLeastOneReconciliationKeyMustBeSpecified' => 'At Least one reconciliation key must be specified, or the reconciliation policy must be to use the primary key.',
'Class:SynchroDataSource/Error:DeleteRetentionDurationMustBeSpecified' => 'A delete retention period must be specified, since objects are to be deleted after being marked as obsolete',
'Class:SynchroDataSource/Error:DeletePolicyUpdateMustBeSpecified' => 'Obsolete objects are to be updated, but no update is specified.',
'Class:SynchroDataSource/Error:DataTableAlreadyExists' => 'The table %1$s already exists in the database. Please use another name for the synchro data table.',
'Core:SynchroReplica:PublicData' => 'Public Data',
'Core:SynchroReplica:PrivateDetails' => 'Private Details',
'Core:SynchroReplica:BackToDataSource' => 'Go Back to the Synchro Data Source: %1$s',
@@ -591,7 +636,7 @@ Dict::Add('EN US', 'English', 'English', array(
'Core:SynchroAtt:update_policy+' => 'Behavior of the updated field',
'Core:SynchroAtt:reconciliation_attcode' => 'Reconciliation Key',
'Core:SynchroAtt:reconciliation_attcode+' => 'Attribute Code for the External Key Reconciliation',
'Core:SyncDataExchangeComment' => '(DataExchange)',
'Core:SyncDataExchangeComment' => '(Data Synchro)',
'Core:Synchro:ListOfDataSources' => 'List of data sources:',
'Core:Synchro:LastSynchro' => 'Last synchronization:',
'Core:Synchro:ThisObjectIsSynchronized' => 'This object is synchronized with an external data source',
@@ -603,7 +648,9 @@ Dict::Add('EN US', 'English', 'English', array(
'Core:SyncDataSourceObsolete' => 'The data source is marked as obsolete. Operation cancelled.',
'Core:SyncDataSourceAccessRestriction' => 'Only adminstrators or the user specified in the data source can execute this operation. Operation cancelled.',
'Core:SyncTooManyMissingReplicas' => 'All records have been untouched for some time (all of the objects could be deleted). Please check that the process that writes into the synchronization table is still running. Operation cancelled.',
'Core:SyncSplitModeCLIOnly' => 'The synchronization can be executed in chunks only if run in mode CLI',
'Core:Synchro:ListReplicas_AllReplicas_Errors_Warnings' => '%1$s replicas, %2$s error(s), %3$s warning(s).',
'Core:SynchroReplica:TargetObject' => 'Synchronized Object: %1$s',
'Class:AsyncSendEmail' => 'Email (asynchronous)',
'Class:AsyncSendEmail/Attribute:to' => 'To',
'Class:AsyncSendEmail/Attribute:subject' => 'Subject',
@@ -681,7 +728,7 @@ Dict::Add('EN US', 'English', 'English', array(
'Class:SynchroReplica/Attribute:sync_source_id' => 'Synchro Data Source',
'Class:SynchroReplica/Attribute:dest_id' => 'Destination object (ID)',
'Class:SynchroReplica/Attribute:dest_class' => 'Destination type',
'Class:SynchroReplica/Attribute:status_last_seen' => 'Lat seen',
'Class:SynchroReplica/Attribute:status_last_seen' => 'Last seen',
'Class:SynchroReplica/Attribute:status' => 'Status',
'Class:SynchroReplica/Attribute:status/Value:modified' => 'Modified',
'Class:SynchroReplica/Attribute:status/Value:new' => 'New',
@@ -690,11 +737,14 @@ Dict::Add('EN US', 'English', 'English', array(
'Class:SynchroReplica/Attribute:status/Value:synchronized' => 'Synchronized',
'Class:SynchroReplica/Attribute:status_dest_creator' => 'Object Created ?',
'Class:SynchroReplica/Attribute:status_last_error' => 'Last Error',
'Class:SynchroReplica/Attribute:status_last_warning' => 'Warnings',
'Class:SynchroReplica/Attribute:info_creation_date' => 'Creation Date',
'Class:SynchroReplica/Attribute:info_last_modified' => 'Last Modified Date',
'Class:appUserPreferences' => 'User Preferences',
'Class:appUserPreferences/Attribute:userid' => 'User',
'Class:appUserPreferences/Attribute:preferences' => 'Prefs',
'Core:ExecProcess:Code1' => 'Wrong command or command finished with errors (e.g. wrong script name)',
'Core:ExecProcess:Code255' => 'PHP Error (parsing, or runtime)',
));
//

View File

@@ -76,6 +76,26 @@ Dict::Add('EN US', 'English', 'English', array(
'Class:AuditRule/Attribute:category_name+' => 'Name of the category for this rule',
));
//
// Class: QueryOQL
//
Dict::Add('EN US', 'English', 'English', array(
'Class:Query' => 'Query',
'Class:Query+' => 'A query is a data set defined in a dynamic way',
'Class:Query/Attribute:name' => 'Name',
'Class:Query/Attribute:name+' => 'Identifies the query',
'Class:Query/Attribute:description' => 'Description',
'Class:Query/Attribute:description+' => 'Long description for the query (purpose, usage, etc.)',
'Class:Query/Attribute:fields' => 'Fields',
'Class:Query/Attribute:fields+' => 'Coma separated list of attributes (or alias.attribute) to export',
'Class:QueryOQL' => 'OQL Query',
'Class:QueryOQL+' => 'A query based on the Object Query Language',
'Class:QueryOQL/Attribute:oql' => 'Expression',
'Class:QueryOQL/Attribute:oql+' => 'OQL Expression',
));
//////////////////////////////////////////////////////////////////////
// Classes in 'addon/userrights'
//////////////////////////////////////////////////////////////////////
@@ -367,7 +387,7 @@ Dict::Add('EN US', 'English', 'English', array(
'UI:Error:IncorrectLinkDefinition_LinkedClass_Class' => 'Incorrect link definition: the class of objects to manage: %1$s was not found as an external key in the class %2$s',
'UI:Error:Object_Class_Id_NotFound' => 'Object: %1$s:%2$d not found.',
'UI:Error:WizardCircularReferenceInDependencies' => 'Error: Circular reference in the dependencies between the fields, check the data model.',
'UI:Error:UploadedFileTooBig' => 'Uploaded file is too big. (Max allowed size is %1$s). Check you PHP configuration for upload_max_filesize and post_max_size.',
'UI:Error:UploadedFileTooBig' => 'The uploaded file is too big. (Max allowed size is %1$s). To modify this limit, contact your iTop administrator. (Check the PHP configuration for upload_max_filesize and post_max_size on the server).',
'UI:Error:UploadedFileTruncated.' => 'Uploaded file has been truncated !',
'UI:Error:NoTmpDir' => 'The temporary directory is not defined.',
'UI:Error:CannotWriteToTmp_Dir' => 'Unable to write the temporary file to the disk. upload_tmp_dir = "%1$s".',
@@ -416,6 +436,7 @@ Dict::Add('EN US', 'English', 'English', array(
'UI:History:StatsDeletes+' => 'Count of objects deleted',
'UI:Loading' => 'Loading...',
'UI:Menu:Actions' => 'Actions',
'UI:Menu:OtherActions' => 'Other Actions',
'UI:Menu:New' => 'New...',
'UI:Menu:Add' => 'Add...',
'UI:Menu:Manage' => 'Manage...',
@@ -428,6 +449,8 @@ Dict::Add('EN US', 'English', 'English', array(
'UI:UndefinedObject' => 'undefined',
'UI:Document:OpenInNewWindow:Download' => 'Open in new window: %1$s, Download: %2$s',
'UI:SelectAllToggle+' => 'Select / Deselect All',
'UI:SplitDateTime-Date' => 'date',
'UI:SplitDateTime-Time' => 'time',
'UI:TruncatedResults' => '%1$d objects displayed out of %2$d',
'UI:DisplayAll' => 'Display All',
'UI:CollapseList' => 'Collapse',
@@ -447,6 +470,7 @@ Dict::Add('EN US', 'English', 'English', array(
'UI:Login:IdentifyYourself' => 'Identify yourself before continuing',
'UI:Login:UserNamePrompt' => 'User Name',
'UI:Login:PasswordPrompt' => 'Password',
'UI:Login:About' => '',
'UI:Login:ChangeYourPassword' => 'Change Your Password',
'UI:Login:OldPasswordPrompt' => 'Old password',
'UI:Login:NewPasswordPrompt' => 'New password',
@@ -531,7 +555,9 @@ Dict::Add('EN US', 'English', 'English', array(
'UI:Audit:HeaderNbObjects' => '# Objects',
'UI:Audit:HeaderNbErrors' => '# Errors',
'UI:Audit:PercentageOk' => '% Ok',
'UI:Audit:ErrorIn_Rule_Reason' => 'OQL Error in the Rule %1$s: %2$s.',
'UI:Audit:ErrorIn_Category_Reason' => 'OQL Error in the Category %1$s: %2$s.',
'UI:RunQuery:Title' => 'iTop - OQL Query Evaluation',
'UI:RunQuery:QueryExamples' => 'Query Examples',
'UI:RunQuery:HeaderPurpose' => 'Purpose',
@@ -543,7 +569,7 @@ Dict::Add('EN US', 'English', 'English', array(
'UI:RunQuery:DevelopedQuery' => 'Redevelopped query expression: ',
'UI:RunQuery:SerializedFilter' => 'Serialized filter: ',
'UI:RunQuery:Error' => 'An error occured while running the query: %1$s',
'UI:Query:UrlForExcel' => 'URL to use for MS-Excel web queries',
'UI:Schema:Title' => 'iTop objects schema',
'UI:Schema:CategoryMenuItem' => 'Category <b>%1$s</b>',
'UI:Schema:Relationships' => 'Relationships',
@@ -603,8 +629,8 @@ Dict::Add('EN US', 'English', 'English', array(
'UI:Schema:LifeCycleAttributeMustChange' => 'Must change',
'UI:Schema:LifeCycleAttributeMustPrompt' => 'User will be prompted to change the value',
'UI:Schema:LifeCycleEmptyList' => 'empty list',
'UI:LinksWidget:Autocomplete+' => 'Type the first 3 characters...',
'UI:Edit:TestQuery' => 'Test query',
'UI:Combo:SelectValue' => '--- select a value ---',
'UI:Label:SelectedObjects' => 'Selected objects: ',
'UI:Label:AvailableObjects' => 'Available objects: ',
@@ -617,7 +643,6 @@ Dict::Add('EN US', 'English', 'English', array(
'UI:RemoveLinkedObjectsOf_Class' => 'Remove selected objects',
'UI:Message:EmptyList:UseAdd' => 'The list is empty, use the "Add..." button to add elements.',
'UI:Message:EmptyList:UseSearchForm' => 'Use the search form above to search for objects to be added.',
'UI:Wizard:FinalStepTitle' => 'Final step: confirmation',
'UI:Title:DeletionOf_Object' => 'Deletion of %1$s',
'UI:Title:BulkDeletionOf_Count_ObjectsOf_Class' => 'Bulk deletion of %1$d objects of class %2$s',
@@ -820,6 +845,9 @@ When associated with a trigger, each action is given an "order" number, specifyi
'Menu:RunQueriesMenu' => 'Run Queries',
'Menu:RunQueriesMenu+' => 'Run any query',
'Menu:QueryMenu' => 'Query phrasebook',
'Menu:QueryMenu+' => 'Query phrasebook',
'Menu:DataAdministration' => 'Data administration',
'Menu:DataAdministration+' => 'Data administration',
@@ -861,14 +889,19 @@ When associated with a trigger, each action is given an "order" number, specifyi
'UI:RelationshipGraph' => 'Graphical view',
'UI:RelationshipList' => 'List',
'UI:OperationCancelled' => 'Operation Cancelled',
'UI:ElementsDisplayed' => 'Filtering',
'Portal:Title' => 'iTop user portal',
'Portal:Refresh' => 'Refresh',
'Portal:Back' => 'Back',
'Portal:WelcomeUserOrg' => 'Welcome %1$s, from %2$s',
'Portal:ShowOngoing' => 'Show open requests',
'Portal:ShowClosed' => 'Show closed requests',
'Portal:CreateNewRequest' => 'Create a new request',
'Portal:ChangeMyPassword' => 'Change my password',
'Portal:Disconnect' => 'Disconnect',
'Portal:OpenRequests' => 'My open requests',
'Portal:ClosedRequests' => 'My closed requests',
'Portal:ResolvedRequests' => 'My resolved requests',
'Portal:SelectService' => 'Select a service from the catalog:',
'Portal:PleaseSelectOneService' => 'Please select one service',
@@ -876,8 +909,11 @@ When associated with a trigger, each action is given an "order" number, specifyi
'Portal:PleaseSelectAServiceSubCategory' => 'Please select one sub-category',
'Portal:DescriptionOfTheRequest' => 'Enter the description of your request:',
'Portal:TitleRequestDetailsFor_Request' => 'Details for request %1$s:',
'Portal:NoOpenRequest' => 'No request in this category.',
'Portal:NoOpenRequest' => 'No request in this category',
'Portal:NoClosedRequest' => 'No request in this category',
'Portal:Button:ReopenTicket' => 'Reopen this ticket',
'Portal:Button:CloseTicket' => 'Close this ticket',
'Portal:Button:UpdateRequest' => 'Update the request',
'Portal:EnterYourCommentsOnTicket' => 'Enter your comments about the resolution of this ticket:',
'Portal:ErrorNoContactForThisUser' => 'Error: the current user is not associated with a Contact/Person. Please contact your administrator.',
'Portal:Attachments' => 'Attachments',
@@ -915,5 +951,19 @@ When associated with a trigger, each action is given an "order" number, specifyi
'UI:ActionNotAllowed' => 'You are not allowed to perform this action on these objects.',
'UI:BulkAction:NoObjectSelected' => 'Please select at least one object to perform this operation',
'UI:AttemptingToChangeASlaveAttribute_Name' => 'The field %1$s is not writable because it is mastered by the data synchronization. Value remains unchanged.',
'UI:Pagination:HeaderSelection' => 'Total: %1$s objects (%2$s objects selected).',
'UI:Pagination:HeaderNoSelection' => 'Total: %1$s objects.',
'UI:Pagination:PageSize' => '%1$s objects per page',
'UI:Pagination:PagesLabel' => 'Pages:',
'UI:Pagination:All' => 'All',
'UI:HierarchyOf_Class' => 'Hierarchy of %1$s',
'UI:Preferences' => 'Preferences...',
'UI:FavoriteOrganizations' => 'My Favorite Organizations',
'UI:FavoriteOrganizations+' => 'Check in the list below the organizations that you want to see in the drop-down menu for a quick access. '.
'Note that this is not a security setting, objects from any organization are still visible and can be accessed by selecting "All Organizations" in the drop-down list.',
'UI:NavigateAwayConfirmationMessage' => 'Any modification will be discarded.',
'UI:Create_Class_InState' => 'Create the %1$s in state: ',
'UI:Button:Refresh' => 'Refresh',
'UI:iTopLogin' => 'iTop Login',
));
?>

View File

@@ -29,7 +29,7 @@
//////////////////////////////////////////////////////////////////////
//
Dict::Add('ES CR', 'English', 'English', array(
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
'Core:AttributeLinkedSet' => 'Array of objects',
'Core:AttributeLinkedSet+' => 'Any kind of objects [subclass] of the same class',

View File

@@ -414,6 +414,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
'UI:History:Changes+' => 'Chambios hechos al objeto',
'UI:Loading' => 'Cargando...',
'UI:Menu:Actions' => 'Acciones',
'UI:Menu:OtherActions' => 'Otras Acciones',
'UI:Menu:New' => 'Nuevo...',
'UI:History:StatsCreations' => 'Created',
'UI:History:StatsCreations+' => 'Count of objects created',

View File

@@ -18,7 +18,7 @@
* @author Erwan Taloc <erwan.taloc@combodo.com>
* @author Romain Quetiez <romain.quetiez@combodo.com>
* @author Denis Flaven <denis.flaven@combodo.com>
* @licence http://www.opensource.org/licenses/gpl-3.0.html LGPL
* @licence http://www.opensource.org/licenses/gpl-3.0.html LGPL
*/
Dict::Add('FR FR', 'French', 'Français', array(
@@ -48,9 +48,11 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Class:ActionEmail/Attribute:importance/Value:low+' => '',
'Class:ActionEmail/Attribute:importance/Value:normal' => 'Normale',
'Class:ActionEmail/Attribute:importance/Value:normal+' => '',
'Class:TriggerOnPortalUpdate' => 'Déclencheur sur mise à jour depuis le portail',
'Class:TriggerOnPortalUpdate+' => '',
'Class:TriggerOnStateEnter' => 'Déclencheur sur un objet entrant dans un état',
'Class:TriggerOnStateEnter+' => '',
'Class:TriggerOnStateLeave' => 'Déclencheur sur un objet quitant un état',
'Class:TriggerOnStateLeave' => 'Déclencheur sur un objet quittant un état',
'Class:TriggerOnStateLeave+' => '',
'Class:TriggerOnObjectCreate' => 'Déclencheur sur la création d\'un objet',
'Class:TriggerOnObjectCreate+' => '',
@@ -278,6 +280,8 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Class:SynchroDataSource/Attribute:url_icon+' => 'Hyperlien vers une icône représentant l\'application source des données',
'Class:SynchroDataSource/Attribute:url_application' => 'Application (hyperlien)',
'Class:SynchroDataSource/Attribute:url_application+' => 'Un hyperlien vers l\'application source des données. Paramètres possibles: $this->nom_de_champ$ et $replica->primary_key$',
'Class:SynchroDataSource/Attribute:database_table_name' => 'Table de données',
'Class:SynchroDataSource/Attribute:database_table_name+' => 'Nom de la table stockant les données de cette source. Un nom par défaut est calculé automatiquement si ce champ est laissé vide.',
'Class:SynchroAttribute' => 'Champs de synchronisation',
'Class:SynchroAttribute+' => '',
'Class:SynchroAttribute/Attribute:sync_source_id' => 'Source de données',
@@ -384,6 +388,8 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Class:SynchroReplica/Attribute:status_dest_creator+' => '',
'Class:SynchroReplica/Attribute:status_last_error' => 'Dernière erreur',
'Class:SynchroReplica/Attribute:status_last_error+' => '',
'Class:SynchroReplica/Attribute:status_last_warning' => 'Avertissements',
'Class:SynchroReplica/Attribute:status_last_warning+' => '',
'Class:SynchroReplica/Attribute:info_creation_date' => 'Date de création',
'Class:SynchroReplica/Attribute:info_creation_date+' => '',
'Class:SynchroReplica/Attribute:info_last_modified' => 'Date de dernière modification',
@@ -436,8 +442,35 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Core:AttributeTemplateHTML+' => 'HTML contenant des espaces réservés pour des données iTop',
'Core:AttributeDateTime' => 'Date/heure',
'Core:AttributeDateTime+' => 'Date et heure (année-mois-jour hh:mm:ss)',
'Core:AttributeDateTime?SmartSearch' => '
<p>
Format de date :<br/>
<b>aaaa-mm-jj hh:mm:ss</b><br/>
Exemple : 2011-07-19 18:40:00
</p>
<p>
Opérateurs :<br/>
<b>&gt;</b><em>date</em><br/>
<b>&lt;</b><em>date</em><br/>
<b>[</b><em>date</em>,<em>date</em><b>]</b>
</p>
<p>
Si l\'heure n\'est pas spécifiée, cela revient à 00:00:00 (minuit)
</p>',
'Core:AttributeDate' => 'Date',
'Core:AttributeDate+' => 'Date (année-mois-jour)',
'Core:AttributeDate?SmartSearch' => '
<p>
Format de date :<br/>
<b>aaaa-mm-jj</b><br/>
Exemple : 2011-07-19
</p>
<p>
Opérateurs :<br/>
<b>&gt;</b><em>date</em><br/>
<b>&lt;</b><em>date</em><br/>
<b>[</b><em>date</em>,<em>date</em><b>]</b>
</p>',
'Core:AttributeDeadline' => 'Délai',
'Core:AttributeDeadline+' => 'Date/heure exprimée relativement à l\'heure courante',
'Core:AttributeExternalKey' => 'Clé externe',
@@ -454,6 +487,13 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Core:AttributeTable+' => 'Tableau à deux dimensions',
'Core:AttributePropertySet' => 'Propriétés',
'Core:AttributePropertySet+' => 'Liste de propriétés (nom et valeur) non typées',
'Core:AttributeFriendlyName' => 'Nom usuel (convivial)',
'Core:AttributeFriendlyName+' => 'Attribut créé automatiquement ; sa valeur est calculée d\'après d\'autres attributs',
'Core:FriendlyName-Label' => 'Nom',
'Core:FriendlyName-Description' => 'Nom usuel',
'Change:ObjectCreated' => 'Elément créé',
'Change:ObjectDeleted' => 'Elément effacé',
'Change:ObjectModified' => 'Elément modifié',
@@ -483,7 +523,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Class:Action/Attribute:finalclass+' => '',
'Class:ActionNotification' => 'notification',
'Class:ActionNotification+' => '',
'Class:Trigger' => 'trigger',
'Class:Trigger' => 'Déclencheur',
'Class:Trigger+' => '',
'Class:Trigger/Attribute:description' => 'Description',
'Class:Trigger/Attribute:description+' => '',
@@ -525,7 +565,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Core:Synchro:History' => 'Historique de synchronisation',
'Core:Synchro:NeverRun' => 'Aucun historique, la synchronisation n\'a pas encore fonctionné',
'Core:Synchro:SynchroEndedOn_Date' => 'La dernière synchronisation s\'est terminée à: %1$s.',
'Core:Synchro:SynchroRunningStartedOn_Date' => 'Synchronisation en cours (début à $1$s)',
'Core:Synchro:SynchroRunningStartedOn_Date' => 'Synchronisation en cours (début à %1$s)',
'Menu:DataSources' => 'Synchronisation',
'Menu:DataSources+' => '',
'Core:Synchro:label_repl_ignored' => 'Ignorés (%1$s)',
@@ -549,6 +589,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Class:SynchroDataSource/Error:AtLeastOneReconciliationKeyMustBeSpecified' => 'Si la politique de réconciliation n\'est pas la clé primaire, au moins une clé de recherche doit être spécifiée',
'Class:SynchroDataSource/Error:DeleteRetentionDurationMustBeSpecified' => 'Pour que les objets soient effacés après avoir été obsoletés, il faut spécifier une durée de rétention',
'Class:SynchroDataSource/Error:DeletePolicyUpdateMustBeSpecified' => 'Les objets obsolètes doivent être mis à jour, mais aucune information de mise à jour n\'est spécifiée',
'Class:SynchroDataSource/Error:DataTableAlreadyExists' => 'La table %1$s existe déjà dans la base de données. Veuillez utiliser un autre nom pour la table des données de cette source.',
'Core:SynchroReplica:PublicData' => 'Données synchronisées',
'Core:SynchroReplica:PrivateDetails' => 'Informations internes',
'Core:SynchroReplica:BackToDataSource' => 'Retourner aux détails de la source de données: %1$s',
@@ -564,7 +605,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Core:SynchroAtt:update_policy+' => '',
'Core:SynchroAtt:reconciliation_attcode' => 'Clé de recherche',
'Core:SynchroAtt:reconciliation_attcode+' => '',
'Core:SyncDataExchangeComment' => '(Synhcronisation)',
'Core:SyncDataExchangeComment' => '(Synchronisation)',
'Core:Synchro:ListOfDataSources' => 'Sources de données:',
'Core:Synchro:LastSynchro' => 'Dernière synchronisation:',
'Core:Synchro:ThisObjectIsSynchronized' => 'Cet objet est synchronisé avec une source de données',
@@ -576,6 +617,8 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Core:SyncDataSourceObsolete' => 'Cette source de données est obsolète. Opération annulée.',
'Core:SyncDataSourceAccessRestriction' => 'Seuls les administrateurs et l\'utilisateur spécifié dans la source de données peuvent exécuter cette synchronisation. Opération annulée.',
'Core:SyncTooManyMissingReplicas' => 'Tous les réplicas sont absents de l\'import. L\'import a-t-il réellement tourné. Opération annulée.',
'Core:Synchro:ListReplicas_AllReplicas_Errors_Warnings' => '%1$s replicas, %2$s erreur(s), %3$s avertissement(s).',
'Core:SynchroReplica:TargetObject' => 'Objet Synchronisé : %1$s',
'Core:Duration_Seconds' => '%1$ds',
'Core:Duration_Minutes_Seconds' => '%1$dmin %2$ds',
'Core:Duration_Hours_Minutes_Seconds' => '%1$dh %2$dmin %3$ds',

View File

@@ -50,6 +50,18 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Class:AuditCategory/Attribute:definition_set+' => 'Expression OQL qui défini le périmètre d\'application de l\'audit',
'Class:AuditCategory/Attribute:rules_list' => 'Règles d\'audit',
'Class:AuditCategory/Attribute:rules_list+' => 'Règles d\'audit pour cette catégorie',
'Class:Query' => 'Requête',
'Class:Query+' => 'Une requête définit un ensemble d\'information de manière dynamique',
'Class:Query/Attribute:name' => 'Nom',
'Class:Query/Attribute:name+' => 'Identification de la requête',
'Class:Query/Attribute:description' => 'Description',
'Class:Query/Attribute:description+' => 'Description complète (finalité, utilisations, public)',
'Class:Query/Attribute:fields' => 'Champs',
'Class:Query/Attribute:fields+' => 'Liste CSV des attributs (ou alias.attribut) à exporter',
'Class:QueryOQL' => 'Requête OQL',
'Class:QueryOQL+' => 'Une requête écrite dans le langage "Object Query Language"',
'Class:QueryOQL/Attribute:oql' => 'Expression',
'Class:QueryOQL/Attribute:oql+' => 'Expression OQL',
'Class:URP_Profiles' => 'Profil',
'Class:URP_Profiles+' => 'Profil utilisateur',
'Class:URP_Profiles/Attribute:name' => 'Nom',
@@ -71,7 +83,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Class:URP_UserProfile/Attribute:reason' => 'Raison',
'Class:URP_UserProfile/Attribute:reason+' => 'Justifie le rôle affecté à cet utilisateur',
'Class:URP_UserOrg' => 'Utilisateur/Organisation',
'Class:URP_UserOrg+' => 'Organizations permises pour l\'utilisateur',
'Class:URP_UserOrg+' => 'Organisations permises pour l\'utilisateur',
'Class:URP_UserOrg/Attribute:userid' => 'Utilisateur',
'Class:URP_UserOrg/Attribute:userid+' => '',
'Class:URP_UserOrg/Attribute:userlogin' => 'Login',
@@ -215,7 +227,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
'UI:WelcomeMenu:OpenIncidents' => 'Incidents en cours: %1$d',
'UI:WelcomeMenu:AllConfigItems' => 'Actifs: %1$d',
'UI:WelcomeMenu:MyIncidents' => 'Mes Incidents',
'UI:AllOrganizations' => ' Toutes les Organizations ',
'UI:AllOrganizations' => ' Toutes les Organisations ',
'UI:YourSearch' => 'Votre recherche',
'UI:LoggedAsMessage' => 'Connecté comme: %1$s',
'UI:LoggedAsMessage+Admin' => 'Connecté comme: %1$s (Administrateur)',
@@ -257,7 +269,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
'UI:Error:IncorrectLinkDefinition_LinkedClass_Class' => 'la définition du lien est incorrecte: la classe d\'objets à gérer: %1$s n\'est référencée par aucune clef externe de la classe %2$s',
'UI:Error:Object_Class_Id_NotFound' => 'L\'objet: %1$s:%2$d est introuvable.',
'UI:Error:WizardCircularReferenceInDependencies' => 'Erreur: Référence circulaire entre les dépendences entre champs, vérifiez le modèle de données.',
'UI:Error:UploadedFileTooBig' => 'Le fichier téléchargé est trop gros. (La taille maximale autorisée est %1$s). Vérifiez la valeur des variables upload_max_filesize et post_max_size dans la configuration PHP.',
'UI:Error:UploadedFileTooBig' => 'Le fichier téléchargé est trop gros. (La taille maximale autorisée est %1$s). Pour modifier cette limite contactez votre administrateur iTop. (Réglages upload_max_filesize et post_max_size dans la configuration PHP sur le serveur)',
'UI:Error:UploadedFileTruncated.' => 'Le fichier téléchargé a été tronqué !',
'UI:Error:NoTmpDir' => 'Il n\'y a aucun répertoire temporaire de défini.',
'UI:Error:CannotWriteToTmp_Dir' => 'Impossible d\'écrire le fichier temporaire sur disque. upload_tmp_dir = "%1$s".',
@@ -303,6 +315,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
'UI:History:StatsDeletes+' => 'Nombre d\'objets effacés',
'UI:Loading' => 'Chargement...',
'UI:Menu:Actions' => 'Actions',
'UI:Menu:OtherActions' => 'Autres Actions',
'UI:Menu:New' => 'Créer...',
'UI:Menu:Add' => 'Ajouter...',
'UI:Menu:Manage' => 'Gérer...',
@@ -315,6 +328,8 @@ Dict::Add('FR FR', 'French', 'Français', array(
'UI:Document:OpenInNewWindow:Download' => 'Ouvrir dans un nouvelle fenêtre: %1$s, Télécharger: %2$s',
'UI:SelectAllToggle+' => 'Tout sélectionner / Tout déselectionner',
'UI:TruncatedResults' => '%1$d objets affichés sur %2$d',
'UI:SplitDateTime-Date' => 'date',
'UI:SplitDateTime-Time' => 'heure',
'UI:DisplayAll' => 'Tout afficher',
'UI:CollapseList' => 'Refermer',
'UI:CountOfResults' => '%1$d objet(s)',
@@ -415,6 +430,8 @@ Dict::Add('FR FR', 'French', 'Français', array(
'UI:Audit:HeaderNbObjects' => 'Nb d\'Objets',
'UI:Audit:HeaderNbErrors' => 'Nb d\'Erreurs',
'UI:Audit:PercentageOk' => '% Ok',
'UI:Audit:ErrorIn_Rule_Reason' => 'Erreur OQL dans la règle %1$s: %2$s.',
'UI:Audit:ErrorIn_Category_Reason' => 'Erreur OQL dans la catégorie %1$s: %2$s.',
'UI:RunQuery:Title' => 'iTop - Evaluation de requêtes OQL',
'UI:RunQuery:QueryExamples' => 'Exemples de requêtes',
'UI:RunQuery:HeaderPurpose' => 'Objectif',
@@ -426,6 +443,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
'UI:RunQuery:DevelopedQuery' => 'Requête OQL décompilée : ',
'UI:RunQuery:SerializedFilter' => 'Version sérialisée : ',
'UI:RunQuery:Error' => 'Une erreur s\'est produite durant l\'exécution de la requête : %1$s',
'UI:Query:UrlForExcel' => 'Lien à copier-coller dans Excel, pour déclarer une source de données à partir du web',
'UI:Schema:Title' => 'Modèle de données iTop',
'UI:Schema:CategoryMenuItem' => 'Catégorie <b>%1$s</b>',
'UI:Schema:Relationships' => 'Relations',
@@ -485,6 +503,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
'UI:Schema:LifeCycleAttributeMustPrompt' => 'L\'utilisateur se verra proposer de changer la valeur',
'UI:Schema:LifeCycleEmptyList' => 'liste vide',
'UI:LinksWidget:Autocomplete+' => 'Tapez les 3 premiers caractères...',
'UI:Edit:TestQuery' => 'Tester la requête',
'UI:Combo:SelectValue' => '--- choisissez une valeur ---',
'UI:Label:SelectedObjects' => 'Objets sélectionnés: ',
'UI:Label:AvailableObjects' => 'Objets disponibles: ',
@@ -679,6 +698,8 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé
'Menu:Notifications:Title' => 'Catégories d\'audit',
'Menu:RunQueriesMenu' => 'Requêtes OQL',
'Menu:RunQueriesMenu+' => 'Executer une requête OQL',
'Menu:QueryMenu' => 'Livre des requêtes',
'Menu:QueryMenu+' => 'Livre des requêtes',
'Menu:DataAdministration' => 'Administration des données',
'Menu:DataAdministration+' => 'Administration des données',
'Menu:UniversalSearchMenu' => 'Recherche Universelle',
@@ -697,7 +718,7 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé
'UI:iTopVersion:Short' => 'iTop version %1$s',
'UI:iTopVersion:Long' => 'iTop version %1$s-%2$s du %3$s',
'UI:PropertiesTab' => 'Propriétés',
'UI:OpenDocumentInNewWindow_' => 'Ouvrir de document dans un autre fenêtre: %1$s',
'UI:OpenDocumentInNewWindow_' => 'Ouvrir ce document dans uns autre fenêtre: %1$s',
'UI:DownloadDocument_' => 'Télécharger ce document: %1$s',
'UI:Document:NoPreview' => 'L\'aperçu n\'est pas disponible pour ce type de documents',
'UI:DeadlineMissedBy_duration' => 'Passé de %1$s',
@@ -711,14 +732,19 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé
'UI:DisplayThisMessageAtStartup' => 'Afficher ce message au démarrage',
'UI:RelationshipGraph' => 'Vue graphique',
'UI:RelationshipList' => 'Liste',
'UI:ElementsDisplayed' => 'Filtrage',
'UI:OperationCancelled' => 'Opération Annulée',
'Portal:Title' => 'Portail utilisateur iTop',
'Portal:Refresh' => 'Rafraîchir',
'Portal:Back' => 'Retour',
'Portal:WelcomeUserOrg' => 'Bienvenue %1$s (%2$s)',
'Portal:ShowOngoing' => 'Requêtes en cours',
'Portal:ShowClosed' => 'Requêtes fermées',
'Portal:CreateNewRequest' => 'Créer une nouvelle requête',
'Portal:ChangeMyPassword' => 'Changer mon mot de passe',
'Portal:Disconnect' => 'Déconnexion',
'Portal:OpenRequests' => 'Mes requêtes en cours',
'Portal:ClosedRequests' => 'Mes requêtes fermées',
'Portal:ResolvedRequests' => 'Mes requêtes résolues',
'Portal:SelectService' => 'Choisissez un service dans le catalogue:',
'Portal:PleaseSelectOneService' => 'Veuillez choisir un service',
@@ -727,7 +753,10 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé
'Portal:DescriptionOfTheRequest' => 'Entrez la description de votre requête:',
'Portal:TitleRequestDetailsFor_Request' => 'Détails de votre requête %1$s:',
'Portal:NoOpenRequest' => 'Aucune requête.',
'Portal:NoClosedRequest' => 'Aucune requête.',
'Portal:Button:ReopenTicket' => 'Réouvrir cette requête',
'Portal:Button:CloseTicket' => 'Clôre cette requête',
'Portal:Button:UpdateRequest' => 'Mettre à jour la requête',
'Portal:EnterYourCommentsOnTicket' => 'Vos commentaires à propos du traitement de cette requête:',
'Portal:ErrorNoContactForThisUser' => 'Erreur: l\'utilisateur courant n\'est pas associé à une Personne/Contact. Contactez votre administrateur.',
'Portal:Attachments' => 'Pièces jointes',
@@ -765,5 +794,18 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé
'UI:ActionNotAllowed' => 'Vous n\'êtes pas autorisé à exécuter cette opération sur ces objets.',
'UI:BulkAction:NoObjectSelected' => 'Veuillez s\électionner au moins un objet pour cette opération.',
'UI:AttemptingToChangeASlaveAttribute_Name' => 'Le champ %1$s ne peut pas être modifié car il est géré par une synchronisation avec une source de données. Valeur inchangée.',
'UI:Pagination:HeaderSelection' => 'Total: %1$s éléments / %2$s éléments sélectionné(s).',
'UI:Pagination:HeaderNoSelection' => 'Total: %1$s éléments.',
'UI:Pagination:PageSize' => '%1$s éléments par page',
'UI:Pagination:PagesLabel' => 'Pages:',
'UI:Pagination:All' => 'Tous',
'UI:HierarchyOf_Class' => 'Hiérarchie de type %1$s',
'UI:Preferences' => 'Preferences...',
'UI:FavoriteOrganizations' => 'Mes Organisations Favorites',
'UI:FavoriteOrganizations+' => 'Cochez dans la liste ci-dessous les organisations que vous voulez voir listées dans le menu principal. '.
'Ceci n\'est pas un réglage de sécurité. Les objets de toutes les organisations sont toujours visibles en choisissant "Toutes les Organisations" dans le menu.',
'UI:NavigateAwayConfirmationMessage' => 'Toute modification sera perdue.',
'UI:Create_Class_InState' => 'Créer l\'objet %1$s dans l\'état: ',
'UI:Button:Refresh' => 'Rafraîchir',
));
?>

View File

@@ -422,7 +422,7 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', array(
'Core:Synchro:History' => 'Szinkronizáció történet',
'Core:Synchro:NeverRun' => 'Ez a szinkronizáció még soha nem futott. Nincs még napló bejegyzés.',
'Core:Synchro:SynchroEndedOn_Date' => 'Az utolsó szinkronizáció lefutásának időpontja: %1$s.',
'Core:Synchro:SynchroRunningStartedOn_Date' => 'Az szinkronizáció elindut $1$s, de még fut.',
'Core:Synchro:SynchroRunningStartedOn_Date' => 'Az szinkronizáció elindut %1$s, de még fut.',
'Menu:DataSources' => 'Szinkronizált adatforrások',
'Menu:DataSources+' => '',
'Core:Synchro:label_repl_ignored' => 'Figyelmen kívül hagyott (%1$s)',

View File

@@ -302,6 +302,7 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', array(
'UI:History:StatsDeletes+' => '',
'UI:Loading' => 'Betöltés...',
'UI:Menu:Actions' => 'Akciók',
'UI:Menu:OtherActions' => 'Egyéb Akciók',
'UI:Menu:New' => 'Új...',
'UI:Menu:Add' => 'Hozzáad...',
'UI:Menu:Manage' => 'Kezel...',
@@ -714,10 +715,14 @@ Akció kiváltó okhoz rendelésekor kap egy sorszámot , amely meghatározza az
'Portal:Title' => 'iTop felhasználói portál',
'Portal:Refresh' => 'Frissítés',
'Portal:Back' => 'Vissza',
'Portal:WelcomeUserOrg' => 'Welcome %1$s, from %2$s',
'Portal:ShowOngoing' => 'Show open requests',
'Portal:ShowClosed' => 'Show closed requests',
'Portal:CreateNewRequest' => 'Új kérés létrehozása',
'Portal:ChangeMyPassword' => 'Jelszó változtatás',
'Portal:Disconnect' => 'Kilépés',
'Portal:OpenRequests' => 'Nyitott kéréseim',
'Portal:ClosedRequests' => 'My closed requests',
'Portal:ResolvedRequests' => 'Megoldott kéréseim',
'Portal:SelectService' => 'Válasszon szolgáltatást a katalógusból:',
'Portal:PleaseSelectOneService' => 'Kérem válasszon egy szolgáltatást',
@@ -726,7 +731,10 @@ Akció kiváltó okhoz rendelésekor kap egy sorszámot , amely meghatározza az
'Portal:DescriptionOfTheRequest' => 'Adja meg a kérés leírásást:',
'Portal:TitleRequestDetailsFor_Request' => '%1$s kérés részletei:',
'Portal:NoOpenRequest' => 'A kategóriához nem tartozik nyitott kérés.',
'Portal:NoClosedRequest' => 'No request in this category',
'Portal:Button:ReopenTicket' => 'Reopen this ticket',
'Portal:Button:CloseTicket' => 'Hibajegy lezárása',
'Portal:Button:UpdateRequest' => 'Update the request',
'Portal:EnterYourCommentsOnTicket' => 'Adjon megjegyzést a megoldáshoz:',
'Portal:ErrorNoContactForThisUser' => 'Hiba: az aktuális felhasználó nem tartozik egyetlen Kapcsolattartóhoz / Szemályhez sem. Kérem vegye felk a kapcsolatot az adminisztrátorral.',
'Portal:Attachments' => 'Csatolmányok',
@@ -764,5 +772,6 @@ Akció kiváltó okhoz rendelésekor kap egy sorszámot , amely meghatározza az
'UI:ActionNotAllowed' => 'Ennek a műveletnek a végrehajtása nem engedélyezett ezen az objektumon.',
'UI:BulkAction:NoObjectSelected' => 'Válasszon ki legalább egy objketumot a művelet végrehajtásához',
'UI:AttemptingToChangeASlaveAttribute_Name' => '%1$s mező nem írható, mert a szinkronizációnál használt kulcs. Érték változatlan maradt.',
'UI:Button:Refresh' => 'Frissítés',
));
?>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,483 @@
<?php
// Copyright (C) 2010 Combodo SARL
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; version 3 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
/**
* Localized data
*
* @author Erwan Taloc <erwan.taloc@combodo.com>
* @author Romain Quetiez <romain.quetiez@combodo.com>
* @author Denis Flaven <denis.flaven@combodo.com>
* @author Tadashi Kaneda <kaneda@smartec.co.jp>
* @license http://www.opensource.org/licenses/gpl-3.0.html LGPL
*/
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Core:AttributeLinkedSet' => 'オブジェクト配列',
'Core:AttributeLinkedSet+' => '同一あるいはサブクラスに属するオブジェクト',
'Core:AttributeLinkedSetIndirect' => 'オブジェクト配列 (N-N)',
'Core:AttributeLinkedSetIndirect+' => '同一クラスの任意のオブジェクト(サブクラス)',
'Core:AttributeInteger' => 'Int型',
'Core:AttributeInteger+' => '数値 (負数あり)',
'Core:AttributeDecimal' => 'Decimal型',
'Core:AttributeDecimal+' => 'Decimal値 (負数あり)',
'Core:AttributeBoolean' => 'ブール型',
'Core:AttributeBoolean+' => 'Bool値',
'Core:AttributeString' => '文字列',
'Core:AttributeString+' => '文字列',
'Core:AttributeClass' => 'クラス',
'Core:AttributeClass+' => 'クラス',
'Core:AttributeApplicationLanguage' => '使用言語',
'Core:AttributeApplicationLanguage+' => '言語・国別 (EN US)',
'Core:AttributeFinalClass' => 'クラス (自動)',
'Core:AttributeFinalClass+' => 'オブジェクトの実クラス (コアで自動的に生成される)',
'Core:AttributePassword' => 'パスワード',
'Core:AttributePassword+' => '外部デバイス用パスワード',
'Core:AttributeEncryptedString' => '暗号化文字列',
'Core:AttributeEncryptedString+' => 'ローカルキーで暗号化された文字列',
'Core:AttributeText' => 'テキスト',
'Core:AttributeText+' => '複数行文字列',
'Core:AttributeHTML' => 'HTML',
'Core:AttributeHTML+' => 'HTML文字列',
'Core:AttributeEmailAddress' => 'メールアドレス',
'Core:AttributeEmailAddress+' => 'メールアドレス',
'Core:AttributeIPAddress' => 'IPアドレス',
'Core:AttributeIPAddress+' => 'IPアドレス',
'Core:AttributeOQL' => 'OQL',
'Core:AttributeOQL+' => 'OQL式',
'Core:AttributeEnum' => '列挙型',
'Core:AttributeEnum+' => 'ナンバリング済み文字列のリスト',
'Core:AttributeTemplateString' => 'テンプレート文字列',
'Core:AttributeTemplateString+' => 'プレースホルダを含む文字列',
'Core:AttributeTemplateText' => 'テンプレートテキスト',
'Core:AttributeTemplateText+' => 'プレースホルダを含むテキスト',
'Core:AttributeTemplateHTML' => 'テンプレートHTML',
'Core:AttributeTemplateHTML+' => 'プレースホルダを含むHTML',
'Core:AttributeWikiText' => 'Wikiアーティクル',
'Core:AttributeWikiText+' => 'Wikiフォーマット済みテキスト',
'Core:AttributeDateTime' => '日付/時刻',
'Core:AttributeDateTime+' => '日付と時刻(年-月-日 hh:mm:ss)',
'Core:AttributeDate' => '日付',
'Core:AttributeDate+' => '日付 (年-月-日)',
'Core:AttributeDeadline' => '締切',
'Core:AttributeDeadline+' => '日付, 現在時刻からの相対表示',
'Core:AttributeExternalKey' => '外部キー',
'Core:AttributeExternalKey+' => '外部(あるいはフォーリン)キー',
'Core:AttributeExternalField' => '外部フィールド',
'Core:AttributeExternalField+' => '外部キーにマッピングされたフィールド',
'Core:AttributeURL' => 'URL',
'Core:AttributeURL+' => '絶対URLもしくは相対URLのテキスト文字列',
'Core:AttributeBlob' => 'Blob',
'Core:AttributeBlob+' => '任意のバイナリコンテンツ(ドキュメント)',
'Core:AttributeOneWayPassword' => '一方向パスワード',
'Core:AttributeOneWayPassword+' => '一方向暗号化(ハッシュ)パスワード',
'Core:AttributeTable' => 'テーブル',
'Core:AttributeTable+' => 'インデックス化された二次元配列',
'Core:AttributePropertySet' => 'プロパティ',
'Core:AttributePropertySet+' => '型づけされていないプロパティのリスト(名前とバリュー)',
));
//////////////////////////////////////////////////////////////////////
// Classes in 'core/cmdb'
//////////////////////////////////////////////////////////////////////
//
//
// Class: CMDBChange
//
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:CMDBChange' => '変更',
'Class:CMDBChange+' => '変更履歴',
'Class:CMDBChange/Attribute:date' => '日付',
'Class:CMDBChange/Attribute:date+' => '変更が記録された日時',
'Class:CMDBChange/Attribute:userinfo' => 'その他情報',
'Class:CMDBChange/Attribute:userinfo+' => '呼出側の定義済み情報',
));
//
// Class: CMDBChangeOp
//
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:CMDBChangeOp' => '変更操作',
'Class:CMDBChangeOp+' => '変更操作履歴',
'Class:CMDBChangeOp/Attribute:change' => '変更',
'Class:CMDBChangeOp/Attribute:change+' => '変更',
'Class:CMDBChangeOp/Attribute:date' => '日付',
'Class:CMDBChangeOp/Attribute:date+' => '変更日時',
'Class:CMDBChangeOp/Attribute:userinfo' => 'ユーザ',
'Class:CMDBChangeOp/Attribute:userinfo+' => '変更者',
'Class:CMDBChangeOp/Attribute:objclass' => 'オブジェクトクラス',
'Class:CMDBChangeOp/Attribute:objclass+' => 'オブジェクトクラス',
'Class:CMDBChangeOp/Attribute:objkey' => 'オブジェクトID',
'Class:CMDBChangeOp/Attribute:objkey+' => 'オブジェクトID',
'Class:CMDBChangeOp/Attribute:finalclass' => '型',
'Class:CMDBChangeOp/Attribute:finalclass+' => '',
));
//
// Class: CMDBChangeOpCreate
//
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:CMDBChangeOpCreate' => 'オブジェクト生成',
'Class:CMDBChangeOpCreate+' => 'オブジェクト生成履歴',
));
//
// Class: CMDBChangeOpDelete
//
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:CMDBChangeOpDelete' => 'オブジェクト削除',
'Class:CMDBChangeOpDelete+' => 'オブジェクト削除履歴',
));
//
// Class: CMDBChangeOpSetAttribute
//
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:CMDBChangeOpSetAttribute' => 'オブジェクト更新',
'Class:CMDBChangeOpSetAttribute+' => 'オブジェクトプロパティの更新履歴',
'Class:CMDBChangeOpSetAttribute/Attribute:attcode' => '属性',
'Class:CMDBChangeOpSetAttribute/Attribute:attcode+' => '更新プロパティのコード',
));
//
// Class: CMDBChangeOpSetAttributeScalar
//
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:CMDBChangeOpSetAttributeScalar' => 'プロパティ更新',
'Class:CMDBChangeOpSetAttributeScalar+' => 'オブジェクトのスカラープロパティの更新履歴',
'Class:CMDBChangeOpSetAttributeScalar/Attribute:oldvalue' => '変更前の値',
'Class:CMDBChangeOpSetAttributeScalar/Attribute:oldvalue+' => '属性の変更前の値',
'Class:CMDBChangeOpSetAttributeScalar/Attribute:newvalue' => '新規の値',
'Class:CMDBChangeOpSetAttributeScalar/Attribute:newvalue+' => '属性の新規の値',
));
// Used by CMDBChangeOp... & derived classes
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Change:ObjectCreated' => 'オブジェクトを生成しました',
'Change:ObjectDeleted' => 'オブジェクトを削除しました',
'Change:ObjectModified' => 'オブジェクトを更新しました',
'Change:AttName_SetTo_NewValue_PreviousValue_OldValue' => '%1$sを%2$sに設定しました (変更前の値: %3$s)',
'Change:Text_AppendedTo_AttName' => '%1$sを%2$sに追加しました',
'Change:AttName_Changed_PreviousValue_OldValue' => '%1$sを更新しました。更新前の値: %2$s',
'Change:AttName_Changed' => '%1$sを更新しました',
));
//
// Class: CMDBChangeOpSetAttributeBlob
//
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:CMDBChangeOpSetAttributeBlob' => 'データ変更',
'Class:CMDBChangeOpSetAttributeBlob+' => 'データ変更履歴',
'Class:CMDBChangeOpSetAttributeBlob/Attribute:prevdata' => '変更前のデータ', //'Previous data',
'Class:CMDBChangeOpSetAttributeBlob/Attribute:prevdata+' => 'この属性の以前の内容', //'previous contents of the attribute',
));
//
// Class: CMDBChangeOpSetAttributeText
//
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:CMDBChangeOpSetAttributeText' => 'テキストの変更', //'text change',
'Class:CMDBChangeOpSetAttributeText+' => 'テキストの変更履歴', //'text change tracking',
'Class:CMDBChangeOpSetAttributeText/Attribute:prevdata' => '以前の内容', //'Previous data',
'Class:CMDBChangeOpSetAttributeText/Attribute:prevdata+' => 'この属性の以前の内容', //'previous contents of the attribute',
));
//
// Class: Event
//
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:Event' => 'ログイベント',// 'Log Event',
'Class:Event+' => 'アプリケーション内部イベント', //'An application internal event',
'Class:Event/Attribute:message' => 'メッセージ', //'message',
'Class:Event/Attribute:message+' => 'イベント概略', //'short description of the event',
'Class:Event/Attribute:date' => '日付', //'date',
'Class:Event/Attribute:date+' => '変更が記録された日時', //'date and time at which the changes have been recorded',
'Class:Event/Attribute:userinfo' => 'ユーザ情報', //'user info',
'Class:Event/Attribute:userinfo+' => 'このイベントをトリガーにアクションを起こすユーザの識別', //'identification of the user that was doing the action that triggered this event',
'Class:Event/Attribute:finalclass' => '型', //'type',
'Class:Event/Attribute:finalclass+' => '',
));
//
// Class: EventNotification
//
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:EventNotification' => '通知イベント', // 'Notification event',
'Class:EventNotification+' => '創出された通知のトレース', //'Trace of a notification that has been sent',
'Class:EventNotification/Attribute:trigger_id' => 'トリガー', //'Trigger',
'Class:EventNotification/Attribute:trigger_id+' => 'ユーザアカウント', //'user account',
'Class:EventNotification/Attribute:action_id' => 'ユーザ', //'user',
'Class:EventNotification/Attribute:action_id+' => 'ユーザアカウント', //'user account',
'Class:EventNotification/Attribute:object_id' => 'オブジェクトID', //'Object id',
'Class:EventNotification/Attribute:object_id+' => 'オブジェクトID(トリガーでクラスが定義済み?)', //'object id (class defined by the trigger ?)',
));
//
// Class: EventNotificationEmail
//
Dict::Add('JA JP', 'Japanese', '日本語', array('Class:EventNotificationEmail' => 'メール送出イベント', //'Email emission event',
'Class:EventNotificationEmail+' => '送出されたメールのトレース',//Trace of an email that has been sent',
'Class:EventNotificationEmail/Attribute:to' => 'TO',
'Class:EventNotificationEmail/Attribute:to+' => 'TO',
'Class:EventNotificationEmail/Attribute:cc' => 'CC',
'Class:EventNotificationEmail/Attribute:cc+' => 'CC',
'Class:EventNotificationEmail/Attribute:bcc' => 'BCC',
'Class:EventNotificationEmail/Attribute:bcc+' => 'BCC',
'Class:EventNotificationEmail/Attribute:from' => 'From',
'Class:EventNotificationEmail/Attribute:from+' => 'メール送信者', //'Sender of the message',
'Class:EventNotificationEmail/Attribute:subject' => 'Subject',
'Class:EventNotificationEmail/Attribute:subject+' => 'Subject',
'Class:EventNotificationEmail/Attribute:body' => 'Body',
'Class:EventNotificationEmail/Attribute:body+' => 'Body',
));
//
// Class: EventIssue
//
Dict::Add('EN US', 'English', 'English', array(
'Class:EventIssue' => 'イシューイベント', //'Issue event',
'Class:EventIssue+' => 'イシュー(警告、エラーetc)のトレース', //'Trace of an issue (warning, error, etc.)',
'Class:EventIssue/Attribute:issue' => 'イシュー', //'Issue',
'Class:EventIssue/Attribute:issue+' => '何が起こったか', //'What happened',
'Class:EventIssue/Attribute:impact' => 'インパクト', //'Impact',
'Class:EventIssue/Attribute:impact+' => 'その結果', //'What are the consequences',
'Class:EventIssue/Attribute:page' => 'ページ', //'Page',
'Class:EventIssue/Attribute:page+' => 'HTTPエントリポイント', //'HTTP entry point',
'Class:EventIssue/Attribute:arguments_post' => 'POSTされた引数', //'Posted arguments',
'Class:EventIssue/Attribute:arguments_post+' => 'HTTP POST引数', //'HTTP POST arguments',
'Class:EventIssue/Attribute:arguments_get' => 'URLパラメータ', //'URL arguments',
'Class:EventIssue/Attribute:arguments_get+' => 'HTTP GETパラメータ', //'HTTP GET arguments',
'Class:EventIssue/Attribute:callstack' => 'コールスタック', //'Callstack',
'Class:EventIssue/Attribute:callstack+' => 'スタックをコールする', //'Call stack',
'Class:EventIssue/Attribute:data' => 'データ', //'Data',
'Class:EventIssue/Attribute:data+' => '詳細情報', //'More information',
));
//
// Class: EventWebService
//
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:EventWebService' => 'ウェブサービスイベント', //'Web service event',
'Class:EventWebService+' => 'ウェブサービス呼出のYトレース', //'Trace of an web service call',
'Class:EventWebService/Attribute:verb' => '動詞', //'Verb',
'Class:EventWebService/Attribute:verb+' => '操作名', //'Name of the operation',
'Class:EventWebService/Attribute:result' => '結果', //'Result',
'Class:EventWebService/Attribute:result+' => '総体的な成功/失敗', //'Overall success/failure',
'Class:EventWebService/Attribute:log_info' => 'インフォログ', //'Info log',
'Class:EventWebService/Attribute:log_info+' => 'インフォログの結果', //'Result info log',
'Class:EventWebService/Attribute:log_warning' => 'ウォーニングログ', //'Warning log',
'Class:EventWebService/Attribute:log_warning+' => 'ウォーニングログ結果', //'Result warning log',
'Class:EventWebService/Attribute:log_error' => 'エラーログ', //'Error log',
'Class:EventWebService/Attribute:log_error+' => 'エラーログ結果', //'Result error log',
'Class:EventWebService/Attribute:data' => 'データ', //'Data',
'Class:EventWebService/Attribute:data+' => 'データ結果', //'Result data',
));
//
// Class: Action
//
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:Action' => 'カスタムアクション', //'Custom Action',
'Class:Action+' => 'ユーザ定義アクション', //'User defined action',
'Class:Action/Attribute:name' => '名前', //'Name',
'Class:Action/Attribute:name+' => '',
'Class:Action/Attribute:description' => '概要', //'Description',
'Class:Action/Attribute:description+' => '',
'Class:Action/Attribute:status' => 'ステータス', //'Status',
'Class:Action/Attribute:status+' => '製品化済み、あるいは?', //'In production or ?',
'Class:Action/Attribute:status/Value:test' => 'テスト済み', //'Being tested',
'Class:Action/Attribute:status/Value:test+' => 'テスト済み', //'Being tested',
'Class:Action/Attribute:status/Value:enabled' => '製品化済み', //'In production',
'Class:Action/Attribute:status/Value:enabled+' => '製品化済み', //'In production',
'Class:Action/Attribute:status/Value:disabled' => '非アクティブ', //'Inactive',
'Class:Action/Attribute:status/Value:disabled+' => '非アクティブ', //'Inactive',
'Class:Action/Attribute:trigger_list' => '関連トリガ', //'Related Triggers',
'Class:Action/Attribute:trigger_list+' => 'このアクションにリンクされたトリガ', //'Triggers linked to this action',
'Class:Action/Attribute:finalclass' => '型', //'Type',
'Class:Action/Attribute:finalclass+' => '',
));
//
// Class: ActionNotification
//
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:ActionNotification' => 'ノーティフィケーション', //'Notification',
'Class:ActionNotification+' => 'ノーティフィケーション(抽象)', //'Notification (abstract)',
));
//
// Class: ActionEmail
//
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:ActionEmail' => 'メール通知', //'Email notification',
'Class:ActionEmail+' => '',
'Class:ActionEmail/Attribute:test_recipient' => 'テストレシピ', //'Test recipient',
'Class:ActionEmail/Attribute:test_recipient+' => 'Detination in case status is set to "Test"',
'Class:ActionEmail/Attribute:from' => 'From',
'Class:ActionEmail/Attribute:from+' => 'Will be sent into the email header',
'Class:ActionEmail/Attribute:reply_to' => 'Reply to',
'Class:ActionEmail/Attribute:reply_to+' => 'Will be sent into the email header',
'Class:ActionEmail/Attribute:to' => 'To',
'Class:ActionEmail/Attribute:to+' => 'メールの宛先', //'Destination of the email',
'Class:ActionEmail/Attribute:cc' => 'Cc',
'Class:ActionEmail/Attribute:cc+' => 'Carbon Copy',
'Class:ActionEmail/Attribute:bcc' => 'bcc',
'Class:ActionEmail/Attribute:bcc+' => 'Blind Carbon Copy',
'Class:ActionEmail/Attribute:subject' => 'subject',
'Class:ActionEmail/Attribute:subject+' => 'メールのタイトル', //'Title of the email',
'Class:ActionEmail/Attribute:body' => 'body',
'Class:ActionEmail/Attribute:body+' => 'メールの本文', //'Contents of the email',
'Class:ActionEmail/Attribute:importance' => 'importance',
'Class:ActionEmail/Attribute:importance+' => '重要度フラグ', //'Importance flag',
'Class:ActionEmail/Attribute:importance/Value:low' => 'low',
'Class:ActionEmail/Attribute:importance/Value:low+' => 'low',
'Class:ActionEmail/Attribute:importance/Value:normal' => 'normal',
'Class:ActionEmail/Attribute:importance/Value:normal+' => 'normal',
'Class:ActionEmail/Attribute:importance/Value:high' => 'high',
'Class:ActionEmail/Attribute:importance/Value:high+' => 'high',
));
//
// Class: Trigger
//
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:Trigger' => 'トリガー', //'Trigger',
'Class:Trigger+' => 'カスタムイベントヘッダ', //'Custom event handler',
'Class:Trigger/Attribute:description' => '概要', //'Description',
'Class:Trigger/Attribute:description+' => '1行概要', //'one line description',
'Class:Trigger/Attribute:action_list' => 'トリガされたアクション', //'Triggered actions',
'Class:Trigger/Attribute:action_list+' => 'トリガが発火した場合に動作するアクション', //'Actions performed when the trigger is activated',
'Class:Trigger/Attribute:finalclass' => '型', //'Type',
'Class:Trigger/Attribute:finalclass+' => '',
));
//
// Class: TriggerOnObject
//
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:TriggerOnObject' => 'トリガ(クラス依存)', //'Trigger (class dependent)',
'Class:TriggerOnObject+' => '指定オブジェクトのクラスへのトリガ', //'Trigger on a given class of objects',
'Class:TriggerOnObject/Attribute:target_class' => 'ターゲットクラス', //'Target class',
'Class:TriggerOnObject/Attribute:target_class+' => '',
));
//
// Class: TriggerOnStateChange
//
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:TriggerOnStateChange' => '(状態変更の)トリガ', // Trigger (on state change)',
'Class:TriggerOnStateChange+' => 'オブジェクト状態変更のトリガ', //'Trigger on object state change',
'Class:TriggerOnStateChange/Attribute:state' => '状態', //'State',
'Class:TriggerOnStateChange/Attribute:state+' => '',
));
//
// Class: TriggerOnStateEnter
//
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:TriggerOnStateEnter' => 'トリガ(ある状態に入る)', // 'Trigger (on entering a state)',
'Class:TriggerOnStateEnter+' => 'オブジェクト状態変更のトリガ: 入場', //'Trigger on object state change - entering',
));
//
// Class: TriggerOnStateLeave
//
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:TriggerOnStateLeave' => '(ある状態から退場する)トリガ', // 'Trigger (on leaving a state)',
'Class:TriggerOnStateLeave+' => 'オブジェクト状態変更のトリガ: 退場', //Trigger on object state change - leaving',
));
//
// Class: TriggerOnObjectCreate
//
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:TriggerOnObjectCreate' => '(オブジェクト生成の)トリガ', //Trigger (on object creation)',
'Class:TriggerOnObjectCreate+' => '指定されたクラスの(子クラスの)オブジェクト生成のトリガ', //Trigger on object creation of [a child class of] the given class',
));
//
// Class: lnkTriggerAction
//
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:lnkTriggerAction' => 'アクション/トリガ', //'Action/Trigger',
'Class:lnkTriggerAction+' => 'トリガとアクション間のリンク', //'Link between a trigger and an action',
'Class:lnkTriggerAction/Attribute:action_id' => 'アクション', //'Action',
'Class:lnkTriggerAction/Attribute:action_id+' => '実行されるべきアクション', //'The action to be executed',
'Class:lnkTriggerAction/Attribute:action_name' => 'アクション', //'Action',
'Class:lnkTriggerAction/Attribute:action_name+' => '',
'Class:lnkTriggerAction/Attribute:trigger_id' => 'トリガ', //'Trigger',
'Class:lnkTriggerAction/Attribute:trigger_id+' => '',
'Class:lnkTriggerAction/Attribute:trigger_name' => 'トリガ', //'Trigger',
'Class:lnkTriggerAction/Attribute:trigger_name+' => '',
'Class:lnkTriggerAction/Attribute:order' => '処理順序', //'Order',
'Class:lnkTriggerAction/Attribute:order+' => 'アクション実行順序', //'Actions execution order',
));
?>

View File

@@ -0,0 +1,915 @@
<?php
// Copyright (C) 2010 Combodo SARL
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; version 3 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
/**
* Localized data
*
* @author Erwan Taloc <erwan.taloc@combodo.com>
* @author Romain Quetiez <romain.quetiez@combodo.com>
* @author Denis Flaven <denis.flaven@combodo.com>
* @author Tadashi Kaneda <kaneda@rworks.jp>
* @license http://www.opensource.org/licenses/gpl-3.0.html LGPL
*/
//////////////////////////////////////////////////////////////////////
// Classes in 'gui'
//////////////////////////////////////////////////////////////////////
//
//////////////////////////////////////////////////////////////////////
// Classes in 'application'
//////////////////////////////////////////////////////////////////////
//
//
// Class: AuditCategory
//
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:AuditCategory' => '監査カテゴリ', //Audit Category',
'Class:AuditCategory+' => '監査全体の内部セクション', //'A section inside the overall audit',
'Class:AuditCategory/Attribute:name' => 'カテゴリ名', //'Category Name',
'Class:AuditCategory/Attribute:name+' => '本カテゴリの短縮名', //'Short name for this category',
'Class:AuditCategory/Attribute:description' => '監査カテゴリ概要', //'Audit Category Description',
'Class:AuditCategory/Attribute:description+' => '本監査カテゴリの詳細記述', //'Long description for this audit category',
'Class:AuditCategory/Attribute:definition_set' => '定義セット', //'Definition Set',
'Class:AuditCategory/Attribute:definition_set+' => '監査するべきオブジェクトの集合を定義するOQL式', //'OQL expression defining the set of objects to audit',
'Class:AuditCategory/Attribute:rules_list' => '監査ルール', //'Audit Rules',
'Class:AuditCategory/Attribute:rules_list+' => '本カテゴリの監査ルール', //'Audit rules for this category',
));
//
// Class: AuditRule
//
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:AuditRule' => '監査ルール', //'Audit Rule',
'Class:AuditRule+' => '指定された監査カテゴリをチェックするためのルール', //'A rule to check for a given Audit category',
'Class:AuditRule/Attribute:name' => 'ルール名', //'Rule Name',
'Class:AuditRule/Attribute:name+' => '本ルールの短縮名', //'Short name for this rule',
'Class:AuditRule/Attribute:description' => '監査ルール概要', //'Audit Rule Description',
'Class:AuditRule/Attribute:description+' => '本監査ルールの詳細記述', //'Long description for this audit rule',
'Class:AuditRule/Attribute:query' => '実行するクエリ', //'Query to Run',
'Class:AuditRule/Attribute:query+' => '実行するOQL式', //'The OQL expression to run',
'Class:AuditRule/Attribute:valid_flag' => '正しいオブジェクト?', // 'Valid Objects?',
'Class:AuditRule/Attribute:valid_flag+' => 'このルールが正しいオブジェクトを返す場合は真、そうでなければ偽', //'True if the rule returns the valid objects, false otherwise',
'Class:AuditRule/Attribute:valid_flag/Value:true' => '真', //'true',
'Class:AuditRule/Attribute:valid_flag/Value:true+' => '真', //'true',
'Class:AuditRule/Attribute:valid_flag/Value:false' => '偽', //'false',
'Class:AuditRule/Attribute:valid_flag/Value:false+' => '偽', //'false',
'Class:AuditRule/Attribute:category_id' => 'カテゴリ', //'Category',
'Class:AuditRule/Attribute:category_id+' => '本ルールのカテゴリ', //'The category for this rule',
'Class:AuditRule/Attribute:category_name' => 'カテゴリ', //'Category',
'Class:AuditRule/Attribute:category_name+' => '本ルールのカテゴリ名', //'Name of the category for this rule',
));
//////////////////////////////////////////////////////////////////////
// Classes in 'addon/userrights'
//////////////////////////////////////////////////////////////////////
//
//
// Class: User
//
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:User' => 'ユーザ', //'User',
'Class:User+' => 'ユーザログイン', //'User login',
'Class:User/Attribute:finalclass' => 'アカウント種別', //'Type of account',
'Class:User/Attribute:finalclass+' => '',
'Class:User/Attribute:contactid' => 'コンタクト(人)', //'Contact (person)',
'Class:User/Attribute:contactid+' => 'ビジネスデータから抽出した個人情報の詳細', //'Personal details from the business data',
'Class:User/Attribute:last_name' => '名字', //'Last name',
'Class:User/Attribute:last_name+' => '適切なコンタクト名', //'Name of the corresponding contact',
'Class:User/Attribute:first_name' => '名前', //'First name',
'Class:User/Attribute:first_name+' => '適切なコンタクトの名前', //'First name of the corresponding contact',
'Class:User/Attribute:email' => 'メールアドレス', //'Email',
'Class:User/Attribute:email+' => '適切なコンタクトのメールアドレス', //'Email of the corresponding contact',
'Class:User/Attribute:login' => 'ログイン', //'Login',
'Class:User/Attribute:login+' => 'ユーザ識別文字列', //'user identification string',
'Class:User/Attribute:language' => '言語', //'Language',
'Class:User/Attribute:language+' => 'ユーザ使用言語', //'user language',
'Class:User/Attribute:language/Value:EN US' => '英語', //'English',
'Class:User/Attribute:language/Value:EN US+' => '英語(米国)', //'English (U.S.)',
'Class:User/Attribute:language/Value:FR FR' => 'フランス語', //'French',
'Class:User/Attribute:language/Value:FR FR+' => 'フランス語(フランス)', //'French (France)',
'Class:User/Attribute:profile_list' => 'プロフィール', //'Profiles',
'Class:User/Attribute:profile_list+' => '役割、この人に委譲された権限', //'Roles, granting rights for that person',
'Class:User/Attribute:allowed_org_list' => '許可された組織', //'Allowed Organizations',
'Class:User/Attribute:allowed_org_list+' => 'このエンドユーザは以下の組織に属するデータの参照を許可されている。組織が指定されていなければ、制限はありません。', //'The end user is allowed to see data belonging to the following organizations. If no organization is specified, there is no restriction.',
'Class:User/Error:LoginMustBeUnique' => 'ログイン名は一意でないといけません。- "%1s" はすでに使われています。', //'Login must be unique - "%1s" is already being used.',
'Class:User/Error:AtLeastOneProfileIsNeeded' => '少なくとも1件のプロフィールがこのユーザに指定されていないといけません。', //'At least one profile must be assigned to this user.',
));
//
// Class: URP_Profiles
//
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:URP_Profiles' => 'プロフィール', //'Profile',
'Class:URP_Profiles+' => 'ユーザプロフィール', //'User profile',
'Class:URP_Profiles/Attribute:name' => '名前', //'Name',
'Class:URP_Profiles/Attribute:name+' => 'ラベル', //'label',
'Class:URP_Profiles/Attribute:description' => '概要', //'Description',
'Class:URP_Profiles/Attribute:description+' => '1行で書くと', //'one line description',
'Class:URP_Profiles/Attribute:user_list' => 'ユーザ', //'Users',
'Class:URP_Profiles/Attribute:user_list+' => 'この役割をもつ人', //'persons having this role',
));
//
// Class: URP_Dimensions
//
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:URP_Dimensions' => '次元', //'dimension',
'Class:URP_Dimensions+' => 'application dimension (defining silos)',
'Class:URP_Dimensions/Attribute:name' => '名前', //'Name',
'Class:URP_Dimensions/Attribute:name+' => 'ラベル', //'label',
'Class:URP_Dimensions/Attribute:description' => '概要', //'Description',
'Class:URP_Dimensions/Attribute:description+' => '1行で書くと', //'one line description',
'Class:URP_Dimensions/Attribute:type' => '種別', //'Type',
'Class:URP_Dimensions/Attribute:type+' => 'クラス名、もしくはデータ型(projection unit)', //'class name or data type (projection unit)',
));
//
// Class: URP_UserProfile
//
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:URP_UserProfile' => 'User to profile',
'Class:URP_UserProfile+' => 'ユーザプロフィール', //'user profiles',
'Class:URP_UserProfile/Attribute:userid' => 'ユーザ', //'User',
'Class:URP_UserProfile/Attribute:userid+' => 'ユーザアカウント', //'user account',
'Class:URP_UserProfile/Attribute:userlogin' => 'ログイン', //'Login',
'Class:URP_UserProfile/Attribute:userlogin+' => 'ユーザのログイン', //'User\'s login',
'Class:URP_UserProfile/Attribute:profileid' => 'プロフィール', //'Profile',
'Class:URP_UserProfile/Attribute:profileid+' => 'プロフィールの用法???', //'usage profile',
'Class:URP_UserProfile/Attribute:profile' => 'プロフィール', //'Profile',
'Class:URP_UserProfile/Attribute:profile+' => 'プロフィール名', //'Profile name',
'Class:URP_UserProfile/Attribute:reason' => '理由', //'Reason',
'Class:URP_UserProfile/Attribute:reason+' => 'なぜ、この人物がこの役割を持つかを説明する', //'explain why this person may have this role',
));
//
// Class: URP_UserOrg
//
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:URP_UserOrg' => 'ユーザ組織', //'User organizations',
'Class:URP_UserOrg+' => '許可された組織', //'Allowed organizations',
'Class:URP_UserOrg/Attribute:userid' => 'ユーザ', //'User',
'Class:URP_UserOrg/Attribute:userid+' => 'ユーザアカウント', //'user account',
'Class:URP_UserOrg/Attribute:userlogin' => 'ログイン', //'Login',
'Class:URP_UserOrg/Attribute:userlogin+' => 'ユーザのログイン', //'User\'s login',
'Class:URP_UserOrg/Attribute:allowed_org_id' => '組織', //'Organization',
'Class:URP_UserOrg/Attribute:allowed_org_id+' => '許可された組織', //'Allowed organization',
'Class:URP_UserOrg/Attribute:allowed_org_name' => '組織', //'Organization',
'Class:URP_UserOrg/Attribute:allowed_org_name+' => '許可された組織', //'Allowed organization',
'Class:URP_UserOrg/Attribute:reason' => '理由', //'Reason',
'Class:URP_UserOrg/Attribute:reason+' => 'なぜこの人物がこの組織に属するデータを参照できるのかを説明する', // 'explain why this person is allowed to see the data belonging to this organization',
));
//
// Class: URP_ProfileProjection
//
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:URP_ProfileProjection' => 'プロファイルプロジェクション???', // 'profile_projection',
'Class:URP_ProfileProjection+' => 'プロファイルプロジェクション???', //'profile projections',
'Class:URP_ProfileProjection/Attribute:dimensionid' => '次元', //'Dimension',
'Class:URP_ProfileProjection/Attribute:dimensionid+' => 'アプリケーション次元', // 'application dimension',
'Class:URP_ProfileProjection/Attribute:dimension' => '次元', //'Dimension',
'Class:URP_ProfileProjection/Attribute:dimension+' => 'アプリケーション次元', //'application dimension',
'Class:URP_ProfileProjection/Attribute:profileid' => 'プロフィール', //'Profile',
'Class:URP_ProfileProjection/Attribute:profileid+' => 'usage profile???',
'Class:URP_ProfileProjection/Attribute:profile' => 'プロフィール', //'Profile',
'Class:URP_ProfileProjection/Attribute:profile+' => 'プロフィール名', //'Profile name',
'Class:URP_ProfileProjection/Attribute:value' => 'Value式', //'Value expression',
'Class:URP_ProfileProjection/Attribute:value+' => '($userを使う)OQL式 | アクセス先 | +attribute code', //'OQL expression (using $user) | constant | | +attribute code',
'Class:URP_ProfileProjection/Attribute:attribute' => '属性', //'Attribute',
'Class:URP_ProfileProjection/Attribute:attribute+' => 'Target attribute code (optional)',
));
//
// Class: URP_ClassProjection
//
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:URP_ClassProjection' => 'class_projection',
'Class:URP_ClassProjection+' => 'クラスの射影???', // 'clas projection',
'Class:URP_ClassProjection/Attribute:dimensionid' => '次元', //'Dimension',
'Class:URP_ClassProjection/Attribute:dimensionid+' => 'アプリケーション次元', //'application dimension',
'Class:URP_ClassProjection/Attribute:dimension' => '次元', //'Dimension',
'Class:URP_ClassProjection/Attribute:dimension+' => 'アプリケーション次元', //'application dimension',
'Class:URP_ClassProjection/Attribute:class' => 'クラス', //'Class',
'Class:URP_ClassProjection/Attribute:class+' => 'ターゲットクラス', //'Target class',
'Class:URP_ClassProjection/Attribute:value' => 'Value式???', //'Value expression',
'Class:URP_ClassProjection/Attribute:value+' => '($this を使った)OQL式 | 定数 | +attribute code', //'OQL expression (using $this) | constant | | +attribute code',
'Class:URP_ClassProjection/Attribute:attribute' => '属性', //'Attribute',
'Class:URP_ClassProjection/Attribute:attribute+' => 'ターゲット属性コード(オプション)', //'Target attribute code (optional)',
));
//
// Class: URP_ActionGrant
//
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:URP_ActionGrant' => 'アクション権限', //'action_permission',
'Class:URP_ActionGrant+' => 'クラスに対する権限', //'permissions on classes',
'Class:URP_ActionGrant/Attribute:profileid' => 'プロファイル', //'Profile',
'Class:URP_ActionGrant/Attribute:profileid+' => 'usage profile',
'Class:URP_ActionGrant/Attribute:profile' => 'プロファイル', //'Profile',
'Class:URP_ActionGrant/Attribute:profile+' => 'usage profile',
'Class:URP_ActionGrant/Attribute:class' => 'クラス', //'Class',
'Class:URP_ActionGrant/Attribute:class+' => 'ターゲットクラス', //'Target class',
'Class:URP_ActionGrant/Attribute:permission' => '権限', //'Permission',
'Class:URP_ActionGrant/Attribute:permission+' => '権限の有無は?', //'allowed or not allowed?',
'Class:URP_ActionGrant/Attribute:permission/Value:yes' => 'はい', //'yes',
'Class:URP_ActionGrant/Attribute:permission/Value:yes+' => 'はい', //'yes',
'Class:URP_ActionGrant/Attribute:permission/Value:no' => 'いいえ', //'no',
'Class:URP_ActionGrant/Attribute:permission/Value:no+' => 'いいえ', //'no',
'Class:URP_ActionGrant/Attribute:action' => 'アクション', //'Action',
'Class:URP_ActionGrant/Attribute:action+' => '指定されたクラスにすべき操作', //'operations to perform on the given class',
));
//
// Class: URP_StimulusGrant
//
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:URP_StimulusGrant' => 'stimulus_permission',
'Class:URP_StimulusGrant+' => 'permissions on stimilus in the life cycle of the object',
'Class:URP_StimulusGrant/Attribute:profileid' => 'プロファイル', //'Profile',
'Class:URP_StimulusGrant/Attribute:profileid+' => 'usage profile',
'Class:URP_StimulusGrant/Attribute:profile' => 'プロファイル', //'Profile',
'Class:URP_StimulusGrant/Attribute:profile+' => 'usage profile',
'Class:URP_StimulusGrant/Attribute:class' => 'クラス', //'Class',
'Class:URP_StimulusGrant/Attribute:class+' => 'ターゲットクラス', //'Target class',
'Class:URP_StimulusGrant/Attribute:permission' => '権限', // 'Permission',
'Class:URP_StimulusGrant/Attribute:permission+' => '権限の有無?', //'allowed or not allowed?',
'Class:URP_StimulusGrant/Attribute:permission/Value:yes' => 'はい', //'yes',
'Class:URP_StimulusGrant/Attribute:permission/Value:yes+' => 'はい', //'yes',
'Class:URP_StimulusGrant/Attribute:permission/Value:no' => 'いいえ', //'no',
'Class:URP_StimulusGrant/Attribute:permission/Value:no+' => 'いいえ', //'no',
'Class:URP_StimulusGrant/Attribute:stimulus' => 'Stimulus',
'Class:URP_StimulusGrant/Attribute:stimulus+' => 'stimulus code',
));
//
// Class: URP_AttributeGrant
//
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:URP_AttributeGrant' => '権限属性', //'attribute_permission',
'Class:URP_AttributeGrant+' => '属性レベルでの権限', //'permissions at the attributes level',
'Class:URP_AttributeGrant/Attribute:actiongrantid' => '実行権限', //'Action grant',
'Class:URP_AttributeGrant/Attribute:actiongrantid+' => '実行権限', //'action grant',
'Class:URP_AttributeGrant/Attribute:attcode' => '属性', //'Attribute',
'Class:URP_AttributeGrant/Attribute:attcode+' => '属性コード', //'attribute code',
));
//
// String from the User Interface: menu, messages, buttons, etc...
//
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Menu:WelcomeMenu' => 'ようこそ', //'Welcome',
'Menu:WelcomeMenu+' => 'ようこそ、iTopへ', //'Welcome to iTop',
'Menu:WelcomeMenuPage' => 'ようこそ', //'Welcome',
'Menu:WelcomeMenuPage+' => 'ようこそ、iTopへ', //'Welcome to iTop',
'UI:WelcomeMenu:Title' => 'ようこそ、iTopへ', //'Welcome to iTop',
// '<p>iTop is a complete, OpenSource, IT Operational Portal.</p>
'UI:WelcomeMenu:LeftBlock' => '<p>iTopは、オープンソースの、これだけで完結したIT業務用ポータルです。</p>
<ul>下記に挙げるものが同梱されています。
<li>IT資産インベントリをドキュメント化、管理を行うための完全なCMDB(コンフィグレーション管理データベース)</li>
<li>IT資産関連で発生した問題のトラッキングとそれに関する議論のためのインシデント管理モジュール</li>
<li>IT資産環境への変更を加える場合のプランニングと変更をトラッキングするための変更管理モジュール</li>
<li>インシデント解決のスピードアップするための既知エラーデータベース</li>
<li>計画停電をすべてドキュメント化し、適切な連絡先に通知するための停電モジュール</li>
<li>IT資産の概観を素早く得るためのダッシュボード</li>
</ul>
<p>すべてのモジュールはそれぞれ独立して別個にセットアップが可能である。</p>',
//'<p>iTop is service provider oriented, it allows IT engineers to manage easily multiple customers or organizations.
'UI:WelcomeMenu:RightBlock' => '<p>iTopはサービスプロバイダ指向であり、ITエンジニアが複数の顧客や組織を簡単に管理できるようになる。
<ul>iTopでは 下記のように、機能豊富なビジネスプロセスを取り揃えた。
<li>効果的なIT資産管理</li>
<li>IT業務の効率化推進</li>
<li>顧客満足度の改善と、経営幹部へ、ビジネスパフォーマンス見える化を提供</li>
</ul>
</p>
<p>iTopは完全にオープンなので、あなたが今使っているIT資産管理インフラとの統合が可能である。</p>
<p>
<ul>この次世代IT資産管理業務ポータルを採用すれば、こんなことが可能になる。
<li>より複雑になりつつある、IT資産環境の管理を確実にする。</li>
<li>自分のペースでITILプロセス実装することができる。</li>
<li>IT資産の中でもっとも重要なアセットである、「ドキュメンテーション」を管理することができる。</li>
</ul>
</p>',
'UI:WelcomeMenu:AllOpenRequests' => 'リクエストを開く: %1$d', //'Open requests: %1$d',
'UI:WelcomeMenu:MyCalls' => 'マイリクエスト', //'My requests',
'UI:WelcomeMenu:OpenIncidents' => 'インシデントを開く: %1$d', //'Open incidents: %1$d',
'UI:WelcomeMenu:AllConfigItems' => '設定項目', //'Configuration Items: %1$d',
'UI:WelcomeMenu:MyIncidents' => '自分にアサインされたインシデント', //'Incidents assigned to me',
'UI:AllOrganizations' => '全組織', //' All Organizations ',
'UI:YourSearch' => 'あなたのサーチ', //'Your Search',
'UI:LoggedAsMessage' => '%1$s としてログインする', //'Logged in as %1$s',
'UI:LoggedAsMessage+Admin' => '%1$s (管理者)としてログインする', //'Logged in as %1$s (Administrator)',
'UI:Button:Logoff' => 'ログオフ', //'Log off',
'UI:Button:GlobalSearch' => 'サーチ', //'Search',
'UI:Button:Search' => ' サーチ', //' Search ',
'UI:Button:Query' => ' クエリ', //' Query ',
'UI:Button:Ok' => 'OK', //'Ok',
'UI:Button:Cancel' => 'キャンセル', //'Cancel',
'UI:Button:Apply' => '適用する', //'Apply',
'UI:Button:Back' => ' << 戻る', //' << Back ',
'UI:Button:Restart' => ' |<< リスタート', //' |<< Restart ',
'UI:Button:Next' => ' 次へ >> ', //' Next >> ',
'UI:Button:Finish' => ' 終了 ', //' Finish ',
'UI:Button:DoImport' => ' インポート実行! ', //' Run the Import ! ',
'UI:Button:Done' => ' 完了 ', //' Done ',
'UI:Button:SimulateImport' => ' インポートをシュミレート ', //' Simulate the Import ',
'UI:Button:Test' => 'テスト実行!', //'Test!',
'UI:Button:Evaluate' => ' 評価 ', //' Evaluate ',
'UI:Button:AddObject' => ' 追加...', //' Add... ',
'UI:Button:BrowseObjects' => 'ブラウズ...', //' Browse... ',
'UI:Button:Add' => ' 追加 ', //' Add ',
'UI:Button:AddToList' => ' << 追加 ', //' << Add ',
'UI:Button:RemoveFromList' => '削除 >> ', //' Remove >> ',
'UI:Button:FilterList' => ' フィルタ... ', //' Filter... ',
'UI:Button:Create' => ' 生成 ', //' Create ',
'UI:Button:Delete' => ' 削除! ', //' Delete ! ',
'UI:Button:ChangePassword' => ' パスワード変更 ', //' Change Password ',
'UI:Button:ResetPassword' => 'パスワードリセット ', //' Reset Password ',
'UI:SearchToggle' => 'サーチ', //'Search',
'UI:ClickToCreateNew' => '新規 %1$s を生成', //'Create a new %1$s',
'UI:SearchFor_Class' => '%1$s オブジェクトをサーチ', //'Search for %1$s objects',
'UI:NoObjectToDisplay' => '表示すべきオブジェクトがありません。', //'No object to display.',
'UI:Error:MandatoryTemplateParameter_object_id' => 'link_attrが指定されている時は、object_idパラメータは必須です。表示テンプレートの定義を確認してください。', //'Parameter object_id is mandatory when link_attr is specified. Check the definition of the display template.',
'UI:Error:MandatoryTemplateParameter_target_attr' => 'link_attrを指定する場合は、target_attrパラメータは必須です。表示テンプレートの定義を確認してください。', //'Parameter target_attr is mandatory when link_attr is specified. Check the definition of the display template.',
'UI:Error:MandatoryTemplateParameter_group_by' => 'group_byパラメータは必須です。表示テンプレートの定義を確認してください。', //'Parameter group_by is mandatory. Check the definition of the display template.',
'UI:Error:InvalidGroupByFields' => 'Invalid list of fields to group by: "%1$s".',
'UI:Error:UnsupportedStyleOfBlock' => 'エラー:"%1$s"はサポートされていないブロックスタイルです。', //'Error: unsupported style of block: "%1$s".',
'UI:Error:IncorrectLinkDefinition_LinkedClass_Class' => 'リンク定義が正しくありません。管理オブジェクトのクラス:%1Ss は、クラス %2$s クラスの外部キーとして見つかりません。', //'Incorrect link definition: the class of objects to manage: %1$s was not found as an external key in the class %2$s',
'UI:Error:Object_Class_Id_NotFound' => 'オブジェクト:%1$s:%2$d が見つかりません。', //'Object: %1$s:%2$d not found.',
'UI:Error:WizardCircularReferenceInDependencies' => 'エラー: フィールド間の依存関係に循環参照があります。データモデルを確認してください。', //'Error: Circular reference in the dependencies between the fields, check the data model.',
'UI:Error:UploadedFileTooBig' => 'アップロードファイルが大きすぎます(上限は %1$s )。PHPの設定にある、upload_max_filesizeと、post_max_sizeを確認してください。', //'Uploaded file is too big. (Max allowed size is %1$s). Check you PHP configuration for upload_max_filesize and post_max_size.',
'UI:Error:UploadedFileTruncated.' => 'アップロードファイルが切り捨てられました!', //'Uploaded file has been truncated !',
'UI:Error:NoTmpDir' => 'この一時ディレクトリは定義されていません。', //'The temporary directory is not defined.',
'UI:Error:CannotWriteToTmp_Dir' => '一時ファイルをディスクに書き込めません。upload_tmp_dir = "%1$s" です。', //'Unable to write the temporary file to the disk. upload_tmp_dir = "%1$s".',
'UI:Error:UploadStoppedByExtension_FileName' => 'extensionにより、アップロードを停止しました。(オリジナルのファイル名は"%1$s"です)。', //'Upload stopped by extension. (Original file name = "%1$s").',
'UI:Error:UploadFailedUnknownCause_Code' => 'ファイルのアップロードに失敗しました。原因は不明(エラーコード: "%1$s")です。', //'File upload failed, unknown cause. (Error code = "%1$s").',
'UI:Error:1ParametersMissing' => 'エラー: この操作には下記のパラメータを指定する必要があります:%1$s', //'Error: the following parameter must be specified for this operation: %1$s.',
'UI:Error:2ParametersMissing' => 'エラー:この操作には、下記のパラメータを指定する必要があります:%1$s , %2$s', //'Error: the following parameters must be specified for this operation: %1$s and %2$s.',
'UI:Error:3ParametersMissing' => 'エラー:この操作には、下記のパラメータを指定する必要があります:%1$s, %2$s, %3$s', //Error: the following parameters must be specified for this operation: %1$s, %2$s and %3$s.',
'UI:Error:4ParametersMissing' => 'エラー:この操作には、下記のパラメータを指定する必要があります:%1$s, %2$s, %3$s,%4$s', //'Error: the following parameters must be specified for this operation: %1$s, %2$s, %3$s and %4$s.',
'UI:Error:IncorrectOQLQuery_Message' => 'エラーOQLクエリが正しくありません%1$s', //'Error: incorrect OQL query: %1$s',
'UI:Error:AnErrorOccuredWhileRunningTheQuery_Message' => 'クエリ;%1$s 実行中にエラーが発生しました。', //'An error occured while running the query: %1$s',
'UI:Error:ObjectAlreadyUpdated' => 'エラー:このオブジェクトはすでに更新済みです。', //'Error: the object has already been updated.',
'UI:Error:ObjectCannotBeUpdated' => 'エラー:オブジェクトを更新できません。', //'Error: object cannot be updated.',
'UI:Error:ObjectsAlreadyDeleted' => 'エラー:オブジェクトは既に削除されています。', //'Error: objects have already been deleted!',
'UI:Error:BulkDeleteNotAllowedOn_Class' => '%1$s クラスのオブジェクトに対するバルク削除は許可されていません。', //'You are not allowed to perform a bulk delete of objects of class %1$s',
'UI:Error:DeleteNotAllowedOn_Class' => '%1$s クラスのオブジェクトの削除は許可されていません。', //'You are not allowed to delete objects of class %1$s',
'UI:Error:BulkModifyNotAllowedOn_Class' => '%1$s クラスのオブジェクトに対するバルクアップデート処理の実行は許可されていません。', //'You are not allowed to perform a bulk update of objects of class %1$s',
'UI:Error:ObjectAlreadyCloned' => 'エラー:このオブジェクトはすでに、クローニングされています。', // 'Error: the object has already been cloned!',
'UI:Error:ObjectAlreadyCreated' => 'エラー:このオブジェクトは既に生成済みです。', //'Error: the object has already been created!',
'UI:Error:Invalid_Stimulus_On_Object_In_State' => 'エラーError: invalid stimulus "%1$s" on object %2$s in state "%3$s".',
'UI:GroupBy:Count' => 'カウント', //'Count',
'UI:GroupBy:Count+' => '要素数', //'Number of elements',
'UI:CountOfObjects' => '%1$d 個のオブジェクトが条件にマッチしました。', //'%1$d objects matching the criteria.',
'UI_CountOfObjectsShort' => '%1$d オブジェクトです。', //'%1$d objects.',
'UI:NoObject_Class_ToDisplay' => '表示できる %1$s はありません。', //'No %1$s to display',
'UI:History:LastModified_On_By' => '最終更新日: %1$s ( %2$s )', //'Last modified on %1$s by %2$s.',
'UI:HistoryTab' => '履歴', //'History',
'UI:NotificationsTab' => '通知', //'Notifications',
'UI:History:BulkImports' => '履歴', //'History',
'UI:History:BulkImports+' => 'CSVインポートのリスト(last first)', //'List of CSV imports (last first)',
'UI:History:BulkImportDetails' => '%2$s により実行された %1$s へのCSVインポート結果の変更???', // 'Changes resulting from the CSV import performed on %1$s (by %2$s)',
'UI:History:Date' => '日付',//'Date',
'UI:History:Date+' => '更新日時', //'Date of the change',
'UI:History:User' => 'ユーザ', //'User',
'UI:History:User+' => 'この変更を行ったユーザ', //'User who made the change',
'UI:History:Changes' => '変更', //'Changes',
'UI:History:Changes+' => 'このオブジェクトを変更する', //'Changes made to the object',
'UI:History:StatsCreations' => '生成された', //'Created',
'UI:History:StatsCreations+' => '生成されたオブジェクト数', //'Count of objects created',
'UI:History:StatsModifs' => '修正された', //'Modified',
'UI:History:StatsModifs+' => '修正されたオブジェクト数', //'Count of objects modified',
'UI:History:StatsDeletes' => '削除された', //'Deleted',
'UI:History:StatsDeletes+' => '削除されたオブジェクト数', //'Count of objects deleted',
'UI:Loading' => '読み込み...', //'Loading...',
'UI:Menu:Actions' => '実行...', //'Actions',
'UI:Menu:OtherActions' => '実行...', //'Actions',
'UI:Menu:New' => '新規...', //'New...',
'UI:Menu:Add' => '追加...', //'Add...',
'UI:Menu:Manage' => '管理する...', //'Manage...',
'UI:Menu:EMail' => 'Eメール', //'eMail',
'UI:Menu:CSVExport' => 'CSVエクスポート', //'CSV Export',
'UI:Menu:Modify' => '修正する...', //'Modify...',
'UI:Menu:Delete' => '削除する...', //'Delete...',
'UI:Menu:Manage' => '管理する...', //'Manage...',
'UI:Menu:BulkDelete' => '削除する', //'Delete...',
'UI:UndefinedObject' => '未定義', //'undefined',
'UI:Document:OpenInNewWindow:Download' => '新規ウィンドウで開く: %1$s, ダウンロード: %2$s', //'Open in new window: %1$s, Download: %2$s',
'UI:SelectAllToggle+' => 'すべて選択 / すべて非選択', //'Select / Deselect All',
'UI:TruncatedResults' => '%1$d objects displayed out of %2$d',
'UI:DisplayAll' => 'すべて表示', //'Display All',
'UI:CollapseList' => '折り畳む', //'Collapse',
'UI:CountOfResults' => '%1$d オブジェクト', //'%1$d object(s)',
'UI:ChangesLogTitle' => '変更履歴(%1$d)', //'Changes log (%1$d):',
'UI:EmptyChangesLogTitle' => '変更履歴は空です。', //'Changes log is empty',
'UI:SearchFor_Class_Objects' => '%1$s オブジェクトを検索', //'Search for %1$s Objects',
'UI:OQLQueryBuilderTitle' => 'OQLクエリビルダ', //'OQL Query Builder',
'UI:OQLQueryTab' => 'OQLクエリ', //'OQL Query',
'UI:SimpleSearchTab' => '単純検索', //'Simple Search',
'UI:Details+' => '詳細情報', //'Details',
'UI:SearchValue:Any' => '* 任意 *', //'* Any *',
'UI:SearchValue:Mixed' => '* 混成 *', //'* mixed *',
'UI:SelectOne' => '-- 選んでください --', //'-- select one --',
'UI:Login:Welcome' => 'iTopへようこそ', //'Welcome to iTop!',
'UI:Login:IncorrectLoginPassword' => 'ログイン/パスワードが正しくありません。再度ログインしてください。', //'Incorrect login/password, please try again.',
'UI:Login:IdentifyYourself' => '続けて作業を行う前に認証を受けてください。', //'Identify yourself before continuing',
'UI:Login:UserNamePrompt' => 'ユーザ名', //'User Name',
'UI:Login:PasswordPrompt' => 'パスワード', //'Password',
'UI:Login:ChangeYourPassword' => 'パスワードを変更してください', //'Change Your Password',
'UI:Login:OldPasswordPrompt' => '既存パスワード',//'Old password',
'UI:Login:NewPasswordPrompt' => '新規パスワード', //'New password',
'UI:Login:RetypeNewPasswordPrompt' => '新規パスワードを再度入力してください。', //'Retype new password',
'UI:Login:IncorrectOldPassword' => 'エラー:既存パスワードが正しくありません。', //'Error: the old password is incorrect',
'UI:LogOffMenu' => 'ログオフ', //'Log off',
'UI:LogOff:ThankYou' => 'iTopをご利用いただき、ありがとうございます。', //'Thank you for using iTop',
'UI:LogOff:ClickHereToLoginAgain' => '再度ログインするにはここをクリックしてください...', //'Click here to login again...',
'UI:ChangePwdMenu' => 'パスワードを変更する...', //'Change Password...',
'UI:AccessRO-All' => 'iTopは参照のみ有効です。', //'iTop is read-only',
'UI:AccessRO-Users' => 'エンドユーザの方はiTopは参照のみ有効です。', //'iTop is read-only for end-users',
'UI:Login:RetypePwdDoesNotMatch' => '2度入力された新規パスワードが一致しません!', //'New password and retyped new password do not match !',
'UI:Button:Login' => 'iTopへ入る', //'Enter iTop',
'UI:Login:Error:AccessRestricted' => 'iTopへのアクセスは制限されています。iTop管理者に問い合わせしてください。', //'iTop access is restricted. Please, contact an iTop administrator.',
'UI:Login:Error:AccessAdmin' => '管理者権限をもつユーザにアクセスが制限されています。iTop管理者に問い合わせしてください。', //'Access restricted to people having administrator privileges. Please, contact an iTop administrator.',
'UI:CSVImport:MappingSelectOne' => '-- 選択してください --', //'-- select one --',
'UI:CSVImport:MappingNotApplicable' => '--このフィールドを無視する --', //'-- ignore this field --',
'UI:CSVImport:NoData' => 'データが空です..., データを指定してください。', // 'Empty data set..., please provide some data!',
'UI:Title:DataPreview' => 'データプレビュー', //'Data Preview',
'UI:CSVImport:ErrorOnlyOneColumn' => 'エラーこのデータにはカラムが1つしか含まれていません。適切なセパレータ文字を選択しましたか?', //'Error: The data contains only one column. Did you select the appropriate separator character?',
'UI:CSVImport:FieldName' => 'フィールド: %1$d', //'Field %1$d',
'UI:CSVImport:DataLine1' => 'データ行 1', //'Data Line 1',
'UI:CSVImport:DataLine2' => 'データ行 2', //'Data Line 2',
'UI:CSVImport:idField' => 'ID (プライマリキー)', //'id (Primary Key)',
'UI:Title:BulkImport' => 'iTop - バルクインポート', //'iTop - Bulk import',
'UI:Title:BulkImport+' => 'CSV インポートウィザード', //'CSV Import Wizard',
'UI:Title:BulkSynchro_nbItem_ofClass_class' => '%2$s クラスの %1$d オブジェクトを同期', //'Synchronization of %1$d objects of class %2$s',
'UI:CSVImport:ClassesSelectOne' => '--選択してください --', //'-- select one --',
'UI:CSVImport:ErrorExtendedAttCode' => '内部エラー: "%2$s" は"%3$s"クラスの外部キーではないので、"%1$s" は正しくないコードです。', // 'Internal error: "%1$s" is an incorrect code because "%2$s" is NOT an external key of the class "%3$s"',
'UI:CSVImport:ObjectsWillStayUnchanged' => '%1$d オブジェクトが変更されないままです。', //'%1$d objects(s) will stay unchanged.',
'UI:CSVImport:ObjectsWillBeModified' => '%1$d オブジェクトが修正されます。', //'%1$d objects(s) will be modified.',
'UI:CSVImport:ObjectsWillBeAdded' => '%1$d オブジェクトが追加されます。', //'%1$d objects(s) will be added.',
'UI:CSVImport:ObjectsWillHaveErrors' => '%1$d オブジェクトにエラーがあります。', //'%1$d objects(s) will have errors.',
'UI:CSVImport:ObjectsRemainedUnchanged' => '%1$d オブジェクトは変更されていません。', //'%1$d objects(s) remained unchanged.',
'UI:CSVImport:ObjectsWereModified' => '%1$d オブジェクトが変更されました。', //'%1$d objects(s) were modified.',
'UI:CSVImport:ObjectsWereAdded' => '%1$d オブジェクトが追加されました。', //'%1$d objects(s) were added.',
'UI:CSVImport:ObjectsHadErrors' => '%1$s オブジェクトにエラーがあります。', //'%1$d objects(s) had errors.',
'UI:Title:CSVImportStep2' => 'ステップ2/5: CSVデータオプション', //'Step 2 of 5: CSV data options',
'UI:Title:CSVImportStep3' => 'ステップ3/5: データマッピング', //'Step 3 of 5: Data mapping',
'UI:Title:CSVImportStep4' => 'ステップ4/5: インポートシミュレーション', //'Step 4 of 5: Import simulation',
'UI:Title:CSVImportStep5' => 'ステップ5/5: インポート完了', //'Step 5 of 5: Import completed',
'UI:CSVImport:LinesNotImported' => 'ロードできなかった行:', //'Lines that could not be loaded:',
'UI:CSVImport:LinesNotImported+' => '下記の行はエラーが含まれていたのでインポートされませんでした。', //'The following lines have not been imported because they contain errors',
'UI:CSVImport:SeparatorComma+' => ', (コンマ)', //', (comma)',
'UI:CSVImport:SeparatorSemicolon+' => '; (セミコロン)', //'; (semicolon)',
'UI:CSVImport:SeparatorTab+' => 'タブ', //'tab',
'UI:CSVImport:SeparatorOther' => 'その他:', //'other:',
'UI:CSVImport:QualifierDoubleQuote+' => '" (ダブルクォート)', //'" (double quote)',
'UI:CSVImport:QualifierSimpleQuote+' => '\' (シングルクォート)', //'\' (simple quote)',
'UI:CSVImport:QualifierOther' => 'その他:', //'other:',
'UI:CSVImport:TreatFirstLineAsHeader' => '1行めをヘッダ(カラム名)として扱う', // 'Treat the first line as a header (column names)',
'UI:CSVImport:Skip_N_LinesAtTheBeginning' => 'ファイル冒頭の%1$s 行をスキップする', //'Skip %1$s line(s) at the beginning of the file',
'UI:CSVImport:CSVDataPreview' => 'CSVデータプレビュー', //'CSV Data Preview',
'UI:CSVImport:SelectFile' => 'インポートするファイルを選択してください:', //'Select the file to import:',
'UI:CSVImport:Tab:LoadFromFile' => 'ファイルからロードしてください', //'Load from a file',
'UI:CSVImport:Tab:CopyPaste' => 'データをコピーペーストしてください', //'Copy and paste data',
'UI:CSVImport:Tab:Templates' => 'テンプレート', //'Templates',
'UI:CSVImport:PasteData' => 'インポートするデータをペーストしてください', //'Paste the data to import:',
'UI:CSVImport:PickClassForTemplate' => 'ダウンロードするテンプレートを選んでください', //'Pick the template to download: ',
'UI:CSVImport:SeparatorCharacter' => 'セパレータ文字', //'Separator character:',
'UI:CSVImport:TextQualifierCharacter' => 'テキスト識別文字', //'Text qualifier character',
'UI:CSVImport:CommentsAndHeader' => 'コメントとヘッダ', //'Comments and header',
'UI:CSVImport:SelectClass' => 'インポートするクラスを選択してください', //'Select the class to import:',
'UI:CSVImport:AdvancedMode' => '拡張モード', //'Advanced mode',
'UI:CSVImport:AdvancedMode+' => '拡張モードでは、オブジェクトに付与されている"id"(プライマリキー)がオブジェクトの更新、リネームに指定可能です。' . //In advanced mode the "id" (primary key) of the objects can be used to update and rename objects.' .
'しかしながら、"id"カラムは(たとえ存在しても)検索条件として指定できるのみであり、他の検索条件と組み合わせて利用することはできません。', //'However the column "id" (if present) can only be used as a search criteria and can not be combined with any other search criteria.',
'UI:CSVImport:SelectAClassFirst' => 'マッピングを設定するには、まず最初にクラスを選択してください。', //'To configure the mapping, select a class first.',
'UI:CSVImport:HeaderFields' => 'フィールド', //'Fields',
'UI:CSVImport:HeaderMappings' => 'マッピング', //'Mappings',
'UI:CSVImport:HeaderSearch' => '検索しますか?', //'Search?',
'UI:CSVImport:AlertIncompleteMapping' => 'すべてのフィールドのマッピングを選択してください。', //'Please select a mapping for every field.',
'UI:CSVImport:AlertNoSearchCriteria' => '少なくとも1つ以上の検索条件を選択してください。', //'Please select at least one search criteria',
'UI:CSVImport:Encoding' => '文字エンコーディング', //'Character encoding',
'UI:UniversalSearchTitle' => 'iTop - ユニバーサルサーチ', //'iTop - Universal Search',
'UI:UniversalSearch:Error' => 'エラー:%1$s', //'Error: %1$s',
'UI:UniversalSearch:LabelSelectTheClass' => '検索するクラスを選択してください。', //'Select the class to search: ',
'UI:Audit:Title' => 'iTop - CMDB 監査', //'iTop - CMDB Audit',
'UI:Audit:InteractiveAudit' => '対話型監査', //'Interactive Audit',
'UI:Audit:HeaderAuditRule' => '監査ルール', //'Audit Rule',
'UI:Audit:HeaderNbObjects' => 'オブジェクト数', //'# Objects',
'UI:Audit:HeaderNbErrors' => 'エラー数', //'# Errors',
'UI:Audit:PercentageOk' => '% OK', //'% Ok',
'UI:RunQuery:Title' => 'iTop - OQLクエリ評価', //'iTop - OQL Query Evaluation',
'UI:RunQuery:QueryExamples' => 'クエリの例', //'Query Examples',
'UI:RunQuery:HeaderPurpose' => '目的', //'Purpose',
'UI:RunQuery:HeaderPurpose+' => 'クエリについての説明', //'Explanation about the query',
'UI:RunQuery:HeaderOQLExpression' => 'OQL式', //'OQL Expression',
'UI:RunQuery:HeaderOQLExpression+' => 'OQL文法によるクエリ', //'The query in OQL syntax',
'UI:RunQuery:ExpressionToEvaluate' => '評価式', //'Expression to evaluate: ',
'UI:RunQuery:MoreInfo' => '本クエリに関する詳細情報', //'More information about the query: ',
'UI:RunQuery:DevelopedQuery' => 'クエリ式の再開発', //'Redevelopped query expression: ',
'UI:RunQuery:SerializedFilter' => '序列化フィルタ:', //'Serialized filter: ',
'UI:RunQuery:Error' => '本クエリ実行時にエラーが発生しました:%1$s', //'An error occured while running the query: %1$s',
'UI:Schema:Title' => 'iTop オブジェクトスキーマ', //'iTop objects schema',
'UI:Schema:CategoryMenuItem' => 'カテゴリ <b>%1$s</b>', //'Category <b>%1$s</b>',
'UI:Schema:Relationships' => '関連', //'Relationships',
'UI:Schema:AbstractClass' => '抽象クラス:このクラスのインスタンスを生成することはできません。', //'Abstract class: no object from this class can be instantiated.',
'UI:Schema:NonAbstractClass' => '非抽象クラス:このクラスのインスタンスを生成できます。', //'Non abstract class: objects from this class can be instantiated.',
'UI:Schema:ClassHierarchyTitle' => 'クラス階層', //'Class hierarchy',
'UI:Schema:AllClasses' => '全クラス', //'All classes',
'UI:Schema:ExternalKey_To' => '%1$s の外部キー', //'External key to %1$s',
'UI:Schema:Columns_Description' => 'カラム: <em>%1$s</em>', //'Columns: <em>%1$s</em>',
'UI:Schema:Default_Description' => 'デフォルト: "%1$s"', //'Default: "%1$s"',
'UI:Schema:NullAllowed' => 'Null許容', //'Null Allowed',
'UI:Schema:NullNotAllowed' => 'Null 非許容', //'Null NOT Allowed',
'UI:Schema:Attributes' => '属性', //'Attributes',
'UI:Schema:AttributeCode' => '属性コード', //'Attribute Code',
'UI:Schema:AttributeCode+' => '属性の内部コード', //'Internal code of the attribute',
'UI:Schema:Label' => 'ラベル', //'Label',
'UI:Schema:Label+' => '属性のラベル', //'Label of the attribute',
'UI:Schema:Type' => '型', //'Type',
'UI:Schema:Type+' => '属性のデータ型', //'Data type of the attribute',
'UI:Schema:Origin' => 'オリジン', //'Origin',
'UI:Schema:Origin+' => 'この属性が定義されているベースクラス', //'The base class in which this attribute is defined',
'UI:Schema:Description' => '概要', //'Description',
'UI:Schema:Description+' => '本属性の概要', //'Description of the attribute',
'UI:Schema:AllowedValues' => '取りうる値', //'Allowed values',
'UI:Schema:AllowedValues+' => '本属性で取りうる値の制限', //'Restrictions on the possible values for this attribute',
'UI:Schema:MoreInfo' => '詳細情報', //'More info',
'UI:Schema:MoreInfo+' => 'データベースに定義された本フィールドの詳細情報', //'More information about the field defined in the database',
'UI:Schema:SearchCriteria' => '検索条件', //'Search criteria',
'UI:Schema:FilterCode' => 'フィルタコード', //'Filter code',
'UI:Schema:FilterCode+' => '本検索条件のコード', //'Code of this search criteria',
'UI:Schema:FilterDescription' => '概要', //'Description',
'UI:Schema:FilterDescription+' => '本検索条件の概要', //'Description of this search criteria',
'UI:Schema:AvailOperators' => '利用可能な演算子', //'Available operators',
'UI:Schema:AvailOperators+' => '本検索条件で利用可能な演算子', //'Possible operators for this search criteria',
'UI:Schema:ChildClasses' => '子クラス', //'Child classes',
'UI:Schema:ReferencingClasses' => '参照クラス', //'Referencing classes',
'UI:Schema:RelatedClasses' => '関係するクラス', //'Related classes',
'UI:Schema:LifeCycle' => 'ライフサイクル', //'Life cycle',
'UI:Schema:Triggers' => 'トリガ', //'Triggers',
'UI:Schema:Relation_Code_Description' => 'リレーション <em>%1$s</em> (%2$s)', //'Relation <em>%1$s</em> (%2$s)',
'UI:Schema:RelationDown_Description' => '下へ: %1$s', //'Down: %1$s',
'UI:Schema:RelationUp_Description' => '上へ: %1$s', //'Up: %1$s',
'UI:Schema:RelationPropagates' => '%1$s: %2$d レベルへ伝播、クエリ:%3$s', //'%1$s: propagate to %2$d levels, query: %3$s',
'UI:Schema:RelationDoesNotPropagate' => '%1$s: 伝播しない (%2$d レベル), クエリ: %3$s', //'%1$s: does not propagates (%2$d levels), query: %3$s',
'UI:Schema:Class_ReferencingClasses_From_By' => '%1$s は%2$s クラスから %3$s フィールドにより参照されている', //'%1$s is referenced by the class %2$s via the field %3$s',
'UI:Schema:Class_IsLinkedTo_Class_Via_ClassAndAttribute' => '%1$s は %3$s::<em>%4$s</em>により%2$s へリンクされています。', //'%1$s is linked to %2$s via %3$s::<em>%4$s</em>',
'UI:Schema:Links:1-n' => 'クラスは%1$sへポイントしています。(1:n リンク)', //'Classes pointing to %1$s (1:n links):',
'UI:Schema:Links:n-n' => 'クラスは%1$sへリンクしています。(n:n リンク)', //'Classes linked to %1$s (n:n links):',
'UI:Schema:Links:All' => '関連する全クラスのグラフ表示', //'Graph of all related classes',
'UI:Schema:NoLifeCyle' => 'このクラスにはライフサイクルが定義されていません。', //'There is no life cycle defined for this class.',
'UI:Schema:LifeCycleTransitions' => 'トランジション', //'Transitions',
'UI:Schema:LifeCyleAttributeOptions' => '属性オプション', //'Attribute options',
'UI:Schema:LifeCycleHiddenAttribute' => '隠し', //'Hidden',
'UI:Schema:LifeCycleReadOnlyAttribute' => '参照限定',// 'Read-only',
'UI:Schema:LifeCycleMandatoryAttribute' => '必須', //'Mandatory',
'UI:Schema:LifeCycleAttributeMustChange' => '変更必須', //'Must change',
'UI:Schema:LifeCycleAttributeMustPrompt' => 'ユーザはこの値を変更するよう、促されます。', //'User will be prompted to change the value',
'UI:Schema:LifeCycleEmptyList' => '空リスト', //'empty list',
'UI:LinksWidget:Autocomplete+' => '最初の3文字をタイプしてください...', //'Type the first 3 characters...',
'UI:Combo:SelectValue' => '--- 値を選んでください ---', //'--- select a value ---',
'UI:Label:SelectedObjects' => '選択されたオブジェクト: ', //'Selected objects: ',
'UI:Label:AvailableObjects' => '選択可能なオブジェクト: ', //'Available objects: ',
'UI:Link_Class_Attributes' => '%1$s 属性', //'%1$s attributes',
'UI:SelectAllToggle+' => '全部を選択 / 全部を非選択', //'Select All / Deselect All',
'UI:AddObjectsOf_Class_LinkedWith_Class_Instance' => '%2$s にリンクされた%1$sオブジェクトを追加%3$s', //'Add %1$s objects linked with %2$s: %3$s',
'UI:AddObjectsOf_Class_LinkedWith_Class' => '%1$s オブジェクトを%2$sとのリンクに追加', //'Add %1$s objects to link with the %2$s',
'UI:ManageObjectsOf_Class_LinkedWith_Class_Instance' => '%2$s とりんくされた%1$sオブジェクトを管理する: %3$s', //'Manage %1$s objects linked with %2$s: %3$s',
'UI:AddLinkedObjectsOf_Class' => '%1$s を追加...', //'Add %1$ss...',
'UI:RemoveLinkedObjectsOf_Class' => '選択したオブジェクトを除外', //'Remove selected objects',
'UI:Message:EmptyList:UseAdd' => 'リストは空です。"追加..."ボタンを利用して要素を追加してください。', //'The list is empty, use the "Add..." button to add elements.',
'UI:Message:EmptyList:UseSearchForm' => '上の検索フォームを使って追加するオブジェクトを検索してください。', //'Use the search form above to search for objects to be added.',
'UI:Wizard:FinalStepTitle' => '最終ステップ:コンファーム', //'Final step: confirmation',
'UI:Title:DeletionOf_Object' => '%1$sの削除', //'Deletion of %1$s',
'UI:Title:BulkDeletionOf_Count_ObjectsOf_Class' => '%2$s クラスの%1$d個のオブジェクトをバルク削除', //'Bulk deletion of %1$d objects of class %2$s',
'UI:Delete:NotAllowedToDelete' => 'このオブジェクトを削除する権限がありません。', //'You are not allowed to delete this object',
'UI:Delete:NotAllowedToUpdate_Fields' => '以下のフィールドを更新する権限が与えられていません: %1$s', //'You are not allowed to update the following field(s): %1$s',
'UI:Error:NotEnoughRightsToDelete' => 'カレントユーザは十分な権限を持っていないので、このオブジェクトは削除することができません。', //'This object could not be deleted because the current user do not have sufficient rights',
'UI:Error:CannotDeleteBecauseOfDepencies' => 'いくつかのマニュアル操作を先に実装する必要があるので、このオブジェクトは削除できません。', //'This object could not be deleted because some manual operations must be performed prior to that',
'UI:Archive_User_OnBehalfOf_User' => '%2$s を代表して %1$s', // '%1$s on behalf of %2$s',
'UI:Delete:AutomaticallyDeleted' => '自動的に削除されました。', //'automatically deleted',
'UI:Delete:AutomaticResetOf_Fields' => 'フィールドの自動リセット: %1$s', //'automatic reset of field(s): %1$s',
'UI:Delete:CleaningUpRefencesTo_Object' => '%1$s への参照すべてをクリア', //'Cleaning up all references to %1$s...',
'UI:Delete:CleaningUpRefencesTo_Several_ObjectsOf_Class' => '%2$s クラスの %1$d個のオブジェクトへの参照をすべてクリア', //'Cleaning up all references to %1$d objects of class %2$s...',
'UI:Delete:Done+' => '実行しました...???', //'What was done...',
'UI:Delete:_Name_Class_Deleted' => '%1$s - %2$s 削除しました。', //'%1$s - %2$s deleted.',
'UI:Delete:ConfirmDeletionOf_Name' => '%1$s の削除', //'Deletion of %1$s',
'UI:Delete:ConfirmDeletionOf_Count_ObjectsOf_Class' => '%2$sクラスの%1$dオブジェクトの削除', //'Deletion of %1$d objects of class %2$s',
'UI:Delete:ShouldBeDeletedAtomaticallyButNotAllowed' => '自動的に削除されるべきだが、そのための権限がありません。', //'Should be automaticaly deleted, but you are not allowed to do so',
'UI:Delete:MustBeDeletedManuallyButNotAllowed' => '手動で削除されるべきだが、このオブジェクトを削除するための権限がありません。アプリケーション管理者に問い合わせてください。', //'Must be deleted manually - but you are not allowed to delete this object, please contact your application admin',
'UI:Delete:WillBeDeletedAutomatically' => '自動的に削除されます。', //'Will be automaticaly deleted',
'UI:Delete:MustBeDeletedManually' => '手動で削除されるべきです。', //'Must be deleted manually',
'UI:Delete:CannotUpdateBecause_Issue' => '自動的に更新されるべきだが: %1$s', //'Should be automatically updated, but: %1$s',
'UI:Delete:WillAutomaticallyUpdate_Fields' => 'は自動的に更新されます。(reset: %1$s)', //'will be automaticaly updated (reset: %1$s)',
'UI:Delete:Count_Objects/LinksReferencing_Object' => '%1$dオブジェクト/リンクは%2$sを参照しています。', //'%1$d objects/links are referencing %2$s',
'UI:Delete:Count_Objects/LinksReferencingTheObjects' => '%1$dオブジェクト/リンクは削除されるべきオブジェクトを参照しています。', //'%1$d objects/links are referencing some of the objects to be deleted',
'UI:Delete:ReferencesMustBeDeletedToEnsureIntegrity' => 'データベース一貫性を確実にするために、いくつかの参照を除去する必要があります。', //'To ensure Database integrity, any reference should be further eliminated',
'UI:Delete:Consequence+' => 'What will be done',
'UI:Delete:SorryDeletionNotAllowed' => '申し訳ございません。このオブジェクトを削除する権限がありません。上述の詳細説明を参照してください。', //'Sorry, you are not allowed to delete this object, see the detailed explanations above',
'UI:Delete:PleaseDoTheManualOperations' => '本オブジェクトの削除を要求する前に、上記にリストされている操作を手動で行ってください。', //'Please perform the manual operations listed above prior to requesting the deletion of this object',
'UI:Delect:Confirm_Object' => '%1$sを削除しようとしています。確認してください。', //'Please confirm that you want to delete %1$s.',
'UI:Delect:Confirm_Count_ObjectsOf_Class' => '以下の%2$sクラスの%1$dオブジェクトを削除しようとしています。確認してください。', //'Please confirm that you want to delete the following %1$d objects of class %2$s.',
'UI:WelcomeToITop' => 'iTopへようこそ', //'Welcome to iTop',
'UI:DetailsPageTitle' => 'iTop - %1$s - %2$sの詳細', //'iTop - %1$s - %2$s details',
'UI:ErrorPageTitle' => 'iTop - エラー', //'iTop - Error',
'UI:ObjectDoesNotExist' => '申し訳ございません。このオブジェクトは既に存在しません。(あるいは参照する権限がありません。)', //'Sorry, this object does not exist (or you are not allowed to view it).',
'UI:SearchResultsPageTitle' => 'iTop - 検索結果', //'iTop - Search Results',
'UI:Search:NoSearch' => '検索するものがありません。', //'Nothing to search for',
'UI:FullTextSearchTitle_Text' => '"%1$s"の結果:', //'Results for "%1$s":',
'UI:Search:Count_ObjectsOf_Class_Found' => '%2$sクラスの%1$dオブジェクトが見つかりました。', //'%1$d object(s) of class %2$s found.',
'UI:Search:NoObjectFound' => 'オブジェクトが見つかりませんでした。', //'No object found.',
'UI:ModificationPageTitle_Object_Class' => 'iTop - %1$s - %2$s 修正???', //'iTop - %1$s - %2$s modification',
'UI:ModificationTitle_Class_Object' => '%1$sの修正 <span class=\"hilite\">%2$s</span>', //'Modification of %1$s: <span class=\"hilite\">%2$s</span>',
'UI:ClonePageTitle_Object_Class' => 'iTop - クローン%1$s - %2$s 修正???', //'iTop - Clone %1$s - %2$s modification',
'UI:CloneTitle_Class_Object' => '%1$sのクローン<span class=\"hilite">%2$s</span>', //'Clone of %1$s: <span class=\"hilite\">%2$s</span>',
'UI:CreationPageTitle_Class' => 'iTop - 新規%1$sを生成', //'iTop - Creation of a new %1$s ',
'UI:CreationTitle_Class' => '新規%1$sの生成', //'Creation of a new %1$s',
'UI:SelectTheTypeOf_Class_ToCreate' => '生成する%1$sの型を選択', //'Select the type of %1$s to create:',
'UI:Class_Object_NotUpdated' => '変更は検出されませんでした。%1$sは修正されて<strong>いません</strong>', //'No change detected, %1$s (%2$s) has <strong>not</strong> been modified.',
'UI:Class_Object_Updated' => '%1$s (%2$s) は更新されました。', //'%1$s (%2$s) updated.',
'UI:BulkDeletePageTitle' => 'iTop - バルク削除', //'iTop - Bulk Delete',
'UI:BulkDeleteTitle' => '削除するオブジェクトを選択してください。', //'Select the objects you want to delete:',
'UI:PageTitle:ObjectCreated' => 'iTopオブジェクトが生成されました。', //'iTop Object Created.',
'UI:Title:Object_Of_Class_Created' => '%1$s - %2$s が生成されました。', //'%1$s - %2$s created.',
'UI:Apply_Stimulus_On_Object_In_State_ToTarget_State' => '状態%3$sにある%1$sを状態%4$s状態をターゲットに、オブジェクト%2$sに適用します。', //'Applying %1$s on object: %2$s in state %3$s to target state: %4$s.',
'UI:ObjectCouldNotBeWritten' => 'そのオブジェクトは書き込みできません: %1$s', //'The object could not be written: %1$s',
'UI:PageTitle:FatalError' => 'iTop - 致命的エラー', // 'iTop - Fatal Error',
'UI:SystemIntrusion' => 'アクセスできません。権限のない操作を行おうとしています。', //'Access denied. You have trying to perform an operation that is not allowed for you.',
'UI:FatalErrorMessage' => '致命的エラー、iTopは処理を継続できません。', //'Fatal error, iTop cannot continue.',
'UI:Error_Details' => 'エラー:%1$s', //'Error: %1$s.',
'UI:PageTitle:ClassProjections' => 'iTop ユーザ管理', //'iTop user management - class projections',
'UI:PageTitle:ProfileProjections' => 'iTop ユーザ管理 - プロファイル立案', //'iTop user management - profile projections',
'UI:UserManagement:Class' => 'クラス', //'Class',
'UI:UserManagement:Class+' => 'オブジェクトのクラス', //'Class of objects',
'UI:UserManagement:ProjectedObject' => 'オブジェクト', //'Object',
'UI:UserManagement:ProjectedObject+' => 'Projected object',
'UI:UserManagement:AnyObject' => '* 任意 *', //'* any *',
'UI:UserManagement:User' => 'ユーザ', //'User',
'UI:UserManagement:User+' => 'User involved in the projection',
'UI:UserManagement:Profile' => 'プロファイル', //'Profile',
'UI:UserManagement:Profile+' => 'Profile in which the projection is specified',
'UI:UserManagement:Action:Read' => '読み込み', //'Read',
'UI:UserManagement:Action:Read+' => 'オブジェクトの読み込み/表示', //'Read/display objects',
'UI:UserManagement:Action:Modify' => '修正', //'Modify',
'UI:UserManagement:Action:Modify+' => 'オブジェクトの生成、編集(修正)', //'Create and edit (modify) objects',
'UI:UserManagement:Action:Delete' => '削除', //'Delete',
'UI:UserManagement:Action:Delete+' => 'オブジェクトの削除', //'Delete objects',
'UI:UserManagement:Action:BulkRead' => '一括読み出し(エクスポート)', //'Bulk Read (Export)',
'UI:UserManagement:Action:BulkRead+' => 'オブジェクトのリスト表示、もしくは一括エクスポート', // 'List objects or export massively',
'UI:UserManagement:Action:BulkModify' => '一括修正', // 'Bulk Modify',
'UI:UserManagement:Action:BulkModify+' => '一括生成/編集(CVSインポート)', //'Massively create/edit (CSV import)',
'UI:UserManagement:Action:BulkDelete' => '一括削除', //'Bulk Delete',
'UI:UserManagement:Action:BulkDelete+' => '複数オブジェクトをまとめて削除', //'Massively delete objects',
'UI:UserManagement:Action:Stimuli' => 'Stimuli',
'UI:UserManagement:Action:Stimuli+' => '許可されている(複合)アクション', //'Allowed (compound) actions',
'UI:UserManagement:Action' => 'アクション', // 'Action',
'UI:UserManagement:Action+' => 'ユーザが実行したアクション', // 'Action performed by the user',
'UI:UserManagement:TitleActions' => 'アクション', //'Actions',
'UI:UserManagement:Permission' => 'パーミッション', //'Permission',
'UI:UserManagement:Permission+' => 'ユーザのパーミッション', // 'User\'s permissions',
'UI:UserManagement:Attributes' => '属性', // 'Attributes',
'UI:UserManagement:ActionAllowed:Yes' => 'はい', //'Yes',
'UI:UserManagement:ActionAllowed:No' => 'いいえ', //'No',
'UI:UserManagement:AdminProfile+' => '管理者にはデータベース中の全てのオブジェクトに対する読み/書きの全権限が与えられます。', //'Administrators have full read/write access to all objects in the database.',
'UI:UserManagement:NoLifeCycleApplicable' => 'N/A',
'UI:UserManagement:NoLifeCycleApplicable+' => 'この暮らすにはライフサイクルは定義されていません。', //'No lifecycle has been defined for this class',
'UI:UserManagement:GrantMatrix' => '権限マトリクス', //'Grant Matrix',
'UI:UserManagement:LinkBetween_User_And_Profile' => '%1$s と %2$s間のリンク', //'Link between %1$s and %2$s',
'UI:UserManagement:LinkBetween_User_And_Org' => '%1$s と %2$s 間のリンク', // 'Link between %1$s and %2$s',
'Menu:AdminTools' => '管理ツール', //'Admin tools',
'Menu:AdminTools+' => '管理ツール', //'Administration tools',
'Menu:AdminTools?' => 'このツールは管理者プロファイルが設定されているユーザにのみアクセスが可能です。', //'Tools accessible only to users having the administrator profile',
'UI:ChangeManagementMenu' => '変更管理', //'Change Management',
'UI:ChangeManagementMenu+' => '変更管理', //'Change Management',
'UI:ChangeManagementMenu:Title' => '変更状況概観', //'Changes Overview',
'UI-ChangeManagementMenu-ChangesByType' => '型別変更内容', //'Changes by type',
'UI-ChangeManagementMenu-ChangesByStatus' => '状態別変更内容', //'Changes by status',
'UI-ChangeManagementMenu-ChangesByWorkgroup' => 'ワークグループ別変更内容', //'Changes by workgroup',
'UI-ChangeManagementMenu-ChangesNotYetAssigned' => 'まだアサインされていない変更', //'Changes not yet assigned',
'UI:ConfigurationItemsMenu'=> '設定項目', //'Configuration Items',
'UI:ConfigurationItemsMenu+'=> 'すべてのデバイス', //'All Devices',
'UI:ConfigurationItemsMenu:Title' => '設定項目概観', //'Configuration Items Overview',
'UI-ConfigurationItemsMenu-ServersByCriticity' => 'サーバ(by criticity)', // 'Servers by criticity',
'UI-ConfigurationItemsMenu-PCsByCriticity' => 'PC (by criticity)', // 'PCs by criticity',
'UI-ConfigurationItemsMenu-NWDevicesByCriticity' => 'ネットワークデバイス (by criticity)', // 'Network devices by criticity',
'UI-ConfigurationItemsMenu-ApplicationsByCriticity' => 'アプリケーション (by criticity)', // 'Applications by criticity',
'UI:ConfigurationManagementMenu' => 'コンフィグレーション管理', //'Configuration Management',
'UI:ConfigurationManagementMenu+' => 'コンフィグレーション管理', // 'Configuration Management',
'UI:ConfigurationManagementMenu:Title' => 'インフラストラクチャ概観', // 'Infrastructure Overview',
'UI-ConfigurationManagementMenu-InfraByType' => '型別インフラオブジェクト', // 'Infrastructure objects by type',
'UI-ConfigurationManagementMenu-InfraByStatus' => '状態別インフラオブジェクト', // 'Infrastructure objects by status',
'UI:ConfigMgmtMenuOverview:Title' => 'コンフィグレーション管理用ダッシュボード', // 'Dashboard for Configuration Management',
'UI-ConfigMgmtMenuOverview-FunctionalCIbyStatus' => '状態別コンフィグレーション項目', //'Configuration Items by status',
'UI-ConfigMgmtMenuOverview-FunctionalCIByType' => '型別コンフィグレーション項目', // 'Configuration Items by type',
'UI:RequestMgmtMenuOverview:Title' => 'リクエスト管理用ダッシュボード', // 'Dashboard for Request Management',
'UI-RequestManagementOverview-RequestByService' => 'サービス別ユーザリクエスト', //'User Requests by service',
'UI-RequestManagementOverview-RequestByPriority' => '優先度別ユーザリクエスト', // 'User Requests by priority',
'UI-RequestManagementOverview-RequestUnassigned' => 'エージェントへ未アサインのユーザリクエスト', // 'User Requests not yet assigned to an agent',
'UI:IncidentMgmtMenuOverview:Title' => 'インシデント管理用ダッシュボード', // 'Dashboard for Incident Management',
'UI-IncidentManagementOverview-IncidentByService' => 'サービス別インシデント', // 'Incidents by service',
'UI-IncidentManagementOverview-IncidentByPriority' => '優先度別インシデント', // 'Incidents by priority',
'UI-IncidentManagementOverview-IncidentUnassigned' => 'エージェントへ未アサインのインシデント', // 'Incidents not yet assigned to an agent',
'UI:ChangeMgmtMenuOverview:Title' => '変更管理用ダッシュボード', // 'Dashboard for Change Management',
'UI-ChangeManagementOverview-ChangeByType' => '型別変更内容', // 'Changes by type',
'UI-ChangeManagementOverview-ChangeUnassigned' => 'エージェントへ未アサインの変更内容', // 'Changes not yet assigned to an agent',
'UI-ChangeManagementOverview-ChangeWithOutage' => '変更すべき一時停止???', // 'Outages due to changes',
'UI:ServiceMgmtMenuOverview:Title' => 'サービス管理用ダッシュボード', // 'Dashboard for Service Management',
'UI-ServiceManagementOverview-CustomerContractToRenew' => '30日以内に契約更新が必要な顧客', // 'Customer contracts to be renewed in 30 days',
'UI-ServiceManagementOverview-ProviderContractToRenew' => '30日以内に契約更新が必要なプロバイダ', // 'Provider contracts to be renewed in 30 days',
'UI:ContactsMenu' => 'コンタクト', // 'Contacts',
'UI:ContactsMenu+' => 'コンタクト', // 'Contacts',
'UI:ContactsMenu:Title' => 'コンタクト概観', // 'Contacts Overview',
'UI-ContactsMenu-ContactsByLocation' => 'ロケーション別コンタクト', // 'Contacts by location',
'UI-ContactsMenu-ContactsByType' => 'タイプ別コンタクト', // 'Contacts by type',
'UI-ContactsMenu-ContactsByStatus' => '状態別コンタクト', //'Contacts by status',
'Menu:CSVImportMenu' => 'CSV インポート', // 'CSV import',
'Menu:CSVImportMenu+' => '一括生成/一括更新', //'Bulk creation or update',
'Menu:DataModelMenu' => 'データモデル', // 'Data Model',
'Menu:DataModelMenu+' => 'データモデル概観', // 'Overview of the Data Model',
'Menu:ExportMenu' => 'エクスポート', // 'Export',
'Menu:ExportMenu+' => '任意のクエリ結果をHTML、CSV、XMLでエクスポートする', // 'Export the results of any query in HTML, CSV or XML',
'Menu:NotificationsMenu' => 'ノーティフィケーション', // 'Notifications',
'Menu:NotificationsMenu+' => 'ノーティフィケーションの設定', // 'Configuration of the Notifications',
'UI:NotificationsMenu:Title' => '<span class="hilite">ノーティフィケーション</span>の設定', // 'Configuration of the <span class="hilite">Notifications</span>',
'UI:NotificationsMenu:Help' => 'ヘルプ', // 'Help'
// 'UI:NotificationsMenu:HelpContent' => '<p>In iTop the notifications are fully customizable. They are based on two sets of objects: <i>triggers and actions</i>.</p>
//<p><i><b>Triggers</b></i> define when a notification will be executed. There are 3 types of triggers for covering 3 differents phases of an object life cycle:
//<ol>
// <li>the "OnCreate" triggers get executed when an object of the specified class is created</li>
// <li>the "OnStateEnter" triggers get executed before an object of the given class enters a specified state (coming from another state)</li>
// <li>the "OnStateLeave" triggers get executed when an object of the given class is leaving a specified state</li>
//</ol>
//</p>
//<p>
//<i><b>Actions</b></i> define the actions to be performed when the triggers execute. For now there is only one kind of action consisting in sending an email message.
//Such actions also define the template to be used for sending the email as well as the other parameters of the message like the recipients, importance, etc.
//</p>
//<p>A special page: <a href="../setup/email.test.php" target="_blank">email.test.php</a> is available for testing and troubleshooting your PHP mail configuration.</p>
//<p>To be executed, actions must be associated to triggers.
//When associated with a trigger, each action is given an "order" number, specifying in which order the actions are to be executed.</p>',
'UI:NotificationsMenu:HelpContent' => '<p>iTopでは、ーティフィケーションはすべてカスタマイズが可能です。ーティフィケーションは<i>トリガーとアクション</i>という二つのオブジェクトがベースになっています。
<p><i><b>トリガー</b></i>は、あるーティフィケーションがいつ実行されるのか、を定義する。トリガーは3つのタイプに分類され、オブジェクトライフサイクルにおける3つの異なるフェーズに対応する
<ol>
<li>"onCreate"トリガーは、指定されたクラスのオブジェクトが生成されたときに実行される。</li>
<li>"onStateEnter"トリガーは、指定されたクラスのオブジェクトが(他の状態から)指定された状態に入る前に実行される。</li>
<li>"onStateLeave"トリガーは、指定されたクラスのオブジェクトが指定された状態から出る際に実行される。</li>
</ol>
</p>
<p>
<i><b>アクション</b></i>はトリガーが実行される際の動作を定義する。例えば今、「メールを送信する」という動作で構成されるたった1種類だけのアクションがあるとしよう。
このようなアクションは、受信者、重要度といったメッセージに付随する他のパラメータと同様、メール送信に利用されるテンプレートも定義する。
</p>
<p>特別なページ: <a href="../setup/email.test.php" target="_blank">email.test.php</p>は、PHPのメール設定をテストしたりトラブルシュートするのに利用可能である。</p>
<p>実行するには、アクションがトリガーに関連づけられている必要がある。
トリガーに関連づけられると、各々のアクションは順番が与えられ、どの順序でそのアクションが実行されるかが指定される。</p>',
'UI:NotificationsMenu:Triggers' => 'トリガー', // 'Triggers',
'UI:NotificationsMenu:AvailableTriggers' => '実行可能トリガー', // 'Available triggers',
'UI:NotificationsMenu:OnCreate' => 'オブジェクトが生成された時', // 'When an object is created',
'UI:NotificationsMenu:OnStateEnter' => 'オブジェクトが指定状態に入った時', // 'When an object enters a given state',
'UI:NotificationsMenu:OnStateLeave' => 'オブジェクトが指定状態から出た時', // 'When an object leaves a given state',
'UI:NotificationsMenu:Actions' => 'アクション', // 'Actions',
'UI:NotificationsMenu:AvailableActions' => '実行可能アクション', // 'Available actions',
'Menu:AuditCategories' => '監査カテゴリ', // 'Audit Categories',
'Menu:AuditCategories+' => '監査カテゴリ', // 'Audit Categories',
'Menu:Notifications:Title' => '監査カテゴリ', // 'Audit Categories',
'Menu:RunQueriesMenu' => 'クエリ実行', // 'Run Queries',
'Menu:RunQueriesMenu+' => '任意のクエリを実行', // 'Run any query',
'Menu:DataAdministration' => 'データ管理', // 'Data administration',
'Menu:DataAdministration+' => 'データ管理', // 'Data administration',
'Menu:UniversalSearchMenu' => '全検索', // 'Universal Search',
'Menu:UniversalSearchMenu+' => '何か...を検索', // 'Search for anything...',
'Menu:ApplicationLogMenu' => 'Log de l\'application',
'Menu:ApplicationLogMenu+' => 'Log de l\'application',
'Menu:ApplicationLogMenu:Title' => 'Log de l\'application',
'Menu:UserManagementMenu' => 'ユーザ管理', // 'User Management',
'Menu:UserManagementMenu+' => 'ユーザ管理', // 'User management',
'Menu:ProfilesMenu' => 'プロファイル', // 'Profiles',
'Menu:ProfilesMenu+' => 'プロファイル', // 'Profiles',
'Menu:ProfilesMenu:Title' => 'プロファイル', // 'Profiles',
'Menu:UserAccountsMenu' => 'ユーザアカウント', // 'User Accounts',
'Menu:UserAccountsMenu+' => 'ユーザアカウント', // 'User Accounts',
'Menu:UserAccountsMenu:Title' => 'ユーザアカウント', // 'User Accounts',
'UI:iTopVersion:Short' => 'iTopバージョン%1$s', // 'iTop version %1$s',
'UI:iTopVersion:Long' => 'iTopバージョン%1$s-%2$s, %3$sビルド', // 'iTop version %1$s-%2$s built on %3$s',
'UI:PropertiesTab' => 'プロパティ', // 'Properties',
'UI:OpenDocumentInNewWindow_' => '新規ウィンドウで本ドキュメント: %1$sを開く', // 'Open this document in a new window: %1$s',
'UI:DownloadDocument_' => '本ドキュメント: %1$sをダウンロードする', // 'Download this document: %1$s',
'UI:Document:NoPreview' => 'このタイプのドキュメントはプレビューできません。', // 'No preview is available for this type of document',
'UI:DeadlineMissedBy_duration' => '%1$s によって消去されました。', // 'Missed by %1$s',
'UI:Deadline_LessThan1Min' => '1分以内', // '< 1 min',
'UI:Deadline_Minutes' => '%1$d 分', // '%1$d min',
'UI:Deadline_Hours_Minutes' => '%1$d時間%2$d分', // '%1$dh %2$dmin',
'UI:Deadline_Days_Hours_Minutes' => '%1$d日%2$d時間%3$d分', // '%1$dd %2$dh %3$dmin',
'UI:Help' => 'ヘルプ', // 'Help',
'UI:PasswordConfirm' => '(確認)', // '(Confirm)',
'UI:BeforeAdding_Class_ObjectsSaveThisObject' => '%1$sオブジェクトをさらに追加する前に、このオブジェクトを保存してください。', // 'Before adding more %1$s objects, save this object.',
'UI:DisplayThisMessageAtStartup' => '起動時にこのメッセージを表示する', // 'Display this message at startup',
'UI:RelationshipGraph' => 'グラフィカル表示', // 'Graphical view',
'UI:RelationshipList' => 'リスト', // 'List',
'UI:OperationCancelled' => '操作はキャンセルされました', // 'Operation Cancelled',
'Portal:Title' => 'iTopユーザポータル', // 'iTop user portal',
'Portal:Refresh' => '更新', // 'Refresh',
'Portal:Back' => '戻る', // 'Back',
'Portal:WelcomeUserOrg' => 'Welcome %1$s, from %2$s',
'Portal:ShowOngoing' => 'Show open requests',
'Portal:ShowClosed' => 'Show closed requests',
'Portal:CreateNewRequest' => '新規リクエストを生成する', // 'Create a new request',
'Portal:ChangeMyPassword' => 'パスワードを変更する', // 'Change my password',
'Portal:Disconnect' => '切断する', // 'Disconnect',
'Portal:OpenRequests' => '発行済みリクエスト', // 'My open requests',
'Portal:ClosedRequests' => 'My closed requests',
'Portal:ResolvedRequests' => '解決済みリクエスト', // 'My resolved requests',
'Portal:SelectService' => 'カタログからサービスを選択してください:', // 'Select a service from the catalog:',
'Portal:PleaseSelectOneService' => 'サービスを1つ選んでください', // 'Please select one service',
'Portal:SelectSubcategoryFrom_Service' => '本サービス:%1$sのサブカテゴリを選んでください', // 'Select a sub-category for the service %1$s:',
'Portal:PleaseSelectAServiceSubCategory' => 'サブカテゴリを1つ選んでください', // 'Please select one sub-category',
'Portal:DescriptionOfTheRequest' => 'あなたのリクエストの詳細を記入してください:', // 'Enter the description of your request:',
'Portal:TitleRequestDetailsFor_Request' => 'リクエスト%1$sの詳細', // Details for request %1$s:',
'Portal:NoOpenRequest' => '本カテゴリにリクエストはありません', // 'No request in this category.',
'Portal:NoClosedRequest' => 'No request in this category',
'Portal:Button:ReopenTicket' => 'Reopen this ticket',
'Portal:Button:CloseTicket' => '本チケットを閉じます。', // 'Close this ticket',
'Portal:Button:UpdateRequest' => 'Update the request',
'Portal:EnterYourCommentsOnTicket' => '本チケットの解決について、コメントを入力してください。', // 'Enter your comments about the resolution of this ticket:',
'Portal:ErrorNoContactForThisUser' => 'エラー:現在のユーザはコンタクト/人物に関連づけられていません。管理者に問い合わせてください。', // 'Error: the current user is not associated with a Contact/Person. Please contact your administrator.',
'Portal:Attachments' => '添付', // 'Attachments',
'Portal:AddAttachment' => ' 添付を付加する ', // ' Add Attachment ',
'Portal:RemoveAttachment' => ' 添付を除去する ', // ' Remove Attachment ',
'Portal:Attachment_No_To_Ticket_Name' => '#%1$d を$2$s ($3$s)に添付する', // 'Attachment #%1$d to %2$s (%3$s)',
'Enum:Undefined' => '定義されていません', // 'Undefined',
'UI:Button:Refresh' => '更新', // 'Refresh',
));
?>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -406,6 +406,7 @@ Dict::Add('RU RU', 'Russian', 'Русский', array(
'UI:History:Changes+' => 'Изменения, внесенные в объект',
'UI:Loading' => 'Загрузка...',
'UI:Menu:Actions' => 'Действия',
'UI:Menu:OtherActions' => 'Другие Действия',
'UI:Menu:New' => 'Новый...',
'UI:Menu:Add' => 'Добавить...',
'UI:Menu:Manage' => 'Управление...',
@@ -847,10 +848,14 @@ Dict::Add('RU RU', 'Russian', 'Русский', array(
'Portal:Title' => 'Пользовательский iTop портал',
'Portal:Refresh' => 'Обновить',
'Portal:Back' => 'Назад',
'Portal:WelcomeUserOrg' => 'Welcome %1$s, from %2$s',
'Portal:ShowOngoing' => 'Show open requests',
'Portal:ShowClosed' => 'Show closed requests',
'Portal:CreateNewRequest' => 'Создать новый запрос',
'Portal:ChangeMyPassword' => 'Изменить мой пароль',
'Portal:Disconnect' => 'Отключить',
'Portal:OpenRequests' => 'Мои открытые запросы',
'Portal:ClosedRequests' => 'My closed requests',
'Portal:ResolvedRequests' => 'Мои решённые запросы',
'Portal:SelectService' => 'Выбери сервис из каталога:',
'Portal:PleaseSelectOneService' => 'Необходимо выбрать хотя-бы один сервис',
@@ -859,11 +864,15 @@ Dict::Add('RU RU', 'Russian', 'Русский', array(
'Portal:DescriptionOfTheRequest' => 'Введи описание запроса:',
'Portal:TitleRequestDetailsFor_Request' => 'Подробности запроса %1$s:',
'Portal:NoOpenRequest' => 'Нет запросов в этой категории.',
'Portal:NoClosedRequest' => 'No request in this category',
'Portal:Button:ReopenTicket' => 'Reopen this ticket',
'Portal:Button:CloseTicket' => 'Закрыть этот "тикет"',
'Portal:Button:UpdateRequest' => 'Update the request',
'Portal:EnterYourCommentsOnTicket' => 'Введите ваши каментарии по решению этого "тикета":',
'Portal:ErrorNoContactForThisUser' => 'Ошибка: текющий пользователь не ассоциирован с Контактом/Человеком. Пожалуйста свяжитесь с вашим администратором.',
'Enum:Undefined' => 'Неопределён',
'UI:Button:Refresh' => 'Обновить',
));

View File

@@ -405,6 +405,7 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array(
'UI:History:Changes+' => 'Nesneye yapılan değişiklikler',
'UI:Loading' => 'Yükleniyor...',
'UI:Menu:Actions' => 'İşlemler',
'UI:Menu:OtherActions' => 'Diğer İşlemler',
'UI:Menu:New' => 'Yeni...',
'UI:Menu:Add' => 'Ekle...',
'UI:Menu:Manage' => 'Yönet...',
@@ -846,10 +847,14 @@ Tetikleme gerçekleştiriğinde işlemler tanımlanan sıra numarası ile gerçe
'Portal:Title' => 'iTop Kullanıcı Portalı',
'Portal:Refresh' => 'Yenile',
'Portal:Back' => 'Geri',
'Portal:WelcomeUserOrg' => 'Welcome %1$s, from %2$s',
'Portal:ShowOngoing' => 'Show open requests',
'Portal:ShowClosed' => 'Show closed requests',
'Portal:CreateNewRequest' => 'Yeni istek yarat',
'Portal:ChangeMyPassword' => 'Şifre değiştir',
'Portal:Disconnect' => ıkış',
'Portal:OpenRequests' => 'Açık isteklerim',
'Portal:ClosedRequests' => 'My closed requests',
'Portal:ResolvedRequests' => 'Çözdüğüm istekler',
'Portal:SelectService' => 'Kataloğdan servis seçiniz:',
'Portal:PleaseSelectOneService' => 'Sevis seçiniz',
@@ -858,13 +863,14 @@ Tetikleme gerçekleştiriğinde işlemler tanımlanan sıra numarası ile gerçe
'Portal:DescriptionOfTheRequest' => 'İstek tanımlaması:',
'Portal:TitleRequestDetailsFor_Request' => 'İsteğin detayı %1$s:',
'Portal:NoOpenRequest' => 'Bu kategoride istek yok.',
'Portal:NoClosedRequest' => 'Bu kategoride istek yok.',
'Portal:Button:ReopenTicket' => 'Reopen this ticket',
'Portal:Button:CloseTicket' => 'Çağrıyı kapat',
'Portal:Button:UpdateRequest' => 'Update the request',
'Portal:EnterYourCommentsOnTicket' => 'İsteğin çözümüne yönelik açıklamalar:',
'Portal:ErrorNoContactForThisUser' => 'Hata: mevcut kullanıcının irtibat bilgisi yok. Sistem yöneticisi ile irtibata geçiniz.',
'Enum:Undefined' => 'Tanımsız',
'UI:Button:Refresh' => 'Yenile',
));
?>

View File

@@ -404,6 +404,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'UI:History:Changes+' => '对该对象所做的变更',
'UI:Loading' => '载入...',
'UI:Menu:Actions' => '动作',
'UI:Menu:OtherActions' => '其他操作',
'UI:Menu:New' => '新建...',
'UI:Menu:Add' => '添加...',
'UI:Menu:Manage' => '管理...',
@@ -845,10 +846,14 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Portal:Title' => 'iTop 用户门户',
'Portal:Refresh' => '刷新',
'Portal:Back' => '返回',
'Portal:WelcomeUserOrg' => 'Welcome %1$s, from %2$s',
'Portal:ShowOngoing' => 'Show open requests',
'Portal:ShowClosed' => 'Show closed requests',
'Portal:CreateNewRequest' => '创建一个新的请求',
'Portal:ChangeMyPassword' => '改变我的密码',
'Portal:Disconnect' => '断开',
'Portal:OpenRequests' => '我的待解决的请求',
'Portal:ClosedRequests' => 'My closed requests',
'Portal:ResolvedRequests' => '我的已解决的请求',
'Portal:SelectService' => '从类目中选择一个服务:',
'Portal:PleaseSelectOneService' => '请选择一个服务',
@@ -857,11 +862,14 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Portal:DescriptionOfTheRequest' => '输入您的请求描述:',
'Portal:TitleRequestDetailsFor_Request' => '请求明细内容 %1$s:',
'Portal:NoOpenRequest' => '该类目中没有请求.',
'Portal:NoClosedRequest' => 'No request in this category',
'Portal:Button:CloseTicket' => '关闭这个单据',
'Portal:Button:UpdateRequest' => 'Update the request',
'Portal:EnterYourCommentsOnTicket' => '输入您对于该单据解决情况的评述:',
'Portal:ErrorNoContactForThisUser' => '错误: 当前用户没有和一个联系人或人员关联. 请联系您的系统管理员.',
'Enum:Undefined' => '未定义',
'UI:Button:Refresh' => '刷新',
));

BIN
images/actions_bkg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 283 B

After

Width:  |  Height:  |  Size: 314 B

BIN
images/favicon.ico Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
images/first.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 720 B

BIN
images/last.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 737 B

BIN
images/mini_tree.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 B

BIN
images/next.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 736 B

BIN
images/play.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
images/preferences.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

BIN
images/prev.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 745 B

Binary file not shown.

View File

@@ -13,17 +13,19 @@
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
function ExtKeyWidget(id, sClass, sAttCode, sSuffix, bSelectMode, oWizHelper)
function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper, sAttCode, bSearchMode)
{
this.id = id;
this.sClass = sClass;
this.sTargetClass = sTargetClass;
this.sFilter = sFilter;
this.sTitle = sTitle;
this.sAttCode = sAttCode;
this.sSuffix = sSuffix;
this.emptyHtml = ''; // content to be displayed when the search results are empty (when opening the dialog)
this.emptyOnClose = true; // Workaround for the JQuery dialog being very slow when opening and closing if the content contains many INPUT tags
this.oWizardHelper = oWizHelper;
this.ajax_request = null;
this.bSelectMode = bSelectMode; // true if the edited field is a SELECT, false if it's an autocomplete
this.bSearchMode = bSearchMode; // true if selecting a value in the context of a search form
this.v_html = '';
var me = this;
@@ -38,7 +40,7 @@ function ExtKeyWidget(id, sClass, sAttCode, sSuffix, bSelectMode, oWizHelper)
{
if (me.ajax_request)
{
me.ajax_request.Abort();
me.ajax_request.abort();
me.ajax_request = null;
}
}
@@ -60,18 +62,30 @@ function ExtKeyWidget(id, sClass, sAttCode, sSuffix, bSelectMode, oWizHelper)
}
var theMap = { sAttCode: me.sAttCode,
iInputId: me.id,
sSuffix: me.sSuffix,
'class': me.sClass,
sValue: value,
sTitle: me.sTitle,
sAttCode: me.sAttCode,
sTargetClass: me.sTargetClass,
bSearchMode: me.bSearchMode,
operation: 'objectSearchForm'
}
if (me.oWizardHelper == null)
{
theMap['json'] = '';
}
else
{
// Not inside a "search form", updating a real object
me.oWizardHelper.UpdateWizard();
theMap['json'] = me.oWizardHelper.ToJSON();
}
// Make sure that we cancel any pending request before issuing another
// since responses may arrive in arbitrary order
me.StopPendingRequest();
// Run the query and get the result back directly in HTML
me.ajax_request = $.post( '../pages/ajax.render.php', theMap,
me.ajax_request = $.post( AddAppContext(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php'), theMap,
function(data)
{
$('#ac_dlg_'+me.id).html(data);
@@ -79,6 +93,7 @@ function ExtKeyWidget(id, sClass, sAttCode, sSuffix, bSelectMode, oWizHelper)
me.UpdateSizes();
me.UpdateButtons();
me.ajax_request = null;
me.DoSearchObjects();
},
'html'
);
@@ -111,7 +126,7 @@ function ExtKeyWidget(id, sClass, sAttCode, sSuffix, bSelectMode, oWizHelper)
this.UpdateButtons = function()
{
var okBtn = $('#btn_ok_'+me.id);
if ($('#fr_'+me.id+' input[name=selectObject]:checked').length > 0)
if ($('#count_'+me.id).val() > 0)
{
okBtn.attr('disabled', '');
}
@@ -123,9 +138,10 @@ function ExtKeyWidget(id, sClass, sAttCode, sSuffix, bSelectMode, oWizHelper)
this.DoSearchObjects = function(id)
{
var theMap = { sAttCode: me.sAttCode,
var theMap = { sTargetClass: me.sTargetClass,
iInputId: me.id,
sSuffix: me.sSuffix
sFilter: me.sFilter,
bSearchMode: me.bSearchMode
}
// Gather the parameters from the search form
@@ -151,8 +167,8 @@ function ExtKeyWidget(id, sClass, sAttCode, sSuffix, bSelectMode, oWizHelper)
}
theMap['sRemoteClass'] = theMap['class']; // swap 'class' (defined in the form) and 'remoteClass'
theMap['class'] = me.sClass;
theMap.operation = 'searchObjectsToSelect'; // Override what is defined in the form itself
theMap.sAttCode = me.sAttCode,
sSearchAreaId = '#dr_'+me.id;
//$(sSearchAreaId).html('<div style="text-align:center;width:100%;height:24px;vertical-align:middle;"><img src="../images/indicator.gif" /></div>');
@@ -164,15 +180,17 @@ function ExtKeyWidget(id, sClass, sAttCode, sSuffix, bSelectMode, oWizHelper)
me.StopPendingRequest();
// Run the query and display the results
me.ajax_request = $.post( '../pages/ajax.render.php', theMap,
me.ajax_request = $.post( AddAppContext(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php'), theMap,
function(data)
{
$(sSearchAreaId).html(data);
$(sSearchAreaId+' .listResults').tableHover();
$(sSearchAreaId+' .listResults').tablesorter( { headers: {0: {sorter: false}}, widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables
$('#fr_'+me.id+' input:radio').click(function() { me.UpdateButtons(); });
me.UpdateButtons();
me.ajax_request = null;
$('#count_'+me.id).change(function(){
me.UpdateButtons();
});
},
'html'
);
@@ -182,16 +200,25 @@ function ExtKeyWidget(id, sClass, sAttCode, sSuffix, bSelectMode, oWizHelper)
this.DoOk = function()
{
var iObjectId = $('#fr_'+me.id+' input[name=selectObject]:checked').val();
var s = $('#'+me.id+'_results').find(':input[name^=storedSelection]');
var iObjectId = 0;
if (s.length > 0)
{
iObjectId = s.val();
}
else
{
iObjectId = $('#fr_'+me.id+' input[name=selectObject]:checked').val();
}
$('#ac_dlg_'+this.id).dialog('close');
$('#label_'+this.id).addClass('ac_dlg_loading');
// Query the server again to get the display name of the selected object
var theMap = { sAttCode: me.sAttCode,
var theMap = { sTargetClass: me.sTargetClass,
iInputId: me.id,
iObjectId: iObjectId,
sSuffix: me.sSuffix,
'class': me.sClass,
sAttCode: me.sAttCode,
bSearchMode: me.bSearchMode,
operation: 'getObjectName'
}
@@ -200,13 +227,19 @@ function ExtKeyWidget(id, sClass, sAttCode, sSuffix, bSelectMode, oWizHelper)
me.StopPendingRequest();
// Run the query and get the result back directly in JSON
me.ajax_request = $.post( '../pages/ajax.render.php', theMap,
me.ajax_request = $.post( AddAppContext(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php'), theMap,
function(data)
{
$('#label_'+me.id).val(data.name);
$('#label_'+me.id).removeClass('ac_dlg_loading');
var prevValue = $('#'+me.id).val();
$('#'+me.id).val(iObjectId);
$('#'+me.id).trigger('validate');
if (prevValue != iObjectId)
{
$('#'+me.id).trigger('validate');
$('#'+me.id).trigger('extkeychange');
$('#'+me.id).trigger('change');
}
$('#label_'+me.id).focus();
me.ajax_request = null;
},
@@ -246,10 +279,9 @@ function ExtKeyWidget(id, sClass, sAttCode, sSuffix, bSelectMode, oWizHelper)
$('#label_'+me.id).addClass('ac_dlg_loading');
}
me.oWizardHelper.UpdateWizard();
var theMap = { sAttCode: me.sAttCode,
var theMap = { sTargetClass: me.sTargetClass,
iInputId: me.id,
sSuffix: me.sSuffix,
'class': me.sClass,
sAttCode: me.sAttCode,
'json': me.oWizardHelper.ToJSON(),
operation: 'objectCreationForm'
}
@@ -259,7 +291,7 @@ function ExtKeyWidget(id, sClass, sAttCode, sSuffix, bSelectMode, oWizHelper)
me.StopPendingRequest();
// Run the query and get the result back directly in HTML
me.ajax_request = $.post( '../pages/ajax.render.php', theMap,
me.ajax_request = $.post( AddAppContext(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php'), theMap,
function(data)
{
$('#ajax_'+me.id).html(data);
@@ -309,10 +341,9 @@ function ExtKeyWidget(id, sClass, sAttCode, sSuffix, bSelectMode, oWizHelper)
if (CheckFields(sFormId, true))
{
$('#'+sFormId).block();
var theMap = { sAttCode: me.sAttCode,
var theMap = { sTargetClass: me.sTargetClass,
iInputId: me.id,
sSuffix: me.sSuffix,
'class': me.sClass,
sAttCode: me.sAttCode,
'json': me.oWizardHelper.ToJSON()
}
@@ -338,13 +369,13 @@ function ExtKeyWidget(id, sClass, sAttCode, sSuffix, bSelectMode, oWizHelper)
me.StopPendingRequest();
// Run the query and get the result back directly in JSON
me.ajax_request = $.post( '../pages/ajax.render.php', theMap,
me.ajax_request = $.post( AddAppContext(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php'), theMap,
function(data)
{
if (me.bSelectMode)
{
// Add the newly created object to the drop-down list and select it
$('<option/>', { value : data.id }).text(data.name).appendTo('#'+me.id);
$('<option/>', { value : data.id }).html(data.name).appendTo('#'+me.id);
$('#'+me.id+' option[value="'+data.id+'"]').attr('selected', 'selected');
$('#'+me.id).focus();
}
@@ -357,6 +388,8 @@ function ExtKeyWidget(id, sClass, sAttCode, sSuffix, bSelectMode, oWizHelper)
$('#label_'+me.id).focus();
}
$('#'+me.id).trigger('validate');
$('#'+me.id).trigger('extkeychange');
$('#'+me.id).trigger('change');
me.ajax_request = null;
},
'json'
@@ -373,6 +406,7 @@ function ExtKeyWidget(id, sClass, sAttCode, sSuffix, bSelectMode, oWizHelper)
$('#label_'+me.id).attr('disabled', 'disabled');
$('#label_'+me.id).css({'background': 'transparent'});
$('#mini_add_'+me.id).hide();
$('#mini_tree_'+me.id).hide();
$('#mini_search_'+me.id).hide();
}
else
@@ -380,7 +414,129 @@ function ExtKeyWidget(id, sClass, sAttCode, sSuffix, bSelectMode, oWizHelper)
$('#label_'+me.id).attr('disabled', '');
$('#label_'+me.id).css({'background': '#fff url(../images/ac-background.gif) no-repeat right'});
$('#mini_add_'+me.id).show();
$('#mini_tree_'+me.id).show();
$('#mini_search_'+me.id).show();
}
}
this.HKDisplay = function()
{
var theMap = { sTargetClass: me.sTargetClass,
sInputId: me.id,
sFilter: me.sFilter,
bSearchMode: me.bSearchMode,
sAttCode: me.sAttCode,
value: $('#'+me.id).val()
};
if (me.bSelectMode)
{
me.v_html = $('#v_'+me.id).html();
$('#v_'+me.id).html('<img src="../images/indicator.gif" />');
}
else
{
$('#label_'+me.id).addClass('ac_dlg_loading');
}
if (me.oWizardHelper == null)
{
theMap['json'] = '';
}
else
{
// Not inside a "search form", updating a real object
me.oWizardHelper.UpdateWizard();
theMap['json'] = me.oWizardHelper.ToJSON();
}
theMap['sRemoteClass'] = me.sTargetClass;
theMap.operation = 'displayHierarchy';
// Make sure that we cancel any pending request before issuing another
// since responses may arrive in arbitrary order
me.StopPendingRequest();
// Run the query and display the results
me.ajax_request = $.post( AddAppContext(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php'), theMap,
function(data)
{
$('#ac_tree_'+me.id).html(data);
var maxHeight = $(window).height()-110;
$('#tree_'+me.id).css({maxHeight: maxHeight});
},
'html'
);
}
this.OnHKResize = function(event, ui)
{
var dh = ui.size.height - ui.originalSize.height;
if (dh != 0)
{
var dlg_content = $('#dlg_tree_'+me.id+' .wizContainer');
var h = dlg_content.height();
dlg_content.height(h + dh);
var tree = $('#tree_'+me.id);
var h = tree.height();
tree.height(h + dh - 1);
}
}
this.OnHKClose = function()
{
if (me.bSelectMode)
{
$('#v_'+me.id).html(me.v_html);
}
else
{
$('#label_'+me.id).removeClass('ac_dlg_loading');
}
$('#label_'+me.id).focus();
$('#dlg_tree_'+me.id).dialog("destroy");
$('#dlg_tree_'+me.id).remove();
}
this.DoHKOk = function()
{
iObjectId = $('#tree_'+me.id+' input[name=selectObject]:checked').val();
$('#dlg_tree_'+me.id).dialog('close');
// Query the server again to get the display name of the selected object
var theMap = { sTargetClass: me.sTargetClass,
iInputId: me.id,
iObjectId: iObjectId,
sAttCode: me.sAttCode,
bSearchMode: me.bSearchMode,
operation: 'getObjectName'
}
// Make sure that we cancel any pending request before issuing another
// since responses may arrive in arbitrary order
me.StopPendingRequest();
// Run the query and get the result back directly in JSON
me.ajax_request = $.post( AddAppContext(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php'), theMap,
function(data)
{
$('#label_'+me.id).val(data.name);
$('#label_'+me.id).removeClass('ac_dlg_loading');
var prevValue = $('#'+me.id).val();
$('#'+me.id).val(iObjectId);
if (prevValue != iObjectId)
{
$('#'+me.id).trigger('validate');
$('#'+me.id).trigger('extkeychange');
$('#'+me.id).trigger('change');
}
$('#label_'+me.id).focus();
me.ajax_request = null;
},
'json'
);
return false; // Do NOT submit the form in case we are called by OnSubmit...
}
}

View File

@@ -10,6 +10,8 @@ var oObj = {};
// of Id 'att_2' in the form
var aFieldsMap = new Array;
window.bInSubmit = false; // For handling form cancellation via OnBeforeUnload events
// Update the whole object from the form and also update its
// JSON (serialized) representation in the (hidden) field
function UpdateObjectFromForm(aFieldsMap, oObj)
@@ -88,34 +90,28 @@ function ActivateStep(iTargetStep)
//$('#wizStep'+(iTargetStep)).block({ message: null });
}
function OnUnload(sTransactionId)
{
if (!window.bInSubmit)
{
// If it's not a submit, then it's a "cancel" (Pressing the Cancel button, closing the window, using the back button...)
$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', {operation: 'on_form_cancel', transaction_id: sTransactionId }, function()
{
// Do nothing for now...
});
}
}
//function AjaxGetValuesDef(oObj, sClass, sAttCode, iFieldId)
//{
// var oJSON = document.getElementById(sJsonFieldId);
// $.get('ajax.render.php?class=' + sClass + '&json_obj=' + oJSON.value + '&att_code=' + sAttCode,
// { operation: "allowed_values" },
// function(data){
// //$('#field_'+iFieldId).html(data);
// }
// );
//}
//
//function AjaxGetDefaultValue(oObj, sClass, sAttCode, iFieldId)
//{
// // Asynchronously call the server to provide a default value if the field is
// // empty
// if (oObj['m_aCurrValues'][sAttCode] == '')
// {
// var oJSON = document.getElementById(sJsonFieldId);
// $.get('../pages/ajax.render.php?class=' + sClass + '&json_obj=' + oJSON.value + '&att_code=' + sAttCode,
// { operation: "default_value" },
// function(json_data){
// var oObj = ReloadObjectFromServer(json_data);
// UpdateFieldFromObject(iFieldId, aFieldsMap, oObj)
// }
// );
// }
//}
function OnSubmit(sFormId)
{
window.bInSubmit=true; // This is a submit, make sure that when the page gets unloaded we don't cancel the action
var bResult = CheckFields(sFormId, true);
if (!bResult)
{
window.bInSubmit = false; // Submit is/will be canceled
}
return bResult;
}
// Store the result of the form validation... there may be several forms per page, beware
var oFormErrors = { err_form0: 0 };
@@ -299,6 +295,35 @@ function ValidatePasswordField(id, sFormId)
return true;
}
//Special validation function for case log fields, taking into account the history
// to determine if the field is empty or not
function ValidateCaseLogField(sFieldId, bMandatory, sFormId)
{
bValid = true;
if ($('#'+sFieldId).attr('disabled'))
{
bValid = true; // disabled fields are not checked
}
else if (!bMandatory)
{
bValid = true;
}
else
{
if (bMandatory)
{
var count = $('#'+sFieldId+'_count').val();
if ( (count == 0) && ($('#'+sFieldId).val() == '') )
{
// No previous entry and no content typed
bValid = false;
}
}
}
ReportFieldValidationStatus(sFieldId, sFormId, bValid);
return bValid;
}
// Manage a 'duration' field
function UpdateDuration(iId)
{
@@ -319,8 +344,12 @@ function OnAutoComplete(id, event, data, formatted)
if (data)
{
// A valid match was found: data[0] => label, data[1] => value
$('#'+id).val(data[1]);
$('#'+id).trigger('change');
if (data[1] != $('#'+id).val())
{
$('#'+id).val(data[1]);
$('#'+id).trigger('change');
$('#'+id).trigger('extkeychange');
}
}
else
{

View File

@@ -388,7 +388,7 @@ $.Autocompleter = function(input, options) {
for (var i=0; i < rows.length; i++) {
var row = $.trim(rows[i]);
if (row) {
row = row.split("|");
row = row.split("\t");
parsed[parsed.length] = {
data: row,
value: row[0],
@@ -668,6 +668,8 @@ $.Autocompleter.Select = function (options, input, select, config) {
var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term);
if ( formatted === false )
continue;
// Escape dangerous characters to prevent XSS vulnerabilities
formatted = formatted.replace('&', '&amp;').replace('"', '&quot;').replace('>', '&gt;').replace('<', '&lt;');
var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
$.data(li, "ac_data", data[i]);
}

View File

@@ -0,0 +1,468 @@
function sprintf(format, etc) {
var arg = arguments;
var i = 1;
return format.replace(/%((%)|s)/g, function (m) { return m[2] || arg[i++] })
}
(function($) {
$.extend({
tablesorterPager: new function() {
function updatePageDisplay(c) {
var s = $(c.cssPageDisplay,c.container).val((c.page+1) + c.seperator + c.totalPages);
}
function setPageSize(table,size, bReload) {
var c = table.config;
c.selectedSize = size;
if (size == -1)
{
size = c.totalRows;
}
c.size = size;
c.totalPages = Math.ceil(c.totalRows / c.size);
c.pagerPositionSet = false;
if (bReload)
{
moveToPage(table);
}
fixPosition(table);
}
function fixPosition(table) {
var c = table.config;
if(!c.pagerPositionSet && c.positionFixed) {
var c = table.config, o = $(table);
if(o.offset) {
c.container.css({
top: o.offset().top + o.height() + 'px',
position: 'absolute'
});
}
c.pagerPositionSet = true;
}
}
function moveToFirstPage(table) {
var c = table.config;
c.page = 0;
moveToPage(table);
}
function moveToLastPage(table) {
var c = table.config;
c.page = (c.totalPages-1);
moveToPage(table);
}
function moveToNextPage(table) {
var c = table.config;
c.page++;
if(c.page >= (c.totalPages-1)) {
c.page = (c.totalPages-1);
}
moveToPage(table);
}
function moveToPrevPage(table) {
var c = table.config;
c.page--;
if(c.page <= 0) {
c.page = 0;
}
moveToPage(table);
}
function moveToPage(table) {
var c = table.config;
if(c.page < 0 || c.page > (c.totalPages-1)) {
c.page = 0;
}
renderTable(table,c.rowsCopy);
}
function checkAll(table, pager, value)
{
// Mark all the displayed items as check or unchecked depending on the value
$(table).find(':checkbox[name^=selectObj]').attr('checked', value);
// Set the 'selectionMode' for the future objects to load
if (value)
{
table.config.selectionMode = 'negative';
}
else
{
table.config.selectionMode = 'positive';
}
$(pager).find(':input[name=selectionMode]').val(table.config.selectionMode);
// Reset the list of saved selection...
resetStoredSelection(pager);
updateCounter(table, pager);
return true;
}
function resetStoredSelection(pager)
{
$(':input[name^=storedSelection]', pager).remove();
}
function storeSelection(table, pager, id, value)
{
var valueToStore = value;
if (table.config.selectionMode == 'negative')
{
valueToStore = !(valueToStore);
}
if (valueToStore)
{
if (table.config.select_mode == 'single')
{
$(':input[name^=storedSelection]', pager).remove(); // Remove any previous selection
}
if ($('#'+id, pager).length ==0)
{
$(pager).append($('<input type="hidden" id="'+id+'" name="storedSelection[]" value="'+id+'"></input>'));
}
}
else
{
if ($('#'+id, pager).length !=0)
{
$('#'+id, pager).remove();
}
}
updateCounter(table, pager);
}
function loadSelection(table, pager)
{
table.config.selectionMode = $(pager).find(':input[name=selectionMode]').val();
updateCounter(table, pager);
}
function updateCounter(table, pager)
{
var ex = $(':input[name^=storedSelection]', pager).length;
var s = ex;
if (table.config.selectionMode == 'negative')
{
s = table.config.totalRows - ex;
}
$('.selectedCount',pager).text(s);
if (table.config.cssCount != '')
{
$(table.config.cssCount).val(s);
$(table.config.cssCount).trigger('change');
}
}
function getData(table, start, end)
{
if (table.ajax_request)
{
table.ajax_request.abort();
table.ajax_request = null;
}
var c = table.config;
var s = c.sortList[0];
var s_col = null;
var s_order = null;
if (s != undefined)
{
s_col = s[0];
s_order = (s[1] == 0) ? 'asc' : 'desc';
}
$('#loading', table.config.container).html('<img src="../images/indicator.gif" />');
table.ajax_request = $.post(AddAppContext(GetAbsoluteUrlAppRoot()+"pages/ajax.render.php"),
{ operation: 'pagination',
filter: c.filter,
extra_param: c.extra_params,
start: start,
end: end,
sort_col: s_col,
sort_order: s_order,
select_mode: c.select_mode,
display_key: c.displayKey,
display_list: c.displayList
},
function(data)
{
table.ajax_request = null; // Ajax request completed
oData = $(data);
var tableBody = $(table.tBodies[0]);
// clear the table body
$.tablesorter.clearTableBody(table);
for(var i = 0; i < end-start; i++) {
//tableBody.append(rows[i]);
//var o = rows[i];
var r = $(oData[i]);
var l = r.length;
for(var j=0; j < l; j++) {
//tableBody[0].appendChild(r);
tableBody[0].appendChild(r[j]);
}
}
fixPosition(table,tableBody);
applySelection(table);
$(table).trigger("applyWidgets");
if( c.page >= c.totalPages ) {
moveToLastPage(table);
}
updatePageDisplay(c);
updateCounter(table, table.config.container);
renderPager(table, table.config.container);
$(table).tableHover();
$('#loading', table.config.container).empty();
saveParams(table.config);
});
}
function applySelection(table)
{
var c = table.config;
if (c.selectionMode == 'negative')
{
$(table).find(':checkbox[name^=selectObj]').attr('checked', true);
}
if (table.config.select_mode == 'multiple')
{
$(table).find(':checkbox[name^=selectObj]').each(function() {
var id = parseInt(this.value, 10);
if ($('#'+id, table.config.container).length > 0)
{
if (c.selectionMode == 'positive')
{
$(this).attr('checked', true);
}
else
{
$(this).attr('checked', false);
}
}
});
$(table).find(':checkbox[name^=selectObj]').change(function() {
storeSelection(table, table.config.container, this.value, this.checked);
});
}
else if (table.config.select_mode == 'single')
{
$(table).find('input[name^=selectObject]:radio').each(function() {
var id = parseInt(this.value, 10);
if ($('#'+id, table.config.container).length > 0)
{
if (c.selectionMode == 'positive')
{
$(this).attr('checked', true);
}
else
{
$(this).attr('checked', false);
}
}
});
$(table).find('input[name^=selectObject]:radio').change(function() {
storeSelection(table, table.config.container, this.value, this.checked);
});
}
}
function renderPager(table, pager)
{
var c = table.config;
var s = c.page - 2;
var nb = Math.ceil(c.totalRows / c.size);
if (s < 0)
{
s = 0;
}
var e = s +5;
if (e > nb)
{
e = nb;
s = e - 5;
if (s < 0) s = 0;
}
txt = '';
for(var i=s; i<e; i++)
{
var page = 1+i;
var link = ' '+page+' ';
if (i != c.page)
{
link = ' <span page="'+i+'" id="gotopage_'+i+'">'+page+'</span> ';
}
else
{
link = ' <span class="curr_page" page="'+i+'">'+page+'</span> ';
}
txt += link;
}
txt += '';
$('#total', pager).text(c.totalRows);
$('#index', pager).html(txt);
for(var j=s; j<e; j++)
{
$('#gotopage_'+j, pager).click(function(){
var idx = $(this).attr('page');
table.config.page = idx;
moveToPage(table);
});
}
}
function renderTable(table) {
var c = table.config;
//var l = rows.length;
var s = (c.page * c.size);
var e = (s + c.size);
if(e > c.totalRows ) {
e = c.totalRows;
}
getData(table, s, e);
}
this.appender = function(table,rows) {
var c = table.config;
if (c.totalRows == 0)
{
c.totalRows = rows.length;
}
c.totalPages = Math.ceil(c.totalRows / c.size);
renderTable(table,rows);
};
function saveParams(config) {
var sPagerId = config.container.attr('id');
var params = { size: config.selectedSize, page: config.page, sortList: config.sortList };
if (window.pager_params == undefined)
{
window.pager_params = {};
}
window.pager_params[sPagerId] = params;
};
function restoreParams(table) {
var sPagerId = config.container.attr('id');
if (window.pager_params != undefined)
{
params = window.pager_params[sPagerId];
if (params != undefined)
{
$(table.config.cssPageSize, table.config.container).val(params.size);
setPageSize(table, params.size, false); // false => don't trigger a reload
if (table.config.sortList != params.sortList)
{
$(table).trigger("sorton", [params.sortList]); // triggers a reload anyway
}
}
}
};
this.defaults = {
size: 10,
offset: 0,
page: 0,
totalRows: 0,
totalPages: 0,
container: null,
cssNext: '.next',
cssPrev: '.prev',
cssFirst: '.first',
cssLast: '.last',
cssPageDisplay: '.pagedisplay',
cssPageSize: '.pagesize',
cssCount: '',
seperator: "/",
positionFixed: false,
appender: this.appender,
filter: '',
extra_params: '',
select_mode: '',
totalSelected: 0,
selectionMode: 'positive',
displayKey: true,
displayList: []
};
this.construct = function(settings) {
return this.each(function() {
config = $.extend(this.config, $.tablesorterPager.defaults, settings);
var table = this, pager = config.container;
this.ajax_request = null;
config.selectedSize = parseInt($(".pagesize",pager).val());
setPageSize(table,config.selectedSize, false);
restoreParams(table, config);
$(this).trigger("appendCache"); // Load the data
$(config.cssFirst,pager).click(function() {
moveToFirstPage(table);
return false;
});
$(config.cssNext,pager).click(function() {
moveToNextPage(table);
return false;
});
$(config.cssPrev,pager).click(function() {
moveToPrevPage(table);
return false;
});
$(config.cssLast,pager).click(function() {
moveToLastPage(table);
return false;
});
$(config.cssPageSize,pager).change(function() {
setPageSize(table,parseInt($(this).val()), true);
return false;
});
$(table).find(':checkbox.checkAll').removeAttr('onclick').click(function() {
return checkAll(table, pager, this.checked);
});
$(table).bind('load_selection', function() {
loadSelection(table, pager);
applySelection(table);
});
});
};
}
});
// extend plugin scope
$.fn.extend({
tablesorterPager: $.tablesorterPager.construct
});
})(jQuery);

View File

@@ -1,5 +1,5 @@
// JavaScript Document
function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix, bDuplicates)
function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix, bDuplicates, oWizHelper)
{
this.id = id;
this.iInputId = iInputId;
@@ -7,6 +7,7 @@ function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix, bDuplicates)
this.sAttCode = sAttCode;
this.sSuffix = sSuffix;
this.bDuplicates = bDuplicates;
this.oWizardHelper = oWizHelper;
var me = this;
this.Init = function()
{
@@ -40,6 +41,12 @@ function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix, bDuplicates)
// Re-run the zebra plugin to properly highlight the remaining lines & and take into account the removed ones
$('#linkedset_'+this.id+' .listResults').trigger('update').trigger("applyWidgets");
if ($('$linkedset_'+this.id+' .selection').length == 0)
{
// All items were removed: add a dummy hidden input to make sure that the linkset will be updated (emptied) when posted
$('#'+me.id+'_empty_row').show();
}
}
this.OnSelectChange = function()
@@ -57,8 +64,28 @@ function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix, bDuplicates)
this.AddObjects = function()
{
$('#dlg_'+me.id).dialog('open');
this.UpdateSizes(null, null);
var me = this;
$('#'+me.id+'_indicatorAdd').html('&nbsp;<img src="../images/indicator.gif"/>');
me.oWizardHelper.UpdateWizard();
var theMap = { sAttCode: me.sAttCode,
iInputId: me.iInputId,
sSuffix: me.sSuffix,
bDuplicates: me.bDuplicates,
'class' : me.sClass,
operation: 'addObjects',
json: me.oWizardHelper.ToJSON()
};
$.post( GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', theMap,
function(data)
{
$('#dlg_'+me.id).html(data);
$('#dlg_'+me.id).dialog('open');
me.UpdateSizes(null, null);
me.SearchObjectsToAdd();
$('#'+me.id+'_indicatorAdd').html('');
},
'html'
);
}
this.SearchObjectsToAdd = function()
@@ -69,6 +96,7 @@ function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix, bDuplicates)
bDuplicates: me.bDuplicates
}
me.UpdateButtons(0);
// Gather the parameters from the search form
$('#SearchFormToAdd_'+me.id+' :input').each(
function(i)
@@ -96,12 +124,15 @@ function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix, bDuplicates)
$(sSearchAreaId).block();
// Run the query and display the results
$.post( '../pages/ajax.render.php', theMap,
$.post( GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', theMap,
function(data)
{
$(sSearchAreaId).html(data);
$(sSearchAreaId+' .listResults').tableHover();
$(sSearchAreaId+' .listResults').tablesorter( { headers: {0: {sorter: false}}, widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables
$('#count_'+me.id).change(function(){
var c = this.value;
me.UpdateButtons(c);
});
$(sSearchAreaId).unblock();
},
'html'
@@ -110,6 +141,19 @@ function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix, bDuplicates)
return false; // Don't submit the form, stay in the current page !
}
this.UpdateButtons = function(iCount)
{
var okBtn = $('#btn_ok_'+me.id);
if (iCount > 0)
{
okBtn.attr('disabled', '');
}
else
{
okBtn.attr('disabled', 'disabled');
}
}
this.DoAddObjects = function()
{
var theMap = { sAttCode: me.sAttCode,
@@ -120,40 +164,83 @@ function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix, bDuplicates)
}
// Gather the parameters from the search form
$('#SearchResultsToAdd_'+me.id+' :checked').each(
function(i)
{
if ( (this.name != '') && ((this.type != 'checkbox') || (this.checked)) )
{
//console.log(this.type);
arrayExpr = /\[\]$/;
if (arrayExpr.test(this.name))
{
// Array
if (theMap[this.name] == undefined)
{
theMap[this.name] = new Array();
}
theMap[this.name].push(this.value);
}
else
var context = $('#SearchResultsToAdd_'+me.id);
var selectionMode = $(':input[name=selectionMode]', context);
if (selectionMode.length > 0)
{
// Paginated table retrieve the mode and the exceptions
var sMode = selectionMode.val();
theMap['selectionMode'] = sMode;
$('#fs_SearchFormToAdd_'+me.id+' :input').each(
function(i)
{
theMap[this.name] = this.value;
}
);
theMap['sRemoteClass'] = theMap['class']; // swap 'class' (defined in the form) and 'remoteClass'
theMap['class'] = me.sClass;
$(' :input[name^=storedSelection]', context).each(function() {
if (theMap[this.name] == undefined)
{
theMap[this.name] = new Array();
}
theMap[this.name].push(this.value);
$(this).remove(); // Remove the selection for the next time the dialog re-opens
});
// Retrieve the 'filter' definition
var table = $('#ResultsToAdd_'+me.id).find('table.listResults')[0];
theMap['filter'] = table.config.filter;
theMap['extra_params'] = table.config.extra_params;
}
// else
// {
// Normal table, retrieve all the checked check-boxes
$(':checked[name^=selectObject]', context).each(
function(i)
{
if ( (this.name != '') && ((this.type != 'checkbox') || (this.checked)) )
{
//console.log(this.type);
arrayExpr = /\[\]$/;
if (arrayExpr.test(this.name))
{
// Array
if (theMap[this.name] == undefined)
{
theMap[this.name] = new Array();
}
theMap[this.name].push(this.value);
}
else
{
theMap[this.name] = this.value;
}
}
$(this).parents('tr:first').remove(); // Remove the whole line, so that, next time the dialog gets displayed it's no longer there
}
}
);
);
// }
theMap['operation'] = 'doAddObjects';
if (me.oWizardHelper == null)
{
theMap['json'] = '';
}
else
{
// Not inside a "search form", updating a real object
me.oWizardHelper.UpdateWizard();
theMap['json'] = me.oWizardHelper.ToJSON();
}
$('#busy_'+me.iInputId).html('&nbsp;<img src="../images/indicator.gif"/>');
// Run the query and display the results
$.post( '../pages/ajax.render.php', theMap,
$.post( GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', theMap,
function(data)
{
//console.log('Data: ' + data);
if (data != '')
{
$('#'+me.id+'_empty_row').remove();
$('#'+me.id+'_empty_row').hide();
$('#linkedset_'+me.id+' .listResults tbody').append(data);
$('#linkedset_'+me.id+' .listResults').trigger('update');
$('#linkedset_'+me.id+' .listResults').tableHover();

View File

@@ -21,7 +21,7 @@ function ReloadTruncatedList(divId, sSerializedFilter, sExtraParams)
console.log('Uh,uh, exception !');
}
}
aTruncatedLists[divId] = $.post('../pages/ajax.render.php?style=list',
aTruncatedLists[divId] = $.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?style=list',
{ operation: 'ajax', filter: sSerializedFilter, extra_params: sExtraParams },
function(data)
{
@@ -39,12 +39,12 @@ function ReloadTruncatedList(divId, sSerializedFilter, sExtraParams)
if (checkbox)
{
// There is a checkbox in the first column, don't make it sortable
table.tablesorter( { headers: { 0: {sorter: false}}, widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables
table.tablesorter( { headers: { 0: {sorter: false}}, widgets: ['myZebra', 'truncatedList']} ).tablesorterPager({container: $("#pager")}); // sortable and zebra tables
}
else
{
// There is NO checkbox in the first column, all columns are considered sortable
table.tablesorter( { widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables
table.tablesorter( { widgets: ['myZebra', 'truncatedList']} ).tablesorterPager({container: $("#pager"), totalRows:97, filter: sSerializedFilter, extra_params: sExtraParams }); // sortable and zebra tables
}
});
$('#'+divId).unblock();
@@ -76,30 +76,12 @@ function ReloadBlock(divId, sStyle, sSerializedFilter, sExtraParams)
{
$('#'+divId).block();
//$('#'+divId).blockUI();
$.post('../pages/ajax.render.php?style='+sStyle,
$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?style='+sStyle,
{ operation: 'ajax', filter: sSerializedFilter, extra_params: sExtraParams },
function(data){
$('#'+divId).empty();
$('#'+divId).append(data);
$('#'+divId).removeClass('loading');
$('#'+divId+' .listResults').tableHover(); // hover tables
$('#'+divId+' .listResults').each( function()
{
var table = $(this);
var id = $(this).parent();
var checkbox = (table.find('th:first :checkbox').length > 0);
if (checkbox)
{
// There is a checkbox in the first column, don't make it sortable
table.tablesorter( { headers: { 0: {sorter: false}}, widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables
}
else
{
// There is NO checkbox in the first column, all columns are considered sortable
table.tablesorter( { widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables
}
});
//$('#'+divId).unblockUI();
}
);
}
@@ -137,8 +119,18 @@ function ReloadSearchForm(divId, sClassName, sBaseClass, sContext)
}
sAction = $('#ds_'+divId+' form').attr('action');
$.post('../pages/ajax.render.php?'+sContext,
{ operation: 'search_form', className: sClassName, baseClass: sBaseClass, currentId: divId, action: sAction },
// Save the current values in the form
var oMap = {};
$('#ds_'+divId+" form :input[name!='']").each(function() {
oMap[this.name] = this.value;
});
oMap.operation = 'search_form';
oMap.className = sClassName;
oMap.baseClass = sBaseClass;
oMap.currentId = divId;
oMap.action = sAction;
$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?'+sContext, oMap,
function(data) {
oDiv.empty();
oDiv.append(data);
@@ -183,7 +175,7 @@ function SetUserPreference(sPreferenceCode, sPrefValue, bPersistent)
oUserPreferences[sPreferenceCode] = sPrefValue;
if (bPersistent && (sPrefValue != sPreviousValue))
{
ajax_request = $.post('../pages/ajax.render.php',
ajax_request = $.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php',
{ operation: 'set_pref', code: sPreferenceCode, value: sPrefValue} ); // Make it persistent
}
}
@@ -210,10 +202,15 @@ function CheckAll(sSelector, bValue)
{
var value = bValue;
$(sSelector).each( function() {
this.checked = value;
if (this.checked != value)
{
this.checked = value;
$(this).trigger('change');
}
});
}
/**
* Toggle (enabled/disabled) the specified field of a form
*/
@@ -284,4 +281,23 @@ function PropagateCheckBox(bCurrValue, aFieldsList, bCheck)
ToogleField(bCheck, aFieldsList[i]);
}
}
}
function FixTableSorter(table)
{
if ($('th.header', table).length == 0)
{
// Table is not sort-able, let's fix it
var checkbox = (table.find('th:first :checkbox').length > 0);
if (checkbox)
{
// There is a checkbox in the first column, don't make it sort-able
table.tablesorter( { headers: { 0: {sorter: false}}, widgets: ['myZebra', 'truncatedList']} ); // sort-able and zebra tables
}
else
{
// There is NO checkbox in the first column, all columns are considered sort-able
table.tablesorter( { widgets: ['myZebra', 'truncatedList']} ); // sort-able and zebra tables
}
}
}

Some files were not shown because too many files have changed in this diff Show More