Compare commits

...

188 Commits

Author SHA1 Message Date
Denis Flaven
89e98a4ad7 (retrofit from trunk) Security: prevent grouping on password fields since it may lead to disclosure of the encrypted version of the password.
SVN:2.1.0[4247]
2016-06-22 14:01:45 +00:00
Denis Flaven
649a9833e9 (retrofit from trunk) Properly sanitize the "switch_env" parameter and take it into account only if it contains a valid value.
SVN:2.1.0[4241]
2016-06-22 12:16:20 +00:00
Romain Quetiez
b3c2396da8 #1209 Setup or Backup failing with french error message 'Effacement du fichier ...' Regression introduced in [r3868]. Occurs when a backup fails and prevents users from seeing the mysql error report.
SVN:2.1.0[4012]
2016-04-22 09:42:05 +00:00
Denis Flaven
5717ae1889 (retrofit from trunk) Optimization/bug (!): Never use the whole object as a placeholder in ApplyParams !!
SVN:2.1.0[3934]
2016-02-29 16:27:17 +00:00
Denis Flaven
823b33b0f5 Use one-way encryption for storing the token used for the "Forgotten password" feature.
SVN:2.1.0[3923]
2016-02-19 18:21:54 +00:00
Denis Flaven
9561ae6292 (retrofit from trunk) #1202: Fix for a security vulnerability in the Configuration Editor.
SVN:2.1.0[3906]
2016-02-11 10:32:53 +00:00
Romain Quetiez
e25a89dac1 #1174 Support HTML fields in the bulk modify form (capability to disable/enable) the field live (retrofit from trunk)
SVN:2.1.0[3893]
2016-02-01 15:22:49 +00:00
Romain Quetiez
cc7f3d46a6 #1165 backup with errors fills up tmp-directories with lots of backup-files (retrofit from trunk)
SVN:2.1.0[3892]
2016-02-01 14:57:54 +00:00
Denis Flaven
95d6b640b1 #1150: Spurious message "A restore is running..." - FIXED !
SVN:2.1.0[3867]
2016-01-20 16:08:12 +00:00
Denis Flaven
4ee0c62701 Support of derived classes in "add_remove" edition mode for AttributeLinkSet fields (the search form was not refreshing / loading properly when toggling the class to search for).
SVN:2.1.0[3825]
2015-11-20 14:29:30 +00:00
Denis Flaven
15f521d470 Make ReloadSearchForm work properly when the "submit" event handler is declared either with or without a "namespace" portion (e.g. 'submit.itop' vs 'submit') - retrofit from trunk
SVN:2.1.0[3819]
2015-11-09 10:52:45 +00:00
Romain Quetiez
86e6c8295f Instrumented the code to help in solving the "restore runing" issue. Retrofit from trunk of [3733] and [3595]: better error handling... check error.log!
SVN:2.1.0[3738]
2015-09-09 09:44:39 +00:00
Romain Quetiez
9f1338ee2a #1130 CAS authentication security leak when cas_memberof is left empty
SVN:2.1.0[3684]
2015-08-18 13:42:47 +00:00
Denis Flaven
96c8ee5e4d Retrofit of file based "transactions" as an alternative to session based ones.
SVN:2.1.0[3669]
2015-08-05 14:12:34 +00:00
Denis Flaven
8e863d4890 Fixed a potential XSS vulnerability.
SVN:2.1.0[3664]
2015-07-30 09:12:42 +00:00
Denis Flaven
cd769d5e49 Bug fix: typo causing the generation of invalid SQL queries (in some rare cases). - fix for the 2.1.0 branch.
SVN:2.1.0[3654]
2015-07-28 12:29:03 +00:00
Denis Flaven
04677fc2c7 Better error reporting (thanks to Stefan Goethals for suggesting it).
SVN:2.1.0[3624]
2015-07-06 17:07:07 +00:00
Denis Flaven
ed6a464d8b Bug fix: don't accept attachments (like images) via Chrome's copy/paste since it may duplicate the text content of a normal copy/paste and moreover causes troubles because there is no file name associated with the pasted content.
SVN:2.1.0[3622]
2015-07-06 14:33:21 +00:00
Denis Flaven
5794f7d1ca #1107: Make sure that all settings are preserved upon update.
SVN:2.1.0[3614]
2015-07-01 08:43:10 +00:00
Denis Flaven
7d42aa48cd Bug fix: make Excel export work on results of the global search.
SVN:2.1.0[3603]
2015-06-22 10:03:30 +00:00
Denis Flaven
614dd21aa9 Added an alternate implementation for storing "transaction" identifiers on disk instead of inside the $_SESSION variable.
SVN:2.1.0[3599]
2015-06-20 13:40:17 +00:00
Denis Flaven
706e6c56ff Mutex instrumentation for troubleshooting...
SVN:2.1.0[3596]
2015-06-19 14:48:13 +00:00
Romain Quetiez
f048c6cb1f JSON/REST: When specifying a case log entry (or the whole), it was not possible to set the user name without knowing a valid user id -retrofit from trunk
SVN:2.1.0[3594]
2015-06-16 09:51:51 +00:00
Romain Quetiez
1804903ee4 Make sure that the SQL mutexes are specific to the current iTop instance, but still preserving the capability for the setup to detect an already running cron job with or without a valid config file -reintegrated from trunk
SVN:2.1.0[3592]
2015-06-15 09:55:31 +00:00
Denis Flaven
17a8a896dc Bug fix: do NOT display the call stack in case of error/exception since it may contain sensitive information.
SVN:2.1.0[3589]
2015-06-05 13:52:07 +00:00
Romain Quetiez
ed85260bb3 Implemented GetForJSON and FromJSONToValue for AttributeLinkedSet (though this is not used for the Rest/JSON services which are doing much more)
SVN:2.1.0[3586]
2015-05-26 11:56:14 +00:00
Romain Quetiez
3c7fac7504 Make it possible to overload RestUtils (static methods called with static:: instead of self::) - iTop NOW REQUIRES PHP 5.3: we have verified, there are very installations of iTop made on PHP 5.2. It is worth to note that PHP 5.3 is already end of life (5.4 will become end of life in 8 months) -retrofit from trunk
SVN:2.1.0[3585]
2015-05-26 11:46:49 +00:00
Romain Quetiez
93feb700e8 ormStopWatch::GetElapsedTime not working in case of queries containing :this-> parameters (the prototype of GetElapsedTime has changed and is NOT compatible with the previous one) -reintegrated from trunk
SVN:2.1.0[3565]
2015-04-27 09:27:00 +00:00
Denis Flaven
48456082a9 Bug fix: prevent a crash of the web services when trying to log a non scalar paramater value...
SVN:2.1.0[3551]
2015-04-16 15:36:09 +00:00
Romain Quetiez
fbc0456496 Modules implementing a lifecycle written in PHP (and having actions executed on transitions) do not work until 2.1.0. The compatibility patch had been implemented but it was not working. Retrofitted from trunk
SVN:2.1.0[3548]
2015-04-16 13:53:02 +00:00
Denis Flaven
0fc3ee12b1 #1088: support of HTMLEditor in the PortalWebPage, for example if the description of a ticket is in HTML.
SVN:2.1.0[3540]
2015-04-07 15:56:16 +00:00
Denis Flaven
ce5f8c93cd Bug fix: properly compute the URLs/URIs for the soap server (and its extensions)
SVN:2.1.0[3537]
2015-04-07 09:59:53 +00:00
Denis Flaven
b38eb97323 Enhancement: allow the API to create entries with a specified user_login.
SVN:2.1.0[3516]
2015-03-24 17:09:51 +00:00
Denis Flaven
e6eb32cd21 #594: properly display attachments inside "properties" by closing the span and the fieldset in non-edit mode.
SVN:2.1.0[3512]
2015-03-23 17:56:40 +00:00
Denis Flaven
c2429f2d05 - Properly handle "suggested" attachments
- Properly pass the name of the uploaded file to the internal JS event

SVN:2.1.0[3497]
2015-02-12 18:03:36 +00:00
Romain Quetiez
4e4f2b4db9 #1060 Internal: improved the symptoms when calling MetaModel::GetAttributeDef with an invalid attribute code (feedback on the class name and no more FATAL errors) -retrofit from trunk
SVN:2.1.0[3493]
2015-02-09 13:15:21 +00:00
Romain Quetiez
2d7f963316 Branching on the revision used to build the official release 2.1.0
SVN:2.1.0[3485]
2015-01-06 08:57:15 +00:00
Denis Flaven
546d181ea9 Addition of the Danish localization contributed by Erik Bøg
SVN:trunk[3480]
2014-12-18 08:56:23 +00:00
Denis Flaven
143cefe4e3 #1041 Protect against some XSS injections
SVN:trunk[3479]
2014-12-18 08:50:04 +00:00
Romain Quetiez
ece152173f Advanced customization: a stop watch can be started in the past (incident ticket created from an alarm)
SVN:trunk[3478]
2014-12-18 08:37:00 +00:00
Denis Flaven
b08de31b3c Prevent duplicate declaration of the "Data Admin" menu (both in XML and PHP) which makes it impossible to customize.
SVN:trunk[3477]
2014-12-17 17:40:01 +00:00
Denis Flaven
0f967a41df Prevent a PHP crash when the icon tag is missing from a highlight_code definition in the XML.
SVN:trunk[3476]
2014-12-17 17:27:24 +00:00
Romain Quetiez
4c3bf70cc4 Completing [3423]: Problem/ev_assign still invoking the legacy verb SetAssignedDate
SVN:trunk[3475]
2014-12-17 08:55:02 +00:00
Romain Quetiez
35dd3f9610 The very final version (removed a misleading header in the readme file)
SVN:trunk[3473]
2014-12-16 15:13:09 +00:00
Romain Quetiez
a7f7424e54 #1039 Continuation of the fix implemented in [3465] that introduced a stopper regression (Fatal Error)
SVN:trunk[3472]
2014-12-16 13:54:40 +00:00
Denis Flaven
83e2974b10 #1040 Graphical display of "impact/depends on" is not consistent with the "list" tab
SVN:trunk[3471]
2014-12-16 13:40:51 +00:00
Romain Quetiez
715ba066d3 Adjusted dictionary entries (meta information about the lifecycle actions)
SVN:trunk[3470]
2014-12-16 09:02:03 +00:00
Romain Quetiez
9502003ff4 Updated the readme file with the latest changes
SVN:trunk[3469]
2014-12-15 16:11:15 +00:00
Romain Quetiez
57c827bb1a Updated the readme file with the latest changes
SVN:trunk[3468]
2014-12-15 16:08:50 +00:00
Denis Flaven
690ac9be75 #1038: dictionary cleanup to avoid misleading/duplicate names when importing Service Subcategories.
SVN:trunk[3467]
2014-12-15 16:08:10 +00:00
Romain Quetiez
4c3c31c44d Injectable methods: labels/descriptions given in the dictionary
SVN:trunk[3466]
2014-12-15 15:49:44 +00:00
Denis Flaven
3c9ace5b53 #1039: prevent concurrent executions of either synchro_import.php or synchro_exec.php for a given data source, since it would lead to unpredictable results.
SVN:trunk[3465]
2014-12-15 15:04:43 +00:00
Denis Flaven
bd5268dc42 Addition of the Ducth translation, thanks to Remie Malik.
SVN:trunk[3464]
2014-12-15 14:34:34 +00:00
Denis Flaven
133b6d4d29 #1037: refresh "priority" when either "impact" or "urgency" changes.
SVN:trunk[3463]
2014-12-15 14:09:17 +00:00
Denis Flaven
fba3990c61 - Proper handling of the validation hierarchy in property sheets.
- Correct behavior for animated submits...

SVN:trunk[3462]
2014-12-12 16:38:17 +00:00
Romain Quetiez
53e997cfba Instrumented Model Factory with means to keep track of touched nodes
SVN:trunk[3461]
2014-12-12 12:17:43 +00:00
Denis Flaven
e738ba35b7 The FormSelectorField now has its own widget to properly cope with its "subfields" in "property sheet" mode (continued).
SVN:trunk[3460]
2014-12-10 17:11:45 +00:00
Romain Quetiez
0773455ebc Cosmetics on the module names (consistency)
SVN:trunk[3459]
2014-12-10 10:48:47 +00:00
Denis Flaven
cafc6a8baf The FormSelectorField now has its own widget to properly cope with its "subfields" in "property sheet" mode.
SVN:trunk[3458]
2014-12-10 10:44:26 +00:00
Romain Quetiez
48f222df0b When adding a case log, existing objects could not be displayed anymore!
SVN:trunk[3457]
2014-12-09 16:07:06 +00:00
Denis Flaven
88726a0634 Support for some (optional) feedback during submit.
SVN:trunk[3456]
2014-12-08 13:19:09 +00:00
Denis Flaven
0ac522fc4c Support for some (optional) feedback during uploads.
SVN:trunk[3455]
2014-12-08 13:18:06 +00:00
Denis Flaven
aa97703b64 Add the appropriate "type" to the parameters passed to the actions on transitions.
SVN:trunk[3454]
2014-12-05 11:02:32 +00:00
Romain Quetiez
30af416394 Rework of the dictionaries: moved menu related entries to the module itop-welcome-itil (which does create most of those menus), while preserving the original copy of the entries so as to be compatible with customizations made with a copy of an older version of itop-welcome-itil
SVN:trunk[3453]
2014-12-04 16:05:11 +00:00
Denis Flaven
1f2ad9ecdb Demo mode: prevent the deletion of Users...
SVN:trunk[3452]
2014-12-04 10:02:14 +00:00
Romain Quetiez
d1f1889c42 Updated the release note with the changes made since the beta release
SVN:trunk[3451]
2014-12-03 16:43:28 +00:00
Romain Quetiez
ca7ee0f138 #1020 Restrict dashboard/shortcut refresh interval
SVN:trunk[3448]
2014-12-03 11:38:19 +00:00
Denis Flaven
909729e9f1 #1028: drop the support of non-numeric primary keys for DBObjects. This makes the queries related to the history (and the indexes) much more efficient. Beware, conversion time at setup can be long if the priv_changeop table is big.
SVN:trunk[3447]
2014-12-03 11:01:29 +00:00
Denis Flaven
a222ba998d Enhanced reporting during the setup: all the queries (create table / alter table) are now logged into "setup.log" along with their execution time.
SVN:trunk[3446]
2014-12-03 10:58:39 +00:00
Romain Quetiez
aedddf9dcc #1026 CSV import of tickets with impact = '', issuing a Notice
SVN:trunk[3445]
2014-12-02 14:45:19 +00:00
Denis Flaven
74de0d33ab #1021: fix alignment of multiple header dashlets in the same cell.
SVN:trunk[3444]
2014-12-02 13:10:44 +00:00
Romain Quetiez
8d8510a412 #1030 Missing values in the history tab (TTO/TTR)
The regression has been introduced in iTop 2.0.2 with the fix for #703 (escape HTML entities). There is no data loss: changes were correctly made and all the changes already recorded will be correctly displayed with the current fix.
The current change consists in a little rework: GetAsHTMLForHistory has been renamed DescribeChange, and it now calls oAttDef->GetAsHTMLForHistory instead of calling oAttDef->GetAsHTML, thus allowing for the factorization of the code that format the change description. AttributeSubitem does not overload DescribeChange. Rather, it simply overloads GetAsHTMLForHistory (viewing the diff may be confusing here).

SVN:trunk[3443]
2014-12-02 10:53:59 +00:00
Denis Flaven
4dd83a0eb6 #985: preserve the displayed sort order when refreshing a table.
SVN:trunk[3442]
2014-12-02 10:26:26 +00:00
Romain Quetiez
671ee353e8 #1027 Regression introduced in [3148] thus in 2.0.3 (cache the reconciliation for external keys on the CSV import) a cache hit on an ambiguous external key was not correctly handled
SVN:trunk[3440]
2014-12-01 19:12:19 +00:00
Denis Flaven
d714235d67 #1016: record the displayed value of the database_table_name field in the database.
This happens:
- when creating a new Synchro Data Source
- when modifying an "old" Synchro Data Source for which the field was empty.

SVN:trunk[3439]
2014-12-01 17:33:38 +00:00
Romain Quetiez
c4b039c9c6 #975 Continuation of the fix implemented in [r3431] - Renaming an enum requires to call RenameEnumValueInDB
SVN:trunk[3438]
2014-12-01 15:19:48 +00:00
Romain Quetiez
29e751278e #1029 Got rid of tags <format> that were not used at all and that were really misleading extension developers
SVN:trunk[3437]
2014-12-01 11:43:12 +00:00
Denis Flaven
87ed5d4a05 Fix for a regression introduced by [r3394] while fixing #1000: handling of OPT_ATT_MUSTCHANGE. The fix was not working when the original value contained several lines.
SVN:trunk[3436]
2014-11-28 17:03:13 +00:00
Denis Flaven
c8a20d01da Bug fix: proper handling of the validation of subforms...
SVN:trunk[3435]
2014-11-28 14:03:21 +00:00
Erwan Taloc
47add0eeea Allow linkage of organization to a Delivery model, directly from the tab "Customers"
SVN:trunk[3434]
2014-11-28 10:28:36 +00:00
Denis Flaven
2a9f69d70e More meta information about the interfaces.
SVN:trunk[3433]
2014-11-27 16:22:05 +00:00
Romain Quetiez
e90e570469 Datamodel version is now 2.1.0
SVN:trunk[3432]
2014-11-27 15:23:51 +00:00
Erwan Taloc
b0e56e5897 modify enumerated list for model type in order to manage Phone CIs : tack #975
SVN:trunk[3431]
2014-11-26 18:56:51 +00:00
Erwan Taloc
c96842d82c replace provider_name by provider_id in the search form of service-subcategories
SVN:trunk[3430]
2014-11-26 18:34:51 +00:00
Erwan Taloc
bc79aecd73 Improve french translation
SVN:trunk[3429]
2014-11-26 18:33:40 +00:00
Erwan Taloc
99755ad7e8 update highlight scale for request-management modules
SVN:trunk[3428]
2014-11-26 18:32:38 +00:00
Erwan Taloc
4867461f69 Add tab to link a problem to incidents
SVN:trunk[3427]
2014-11-26 17:38:53 +00:00
Erwan Taloc
3d21eecbba Translate missing tabs translation related_requests_list
SVN:trunk[3426]
2014-11-26 17:35:32 +00:00
Romain Quetiez
b822cff269 Instrumented the code to ease the troubleshooting of the computing of working hours
SVN:trunk[3425]
2014-11-25 15:25:28 +00:00
Romain Quetiez
3aa0b77751 Cosmetic on the XML: identify lifecycle functions parameters
SVN:trunk[3424]
2014-11-19 14:15:10 +00:00
Romain Quetiez
f4b10d3e81 #1022 Do cascade the resolution of an incident to its child requests + rework of the lifecycle/actions to ease the extensibility (New handlers: Rest, Copy, SetCurrentDate, SetCurrentUser, SetElapsedTime)
SVN:trunk[3423]
2014-11-19 09:44:52 +00:00
Denis Flaven
ca90f9b32a Oops, typo in "Prevent the JS validation (on focus) to create multiple entries for the same field, since it breaks the validation."
SVN:trunk[3422]
2014-11-18 17:25:08 +00:00
Denis Flaven
9ca051d9d0 Prevent the JS validation (on focus) to create multiple entries for the same field, since it breaks the validation.
SVN:trunk[3421]
2014-11-18 15:31:51 +00:00
Romain Quetiez
291f05683c New function: ormStopWatch::GetElapsedTime to compute the cumulated elapsed time on a stop watch still running -not used yet (but tested!)
SVN:trunk[3420]
2014-11-18 14:54:10 +00:00
Denis Flaven
4889ed5ac2 Read-only mode for icon selector widget: display at least the icon.
SVN:trunk[3419]
2014-11-18 09:22:48 +00:00
Denis Flaven
3fa354d00d Predefined objects are now handled by RuntimeEnvironment
SVN:trunk[3418]
2014-11-14 10:43:24 +00:00
Denis Flaven
f62934829d Fix of read_only flags for some attributes (previous verison had typos, i.e. "readonly" instead of "read_only", which made the factorisation of the state flags incorrect)
SVN:trunk[3416]
2014-11-10 13:58:37 +00:00
Denis Flaven
dee7f7b7aa Upgrade to Raphael 2.1.2
SVN:trunk[3415]
2014-11-07 13:25:37 +00:00
Romain Quetiez
1d0bfa7c92 XML format conversion utilities: no version means '1.0'
SVN:trunk[3414]
2014-11-06 15:51:08 +00:00
Denis Flaven
8922c435b4 Setup wizard cosmetics: added a "Reload" button to refresh the first page of the wizard when the prerequisites are not met.
SVN:trunk[3413]
2014-11-06 10:41:03 +00:00
Denis Flaven
ec1ec854fb Compiler: support "OPT_ATT_NORMAL" flags when an empty set of flags is defined in order to reset the flags on an attribute. Useful for inherited flags.
SVN:trunk[3412]
2014-11-06 10:39:44 +00:00
Romain Quetiez
dfc248b836 XML format conversion utilities: reworked the API to implement a CheckConvert and handle any kind of upgrade/downgrade (easy way to add a new format like 1.2)
SVN:trunk[3411]
2014-11-06 10:14:41 +00:00
Denis Flaven
481d05d082 Slightly better wording for describing the relations on the impact/dependency graph.
SVN:trunk[3410]
2014-11-05 18:30:39 +00:00
Denis Flaven
4e4d3cf3da Enhanced version of the SWF navigator providing a better stability for big numbers of elements.
SVN:trunk[3409]
2014-11-05 18:28:54 +00:00
Denis Flaven
5fc68557dc Enhanced version of the SWF navigator providing a better stability for big numbers of elements.
SVN:trunk[3408]
2014-11-05 18:27:56 +00:00
Denis Flaven
7482a52fd9 Reverting to revision 3397
SVN:trunk[3407]
2014-11-03 16:12:11 +00:00
Romain Quetiez
50ca6cdd0f Backup tools disabled when the demo mode is active
SVN:trunk[3406]
2014-11-03 15:12:47 +00:00
Romain Quetiez
e83e1262a5 Configuration editor disabled when the demo mode is active
SVN:trunk[3405]
2014-11-03 15:12:07 +00:00
Romain Quetiez
51bd403638 Draft of the readme file for 2.1.0 beta
SVN:trunk[3404]
2014-11-03 11:17:40 +00:00
Denis Flaven
c786e8308a Incrementation of the version number of the 2.x modules to reflect the change of the XML format.
SVN:trunk[3403]
2014-11-03 09:21:03 +00:00
Romain Quetiez
a7d3a5a488 New functionality: data backup / restore. By default, performs a daily backup, keeping the five last backups. Allows manual backups too.
SVN:trunk[3402]
2014-11-03 09:18:54 +00:00
Romain Quetiez
2c70c60d2b New functionality: a rudimentary configuration editor (admins only)
SVN:trunk[3401]
2014-11-03 09:17:27 +00:00
Denis Flaven
96f02d1557 Integration of the Excel (XLSX) export feature... (regression fix)
SVN:trunk[3400]
2014-11-01 07:46:57 +00:00
Denis Flaven
de6ddffe65 #998 Accurately check the configured mail transport and report information accordingly in the email.test page.
Add two "debug" transports for Swift mailer: Null and LogFile which are useful for staging environments where one does not want to send real emails.

SVN:trunk[3399]
2014-10-31 21:14:55 +00:00
Denis Flaven
2c59fb894f Integration of the Excel (XLSX) export feature. (Limitation: export.php takes into account neither the "fields" parameter nor the list of fields defined in the QueryPhrasebook when exporting in XLSX format)
SVN:trunk[3398]
2014-10-31 17:59:14 +00:00
Romain Quetiez
77cf399c72 Portal: handle mandatory attributes in Reopen/Close dialogs
SVN:trunk[3397]
2014-10-31 15:42:13 +00:00
Romain Quetiez
19a2180c2b #1000 Implemented the behavior for the flag OPT_ATT_MUSTCHANGE, and took the opportunity to add a feedback when a field is mandatory OR when the format is wrong
SVN:trunk[3394]
2014-10-31 14:55:12 +00:00
Romain Quetiez
18b73de512 #1012 Losing half of the connection when changing a port (connections between network devices). I took the opportunity to simplify the connection management as it was initiated in change [3388].
SVN:trunk[3393]
2014-10-30 10:53:30 +00:00
Denis Flaven
bb741c39f4 #1011 Proper resizing of the dialog box for managing 1:n links - removed a trace.
SVN:trunk[3390]
2014-10-30 08:54:48 +00:00
Denis Flaven
c73ef6ae72 #1011 Proper resizing of the dialog box for managing 1:n links.
SVN:trunk[3389]
2014-10-30 08:52:00 +00:00
Romain Quetiez
5abd9c6dad #1008 Error when deleting a Network Device connected to another Network Device (does not happen if the other end is another type of "ConnectedDevice")
SVN:trunk[3388]
2014-10-29 20:44:24 +00:00
Romain Quetiez
a6db04bafd #1007 Unexpected change of the case log when doing massive update of a User Request (+ hide the checkbox for the status because it makes no sense)
SVN:trunk[3387]
2014-10-29 15:46:44 +00:00
Romain Quetiez
04f46f4798 #979 Data synchro: recover the DB triggers (backup/restore). I've tested various combinations of the previous check (new columns to synchronize) and the new one (missing triggers).
SVN:trunk[3386]
2014-10-29 13:15:36 +00:00
Romain Quetiez
5ecf6b9e9d Fixed regression introduced in [3224] thus in iTop 2.0.3, in the data model view: could not see the OQL constraints on external keys
SVN:trunk[3385]
2014-10-28 16:07:42 +00:00
Romain Quetiez
0135573956 #995 Make sure that tto/ttr passed gets set even if the CRON has not been run (and as soon as some overrun has been counted)
SVN:trunk[3384]
2014-10-28 15:55:56 +00:00
Romain Quetiez
a59915e5ee Completing the change [3291]: the cron could not trigger a timeout on the stop watches, and the 'persistent' flag was forcibly set to true (whereas we expect the default value to be false!)
SVN:trunk[3383]
2014-10-28 15:51:55 +00:00
Denis Flaven
ebb3767ee4 RenderAsDialog now takes into account its "width" parameter.
SVN:trunk[3382]
2014-10-28 14:49:15 +00:00
Romain Quetiez
047166f002 #983 Sortering not possible on multi-column queries
SVN:trunk[3381]
2014-10-28 10:47:00 +00:00
Romain Quetiez
f45c783396 #965 Since 2.0.3, for each synchronized object, around 100 queries are performed (2 are required), and this is multiplied be the number of duplicate replicas (then resulting in a significant slowdown).
SVN:trunk[3380]
2014-10-28 08:53:30 +00:00
Denis Flaven
72f516685e #974 prevent multiple javascript refresh when reloading the "initial state" of a ticket.
SVN:trunk[3379]
2014-10-27 16:37:52 +00:00
Romain Quetiez
2b4400c55d XML: normalized the format (allows for implementing a quick diff algorithm)
SVN:trunk[3378]
2014-10-27 16:13:38 +00:00
Romain Quetiez
1df87b6331 #969 XML: the menu option enable_admin_only hides the menu for everyone
SVN:trunk[3377]
2014-10-23 19:22:18 +00:00
Romain Quetiez
cd7490472e #970 and #650 Corrupted attachements. Reworked the cleanup of undesired output, to protect it against the case when the output buffer is unfortunately closed. On the other hand, I found out that several output buffer can be stacked. Thus the protection could be further improved (difficulty: that can be web server dependent).
SVN:trunk[3376]
2014-10-23 15:48:49 +00:00
Romain Quetiez
b65131e4f5 #971 XML: could not specify an icon as a path to a file
SVN:trunk[3375]
2014-10-23 08:54:39 +00:00
Romain Quetiez
fac8604cc6 #972 Incomprehensible error message during setup, with a sample extension provided by Combodo! (empty user rights tag) -This fix improves a lot, but the final fix should be to compile user rights in a separate directory. To be further discussed.
SVN:trunk[3374]
2014-10-22 15:31:53 +00:00
Romain Quetiez
e25da2a7c4 Complete the commit [3291] that changes the structure returned by MetaModel::EnumTransitions. I have carefully reviewed every call to EnumTransitions... and found out only one single missing case in the data model view.
SVN:trunk[3373]
2014-10-22 10:44:58 +00:00
Romain Quetiez
6d9e7f690f #993 The about box does not show up when the directory extensions is missing
SVN:trunk[3372]
2014-10-22 09:48:13 +00:00
Denis Flaven
9c62952743 Typo: sKey => iKey.
Prefix for the dialogs to prevent a collision of IDs

SVN:trunk[3371]
2014-10-21 14:13:49 +00:00
Romain Quetiez
03c4964072 Protected the property fields against the collision of ids within the same page (even if that is a bug, make it work not too bad!)
SVN:trunk[3370]
2014-10-20 15:15:39 +00:00
Denis Flaven
711949414d Fixed the support of a non-default port for MySQL, thanks to theBigOne!
SVN:trunk[3368]
2014-10-16 14:32:10 +00:00
Romain Quetiez
055a87306e Forms: drop-down box default value label could be changed (or this entry could be entirely removed)
SVN:trunk[3367]
2014-10-16 10:15:41 +00:00
Romain Quetiez
37ebb51a2b Computation of user rights: added a config flag to force the legacy algorithm (user_rights_legacy, defaulting to false)
SVN:trunk[3366]
2014-10-16 10:14:17 +00:00
Romain Quetiez
1f8d4d379f Computation of user rights: deny on a parent class must give DENY even if the class is explicitely ALLOW on the same profile (that was already working if the rules are given on several profiles). Note that this has a cost when building the grant matrix!
SVN:trunk[3365]
2014-10-16 09:02:47 +00:00
Romain Quetiez
1300811007 Aligned the compiler with the new XML format (1.1):
- Profile actions (was buggy)
- Sort the states declaration (could be buggy depending on the order of the declaration withing the XML)

SVN:trunk[3364]
2014-10-15 15:07:52 +00:00
Denis Flaven
fbdd0dfd57 - Migration of the XML files to the version 1.1 of the XML schema.
- Refactoring of the "state flags" definitions to take advantage of the "inherit_flags_from" capability available in the new XML schema.

SVN:trunk[3363]
2014-10-14 09:56:21 +00:00
Denis Flaven
bc79663a3e Split in a separate "tool" class the upgrade of the format of the datamodel from 1.0 to 1.1.
Marked the XML as version 1.1.

SVN:trunk[3362]
2014-10-13 14:42:55 +00:00
Romain Quetiez
570e4f8589 New XML format, updated the transformation tool (UpgradeDocument)
SVN:trunk[3361]
2014-10-10 08:22:51 +00:00
Romain Quetiez
472802e11b Cosmetic improvement of an error message (data model compilation - collision of ids)
SVN:trunk[3360]
2014-10-07 14:41:11 +00:00
Denis Flaven
f794d0222e Forms enhancements and XML format touch-up.
SVN:trunk[3359]
2014-10-03 09:56:20 +00:00
Denis Flaven
b42a43d47b #968 (continued) do not loose the "advanced mode" flag in the interactive display.
SVN:trunk[3357]
2014-10-02 09:36:32 +00:00
Romain Quetiez
110aace270 Form fields: added callbacks ('equals' and 'get_field_value') to allow the implementation of enhanced form fields
SVN:trunk[3356]
2014-10-01 12:17:15 +00:00
Romain Quetiez
68dd0513c6 #968 Interactive CSV Export truncated or missing characters (since 2.0.3)
SVN:trunk[3354]
2014-09-25 15:21:20 +00:00
Romain Quetiez
08e757a08a #991 CSV export truncated (system dependent, since 2.0) due to a bug in iconv, the workaround is to do little by little
SVN:trunk[3352]
2014-09-25 15:01:45 +00:00
Romain Quetiez
996c590793 Code cleanup: reworked the user rights data model, while strictly preserving the current functionality (checked using the tool dump_profiles.php, with simple to full ITIL configurations). Class groups have been renamed/merged/removed. This is documented in the migration notes (wiki).
SVN:trunk[3351]
2014-09-24 09:18:14 +00:00
Romain Quetiez
c93cc3a5bf lnkVirtualDeviceToVolume and lnkTriggerAction are link classes and should be declared as such
SVN:trunk[3350]
2014-09-24 09:06:30 +00:00
Romain Quetiez
bdccf6ea72 Transmission of user rights along N-N links: must work both with DEL_AUTO and DEL_SILENT external keys (found with a code review, DEL_SILENT is still rarely used)
SVN:trunk[3349]
2014-09-24 08:56:08 +00:00
Romain Quetiez
ed60346ae6 Code cleanup (removed dead and misleading code branches), following revision [3347]
SVN:trunk[3348]
2014-09-23 13:38:22 +00:00
Denis Flaven
4953ea7701 Rework of the ModelFactory API to make it simpler and safer.
SVN:trunk[3347]
2014-09-22 15:19:56 +00:00
Denis Flaven
82a8a0bba7 #932: fixed a regression introduced by [r3319]... in case a criteria is present several times.
SVN:trunk[3344]
2014-09-17 15:41:35 +00:00
Romain Quetiez
55c818b6b7 Dehardcoded the size of the attachments preview
SVN:trunk[3343]
2014-09-16 12:26:50 +00:00
Romain Quetiez
372c0835f7 #988 Could not change the case of a login (cosmetic improvement to make sure this piece of code is the right example that can be shared amongst the developpers community)
SVN:trunk[3342]
2014-09-16 10:16:10 +00:00
Denis Flaven
52a028301f Bug fix: FetchAssoc was broken when dealing with in-memory sets.
SVN:trunk[3340]
2014-09-16 09:45:39 +00:00
Romain Quetiez
61b88d2689 #989 Developper issue: query arguments having a null value are dismissed
SVN:trunk[3339]
2014-09-16 09:45:17 +00:00
Romain Quetiez
fa856c32cd #988 Could not change the case of a login
SVN:trunk[3338]
2014-09-16 08:04:37 +00:00
Romain Quetiez
6d693bbfc7 #778 Issue on list sort order when editing an element - fixed a regression
SVN:trunk[3335]
2014-09-15 16:06:46 +00:00
Romain Quetiez
7117b751b3 #778 Issue on list sort order when editing an element (N-N link tabs)
SVN:trunk[3322]
2014-09-15 13:40:56 +00:00
Romain Quetiez
c30a88c9ef #986 Search form: handle indirect external keys
SVN:trunk[3321]
2014-09-15 10:01:19 +00:00
Romain Quetiez
c1085fc398 #987 Usage login prevents from user deletion
SVN:trunk[3320]
2014-09-12 12:58:18 +00:00
Romain Quetiez
42ac871f8e #932 Search form should be prefilled when running a search "shortcut" - very little progress: fixed the case when several criteria are given
SVN:trunk[3319]
2014-09-12 09:37:25 +00:00
Romain Quetiez
b1a404d909 #985 Shortcut auto refresh degrading table cosmetics
SVN:trunk[3318]
2014-09-12 08:18:06 +00:00
Romain Quetiez
ba82031a59 #984 Dashboard auto refresh degrading table functionalities like sorting
SVN:trunk[3317]
2014-09-12 08:15:14 +00:00
Romain Quetiez
c8568af43b #976 The fix [3306] introduced a regression: Call to undefined function DBInsert()
SVN:trunk[3315]
2014-09-11 08:17:37 +00:00
Romain Quetiez
7ed60f711c mproved the processing of background task to enable more advanced functionalities like queuing (protection against reentrance)
SVN:trunk[3313]
2014-09-03 09:53:55 +00:00
Romain Quetiez
e831b1a486 #978 Email test utility always reporting "default SMTP port"
SVN:trunk[3312]
2014-09-01 09:09:32 +00:00
Romain Quetiez
b5db86472d Fixed bug in the dashboard forms.
SVN:trunk[3311]
2014-08-29 12:54:55 +00:00
Denis Flaven
ed9ba815ab #976: (removed some debug traces) make sure that we do not bypass the method that computes the reference for newly created tickets.
SVN:trunk[3307]
2014-08-28 15:56:25 +00:00
Denis Flaven
7b47e8c480 #976: make sure that we do not bypass the method that computes the reference for newly created tickets.
SVN:trunk[3306]
2014-08-28 15:53:02 +00:00
Romain Quetiez
d2cd758ecc Automatically add an id on the user rights profile/actions to allow a finer granularity for the deltas.
SVN:trunk[3305]
2014-08-28 15:52:05 +00:00
Denis Flaven
d2ea4023c2 Protect dashboards against invalid queries in "grouped by" dashlets.
SVN:trunk[3304]
2014-08-28 12:07:26 +00:00
Romain Quetiez
f0ee5112b9 Improved the processing of background task to enable more advanced functionalities like queuing (factorized the error handling)
SVN:trunk[3302]
2014-08-21 08:53:16 +00:00
Romain Quetiez
ada70b97d4 Improved the processing of background task to enable more advanced functionalities like queuing
SVN:trunk[3300]
2014-08-19 10:08:35 +00:00
Denis Flaven
a546b39301 Support of more sophisticated forms layout...
SVN:trunk[3299]
2014-08-16 16:17:12 +00:00
Denis Flaven
8f2cd66bf6 Declaration of generic methods which can be run on tickets.
SVN:trunk[3298]
2014-08-14 15:46:43 +00:00
Denis Flaven
868d0d1b19 Renamed the "
SVN:trunk[3297]
2014-08-13 15:04:42 +00:00
Denis Flaven
7d619f278e Added <inherit_flags_from> to inherit the flags from another state.
SVN:trunk[3296]
2014-08-13 15:04:25 +00:00
Denis Flaven
fd9008a163 New concept in the XML: HighlightScale to avoid overloading GetIcon and GetHilightClass...
SVN:trunk[3291]
2014-07-28 15:16:16 +00:00
Denis Flaven
357ae4abb1 Automatically add an id on the transitions to allow a finer granularity for the deltas.
SVN:trunk[3287]
2014-07-24 12:46:56 +00:00
Denis Flaven
2108a74f87 Added an identifier on the transitions to allow a finer granularity for the deltas.
SVN:trunk[3286]
2014-07-24 12:42:40 +00:00
241 changed files with 24421 additions and 7460 deletions

View File

@@ -271,6 +271,19 @@ class URP_UserProfile extends UserRightsBaseClassGUI
{
return Dict::Format('UI:UserManagement:LinkBetween_User_And_Profile', $this->Get('userlogin'), $this->Get('profile'));
}
public function CheckToDelete(&$oDeletionPlan)
{
if (MetaModel::GetConfig()->Get('demo_mode'))
{
// Users deletion is NOT allowed in demo mode
$oDeletionPlan->AddToDelete($this, null);
$oDeletionPlan->SetDeletionIssues($this, array('deletion not allowed in demo mode.'), true);
$oDeletionPlan->ComputeResults();
return false;
}
return parent::CheckToDelete($oDeletionPlan);
}
}
class URP_UserOrg extends UserRightsBaseClassGUI

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2014 Combodo SARL
//
// This file is part of iTop.
//
@@ -225,9 +225,8 @@ EOF
EOF
);
}
$s_captured_output = ob_get_contents();
ob_end_clean();
if (($this->sContentType == 'text/html') && ($this->sContentDisposition == 'inline'))
$s_captured_output = $this->ob_get_clean_safe();
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);
@@ -393,4 +392,3 @@ EOF
}
}
?>

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
@@ -781,8 +781,12 @@ class RestUtils
$oSearch = new DBObjectSearch($sClass);
foreach ($oCriteria as $sAttCode => $value)
{
$realValue = self::MakeValue($sClass, $sAttCode, $value);
$realValue = static::MakeValue($sClass, $sAttCode, $value);
$oSearch->AddCondition($sAttCode, $realValue, '=');
if (is_object($value) || is_array($value))
{
$value = json_encode($value);
}
$aCriteriaReport[] = "$sAttCode: $value ($realValue)";
}
$oSet = new DBObjectSet($oSearch);
@@ -814,7 +818,7 @@ class RestUtils
{
if (is_object($key))
{
$res = self::FindObjectFromCriteria($sClass, $key);
$res = static::FindObjectFromCriteria($sClass, $key);
}
elseif (is_numeric($key))
{
@@ -878,7 +882,7 @@ class RestUtils
$oSearch = new DBObjectSearch($sClass);
foreach ($key as $sAttCode => $value)
{
$realValue = self::MakeValue($sClass, $sAttCode, $value);
$realValue = static::MakeValue($sClass, $sAttCode, $value);
$oSearch->AddCondition($sAttCode, $realValue, '=');
}
}
@@ -922,7 +926,7 @@ class RestUtils
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
if ($oAttDef instanceof AttributeExternalKey)
{
$oExtKeyObject = self::FindObjectFromKey($oAttDef->GetTargetClass(), $value, true /* allow null */);
$oExtKeyObject = static::FindObjectFromKey($oAttDef->GetTargetClass(), $value, true /* allow null */);
$value = ($oExtKeyObject != null) ? $oExtKeyObject->GetKey() : 0;
}
elseif ($oAttDef instanceof AttributeLinkedSet)
@@ -935,7 +939,7 @@ class RestUtils
$aLinks = array();
foreach($value as $oValues)
{
$oLnk = self::MakeObjectFromFields($sLnkClass, $oValues);
$oLnk = static::MakeObjectFromFields($sLnkClass, $oValues);
$aLinks[] = $oLnk;
}
$value = DBObjectSet::FromArray($sLnkClass, $aLinks);
@@ -966,7 +970,7 @@ class RestUtils
$oObject = MetaModel::NewObject($sClass);
foreach ($aFields as $sAttCode => $value)
{
$realValue = self::MakeValue($sClass, $sAttCode, $value);
$realValue = static::MakeValue($sClass, $sAttCode, $value);
try
{
$oObject->Set($sAttCode, $realValue);
@@ -993,7 +997,7 @@ class RestUtils
$sClass = get_class($oObject);
foreach ($aFields as $sAttCode => $value)
{
$realValue = self::MakeValue($sClass, $sAttCode, $value);
$realValue = static::MakeValue($sClass, $sAttCode, $value);
try
{
$oObject->Set($sAttCode, $realValue);

View File

@@ -125,27 +125,29 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
// Master data sources
$sSynchroIcon = '';
$oReplicaSet = $this->GetMasterReplica();
$bSynchronized = false;
$oCreatorTask = null;
$bCanBeDeletedByTask = false;
$bCanBeDeletedByUser = true;
$aMasterSources = array();
if ($oReplicaSet->Count() > 0)
$aSyncData = $this->GetSynchroData();
if (count($aSyncData) > 0)
{
$bSynchronized = true;
while($aData = $oReplicaSet->FetchAssoc())
foreach ($aSyncData as $iSourceId => $aSourceData)
{
// Assumption: $aData['datasource'] will not be null because the data source id is always set...
$sApplicationURL = $aData['datasource']->GetApplicationUrl($this, $aData['replica']);
$sLink = $aData['datasource']->GetName();
$oDataSource = $aSourceData['source'];
$oReplica = reset($aSourceData['replica']); // Take the first one!
$sApplicationURL = $oDataSource->GetApplicationUrl($this, $oReplica);
$sLink = $oDataSource->GetName();
if (!empty($sApplicationURL))
{
$sLink = "<a href=\"$sApplicationURL\" target=\"_blank\">".$aData['datasource']->GetName()."</a>";
$sLink = "<a href=\"$sApplicationURL\" target=\"_blank\">".$oDataSource->GetName()."</a>";
}
if ($aData['replica']->Get('status_dest_creator') == 1)
if ($oReplica->Get('status_dest_creator') == 1)
{
$oCreatorTask = $aData['datasource'];
$oCreatorTask = $oDataSource;
$bCreatedByTask = true;
}
else
@@ -154,12 +156,12 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
}
if ($bCreatedByTask)
{
$sDeletePolicy = $aData['datasource']->Get('delete_policy');
$sDeletePolicy = $oDataSource->Get('delete_policy');
if (($sDeletePolicy == 'delete') || ($sDeletePolicy == 'update_then_delete'))
{
$bCanBeDeletedByTask = true;
}
$sUserDeletePolicy = $aData['datasource']->Get('user_delete_policy');
$sUserDeletePolicy = $oDataSource->Get('user_delete_policy');
if ($sUserDeletePolicy == 'nobody')
{
$bCanBeDeletedByUser = false;
@@ -172,9 +174,9 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
{
}
}
$aMasterSources[$aData['datasource']->GetKey()]['datasource'] = $aData['datasource'];
$aMasterSources[$aData['datasource']->GetKey()]['url'] = $sLink;
$aMasterSources[$aData['datasource']->GetKey()]['last_synchro'] = $aData['replica']->Get('status_last_seen');
$aMasterSources[$iSourceId]['datasource'] = $oDataSource;
$aMasterSources[$iSourceId]['url'] = $sLink;
$aMasterSources[$iSourceId]['last_synchro'] = $oReplica->Get('status_last_seen');
}
if (is_object($oCreatorTask))
@@ -941,12 +943,12 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
return $oDataTable->Display($oPage, $oSettings, $bDisplayMenu, $sSelectMode, $bViewLink, $aExtraParams);
}
static function DisplaySetAsCSV(WebPage $oPage, CMDBObjectSet $oSet, $aParams = array())
static function DisplaySetAsCSV(WebPage $oPage, CMDBObjectSet $oSet, $aParams = array(), $sCharset = 'UTF-8')
{
$oPage->add(self::GetSetAsCSV($oSet, $aParams));
$oPage->add(self::GetSetAsCSV($oSet, $aParams, $sCharset));
}
static function GetSetAsCSV(DBObjectSet $oSet, $aParams = array())
static function GetSetAsCSV(DBObjectSet $oSet, $aParams = array(), $sCharset = 'UTF-8')
{
$sSeparator = isset($aParams['separator']) ? $aParams['separator'] : ','; // default separator is comma
$sTextQualifier = isset($aParams['text_qualifier']) ? $aParams['text_qualifier'] : '"'; // default text qualifier is double quote
@@ -1064,7 +1066,8 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
else
{
$value = $oObj->Get($sAttCodeEx);
$aRow[] = $oAttDef->GetAsCSV($value, $sSeparator, $sTextQualifier, $oObj, $bLocalize);
$sCSVValue = $oAttDef->GetAsCSV($value, $sSeparator, $sTextQualifier, $oObj, $bLocalize);
$aRow[] = iconv('UTF-8', $sCharset.'//IGNORE//TRANSLIT', $sCSVValue);
}
}
}
@@ -1401,6 +1404,8 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
$sHtml .= "<p>\n";
$aFilterCriteria = $oSet->GetFilter()->GetCriteria();
$aMapCriteria = array();
// Todo: Investigate... The search criteria is an expression, i.e. a tree!
// I wonder if that code could work... cleanup required/recommended
foreach($aFilterCriteria as $aCriteria)
{
$aMapCriteria[$aCriteria['filtercode']][] = array('value' => $aCriteria['value'], 'opcode' => $aCriteria['opcode']);
@@ -1428,6 +1433,7 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
$sFilterValue = $aMapCriteria[$sFilterCode][0]['value'];
$sFilterOpCode = $aMapCriteria[$sFilterCode][0]['opcode'];
}
// Todo: Investigate...
if ($sFilterCode != 'company')
{
$oUnlimitedFilter->AddCondition($sFilterCode, $sFilterValue, $sFilterOpCode);
@@ -1436,20 +1442,24 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
}
$oAttDef = MetaModel::GetAttributeDef($sClassName, $sFilterCode);
if ($oAttDef->IsExternalKey())
if ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE))
{
$sTargetClass = $oAttDef->GetTargetClass();
$oKeyAttDef = $oAttDef->GetFinalAttDef();
$sKeyAttClass = $oKeyAttDef->GetHostClass();
$sKeyAttCode = $oKeyAttDef->GetCode();
$sTargetClass = $oKeyAttDef->GetTargetClass();
$oSearch = new DBObjectSearch($sTargetClass);
$oSearch->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', true);
$oAllowedValues = new DBObjectSet($oSearch);
$iFieldSize = $oAttDef->GetMaxSize();
$iMaxComboLength = $oAttDef->GetMaximumComboLength();
$sHtml .= "<label>".MetaModel::GetFilterLabel($sClassName, $sFilterCode).":</label>&nbsp;";
$iFieldSize = $oKeyAttDef->GetMaxSize();
$iMaxComboLength = $oKeyAttDef->GetMaximumComboLength();
$sHtml .= "<label>".MetaModel::GetFilterLabel($sKeyAttClass, $sKeyAttCode).":</label>&nbsp;";
$aExtKeyParams = $aExtraParams;
$aExtKeyParams['iFieldSize'] = $oAttDef->GetMaxSize();
$aExtKeyParams['iMinChars'] = $oAttDef->GetMinAutoCompleteChars();
$sHtml .= UIExtKeyWidget::DisplayFromAttCode($oPage, $sFilterCode, $sClassName, $oAttDef->GetLabel(), $oAllowedValues, $sFilterValue, $sSearchFormId.'search_'.$sFilterCode, false, $sFilterCode, '', $aExtKeyParams, true);
$aExtKeyParams['iFieldSize'] = $oKeyAttDef->GetMaxSize();
$aExtKeyParams['iMinChars'] = $oKeyAttDef->GetMinAutoCompleteChars();
$sHtml .= UIExtKeyWidget::DisplayFromAttCode($oPage, $sKeyAttCode, $sKeyAttClass, $oAttDef->GetLabel(), $oAllowedValues, $sFilterValue, $sSearchFormId.'search_'.$sFilterCode, false, $sFilterCode, '', $aExtKeyParams, true);
}
else
{
@@ -1512,7 +1522,7 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
{
if (is_scalar($sValue))
{
$sHtml .= "<input type=\"hidden\" name=\"$sName\" value=\"$sValue\" />\n";
$sHtml .= "<input type=\"hidden\" name=\"$sName\" value=\"".htmlentities($sValue, ENT_QUOTES, 'UTF-8')."\" />\n";
}
}
$sHtml .= "<input type=\"hidden\" name=\"class\" value=\"$sClassName\" />\n";
@@ -1844,14 +1854,19 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
{
$sNullValue = "'$sNullValue'"; // Add quotes to turn this into a JS string if it's not a number
}
$oPage->add_ready_script("$('#$iId').bind('".implode(' ', $aEventsList)."', function(evt, sFormId) { return ValidateField('$iId', '$sPattern', $bMandatory, sFormId, $sNullValue) } );\n"); // Bind to a custom event: validate
$sOriginalValue = ($iFlags & OPT_ATT_MUSTCHANGE) ? json_encode($value) : 'undefined';
$oPage->add_ready_script("$('#$iId').bind('".implode(' ', $aEventsList)."', function(evt, sFormId) { return ValidateField('$iId', '$sPattern', $bMandatory, sFormId, $sNullValue, $sOriginalValue) } );\n"); // Bind to a custom event: validate
}
$aDependencies = MetaModel::GetDependentAttributes($sClass, $sAttCode); // List of attributes that depend on the current one
if (count($aDependencies) > 0)
{
$oPage->add_ready_script("$('#$iId').bind('change', function(evt, sFormId) { return oWizardHelper{$sFormPrefix}.UpdateDependentFields(['".implode("','", $aDependencies)."']) } );\n"); // Bind to a custom event: validate
// Unbind first to avoid duplicate event handlers in case of reload of the whole (or part of the) form
$oPage->add_ready_script("$('#$iId').unbind('change.dependencies').bind('change.dependencies', function(evt, sFormId) { return oWizardHelper{$sFormPrefix}.UpdateDependentFields(['".implode("','", $aDependencies)."']) } );\n"); // Bind to a custom event: validate
}
}
$oPage->add_dict_entry('UI:ValueMustBeSet');
$oPage->add_dict_entry('UI:ValueMustBeChanged');
$oPage->add_dict_entry('UI:ValueInvalidFormat');
return "<div>{$sHTMLValue}</div>";
}
@@ -2460,7 +2475,7 @@ EOF
{
// Possible return values are:
// HILIGHT_CLASS_CRITICAL, HILIGHT_CLASS_WARNING, HILIGHT_CLASS_OK, HILIGHT_CLASS_NONE
$current = HILIGHT_CLASS_NONE; // Not hilighted by default
$current = parent::GetHilightClass(); // Default computation
// Invoke extensions before the deletion (the deletion will do some cleanup and we might loose some information
foreach (MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance)
@@ -3074,7 +3089,7 @@ EOF
// Attribute is read-only
$sHTMLValue = $this->GetAsHTML($sAttCode);
$sHTMLValue .= '<input type="hidden" id="'.$sInputId.'" name="attr_'.$sPrefix.$sAttCode.'" value="'.htmlentities($this->Get($sAttCode), ENT_QUOTES, 'UTF-8').'"/>';
$sHTMLValue .= '<input type="hidden" id="'.$sInputId.'" name="attr_'.$sPrefix.$sAttCode.'" value="'.htmlentities($this->GetEditValue($sAttCode), ENT_QUOTES, 'UTF-8').'"/>';
$aFieldsMap[$sAttCode] = $sInputId;
$sComment .= $sSynchroIcon;
}
@@ -3251,7 +3266,11 @@ EOF
$currValue = $aKeys[0]; // The only value is the first key
//echo "<p>current value for $sAttCode : $currValue</p>";
$oDummyObj->Set($sAttCode, $currValue);
$aComments[$sAttCode] = '<input type="checkbox" checked id="enable_'.$iFormId.'_'.$sAttCode.'" onClick="ToogleField(this.checked, \''.$iFormId.'_'.$sAttCode.'\')"/>';
$aComments[$sAttCode] = '';
if ($sAttCode != MetaModel::GetStateAttributeCode($sClass))
{
$aComments[$sAttCode] .= '<input type="checkbox" checked id="enable_'.$iFormId.'_'.$sAttCode.'" onClick="ToogleField(this.checked, \''.$iFormId.'_'.$sAttCode.'\')"/>';
}
$aComments[$sAttCode] .= '<div class="mono_value">1</div>';
}
else
@@ -3278,7 +3297,11 @@ EOF
$sReadyScript .= "$('#multi_values_$sAttCode').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );";
$oDummyObj->Set($sAttCode, null);
$aComments[$sAttCode] = '<input type="checkbox" id="enable_'.$iFormId.'_'.$sAttCode.'" onClick="ToogleField(this.checked, \''.$iFormId.'_'.$sAttCode.'\')"/>';
$aComments[$sAttCode] = '';
if ($sAttCode != MetaModel::GetStateAttributeCode($sClass))
{
$aComments[$sAttCode] .= '<input type="checkbox" id="enable_'.$iFormId.'_'.$sAttCode.'" onClick="ToogleField(this.checked, \''.$iFormId.'_'.$sAttCode.'\')"/>';
}
$aComments[$sAttCode] .= '<div class="multi_values" id="multi_values_'.$sAttCode.'">'.$iCount.'</div>';
}
$sReadyScript .= 'ToogleField('.(($iCount == 1) ? 'true': 'false').', \''.$iFormId.'_'.$sAttCode.'\');'."\n";

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2014 Combodo SARL
//
// This file is part of iTop.
//
@@ -39,7 +39,11 @@ class CSVPage extends WebPage
public function output()
{
$this->add_header("Content-Length: ".strlen(trim($this->s_content)));
$this->add_header("Content-Length: ".strlen(trim($this->s_content)));
// Get the unexpected output but do nothing with it
$sTrash = $this->ob_get_clean_safe();
foreach($this->a_headers as $s_header)
{
header($s_header);
@@ -105,4 +109,3 @@ class CSVPage extends WebPage
}
}
?>

View File

@@ -91,7 +91,7 @@ abstract class Dashboard
}
if ($oAutoReloadInterval = $oAutoReloadNode->getElementsByTagName('interval')->item(0))
{
$this->iAutoReloadSec = max(5, (int)$oAutoReloadInterval->textContent);
$this->iAutoReloadSec = max(MetaModel::GetConfig()->Get('min_reload_interval'), (int)$oAutoReloadInterval->textContent);
}
}
@@ -235,7 +235,7 @@ abstract class Dashboard
$this->sLayoutClass = $aParams['layout_class'];
$this->sTitle = $aParams['title'];
$this->bAutoReload = $aParams['auto_reload'] == 'true';
$this->iAutoReloadSec = max(5, (int) $aParams['auto_reload_sec']);
$this->iAutoReloadSec = max(MetaModel::GetConfig()->Get('min_reload_interval'), (int) $aParams['auto_reload_sec']);
foreach($aParams['cells'] as $aCell)
{
@@ -300,7 +300,7 @@ abstract class Dashboard
public function SetAutoReloadInterval($iAutoReloadSec)
{
$this->iAutoReloadSec = max(5, (int)$iAutoReloadSec);
$this->iAutoReloadSec = max(MetaModel::GetConfig()->Get('min_reload_interval'), (int)$iAutoReloadSec);
}
public function AddDashlet($oDashlet)
@@ -312,7 +312,7 @@ abstract class Dashboard
public function Render($oPage, $bEditMode = false, $aExtraParams = array())
{
$oPage->add('<h1>'.Dict::S($this->sTitle).'</h1>');
$oPage->add('<h1>'.htmlentities(Dict::S($this->sTitle), ENT_QUOTES, 'UTF-8', false).'</h1>');
$oLayout = new $this->sLayoutClass;
$oLayout->Render($oPage, $this->aCells, $bEditMode, $aExtraParams);
if (!$bEditMode)
@@ -357,16 +357,17 @@ abstract class Dashboard
$oField = new DesignerBooleanField('auto_reload', Dict::S('UI:DashboardEdit:AutoReload'), $this->bAutoReload);
$oForm->AddField($oField);
$oField = new DesignerTextField('auto_reload_sec', Dict::S('UI:DashboardEdit:AutoReloadSec'), $this->iAutoReloadSec);
$oField->SetValidationPattern('^$|^0*([5-9]|[1-9][0-9]+)$'); // Can be empty, or a number > 4
$oField = new DesignerIntegerField('auto_reload_sec', Dict::S('UI:DashboardEdit:AutoReloadSec'), $this->iAutoReloadSec);
$oField->SetBoundaries(MetaModel::GetConfig()->Get('min_reload_interval'), null); // no upper limit
$oForm->AddField($oField);
$this->SetFormParams($oForm);
$oForm->RenderAsPropertySheet($oPage, false, '.itop-dashboard');
$oPage->add('</div>');
$sRateTitle = addslashes(Dict::S('UI:DashboardEdit:AutoReloadSec+'));
$sRateTitle = addslashes(Dict::Format('UI:DashboardEdit:AutoReloadSec+', MetaModel::GetConfig()->Get('min_reload_interval')));
$oPage->add_ready_script(
<<<EOF
// Note: the title gets deleted by the validation mechanism

View File

@@ -506,11 +506,19 @@ abstract class DashletGroupBy extends Dashlet
$sGroupBy = $this->aProperties['group_by'];
$sStyle = $this->aProperties['style'];
// First perform the query - if the OQL is not ok, it will generate an exception : no need to go further
$oQuery = $this->oModelReflection->GetQuery($sQuery);
$sClass = $oQuery->GetClass();
$sClassAlias = $oQuery->GetClassAlias();
// First perform the query - if the OQL is not ok, it will generate an exception : no need to go further
try
{
$oQuery = $this->oModelReflection->GetQuery($sQuery);
$sClass = $oQuery->GetClass();
$sClassAlias = $oQuery->GetClassAlias();
}
catch(Exception $e)
{
// Invalid query, let the user edit the dashlet/dashboard anyhow
$sClass = '';
$sClassAlias = '';
}
// Check groupby... it can be wrong at this stage
if (preg_match('/^(.*):(.*)$/', $sGroupBy, $aMatches))
{
@@ -725,7 +733,8 @@ abstract class DashletGroupBy extends Dashlet
if (is_subclass_of($sAttType, 'AttributeFriendlyName')) continue;
if ($sAttType == 'AttributeExternalField') continue;
if (is_subclass_of($sAttType, 'AttributeExternalField')) continue;
if ($sAttType == 'AttributeOneWayPassword') continue;
$sLabel = $this->oModelReflection->GetLabel($sClass, $sAttCode);
$aGroupBy[$sAttCode] = $sLabel;

View File

@@ -551,6 +551,7 @@ EOF;
$oPage->add_ready_script(
<<<EOF
var oTable = $('#{$this->iListId} table.listResults');
oTable.tableHover();
oTable.tablesorter( { $sHeaders widgets: ['myZebra', 'truncatedList']} ).tablesorterPager({container: $('#pager{$this->iListId}'), totalRows:$iCount, size: $iPageSize, filter: '$sOQL', extra_params: '$sExtraParams', select_mode: '$sSelectModeJS', displayKey: $sDisplayKey, columns: $sJSColumns, class_aliases: $sJSClassAliases $sCssCount});
EOF
);

View File

@@ -226,7 +226,7 @@ class DisplayBlock
if (is_numeric($aExtraParams['auto_reload']) && ($aExtraParams['auto_reload'] > 0))
{
$bAutoReload = true;
$iReloadInterval = max(5, $aExtraParams['auto_reload'])*1000;
$iReloadInterval = max(MetaModel::GetConfig()->Get('min_reload_interval'), $aExtraParams['auto_reload'])*1000;
}
else
{
@@ -393,7 +393,7 @@ class DisplayBlock
{
if (isset($aExtraParams['group_by_label']))
{
$oGroupByExp = Expression::FromOQL($aExtraParams['group_by']);
$oGroupByExp = Expression::FromOQL($aExtraParams['group_by']);
$sGroupByLabel = $aExtraParams['group_by_label'];
}
else
@@ -404,6 +404,21 @@ class DisplayBlock
$sGroupByLabel = MetaModel::GetLabel($this->m_oFilter->GetClass(), $aExtraParams['group_by']);
}
// Security filtering
$aFields = $oGroupByExp->ListRequiredFields();
foreach($aFields as $sFieldAlias)
{
if (preg_match('/^([^.]+)\\.([^.]+)$/', $sFieldAlias, $aMatches))
{
$sFieldClass = $this->m_oFilter->GetClassName($aMatches[1]);
$oAttDef = MetaModel::GetAttributeDef($sFieldClass, $aMatches[2]);
if ($oAttDef instanceof AttributeOneWayPassword)
{
throw new Exception('Grouping on password fields is not supported.');
}
}
}
$aGroupBy = array();
$aGroupBy['grouped_by_1'] = $oGroupByExp;
$sSql = MetaModel::MakeGroupByQuery($this->m_oFilter, $aQueryParams, $aGroupBy, true);
@@ -765,6 +780,7 @@ class DisplayBlock
$sHyperlink = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search&'.$oAppContext->GetForLink().'&filter='.urlencode($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>';
$sHtml .= '<div style="clear:both;"></div>';
break;
case 'csv':
@@ -783,7 +799,8 @@ class DisplayBlock
$sLinkToToggle = $sLinkToToggle.'&advanced=1';
$sChecked = '';
}
$sAjaxLink = $sDownloadLink.'&charset=UTF-8'; // Includes &fields_advanced=1 if in advanced mode
/*
$sCSVData = cmdbAbstractObject::GetSetAsCSV($this->m_oSet, array('fields_advanced' => $bAdvancedMode));
$sCharset = MetaModel::GetConfig()->Get('csv_file_default_charset');
@@ -833,7 +850,7 @@ class DisplayBlock
$sHtml .= "<div id=\"csv_content_loading\"><div style=\"width: 250px; height: 20px; background: url(../setup/orange-progress.gif); border: 1px #999 solid; margin-left:auto; margin-right: auto; text-align: center;\">".Dict::S('UI:Loading')."</div></div><textarea id=\"csv_content\" style=\"display:none;\">\n";
//$sHtml .= htmlentities($sCSVData, ENT_QUOTES, 'UTF-8');
$sHtml .= "</textarea>\n";
$oPage->add_ready_script("$.post('$sDownloadLink', {}, function(data) { $('#csv_content').html(data); $('#csv_content_loading').hide(); $('#csv_content').show();} );");
$oPage->add_ready_script("$.post('$sAjaxLink', {}, function(data) { $('#csv_content').html(data); $('#csv_content_loading').hide(); $('#csv_content').show();} );");
break;
case 'modify':

View File

@@ -0,0 +1,536 @@
<?php
require_once('xlsxwriter.class.php');
class ExcelExporter
{
protected $sToken;
protected $aStatistics;
protected $sState;
protected $fStartTime;
protected $oSearch;
protected $aObjectsIDs;
protected $aTableHeaders;
protected $aAuthorizedClasses;
protected $iChunkSize = 1000;
protected $iPosition;
protected $sOutputFilePath;
protected $bAdvancedMode;
public function __construct($sToken = null)
{
$this->aStatistics = array(
'objects_count' => 0,
'total_duration' => 0,
'data_retrieval_duration' => 0,
'excel_build_duration' => 0,
'excel_write_duration' => 0,
'peak_memory_usage' => 0,
);
$this->fStartTime = microtime(true);
$this->oSearch = null;
$this->sState = 'new';
$this->aObjectsIDs = array();
$this->iPosition = 0;
$this->aAuthorizedClasses = null;
$this->aTableHeaders = null;
$this->sOutputFilePath = null;
$this->bAdvancedMode = false;
$this->CheckDataDir();
if ($sToken == null)
{
$this->sToken = $this->GetNewToken();
}
else
{
$this->sToken = $sToken;
$this->ReloadState();
}
}
public function __destruct()
{
if (($this->sState != 'done') && ($this->sState != 'error') && ($this->sToken != null))
{
// Operation in progress, save the state
$this->SaveState();
}
else
{
// Operation completed, cleanup the temp files
@unlink($this->GetStateFile());
@unlink($this->GetDataFile());
}
self::CleanupOldFiles();
}
public function SetChunkSize($iChunkSize)
{
$this->iChunkSize = $iChunkSize;
}
public function SetOutputFilePath($sDestFilePath)
{
$this->sOutputFilePath = $sDestFilePath;
}
public function SetAdvancedMode($bAdvanced)
{
$this->bAdvancedMode = $bAdvanced;
}
public function SaveState()
{
$aState = array(
'state' => $this->sState,
'statistics' => $this->aStatistics,
'filter' => $this->oSearch->serialize(),
'position' => $this->iPosition,
'chunk_size' => $this->iChunkSize,
'object_ids' => $this->aObjectsIDs,
'output_file_path' => $this->sOutputFilePath,
'advanced_mode' => $this->bAdvancedMode,
);
file_put_contents($this->GetStateFile(), json_encode($aState));
return $this->sToken;
}
public function ReloadState()
{
if ($this->sToken == null)
{
throw new Exception('ExcelExporter not initialized with a token, cannot reload state');
}
if (!file_exists($this->GetStateFile()))
{
throw new Exception("ExcelExporter: missing status file '".$this->GetStateFile()."', cannot reload state.");
}
$sJson = file_get_contents($this->GetStateFile());
$aState = json_decode($sJson, true);
if ($aState === null)
{
throw new Exception("ExcelExporter:corrupted status file '".$this->GetStateFile()."', not a JSON, cannot reload state.");
}
$this->sState = $aState['state'];
$this->aStatistics = $aState['statistics'];
$this->oSearch = DBObjectSearch::unserialize($aState['filter']);
$this->iPosition = $aState['position'];
$this->iChunkSize = $aState['chunk_size'];
$this->aObjectsIDs = $aState['object_ids'];
$this->sOutputFilePath = $aState['output_file_path'];
$this->bAdvancedMode = $aState['advanced_mode'];
}
public function SetObjectList($oSearch)
{
$this->oSearch = $oSearch;
}
public function Run()
{
$sCode = 'error';
$iPercentage = 100;
$sMessage = Dict::Format('ExcelExporter:ErrorUnexpected_State', $this->sState);
$fTime = microtime(true);
try
{
switch($this->sState)
{
case 'new':
$oIDSet = new DBObjectSet($this->oSearch);
$oIDSet->OptimizeColumnLoad(array('id'));
$this->aObjectsIDs = array();
while($oObj = $oIDSet->Fetch())
{
$this->aObjectsIDs[] = $oObj->GetKey();
}
$sCode = 'retrieving-data';
$iPercentage = 5;
$sMessage = Dict::S('ExcelExporter:RetrievingData');
$this->iPosition = 0;
$this->aStatistics['objects_count'] = count($this->aObjectsIDs);
$this->aStatistics['data_retrieval_duration'] += microtime(true) - $fTime;
// The first line of the file is the "headers" specifying the label and the type of each column
$this->GetFieldsList($oIDSet, $this->bAdvancedMode);
$sRow = json_encode($this->aTableHeaders);
$hFile = @fopen($this->GetDataFile(), 'ab');
if ($hFile === false)
{
throw new Exception('ExcelExporter: Failed to open temporary data file: "'.$this->GetDataFile().'" for writing.');
}
fwrite($hFile, $sRow."\n");
fclose($hFile);
// Next state
$this->sState = 'retrieving-data';
break;
case 'retrieving-data':
$oCurrentSearch = clone $this->oSearch;
$aIDs = array_slice($this->aObjectsIDs, $this->iPosition, $this->iChunkSize);
$oCurrentSearch->AddCondition('id', $aIDs, 'IN');
$hFile = @fopen($this->GetDataFile(), 'ab');
if ($hFile === false)
{
throw new Exception('ExcelExporter: Failed to open temporary data file: "'.$this->GetDataFile().'" for writing.');
}
$oSet = new DBObjectSet($oCurrentSearch);
$this->GetFieldsList($oSet, $this->bAdvancedMode);
while($aObjects = $oSet->FetchAssoc())
{
$aRow = array();
foreach($this->aAuthorizedClasses as $sAlias => $sClassName)
{
$oObj = $aObjects[$sAlias];
if ($this->bAdvancedMode)
{
$aRow[] = $oObj->GetKey();
}
foreach($this->aFieldsList[$sAlias] as $sAttCodeEx => $oAttDef)
{
$value = $oObj->Get($sAttCodeEx);
if ($value instanceOf ormCaseLog)
{
// Extract the case log as text and remove the "===" which make Excel think that the cell contains a formula the next time you edit it!
$sExcelVal = trim(preg_replace('/========== ([^=]+) ============/', '********** $1 ************', $value->GetText()));
}
else
{
$sExcelVal = $oAttDef->GetEditValue($value, $oObj);
}
$aRow[] = $sExcelVal;
}
}
$sRow = json_encode($aRow);
fwrite($hFile, $sRow."\n");
}
fclose($hFile);
if (($this->iPosition + $this->iChunkSize) > count($this->aObjectsIDs))
{
// Next state
$this->sState = 'building-excel';
$sCode = 'building-excel';
$iPercentage = 80;
$sMessage = Dict::S('ExcelExporter:BuildingExcelFile');
}
else
{
$sCode = 'retrieving-data';
$this->iPosition += $this->iChunkSize;
$iPercentage = 5 + round(75 * ($this->iPosition / count($this->aObjectsIDs)));
$sMessage = Dict::S('ExcelExporter:RetrievingData');
}
break;
case 'building-excel':
$hFile = @fopen($this->GetDataFile(), 'rb');
if ($hFile === false)
{
throw new Exception('ExcelExporter: Failed to open temporary data file: "'.$this->GetDataFile().'" for reading.');
}
$sHeaders = fgets($hFile);
$aHeaders = json_decode($sHeaders, true);
$aData = array();
while($sLine = fgets($hFile))
{
$aRow = json_decode($sLine);
$aData[] = $aRow;
}
fclose($hFile);
@unlink($this->GetDataFile());
$fStartExcel = microtime(true);
$writer = new XLSXWriter();
$writer->setAuthor(UserRights::GetUserFriendlyName());
$writer->writeSheet($aData,'Sheet1', $aHeaders);
$fExcelTime = microtime(true) - $fStartExcel;
$this->aStatistics['excel_build_duration'] = $fExcelTime;
$fTime = microtime(true);
$writer->writeToFile($this->GetExcelFilePath());
$fExcelSaveTime = microtime(true) - $fTime;
$this->aStatistics['excel_write_duration'] = $fExcelSaveTime;
// Next state
$this->sState = 'done';
$sCode = 'done';
$iPercentage = 100;
$sMessage = Dict::S('ExcelExporter:Done');
break;
case 'done':
$this->sState = 'done';
$sCode = 'done';
$iPercentage = 100;
$sMessage = Dict::S('ExcelExporter:Done');
break;
}
}
catch(Exception $e)
{
$sCode = 'error';
$sMessage = $e->getMessage();
}
$this->aStatistics['total_duration'] += microtime(true) - $fTime;
$peak_memory = memory_get_peak_usage(true);
if ($peak_memory > $this->aStatistics['peak_memory_usage'])
{
$this->aStatistics['peak_memory_usage'] = $peak_memory;
}
return array(
'code' => $sCode,
'message' => $sMessage,
'percentage' => $iPercentage,
);
}
public function GetExcelFilePath()
{
if ($this->sOutputFilePath == null)
{
return APPROOT.'data/bulk_export/'.$this->sToken.'.xlsx';
}
else
{
return $this->sOutputFilePath;
}
}
public static function GetExcelFileFromToken($sToken)
{
return @file_get_contents(APPROOT.'data/bulk_export/'.$sToken.'.xlsx');
}
public static function CleanupFromToken($sToken)
{
@unlink(APPROOT.'data/bulk_export/'.$sToken.'.status');
@unlink(APPROOT.'data/bulk_export/'.$sToken.'.data');
@unlink(APPROOT.'data/bulk_export/'.$sToken.'.xlsx');
}
public function Cleanup()
{
self::CleanupFromToken($this->sToken);
}
/**
* Delete all files in the data/bulk_export directory which are older than 1 day
* unless a different delay is configured.
*/
public static function CleanupOldFiles()
{
$aFiles = glob(APPROOT.'data/bulk_export/*.*');
$iDelay = MetaModel::GetConfig()->Get('xlsx_exporter_cleanup_old_files_delay');
if($iDelay > 0)
{
foreach($aFiles as $sFile)
{
$iModificationTime = filemtime($sFile);
if($iModificationTime < (time() - $iDelay))
{
// Temporary files older than one day are deleted
//echo "Supposed to delete: '".$sFile." (Unix Modification Time: $iModificationTime)'\n";
@unlink($sFile);
}
}
}
}
public function DisplayStatistics(Page $oPage)
{
$aStats = array(
'Number of objects exported' => $this->aStatistics['objects_count'],
'Total export duration' => sprintf('%.3f s', $this->aStatistics['total_duration']),
'Data retrieval duration' => sprintf('%.3f s', $this->aStatistics['data_retrieval_duration']),
'Excel build duration' => sprintf('%.3f s', $this->aStatistics['excel_build_duration']),
'Excel write duration' => sprintf('%.3f s', $this->aStatistics['excel_write_duration']),
'Peak memory usage' => self::HumanDisplay($this->aStatistics['peak_memory_usage']),
);
if ($oPage instanceof CLIPage)
{
$oPage->add($this->GetStatistics('text'));
}
else
{
$oPage->add($this->GetStatistics('html'));
}
}
public function GetStatistics($sFormat = 'html')
{
$sStats = '';
$aStats = array(
'Number of objects exported' => $this->aStatistics['objects_count'],
'Total export duration' => sprintf('%.3f s', $this->aStatistics['total_duration']),
'Data retrieval duration' => sprintf('%.3f s', $this->aStatistics['data_retrieval_duration']),
'Excel build duration' => sprintf('%.3f s', $this->aStatistics['excel_build_duration']),
'Excel write duration' => sprintf('%.3f s', $this->aStatistics['excel_write_duration']),
'Peak memory usage' => self::HumanDisplay($this->aStatistics['peak_memory_usage']),
);
if ($sFormat == 'text')
{
foreach($aStats as $sLabel => $sValue)
{
$sStats .= "+------------------------------+----------+\n";
$sStats .= sprintf("|%-30s|%10s|\n", $sLabel, $sValue);
}
$sStats .= "+------------------------------+----------+";
}
else
{
$sStats .= '<table><tbody>';
foreach($aStats as $sLabel => $sValue)
{
$sStats .= "<tr><td>$sLabel</td><td>$sValue</td></tr>";
}
$sStats .= '</tbody></table>';
}
return $sStats;
}
public static function HumanDisplay($iSize)
{
$aUnits = array('B','KB','MB','GB','TB','PB');
return @round($iSize/pow(1024,($i=floor(log($iSize,1024)))),2).' '.$aUnits[$i];
}
protected function CheckDataDir()
{
if(!is_dir(APPROOT."data/bulk_export"))
{
@mkdir(APPROOT."data/bulk_export", 0777, true /* recursive */);
clearstatcache();
}
if (!is_writable(APPROOT."data/bulk_export"))
{
throw new Exception('Data directory "'.APPROOT.'data/bulk_export" could not be written.');
}
}
protected function GetStateFile($sToken = null)
{
if ($sToken == null)
{
$sToken = $this->sToken;
}
return APPROOT."data/bulk_export/$sToken.status";
}
protected function GetDataFile()
{
return APPROOT.'data/bulk_export/'.$this->sToken.'.data';
}
protected function GetNewToken()
{
$iNum = rand();
do
{
$iNum++;
$sToken = sprintf("%08x", $iNum);
$sFileName = $this->GetStateFile($sToken);
$hFile = @fopen($sFileName, 'x');
}
while($hFile === false);
fclose($hFile);
return $sToken;
}
protected function GetFieldsList($oSet, $bFieldsAdvanced = false, $bLocalize = true, $aFields = null)
{
$this->aFieldsList = array();
$oAppContext = new ApplicationContext();
$aClasses = $oSet->GetFilter()->GetSelectedClasses();
$this->aAuthorizedClasses = array();
foreach($aClasses as $sAlias => $sClassName)
{
if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) && (UR_ALLOWED_YES || UR_ALLOWED_DEPENDS))
{
$this->aAuthorizedClasses[$sAlias] = $sClassName;
}
}
$aAttribs = array();
$this->aTableHeaders = array();
foreach($this->aAuthorizedClasses as $sAlias => $sClassName)
{
$aList[$sAlias] = array();
foreach(MetaModel::ListAttributeDefs($sClassName) as $sAttCode => $oAttDef)
{
if (is_null($aFields) || (count($aFields) == 0))
{
// Standard list of attributes (no link sets)
if ($oAttDef->IsScalar() && ($oAttDef->IsWritable() || $oAttDef->IsExternalField()))
{
$sAttCodeEx = $oAttDef->IsExternalField() ? $oAttDef->GetKeyAttCode().'->'.$oAttDef->GetExtAttCode() : $sAttCode;
if ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE))
{
if ($bFieldsAdvanced)
{
$aList[$sAlias][$sAttCodeEx] = $oAttDef;
if ($oAttDef->IsExternalKey(EXTKEY_RELATIVE))
{
$sRemoteClass = $oAttDef->GetTargetClass();
foreach(MetaModel::GetReconcKeys($sRemoteClass) as $sRemoteAttCode)
{
$this->aFieldsList[$sAlias][$sAttCode.'->'.$sRemoteAttCode] = MetaModel::GetAttributeDef($sRemoteClass, $sRemoteAttCode);
}
}
}
}
else
{
// Any other attribute
$this->aFieldsList[$sAlias][$sAttCodeEx] = $oAttDef;
}
}
}
else
{
// User defined list of attributes
if (in_array($sAttCode, $aFields) || in_array($sAlias.'.'.$sAttCode, $aFields))
{
$this->aFieldsList[$sAlias][$sAttCode] = $oAttDef;
}
}
}
if ($bFieldsAdvanced)
{
$this->aTableHeaders['id'] = '0';
}
foreach($this->aFieldsList[$sAlias] as $sAttCodeEx => $oAttDef)
{
$sLabel = $bLocalize ? MetaModel::GetLabel($sClassName, $sAttCodeEx, isset($aParams['showMandatoryFields'])) : $sAttCodeEx;
if($oAttDef instanceof AttributeDateTime)
{
$this->aTableHeaders[$sLabel] = 'datetime';
}
else
{
$this->aTableHeaders[$sLabel] = 'string';
}
}
}
}
}

View File

@@ -38,7 +38,10 @@ class DesignerForm
protected $bReadOnly;
protected $sHierarchyPath; // Needed to manage the visibility of nested subform
protected $sHierarchyParent; // Needed to manage the visibility of nested subform
protected $sHierarchySelector; // Needed to manage the visibility of nested subform
protected $bDisplayed;
protected $aDefaultValues;
protected $sFieldsSuffix;
public function __construct()
{
@@ -47,14 +50,17 @@ class DesignerForm
$this->sScript = '';
$this->sReadyScript = '';
$this->sFormPrefix = '';
$this->sFieldsSuffix = '';
$this->sParamsContainer = '';
$this->sFormId = 'form_'.rand();
$this->oParentForm = null;
$this->bReadOnly = false;
$this->sHierarchyPath = '';
$this->sHierarchyParent = '';
$this->sHierarchySelector = '';
$this->StartFieldSet($this->sCurrentFieldSet);
$this->bDisplayed = true;
$this->aDefaultvalues = array();
}
public function AddField(DesignerFormField $oField)
@@ -78,15 +84,14 @@ class DesignerForm
public function Render($oP, $bReturnHTML = false)
{
$sFormId = $this->GetFormId();
if ($this->oParentForm == null)
{
$sFormId = $this->sFormId;
$sReturn = '<form id="'.$sFormId.'">';
}
else
{
$sReturn = '';
$sFormId = $this->oParentForm->sFormId;
}
$sHiddenFields = '';
foreach($this->aFieldSets as $sLabel => $aFields)
@@ -102,7 +107,7 @@ class DesignerForm
$aRow = $oField->Render($oP, $sFormId);
if ($oField->IsVisible())
{
$sValidation = '&nbsp;<span class="prop_apply">'.$this->GetValidationArea($oField->GetCode()).'</span>';
$sValidation = '&nbsp;<span class="prop_apply">'.$this->GetValidationArea($oField->GetFieldId()).'</span>';
$sField = $aRow['value'].$sValidation;
$aDetails[] = array('label' => $aRow['label'], 'value' => $sField);
}
@@ -140,6 +145,11 @@ class DesignerForm
$oP->add($sReturn);
}
}
public function GetFieldSets()
{
return $this->aFieldSets;
}
public function SetSubmitParams($sSubmitToUrl, $aSubmitParams)
{
@@ -153,6 +163,11 @@ class DesignerForm
$this->aSubmitParams = $oParentForm->aSubmitParams;
}
public function GetSubmitParams()
{
return array( 'url' => $this->sSubmitTo, 'params' => $this->aSubmitParams);
}
/**
* Helper to handle subforms hide/show
*/
@@ -168,7 +183,7 @@ class DesignerForm
{
return $this->sHierarchyPath;
}
/**
* Helper to handle subforms hide/show
*/
@@ -191,24 +206,21 @@ class DesignerForm
$sReturn = '';
$sActionUrl = addslashes($this->sSubmitTo);
$sJSSubmitParams = json_encode($this->aSubmitParams);
$sFormId = $this->GetFormId();
if ($this->oParentForm == null)
{
$sFormId = $this->sFormId;
$sReturn = '<form id="'.$sFormId.'" onsubmit="return false;">';
$sReturn .= '<table class="prop_table">';
$sReturn .= '<thead><tr><th class="prop_header">'.Dict::S('UI:Form:Property').'</th><th class="prop_header">'.Dict::S('UI:Form:Value').'</th><th colspan="2" class="prop_header">&nbsp;</th></tr></thead><tbody>';
}
else
{
$sFormId = $this->oParentForm->sFormId;
}
$sHiddenFields = '';
foreach($this->aFieldSets as $sLabel => $aFields)
{
$aDetails = array();
if ($sLabel != '')
{
$sReturn .= '<tr><th colspan="4">'.$sLabel.'</th></tr>';
$sReturn .= $this->StartRow().'<th colspan="4">'.$sLabel.'</th>'.$this->EndRow();
}
@@ -218,18 +230,42 @@ class DesignerForm
if ($oField->IsVisible())
{
$sFieldId = $this->GetFieldId($oField->GetCode());
$sValidation = $this->GetValidationArea($oField->GetCode(), '<span title="Apply" class="ui-icon ui-icon-circle-check"/>');
$sValidationFields = '</td><td class="prop_icon prop_apply">'.$sValidation.'</td><td class="prop_icon prop_cancel"><span title="Revert" class="ui-icon ui-icon-circle-close"/></td></tr>';
$sReturn .= '<tr id="row_'.$sFieldId.'"><td class="prop_label">'.$aRow['label'].'</td><td class="prop_value">'.$aRow['value'];
if (!($oField instanceof DesignerFormSelectorField))
$sValidation = $this->GetValidationArea($sFieldId, '<span title="Apply" class="ui-icon ui-icon-circle-check"/>');
$sValidationFields = '</td><td class="prop_icon prop_apply">'.$sValidation.'</td><td class="prop_icon prop_cancel"><span title="Revert" class="ui-icon ui-icon-circle-close"/></td>'.$this->EndRow();
$sPath = $this->GetHierarchyPath().'/'.$oField->GetCode();
if (is_null($aRow['label']))
{
$sReturn .= $this->StartRow($sFieldId).'<td class="prop_value" colspan="2">'.$aRow['value'];
}
else
{
$sReturn .= $this->StartRow($sFieldId).'<td class="prop_label">'.$aRow['label'].'</td><td class="prop_value">'.$aRow['value'];
}
if (!($oField instanceof DesignerFormSelectorField) && !($oField instanceof DesignerMultipleSubFormField))
{
$sReturn .= $sValidationFields;
}
$sNotifyParentSelectorJS = is_null($sNotifyParentSelector) ? 'null' : "'".addslashes($sNotifyParentSelector)."'";
$sAutoApply = $oField->IsAutoApply() ? 'true' : 'false';
$sHandlerEquals = $oField->GetHandlerEquals();
$sHandlerGetValue = $oField->GetHandlerGetValue();
$sWidgetClass = $oField->GetWidgetClass();
$sJSExtraParams = '';
if (count($oField->GetWidgetExtraParams()) > 0)
{
$aExtraParams = array();
foreach($oField->GetWidgetExtraParams() as $key=> $value)
{
$aExtraParams[] = "'$key': ".json_encode($value);
}
$sJSExtraParams = ', '.implode(', ', $aExtraParams);
}
$this->AddReadyScript(
<<<EOF
$('#row_$sFieldId').property_field({parent_selector: $sNotifyParentSelectorJS, field_id: '$sFieldId', auto_apply: $sAutoApply, value: '', submit_to: '$sActionUrl', submit_parameters: $sJSSubmitParams });
$('#row_$sFieldId').$sWidgetClass({parent_selector: $sNotifyParentSelectorJS, field_id: '$sFieldId', equals: $sHandlerEquals, get_field_value: $sHandlerGetValue, auto_apply: $sAutoApply, value: '', submit_to: '$sActionUrl', submit_parameters: $sJSSubmitParams $sJSExtraParams });
EOF
);
}
@@ -247,7 +283,7 @@ EOF
$sReturn .= '</table>';
$sReturn .= $sHiddenFields;
$sReturn .= '</form>';
$sReturn .= '<div id="prop_submit_result"/>'; // for the return of the submit operation
$sReturn .= '<div id="prop_submit_result"></div>'; // for the return of the submit operation
}
else
{
@@ -287,9 +323,26 @@ EOF
{
$oP->add($sReturn);
}
}
}
public function StartRow($sFieldId = null)
{
if ($sFieldId != null)
{
return '<tr id="row_'.$sFieldId.'" data-path="'.$this->GetHierarchyPath().'" data-selector="'.$this->GetHierarchyParent().'">';
}
return '<tr data-path="'.$this->GetHierarchyPath().'" data-selector="'.$this->GetHierarchyParent().'">';
}
public function EndRow()
{
return '</tr>';
}
public function RenderAsDialog($oPage, $sDialogId, $sDialogTitle, $iDialogWidth, $sOkButtonLabel, $sIntroduction = null)
{
$this->SetPrefix('dlg_'); // To make sure that the controls have different IDs that the property sheet which may be displayed at the same time
$sDialogTitle = addslashes($sDialogTitle);
$sOkButtonLabel = addslashes($sOkButtonLabel);
$sCancelButtonLabel = Dict::S('UI:Button:Cancel');
@@ -306,13 +359,21 @@ EOF
<<<EOF
$('#$sDialogId').dialog({
height: 'auto',
width: 500,
width: $iDialogWidth,
modal: true,
title: '$sDialogTitle',
buttons: [
{ text: "$sOkButtonLabel", click: function() {
var oForm = $(this).closest('.ui-dialog').find('form');
oForm.submit();
if (AnimateDlgButtons)
{
sFormId = oForm.attr('id');
if (oFormValidation[sFormId].length == 0)
{
AnimateDlgButtons(this);
}
}
} },
{ text: "$sCancelButtonLabel", click: function() { KillAllMenus(); $(this).dialog( "close" ); $(this).remove(); } },
],
@@ -344,9 +405,29 @@ EOF
public function GetPrefix()
{
return $this->sFormPrefix;
$sPrefix = '';
if ($this->oParentForm != null)
{
$sPrefix = $this->oParentForm->GetPrefix();
}
return $sPrefix.$this->sFormPrefix;
}
public function SetSuffix($sSuffix)
{
$this->sFieldsSuffix = $sSuffix;
}
public function GetSuffix()
{
$sSuffix = '';
if ($this->oParentForm != null)
{
$sSuffix = $this->oParentForm->GetSuffix();
}
return $sSuffix.$this->sFieldsSuffix;
}
public function SetReadOnly($bReadOnly = true)
{
$this->bReadOnly = $bReadOnly;
@@ -386,11 +467,39 @@ EOF
$this->oParentForm = $oParentForm;
}
public function SetDefaultValues($aDefaultValues)
{
if (!is_array($aDefaultValues)) return;
foreach($this->aFieldSets as $sLabel => $aFields)
{
foreach($aFields as $oField)
{
$oField->SetDefaultValueFrom($aDefaultValues);
}
}
}
public function GetDefaultValues()
{
return $this->aDefaultValues;
}
public function GetParentForm()
{
return $this->oParentForm;
}
public function GetFormId()
{
if ($this->oParentForm)
{
$this->oParentForm->GetFormId();
}
return $this->sFormId;
}
public function SetDisplayed($bDisplayed)
{
$this->bDisplayed = $bDisplayed;
@@ -420,27 +529,42 @@ EOF
public function GetFieldId($sCode)
{
return $this->sFormPrefix.'attr_'.$sCode;
return $this->GetPrefix().'attr_'.$sCode;
}
public function GetFieldName($sCode)
{
return 'attr_'.$sCode;
return 'attr_'.$sCode.$this->GetSuffix();
}
public function GetParamName($sCode)
{
return 'attr_'.$sCode;
return 'attr_'.$sCode.$this->GetSuffix();
}
public function GetValidationArea($sCode, $sContent = '')
public function GetValidationArea($sId, $sContent = '')
{
return "<span style=\"display:inline-block;width:20px;\" id=\"v_{$this->sFormPrefix}attr_$sCode\"><span class=\"ui-icon ui-icon-alert\"></span>$sContent</span>";
return "<span style=\"display:inline-block;width:20px;\" id=\"v_{$sId}\"><span class=\"ui-icon ui-icon-alert\"></span>$sContent</span>";
}
public function GetAsyncActionClass()
{
return $this->sAsyncActionClass;
}
public function FindField($sFieldCode)
{
$oFoundField = false;
foreach($this->aFieldSets as $sLabel => $aFields)
{
foreach($aFields as $oField)
{
$oFoundField = $oField->FindField($sFieldCode);
if ($oFoundField !== false) break;
}
if ($oFoundField !== false) break;
}
return $oFoundField;
}
}
class DesignerTabularForm extends DesignerForm
@@ -456,6 +580,11 @@ class DesignerTabularForm extends DesignerForm
{
$this->aTable[] = $aRow;
}
public function RenderAsPropertySheet($oP, $bReturnHTML = false, $sNotifyParentSelector = null)
{
return $this->Render($oP, $bReturnHTML);
}
public function Render($oP, $bReturnHTML = false)
{
@@ -556,6 +685,7 @@ class DesignerFormField
protected $bAutoApply;
protected $aCSSClasses;
protected $bDisplayed;
protected $aWidgetExtraParams;
public function __construct($sCode, $sLabel, $defaultValue)
{
@@ -567,6 +697,7 @@ class DesignerFormField
$this->bAutoApply = false;
$this->aCSSClasses = array();
$this->bDisplayed = true;
$this->aWidgetExtraParams = array();
}
public function GetCode()
@@ -615,6 +746,21 @@ class DesignerFormField
return $this->bDisplayed;
}
public function GetFieldId()
{
return $this->oForm->GetFieldId($this->sCode);
}
public function GetWidgetClass()
{
return 'property_field';
}
public function GetWidgetExtraParams()
{
return $this->aWidgetExtraParams;
}
public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
{
$sId = $this->oForm->GetFieldId($this->sCode);
@@ -658,6 +804,36 @@ class DesignerFormField
{
$this->aCSSClasses[] = $sCSSClass;
}
/**
* A way to set/change the default value after constructing the field
*/
public function SetDefaultValueFrom($aAllDefaultValue)
{
if (array_key_exists($this->GetCode(), $aAllDefaultValue))
{
$this->defaultValue = $aAllDefaultValue[$this->GetCode()];
}
}
public function FindField($sFieldCode)
{
if ($this->sCode == $sFieldCode)
{
return $this;
}
return false;
}
public function GetHandlerEquals()
{
return 'null';
}
public function GetHandlerGetValue()
{
return 'null';
}
}
class DesignerLabelField extends DesignerFormField
@@ -674,7 +850,7 @@ class DesignerLabelField extends DesignerFormField
{
$sId = $this->oForm->GetFieldId($this->sCode);
$sName = $this->oForm->GetFieldName($this->sCode);
return array('label' => $this->sLabel, 'value' => $sDescription);
return array('label' => $this->sLabel, 'value' => $this->sDescription);
}
public function ReadParam(&$aValues)
@@ -723,6 +899,7 @@ class DesignerTextField extends DesignerFormField
public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
{
$sId = $this->oForm->GetFieldId($this->sCode);
$sName = $this->oForm->GetFieldName($this->sCode);
if ($this->IsReadOnly())
{
@@ -744,7 +921,7 @@ class DesignerTextField extends DesignerFormField
$sMandatory = $this->bMandatory ? 'true' : 'false';
$oP->add_ready_script(
<<<EOF
$('#$sId').bind('change keyup validate', function() { ValidateWithPattern('$sId', $sMandatory, '$sPattern', '$sFormId', $sForbiddenValues, '$sExplainForbiddenValues'); } );
$('#$sId').bind('change keyup validate', function() { ValidateWithPattern('$sId', $sMandatory, '$sPattern', $(this).closest('form').attr('id'), $sForbiddenValues, '$sExplainForbiddenValues'); } );
{
var myTimer = null;
$('#$sId').bind('keyup', function() { clearTimeout(myTimer); myTimer = setTimeout(function() { $('#$sId').trigger('change', {} ); }, 100); });
@@ -798,7 +975,7 @@ class DesignerLongTextField extends DesignerTextField
$sReadOnly = $this->IsReadOnly() ? 'readonly' : '';
$oP->add_ready_script(
<<<EOF
$('#$sId').bind('change keyup validate', function() { ValidateWithPattern('$sId', $sMandatory, '$sPattern', '$sFormId', $sForbiddenValues, '$sExplainForbiddenValues'); } );
$('#$sId').bind('change keyup validate', function() { ValidateWithPattern('$sId', $sMandatory, '$sPattern', $(this).closest('form').attr('id'), $sForbiddenValues, '$sExplainForbiddenValues'); } );
{
var myTimer = null;
$('#$sId').bind('keyup', function() { clearTimeout(myTimer); myTimer = setTimeout(function() { $('#$sId').trigger('change', {} ); }, 100); });
@@ -814,11 +991,80 @@ EOF
}
}
class DesignerIntegerField extends DesignerFormField
{
protected $iMin; // Lower boundary, inclusive
protected $iMax; // Higher boundary, inclusive
public function __construct($sCode, $sLabel = '', $defaultValue = '')
{
parent::__construct($sCode, $sLabel, $defaultValue);
$this->iMin = 0; // Positive integer is the default
$this->iMax = null;
}
public function SetBoundaries($iMin = null, $iMax = null)
{
$this->iMin = $iMin;
$this->iMax = $iMax;
}
public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
{
$sId = $this->oForm->GetFieldId($this->sCode);
$sName = $this->oForm->GetFieldName($this->sCode);
if ($this->IsReadOnly())
{
$sHtmlValue = "<span>".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."<input type=\"hidden\" id=\"$sId\" name=\"$sName\" value=\"".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."\"/></span>";
}
else
{
$sMin = json_encode($this->iMin);
$sMax = json_encode($this->iMax);
$sMandatory = $this->bMandatory ? 'true' : 'false';
$oP->add_ready_script(
<<<EOF
$('#$sId').bind('change keyup validate', function() { ValidateInteger('$sId', $sMandatory, $(this).closest('form').attr('id'), $sMin, $sMax); } );
{
var myTimer = null;
$('#$sId').bind('keyup', function() { clearTimeout(myTimer); myTimer = setTimeout(function() { $('#$sId').trigger('change', {} ); }, 100); });
}
EOF
);
$sCSSClasses = '';
if (count($this->aCSSClasses) > 0)
{
$sCSSClasses = 'class="'.implode(' ', $this->aCSSClasses).'"';
}
$sHtmlValue = "<input type=\"text\" $sCSSClasses id=\"$sId\" name=\"$sName\" value=\"".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."\">";
}
return array('label' => $this->sLabel, 'value' => $sHtmlValue);
}
public function ReadParam(&$aValues)
{
parent::ReadParam($aValues);
if (!is_null($this->iMin) && ($aValues[$this->sCode] < $this->iMin))
{
// Reject the value...
$aValues[$this->sCode] = $this->defaultValue;
}
if (!is_null($this->iMax) && ($aValues[$this->sCode] > $this->iMax))
{
// Reject the value...
$aValues[$this->sCode] = $this->defaultValue;
}
}
}
class DesignerComboField extends DesignerFormField
{
protected $aAllowedValues;
protected $bMultipleSelection;
protected $bOtherChoices;
protected $sNullLabel;
public function __construct($sCode, $sLabel = '', $defaultValue = '')
{
@@ -826,6 +1072,7 @@ class DesignerComboField extends DesignerFormField
$this->aAllowedValues = array();
$this->bMultipleSelection = false;
$this->bOtherChoices = false;
$this->sNullLabel = Dict::S('UI:SelectOne');
$this->bAutoApply = true;
}
@@ -844,11 +1091,17 @@ class DesignerComboField extends DesignerFormField
{
$this->bOtherChoices = $bOtherChoices;
}
/**
* An empty label will disable the default empty value
*/
public function SetNullLabel($sLabel)
{
$this->sNullLabel = $sLabel;
}
public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
{
$sId = $this->oForm->GetFieldId($this->sCode);
$sName = $this->oForm->GetFieldName($this->sCode);
$sChecked = $this->defaultValue ? 'checked' : '';
@@ -893,7 +1146,10 @@ class DesignerComboField extends DesignerFormField
else
{
$sHtml = "<select $sCSSClasses id=\"$sId\" name=\"$sName\">";
$sHtml .= "<option value=\"\">".Dict::S('UI:SelectOne')."</option>";
if ($this->sNullLabel != '')
{
$sHtml .= "<option value=\"\">".$this->sNullLabel."</option>";
}
}
foreach($this->aAllowedValues as $sKey => $sDisplayValue)
{
@@ -916,7 +1172,7 @@ class DesignerComboField extends DesignerFormField
}
$oP->add_ready_script(
<<<EOF
$('#$sId').bind('change validate', function() { ValidateWithPattern('$sId', $sMandatory, '', '$sFormId', null, null); } );
$('#$sId').bind('change validate', function() { ValidateWithPattern('$sId', $sMandatory, '', $(this).closest('form').attr('id'), null, null); } );
EOF
);
}
@@ -993,7 +1249,6 @@ class DesignerBooleanField extends DesignerFormField
}
}
class DesignerHiddenField extends DesignerFormField
{
public function __construct($sCode, $sLabel = '', $defaultValue = '')
@@ -1055,14 +1310,19 @@ class DesignerIconSelectionField extends DesignerFormField
$sPostUploadTo = ($this->sUploadUrl == null) ? 'null' : "'{$this->sUploadUrl}'";
if (!$this->IsReadOnly())
{
$sValue = "<input type=\"hidden\" id=\"$sId\" name=\"$sName\" value=\"{$this->defaultValue}\"/>";
$oP->add_ready_script(
<<<EOF
$('#$sId').icon_select({current_idx: $idx, items: $sJSItems, post_upload_to: $sPostUploadTo});
EOF
);
}
else
{
$sValue = '<img src="'.$this->MakeFileUrl($this->defaultValue).'" />';
}
$sReadOnly = $this->IsReadOnly() ? 'disabled' : '';
return array('label' =>$this->sLabel, 'value' => "<input type=\"hidden\" id=\"$sId\" name=\"$sName\" value=\"{$this->defaultValue}\"/>");
return array('label' => $this->sLabel, 'value' => $sValue);
}
}
@@ -1175,6 +1435,11 @@ class DesignerFormSelectorField extends DesignerFormField
$this->aSubForms = array();
}
public function GetWidgetClass()
{
return 'selector_property_field';
}
public function AddSubForm($oSubForm, $sLabel, $sValue)
{
$idx = count($this->aSubForms);
@@ -1192,6 +1457,8 @@ class DesignerFormSelectorField extends DesignerFormField
$sName = $this->oForm->GetFieldName($this->sCode);
$sReadOnly = $this->IsReadOnly() ? 'disabled="disabled"' : '';
$this->aCSSClasses[] = 'formSelector';
$sCSSClasses = '';
if (count($this->aCSSClasses) > 0)
{
@@ -1205,12 +1472,12 @@ class DesignerFormSelectorField extends DesignerFormField
$aHiddenValues = array();
$sDisplayValue = '';
$sHiddenValue = '';
foreach($this->aSubForms as $sKey => $aFormData)
foreach($this->aSubForms as $iKey => $aFormData)
{
if ($sKey == $this->defaultValue)
if ($iKey == $this->defaultValue) // Default value is actually the index
{
$sDisplayValue = htmlentities($aFormData['label'], ENT_QUOTES, 'UTF-8');
$sHiddenValue = "<input type=\"hidden\" id=\"$sId\" name=\"$sName\" value=\"".htmlentities($sKey, ENT_QUOTES, 'UTF-8')."\"/>";
$sHiddenValue = "<input type=\"hidden\" id=\"$sId\" name=\"$sName\" value=\"".htmlentities($iKey, ENT_QUOTES, 'UTF-8')."\"/>";
break;
}
}
@@ -1221,11 +1488,11 @@ class DesignerFormSelectorField extends DesignerFormField
$sHtml = "<select $sCSSClasses id=\"$sId\" name=\"$sName\" $sReadOnly>";
foreach($this->aSubForms as $sKey => $aFormData)
foreach($this->aSubForms as $iKey => $aFormData)
{
$sDisplayValue = htmlentities($aFormData['label'], ENT_QUOTES, 'UTF-8');;
$sSelected = ($sKey == $this->defaultValue) ? 'selected' : '';
$sHtml .= "<option value=\"".htmlentities($sKey, ENT_QUOTES, 'UTF-8')."\" $sSelected>".$sDisplayValue."</option>";
$sSelected = ($iKey == $this->defaultValue) ? 'selected' : '';
$sHtml .= "<option value=\"$iKey\" $sSelected>".$sDisplayValue."</option>";
}
$sHtml .= "</select>";
}
@@ -1254,19 +1521,21 @@ class DesignerFormSelectorField extends DesignerFormField
// - data-path : uniquely identifies the combination of users choices that must be made to show the node
// - data-state : records the state, depending on the user choice on the FormSelectorField just above the node, but indepentantly from the visibility in the page (can be visible in the form itself being in a hidden form)
// Then a series of actions are performed to hide and show the relevant nodes, depending on the user choice
$sSelector = $this->oForm->GetHierarchyPath().'/'.$this->sCode;
$sSelector = $this->oForm->GetHierarchyPath().'/'.$this->sCode.$this->oForm->GetSuffix();
$oSubForm->SetHierarchyParent($sSelector);
$sPath = $this->oForm->GetHierarchyPath().'/'.$this->sCode.':'.$sKey;
$sPath = $this->oForm->GetHierarchyPath().'/'.$this->sCode.$this->oForm->GetSuffix().'-'.$sKey;
$oSubForm->SetHierarchyPath($sPath);
$oSubForm->SetDisplayed($sKey == $this->defaultValue);
$sState = ($sKey == $this->defaultValue) ? 'visible' : 'hidden';
$sHtml .= "</tbody><tbody data-selector=\"$sSelector\" data-path=\"$sPath\" data-state=\"$sState\" $sStyle>";
//$sHtml .= "</tbody><tbody data-selector=\"$sSelector\" data-path=\"$sPath\" data-state=\"$sState\" $sStyle>";
$sHtml .= $oSubForm->RenderAsPropertySheet($oP, true);
$sState = $this->oForm->IsDisplayed() ? 'visible' : 'hidden';
$sParentStyle = '';
if ($oParent = $this->oForm->GetParentForm())
{
$sParentStyle = ($oParent->IsDisplayed()) ? '' : 'style="display:none"';
$sParentSelector = $oParent->GetHierarchyParent();
$sParentPath = $oParent->GetHierarchyPath();
}
@@ -1275,7 +1544,8 @@ class DesignerFormSelectorField extends DesignerFormField
$sParentSelector = '';
$sParentPath = '';
}
$sHtml .= "</tbody><tbody data-selector=\"$sParentSelector\" data-path=\"$sParentPath\" data-state=\"$sState\" $sStyle>";
//$sHtml .= "</tbody><tbody data-selector=\"$sParentSelector\" data-path=\"$sParentPath\" data-state=\"$sState\" $sParentStyle>";
}
else
{
@@ -1287,27 +1557,8 @@ class DesignerFormSelectorField extends DesignerFormField
if ($sRenderMode == 'property')
{
$sSelector = $this->oForm->GetHierarchyPath().'/'.$this->sCode;
$oP->add_ready_script(
<<<EOF
$('#$sId').bind('change reverted', function() {
// Mark all the direct children as hidden
$('tbody[data-selector="$sSelector"]').attr('data-state', 'hidden');
// Mark the selected one as visible
var sSelectedHierarchy = '$sSelector:'+this.value;
$('tbody[data-path="'+sSelectedHierarchy+'"]').attr('data-state', 'visible');
// Show all items behind the current one
$('tbody[data-path^="$sSelector"]').show();
// Hide items behind the current one as soon as it is behind a hidden node (or itself is marked as hidden)
$('tbody[data-path^="$sSelector"][data-state="hidden"]').each(function(){
$(this).hide();
var sPath = $(this).attr('data-path');
$('tbody[data-path^="'+sPath+'/"]').hide();
});
});
EOF
);
$sSelector = $this->oForm->GetHierarchyPath().'/'.$this->sCode.$this->oForm->GetSuffix();
$this->aWidgetExtraParams['data_selector'] = $sSelector;
}
else
{
@@ -1330,6 +1581,44 @@ EOF
$this->aSubForms[$sKey]['form']->SetParentForm($this->oForm);
$this->aSubForms[$sKey]['form']->ReadParams($aValues);
}
public function SetDefaultValueFrom($aAllDefaultValues)
{
if (array_key_exists($this->GetCode(), $aAllDefaultValues))
{
$selectedValue = $aAllDefaultValues[$this->GetCode()];
foreach($this->aSubForms as $iKey => $aFormData)
{
$sId = $this->oForm->GetFieldId($this->sCode);
if ($selectedValue == $aFormData['value'])
{
$this->defaultValue =$iKey;
$aDefaultValues = $this->oForm->GetDefaultValues();
$oSubForm = $aFormData['form'];
$oSubForm->SetDefaultValues($aAllDefaultValues);
}
}
}
}
public function FindField($sFieldCode)
{
$oField = parent::FindField($sFieldCode);
if ($oField === false)
{
// Look in the subforms
foreach($this->aSubForms as $sKey => $aFormData)
{
$oSubForm = $aFormData['form'];
$oField = $oSubForm->FindField($sFieldCode);
if ($oField !== false)
{
break;
}
}
}
return $oField;
}
}
class DesignerSubFormField extends DesignerFormField
@@ -1362,6 +1651,31 @@ class DesignerSubFormField extends DesignerFormField
$this->oSubForm->SetParentForm($this->oForm);
$this->oSubForm->ReadParams($aValues);
}
public function FindField($sFieldCode)
{
$oField = parent::FindField($sFieldCode);
if ($oField === false)
{
// Look in the subform
$oField = $this->oSubForm->FindField($sFieldCode);
}
return $oField;
}
}
class DesignerStaticTextField extends DesignerFormField
{
public function __construct($sCode, $sLabel = '', $defaultValue = '')
{
parent::__construct($sCode, $sLabel, $defaultValue);
}
public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
{
return array('label' => $this->sLabel, 'value' => $this->defaultValue);
}
}
?>

View File

@@ -587,8 +587,7 @@ EOF
header($s_header);
}
}
$s_captured_output = ob_get_contents();
ob_end_clean();
$s_captured_output = $this->ob_get_clean_safe();
$sHtml = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n";
$sHtml .= "<html>\n";
$sHtml .= "<head>\n";
@@ -1039,4 +1038,3 @@ EOF
$this->m_sMessage = $sMessage;
}
}
?>

View File

@@ -305,16 +305,20 @@ class LoginWebPage extends NiceWebPage
{
$this->add("<p>".Dict::Format('UI:ResetPwd-Error-WrongLogin', $sAuthUser)."</p>\n");
}
elseif ($oUser->Get('reset_pwd_token') != $sToken)
{
$this->add("<p>".Dict::S('UI:ResetPwd-Error-InvalidToken')."</p>\n");
}
else
{
$this->add("<p>".Dict::Format('UI:ResetPwd-Error-EnterPassword', $oUser->GetFriendlyName())."</p>\n");
$sInconsistenPwdMsg = Dict::S('UI:Login:RetypePwdDoesNotMatch');
$this->add_script(
$oEncryptedToken = $oUser->Get('reset_pwd_token');
if (!$oEncryptedToken->CheckPassword($sToken))
{
$this->add("<p>".Dict::S('UI:ResetPwd-Error-InvalidToken')."</p>\n");
}
else
{
$this->add("<p>".Dict::Format('UI:ResetPwd-Error-EnterPassword', $oUser->GetFriendlyName())."</p>\n");
$sInconsistenPwdMsg = Dict::S('UI:Login:RetypePwdDoesNotMatch');
$this->add_script(
<<<EOF
function DoCheckPwd()
{
@@ -326,18 +330,19 @@ function DoCheckPwd()
return true;
}
EOF
);
$this->add("<form method=\"post\">\n");
$this->add("<table>\n");
$this->add("<tr><td style=\"text-align:right\"><label for=\"new_pwd\">".Dict::S('UI:Login:NewPasswordPrompt').":</label></td><td style=\"text-align:left\"><input type=\"password\" id=\"new_pwd\" name=\"new_pwd\" value=\"\" /></td></tr>\n");
$this->add("<tr><td style=\"text-align:right\"><label for=\"retype_new_pwd\">".Dict::S('UI:Login:RetypeNewPasswordPrompt').":</label></td><td style=\"text-align:left\"><input type=\"password\" id=\"retype_new_pwd\" name=\"retype_new_pwd\" value=\"\" /></td></tr>\n");
$this->add("<tr><td colspan=\"2\" class=\"center v-spacer\"><span class=\"btn_border\"><input type=\"submit\" onClick=\"return DoCheckPwd();\" value=\"".Dict::S('UI:Button:ChangePassword')."\" /></span></td></tr>\n");
$this->add("</table>\n");
$this->add("<input type=\"hidden\" name=\"loginop\" value=\"do_reset_pwd\" />\n");
$this->add("<input type=\"hidden\" name=\"auth_user\" value=\"".htmlentities($sAuthUser, ENT_QUOTES, 'UTF-8')."\" />\n");
$this->add("<input type=\"hidden\" name=\"token\" value=\"".htmlentities($sToken, ENT_QUOTES, 'UTF-8')."\" />\n");
$this->add("</form>\n");
$this->add("</div\n");
);
$this->add("<form method=\"post\">\n");
$this->add("<table>\n");
$this->add("<tr><td style=\"text-align:right\"><label for=\"new_pwd\">".Dict::S('UI:Login:NewPasswordPrompt').":</label></td><td style=\"text-align:left\"><input type=\"password\" id=\"new_pwd\" name=\"new_pwd\" value=\"\" /></td></tr>\n");
$this->add("<tr><td style=\"text-align:right\"><label for=\"retype_new_pwd\">".Dict::S('UI:Login:RetypeNewPasswordPrompt').":</label></td><td style=\"text-align:left\"><input type=\"password\" id=\"retype_new_pwd\" name=\"retype_new_pwd\" value=\"\" /></td></tr>\n");
$this->add("<tr><td colspan=\"2\" class=\"center v-spacer\"><span class=\"btn_border\"><input type=\"submit\" onClick=\"return DoCheckPwd();\" value=\"".Dict::S('UI:Button:ChangePassword')."\" /></span></td></tr>\n");
$this->add("</table>\n");
$this->add("<input type=\"hidden\" name=\"loginop\" value=\"do_reset_pwd\" />\n");
$this->add("<input type=\"hidden\" name=\"auth_user\" value=\"".htmlentities($sAuthUser, ENT_QUOTES, 'UTF-8')."\" />\n");
$this->add("<input type=\"hidden\" name=\"token\" value=\"".htmlentities($sToken, ENT_QUOTES, 'UTF-8')."\" />\n");
$this->add("</form>\n");
$this->add("</div\n");
}
}
}
@@ -357,21 +362,25 @@ EOF
{
$this->add("<p>".Dict::Format('UI:ResetPwd-Error-WrongLogin', $sAuthUser)."</p>\n");
}
elseif ($oUser->Get('reset_pwd_token') != $sToken)
{
$this->add("<p>".Dict::S('UI:ResetPwd-Error-InvalidToken')."</p>\n");
}
else
{
// Trash the token and change the password
$oUser->Set('reset_pwd_token', '');
$oUser->SetPassword($sNewPwd); // Does record the change into the DB
$this->add("<p>".Dict::S('UI:ResetPwd-Ready')."</p>");
$sUrl = utils::GetAbsoluteUrlAppRoot();
$this->add("<p><a href=\"$sUrl\">".Dict::S('UI:ResetPwd-Login')."</a></p>");
$oEncryptedPassword = $oUser->Get('reset_pwd_token');
if (!$oEncryptedPassword->CheckPassword($sToken))
{
$this->add("<p>".Dict::S('UI:ResetPwd-Error-InvalidToken')."</p>\n");
}
else
{
// Trash the token and change the password
$oUser->Set('reset_pwd_token', '');
$oUser->SetPassword($sNewPwd); // Does record the change into the DB
$this->add("<p>".Dict::S('UI:ResetPwd-Ready')."</p>");
$sUrl = utils::GetAbsoluteUrlAppRoot();
$this->add("<p><a href=\"$sUrl\">".Dict::S('UI:ResetPwd-Login')."</a></p>");
}
$this->add("</div\n");
}
$this->add("</div\n");
}
public function DisplayChangePwdForm($bFailedLogin = false)

View File

@@ -90,6 +90,8 @@ class PortalWebPage extends NiceWebPage
$this->add_linked_script("../js/jquery.qtip-1.0.min.js");
$this->add_linked_script('../js/jquery.multiselect.min.js');
$this->add_linked_script("../js/ajaxfileupload.js");
$this->add_linked_script("../js/ckeditor/ckeditor.js");
$this->add_linked_script("../js/ckeditor/adapters/jquery.js");
$this->add_ready_script(
<<<EOF
try
@@ -231,6 +233,20 @@ EOF
var next_step = $('input[id=next_step]');
next_step.val(sStep);
}
// For disabling the CKEditor at init time when the corresponding textarea is disabled !
CKEDITOR.plugins.add( 'disabler',
{
init : function( editor )
{
editor.on( 'instanceReady', function(e)
{
e.removeListener();
$('#'+ editor.name).trigger('update');
});
}
});
EOF
);
@@ -826,7 +842,7 @@ EOF
}
$sStepHistory = implode(',', $aPreviousSteps);
$this->add("<input type=\"hidden\" id=\"step_history\" name=\"step_history\" value=\"$sStepHistory\">");
$this->add("<input type=\"hidden\" id=\"step_history\" name=\"step_history\" value=\"".htmlentities($sStepHistory, ENT_QUOTES, 'UTF-8')."\">");
if (!is_null($sNextStep))
{

View File

@@ -254,8 +254,8 @@ class ShortcutOQL extends Shortcut
$oField = new DesignerBooleanField('auto_reload', Dict::S('Class:ShortcutOQL/Attribute:auto_reload'), false);
$oForm->AddField($oField);
$oField = new DesignerTextField('auto_reload_sec', Dict::S('Class:ShortcutOQL/Attribute:auto_reload_sec'), MetaModel::GetConfig()->GetStandardReloadInterval());
$oField->SetValidationPattern('^$|^0*([5-9]|[1-9][0-9]+)$'); // Can be empty, or a number > 4
$oField = new DesignerIntegerField('auto_reload_sec', Dict::S('Class:ShortcutOQL/Attribute:auto_reload_sec'), MetaModel::GetConfig()->GetStandardReloadInterval());
$oField->SetBoundaries(MetaModel::GetConfig()->Get('min_reload_interval'), null); // no upper limit
$oField->SetMandatory(false);
$oForm->AddField($oField);
@@ -284,7 +284,7 @@ class ShortcutOQL extends Shortcut
$oAppContext = new ApplicationContext();
$sContext = $oAppContext->GetForLink();
$sRateTitle = addslashes(Dict::S('Class:ShortcutOQL/Attribute:auto_reload_sec+'));
$sRateTitle = addslashes(Dict::Format('Class:ShortcutOQL/Attribute:auto_reload_sec/tip', MetaModel::GetConfig()->Get('min_reload_interval')));
$oPage->add_ready_script(
<<<EOF

View File

@@ -28,10 +28,11 @@ require_once(APPROOT.'/core/cmdbobject.class.inc.php');
require_once(APPROOT.'/application/utils.inc.php');
session_name('itop-'.md5(APPROOT));
session_start();
if (isset($_REQUEST['switch_env']))
$sSwitchEnv = utils::ReadParam('switch_env', null);
if (($sSwitchEnv != null) && (file_exists(APPCONF.$sSwitchEnv.'/'.ITOP_CONFIG_FILE)))
{
$sEnv = $_REQUEST['switch_env'];
$_SESSION['itop_env'] = $sEnv;
$_SESSION['itop_env'] = $sSwitchEnv;
$sEnv = $sSwitchEnv;
// TODO: reset the credentials as well ??
}
else if (isset($_SESSION['itop_env']))

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
@@ -19,7 +19,8 @@
/**
* This class records the pending "transactions" corresponding to forms that have not been
* submitted yet, in order to prevent double submissions. When created a transaction remains valid
* until the user's session expires
* until the user's session expires. This class is actually a wrapper to the underlying implementation
* which choice is configured via the parameter 'transaction_storage'
*
* @package iTop
* @copyright Copyright (C) 2010-2012 Combodo SARL
@@ -28,6 +29,81 @@
class privUITransaction
{
/**
* Create a new transaction id, store it in the session and return its id
* @param void
* @return int The identifier of the new transaction
*/
public static function GetNewTransactionId()
{
$bTransactionsEnabled = MetaModel::GetConfig()->Get('transactions_enabled');
if (!$bTransactionsEnabled)
{
return 'notransactions'; // Any value will do
}
$sClass = 'privUITransaction'.MetaModel::GetConfig()->Get('transaction_storage');
if (!class_exists($sClass, false))
{
IssueLog::Error("Incorrect value '".MetaModel::GetConfig()->Get('transaction_storage')."' for 'transaction_storage', the class '$sClass' does not exists. Using privUITransactionSession instead for storing sessions.");
$sClass = 'privUITransactionSession';
}
return (string)$sClass::GetNewTransactionId();
}
/**
* Check whether a transaction is valid or not and (optionally) remove the valid transaction from
* the session so that another call to IsTransactionValid for the same transaction id
* will return false
* @param int $id Identifier of the transaction, as returned by GetNewTransactionId
* @param bool $bRemoveTransaction True if the transaction must be removed
* @return bool True if the transaction is valid, false otherwise
*/
public static function IsTransactionValid($id, $bRemoveTransaction = true)
{
$bTransactionsEnabled = MetaModel::GetConfig()->Get('transactions_enabled');
if (!$bTransactionsEnabled)
{
return true; // All values are valid
}
$sClass = 'privUITransaction'.MetaModel::GetConfig()->Get('transaction_storage');
if (!class_exists($sClass, false))
{
$sClass = 'privUITransactionSession';
}
return $sClass::IsTransactionValid($id, $bRemoveTransaction);
}
/**
* Removes the transaction specified by its id
* @param int $id The Identifier (as returned by GetNewTranscationId) of the transaction to be removed.
* @return void
*/
public static function RemoveTransaction($id)
{
$bTransactionsEnabled = MetaModel::GetConfig()->Get('transactions_enabled');
if (!$bTransactionsEnabled)
{
return; // Nothing to do
}
$sClass = 'privUITransaction'.MetaModel::GetConfig()->Get('transaction_storage');
if (!class_exists($sClass, false))
{
$sClass = 'privUITransactionSession';
}
$sClass::RemoveTransaction($id);
}
}
/**
* The original (and by default) mechanism for storing transaction information
* as an array in the $_SESSION variable
*
*/
class privUITransactionSession
{
/**
* Create a new transaction id, store it in the session and return its id
@@ -99,4 +175,178 @@ class privUITransaction
}
}
}
?>
/**
* An alternate implementation for storing the transactions as temporary files
* Useful when using an in-memory storage for the session which do not
* guarantee mutual exclusion for writing
*/
class privUITransactionFile
{
/**
* Create a new transaction id, store it in the session and return its id
* @param void
* @return int The identifier of the new transaction
*/
public static function GetNewTransactionId()
{
if (!is_dir(APPROOT.'data/transactions'))
{
if (!is_writable(APPROOT.'data'))
{
throw new Exception('The directory "'.APPROOT.'data" must be writable to the application.');
}
if (!@mkdir(APPROOT.'data/transactions'))
{
throw new Exception('Failed to create the directory "'.APPROOT.'data/transactions". Ajust the rights on the parent directory or let an administrator create the transactions directory and give the web sever enough rights to write into it.');
}
}
if (!is_writable(APPROOT.'data/transactions'))
{
throw new Exception('The directory "'.APPROOT.'data/transactions" must be writable to the application.');
}
self::CleanupOldTransactions();
$id = basename(tempnam(APPROOT.'data/transactions', self::GetUserPrefix()));
self::Info('GetNewTransactionId: Created transaction: '.$id);
return (string)$id;
}
/**
* Check whether a transaction is valid or not and (optionally) remove the valid transaction from
* the session so that another call to IsTransactionValid for the same transaction id
* will return false
* @param int $id Identifier of the transaction, as returned by GetNewTransactionId
* @param bool $bRemoveTransaction True if the transaction must be removed
* @return bool True if the transaction is valid, false otherwise
*/
public static function IsTransactionValid($id, $bRemoveTransaction = true)
{
$sFilepath = APPROOT.'data/transactions/'.$id;
clearstatcache(true, $sFilepath);
$bResult = file_exists($sFilepath);
if ($bResult)
{
if ($bRemoveTransaction)
{
$bResult = @unlink($sFilepath);
if (!$bResult)
{
self::Error('IsTransactionValid: FAILED to remove transaction '.$id);
}
else
{
self::Info('IsTransactionValid: OK. Removed transaction: '.$id);
}
}
}
else
{
self::Info("IsTransactionValid: Transaction '$id' not found. Pending transactions for this user:\n".implode("\n", self::GetPendingTransactions()));
}
return $bResult;
}
/**
* Removes the transaction specified by its id
* @param int $id The Identifier (as returned by GetNewTransactionId) of the transaction to be removed.
* @return void
*/
public static function RemoveTransaction($id)
{
$bSuccess = true;
$sFilepath = APPROOT.'data/transactions/'.$id;
clearstatcache(true, $sFilepath);
if(!file_exists($sFilepath))
{
$bSuccess = false;
self::Error("RemoveTransaction: Transaction '$id' not found. Pending transactions for this user:\n".implode("\n", self::GetPendingTransactions()));
}
$bSuccess = @unlink($sFilepath);
if (!$bSuccess)
{
self::Error('RemoveTransaction: FAILED to remove transaction '.$id);
}
else
{
self::Info('RemoveTransaction: OK '.$id);
}
return $bSuccess;
}
/**
* Cleanup old transactions which have been pending since more than 24 hours
* Use filemtime instead of filectime since filectime may be affected by operations on the directory (like changing the access rights)
*/
protected static function CleanupOldTransactions()
{
$iLimit = time() - 24*3600;
clearstatcache();
$aTransactions = glob(APPROOT.'data/transactions/*-*');
foreach($aTransactions as $sFileName)
{
if (filemtime($sFileName) < $iLimit)
{
@unlink($sFileName);
self::Info('CleanupOldTransactions: Deleted transaction: '.$sFileName);
}
}
}
/**
* For debugging purposes: gets the pending transactions of the current user
* as an array, with the date of the creation of the transaction file
*/
protected static function GetPendingTransactions()
{
clearstatcache();
$aResult = array();
$aTransactions = glob(APPROOT.'data/transactions/'.self::GetUserPrefix().'*');
foreach($aTransactions as $sFileName)
{
$aResult[] = date('Y-m-d H:i:s', filemtime($sFileName)).' - '.basename($sFileName);
}
sort($aResult);
return $aResult;
}
protected static function GetUserPrefix()
{
$sPrefix = substr(UserRights::GetUser(), 0, 10);
$sPrefix = preg_replace('/[^a-zA-Z0-9-_]/', '_', $sPrefix);
return $sPrefix.'-';
}
protected static function Info($sText)
{
self::Write('Info | '.$sText);
}
protected static function Warning($sText)
{
self::Write('Warning | '.$sText);
}
protected static function Error($sText)
{
self::Write('Error | '.$sText);
}
protected static function Write($sText)
{
$bLogEnabled = MetaModel::GetConfig()->Get('log_transactions');
if ($bLogEnabled)
{
$hLogFile = @fopen(APPROOT.'log/transactions.log', 'a');
if ($hLogFile !== false)
{
flock($hLogFile, LOCK_EX);
$sDate = date('Y-m-d H:i:s');
fwrite($hLogFile, "$sDate | $sText\n");
fflush($hLogFile);
flock($hLogFile, LOCK_UN);
fclose($hLogFile);
}
}
}
}

View File

@@ -259,7 +259,7 @@ EOF
$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}{$sFieldName}\" value=\"$value\" />\n";
$sHTMLValue .= "<input type=\"hidden\" id=\"$this->iId\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" value=\"".htmlentities($value, ENT_QUOTES, 'UTF-8')."\" />\n";
$JSSearchMode = $this->bSearchMode ? 'true' : 'false';
// Scripts to start the autocomplete and bind some events to it

View File

@@ -80,7 +80,7 @@ class UIHTMLEditorWidget
// Could also be bound to 'instanceReady.ckeditor'
$oPage->add_ready_script("$('#$iId').bind('validate', function(evt, sFormId) { return ValidateCKEditField('$iId', '', {$this->m_sMandatory}, sFormId, '') } );\n");
$oPage->add_ready_script("$('#$iId').bind('update', function() { BlockField('cke_$iId', $('#$iId').attr('disabled')); } );\n");
$oPage->add_ready_script("$('#$iId').bind('update', function() { BlockField('cke_$iId', $('#$iId').attr('disabled')); $(this).data('ckeditorInstance').setReadOnly($(this).prop('disabled')); } );\n");
return $sHtmlValue;
}

View File

@@ -294,7 +294,7 @@ class UILinksWidgetDirect
$valuesDef = $oLinksetDef->GetValuesDef();
if ($valuesDef === null)
{
$oFilter = new DBObjectSearch($this->sLinkedClass);
$oFilter = new DBObjectSearch($sRemoteClass);
}
else
{

View File

@@ -239,7 +239,7 @@ EOF
*/
protected function DisplayFormTable(WebPage $oP, $aConfig, $aData)
{
$sHtml = '';
$sHtml = "<input type=\"hidden\" name=\"attr_{$this->m_sAttCode}{$this->m_sNameSuffix}\" value=\"\">";
$sHtml .= "<table class=\"listResults\">\n";
// Header
$sHtml .= "<thead>\n";
@@ -259,11 +259,11 @@ EOF
$sEmptyRowStyle = 'style="display:none;"';
}
$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)
{
$sHtml .= $this->DisplayFormRow($oP, $aConfig, $aRow, $iRowId);
}
$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')."</td></tr>";
$sHtml .= "</tbody>\n";
// Footer

View File

@@ -130,7 +130,7 @@ class appUserPreferences extends DBObject
/**
* Call this function if the user has changed (like when doing a logoff...)
*/
static public function Reset()
static public function ResetPreferences()
{
self::$oUserPrefs = null;
}

View File

@@ -783,11 +783,16 @@ class utils
$sOQL = addslashes($param->GetFilter()->ToOQL(true));
$sFilter = urlencode($param->GetFilter()->serialize());
$sUrl = utils::GetAbsoluteUrlAppRoot()."pages/$sUIPage?operation=search&filter=".$sFilter."&{$sContext}";
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/xlsx-export.js');
$sXlsxFilter = $param->GetFilter()->serialize();
$sXlsxJSFilter = addslashes($sXlsxFilter);
$aResult = array(
new SeparatorPopupMenuItem(),
// Static menus: Email this page, CSV Export & Add to Dashboard
new URLPopupMenuItem('UI:Menu:EMail', Dict::S('UI:Menu:EMail'), "mailto:?body=".urlencode($sUrl).' '), // Add an extra space to make it work in Outlook
new URLPopupMenuItem('UI:Menu:CSVExport', Dict::S('UI:Menu:CSVExport'), $sUrl."&format=csv"),
new JSPopupMenuItem('xlsx-export', Dict::S('ExcelExporter:ExportMenu'), "XlsxExportDialog('$sXlsxJSFilter');", array()),
new JSPopupMenuItem('UI:Menu:AddToDashboard', Dict::S('UI:Menu:AddToDashboard'), "DashletCreationDlg('$sOQL')"),
new JSPopupMenuItem('UI:Menu:ShortcutList', Dict::S('UI:Menu:ShortcutList'), "ShortcutListDlg('$sOQL', '$sDataTableId', '$sContext')"),
);
@@ -802,11 +807,14 @@ class utils
$sUIPage = cmdbAbstractObject::ComputeStandardUIPage(get_class($oObj));
$oAppContext = new ApplicationContext();
$sContext = $oAppContext->GetForLink();
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/xlsx-export.js');
$sXlsxJSFilter = addslashes($sFilter);
$aResult = array(
new SeparatorPopupMenuItem(),
// Static menus: Email this page & CSV Export
new URLPopupMenuItem('UI:Menu:EMail', Dict::S('UI:Menu:EMail'), "mailto:?subject=".urlencode($oObj->GetRawName())."&body=".urlencode($sUrl).' '), // Add an extra space to make it work in Outlook
new URLPopupMenuItem('UI:Menu:CSVExport', Dict::S('UI:Menu:CSVExport'), utils::GetAbsoluteUrlAppRoot()."pages/$sUIPage?operation=search&filter=".urlencode($sFilter)."&format=csv&{$sContext}"),
new JSPopupMenuItem('xlsx-export', Dict::S('ExcelExporter:ExportMenu'), "XlsxExportDialog('$sXlsxJSFilter');", array()),
);
break;

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2013 Combodo SARL
// Copyright (C) 2010-2014 Combodo SARL
//
// This file is part of iTop.
//
@@ -68,6 +68,7 @@ class WebPage implements Page
protected $sContentType;
protected $sContentDisposition;
protected $sContentFileName;
protected $bTrashUnexpectedOutput;
protected $s_sOutputFormat;
protected $a_OutputOptions;
@@ -88,6 +89,7 @@ class WebPage implements Page
$this->sContentType = '';
$this->sContentDisposition = '';
$this->sContentFileName = '';
$this->bTrashUnexpectedOutput = false;
$this->s_OutputFormat = utils::ReadParam('output_format', 'html');
$this->a_OutputOptions = array();
ob_start(); // Start capturing the output
@@ -392,22 +394,55 @@ class WebPage implements Page
}
/**
* Discard unexpected output data
* Discard unexpected output data (such as PHP warnings)
* This is a MUST when the Page output is DATA (download of a document, download CSV export, download ...)
*/
public function TrashUnexpectedOutput()
{
// This protection is redundant with a protection implemented in MetaModel::IncludeModule
// which detects such issues while loading module files
// Here, the purpose is to detect and discard characters produced by the code execution (echo)
$sPreviousContent = ob_get_clean();
if (trim($sPreviousContent) != '')
$this->bTrashUnexpectedOutput = true;
}
/**
* Read the output buffer and deal with its contents:
* - trash unexpected output if the flag has been set
* - report unexpected behaviors such as the output buffering being stopped
*
* Possible improvement: I've noticed that several output buffers are stacked,
* if they are not empty, the output will be corrupted. The solution would
* consist in unstacking all of them (and concatenate the contents).
*/
protected function ob_get_clean_safe()
{
$sOutput = ob_get_contents();
if ($sOutput === false)
{
if (Utils::GetConfig() && Utils::GetConfig()->Get('debug_report_spurious_chars'))
$sMsg = "Design/integration issue: No output buffer. Some piece of code has called ob_get_clean() or ob_end_clean() without calling ob_start()";
if ($this->bTrashUnexpectedOutput)
{
IssueLog::Error("Output already started before downloading file:\nContent was:'$sPreviousContent'\n");
IssueLog::Error($sMsg);
$sOutput = '';
}
else
{
$sOutput = $sMsg;
}
}
else
{
ob_end_clean(); // on some versions of PHP doing so when the output buffering is stopped can cause a notice
if ($this->bTrashUnexpectedOutput)
{
if (trim($sOutput) != '')
{
if (Utils::GetConfig() && Utils::GetConfig()->Get('debug_report_spurious_chars'))
{
IssueLog::Error("Trashing unexpected output:'$s_captured_output'\n");
}
}
$sOutput = '';
}
}
return $sOutput;
}
/**
@@ -419,8 +454,8 @@ class WebPage implements Page
{
header($s_header);
}
$s_captured_output = ob_get_contents();
ob_end_clean();
$s_captured_output = $this->ob_get_clean_safe();
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";

View File

@@ -0,0 +1,456 @@
<?php
/* @author Mark Jones
* @license MIT License
* */
if (!class_exists('ZipArchive')) { throw new Exception('ZipArchive not found'); }
Class XLSXWriter
{
//------------------------------------------------------------------
protected $author ='Doc Author';
protected $sheets_meta = array();
protected $shared_strings = array();//unique set
protected $shared_string_count = 0;//count of non-unique references to the unique set
protected $temp_files = array();
public function __construct(){}
public function setAuthor($author='') { $this->author=$author; }
public function __destruct()
{
if (!empty($this->temp_files)) {
foreach($this->temp_files as $temp_file) {
@unlink($temp_file);
}
}
}
protected function tempFilename()
{
$filename = tempnam("/tmp", "xlsx_writer_");
$this->temp_files[] = $filename;
return $filename;
}
public function writeToStdOut()
{
$temp_file = $this->tempFilename();
self::writeToFile($temp_file);
readfile($temp_file);
}
public function writeToString()
{
$temp_file = $this->tempFilename();
self::writeToFile($temp_file);
$string = file_get_contents($temp_file);
return $string;
}
public function writeToFile($filename)
{
@unlink($filename);//if the zip already exists, overwrite it
$zip = new ZipArchive();
if (empty($this->sheets_meta)) { self::log("Error in ".__CLASS__."::".__FUNCTION__.", no worksheets defined."); return; }
if (!$zip->open($filename, ZipArchive::CREATE)) { self::log("Error in ".__CLASS__."::".__FUNCTION__.", unable to create zip."); return; }
$zip->addEmptyDir("docProps/");
$zip->addFromString("docProps/app.xml" , self::buildAppXML() );
$zip->addFromString("docProps/core.xml", self::buildCoreXML());
$zip->addEmptyDir("_rels/");
$zip->addFromString("_rels/.rels", self::buildRelationshipsXML());
$zip->addEmptyDir("xl/worksheets/");
foreach($this->sheets_meta as $sheet_meta) {
$zip->addFile($sheet_meta['filename'], "xl/worksheets/".$sheet_meta['xmlname'] );
}
if (!empty($this->shared_strings)) {
$zip->addFile($this->writeSharedStringsXML(), "xl/sharedStrings.xml" ); //$zip->addFromString("xl/sharedStrings.xml", self::buildSharedStringsXML() );
}
$zip->addFromString("xl/workbook.xml" , self::buildWorkbookXML() );
$zip->addFile($this->writeStylesXML(), "xl/styles.xml" ); //$zip->addFromString("xl/styles.xml" , self::buildStylesXML() );
$zip->addFromString("[Content_Types].xml" , self::buildContentTypesXML() );
$zip->addEmptyDir("xl/_rels/");
$zip->addFromString("xl/_rels/workbook.xml.rels", self::buildWorkbookRelsXML() );
$zip->close();
}
public function writeSheet(array $data, $sheet_name='', array $header_types=array() )
{
$data = empty($data) ? array( array('') ) : $data;
$sheet_filename = $this->tempFilename();
$sheet_default = 'Sheet'.(count($this->sheets_meta)+1);
$sheet_name = !empty($sheet_name) ? $sheet_name : $sheet_default;
$this->sheets_meta[] = array('filename'=>$sheet_filename, 'sheetname'=>$sheet_name ,'xmlname'=>strtolower($sheet_default).".xml" );
$header_offset = empty($header_types) ? 0 : 1;
$row_count = count($data) + $header_offset;
$column_count = count($data[self::array_first_key($data)]);
$max_cell = self::xlsCell( $row_count-1, $column_count-1 );
$tabselected = count($this->sheets_meta)==1 ? 'true' : 'false';//only first sheet is selected
$cell_formats_arr = empty($header_types) ? array_fill(0, $column_count, 'string') : array_values($header_types);
$header_row = empty($header_types) ? array() : array_keys($header_types);
$fd = fopen($sheet_filename, "w+");
if ($fd===false) { self::log("write failed in ".__CLASS__."::".__FUNCTION__."."); return; }
fwrite($fd,'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'."\n");
fwrite($fd,'<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">');
fwrite($fd, '<sheetPr filterMode="false">');
fwrite($fd, '<pageSetUpPr fitToPage="false"/>');
fwrite($fd, '</sheetPr>');
fwrite($fd, '<dimension ref="A1:'.$max_cell.'"/>');
fwrite($fd, '<sheetViews>');
fwrite($fd, '<sheetView colorId="64" defaultGridColor="true" rightToLeft="false" showFormulas="false" showGridLines="true" showOutlineSymbols="true" showRowColHeaders="true" showZeros="true" tabSelected="'.$tabselected.'" topLeftCell="A1" view="normal" windowProtection="false" workbookViewId="0" zoomScale="100" zoomScaleNormal="100" zoomScalePageLayoutView="100">');
fwrite($fd, '<selection activeCell="A1" activeCellId="0" pane="topLeft" sqref="A1"/>');
fwrite($fd, '</sheetView>');
fwrite($fd, '</sheetViews>');
fwrite($fd, '<cols>');
fwrite($fd, '<col collapsed="false" hidden="false" max="1025" min="1" style="0" width="19"/>');
fwrite($fd, '</cols>');
fwrite($fd, '<sheetData>');
if (!empty($header_row))
{
fwrite($fd, '<row collapsed="false" customFormat="false" customHeight="false" hidden="false" ht="12.1" outlineLevel="0" r="'.(1).'">');
foreach($header_row as $k=>$v)
{
$this->writeCell($fd, 0, $k, $v, $cell_format='string');
}
fwrite($fd, '</row>');
}
foreach($data as $i=>$row)
{
fwrite($fd, '<row collapsed="false" customFormat="false" customHeight="false" hidden="false" ht="12.1" outlineLevel="0" r="'.($i+$header_offset+1).'">');
foreach($row as $k=>$v)
{
$this->writeCell($fd, $i+$header_offset, $k, $v, $cell_formats_arr[$k]);
}
fwrite($fd, '</row>');
}
fwrite($fd, '</sheetData>');
fwrite($fd, '<printOptions headings="false" gridLines="false" gridLinesSet="true" horizontalCentered="false" verticalCentered="false"/>');
fwrite($fd, '<pageMargins left="0.5" right="0.5" top="1.0" bottom="1.0" header="0.5" footer="0.5"/>');
fwrite($fd, '<pageSetup blackAndWhite="false" cellComments="none" copies="1" draft="false" firstPageNumber="1" fitToHeight="1" fitToWidth="1" horizontalDpi="300" orientation="portrait" pageOrder="downThenOver" paperSize="1" scale="100" useFirstPageNumber="true" usePrinterDefaults="false" verticalDpi="300"/>');
fwrite($fd, '<headerFooter differentFirst="false" differentOddEven="false">');
fwrite($fd, '<oddHeader>&amp;C&amp;&quot;Times New Roman,Regular&quot;&amp;12&amp;A</oddHeader>');
fwrite($fd, '<oddFooter>&amp;C&amp;&quot;Times New Roman,Regular&quot;&amp;12Page &amp;P</oddFooter>');
fwrite($fd, '</headerFooter>');
fwrite($fd,'</worksheet>');
fclose($fd);
}
protected function writeCell($fd, $row_number, $column_number, $value, $cell_format)
{
static $styles = array('money'=>1,'dollar'=>1,'datetime'=>2,'date'=>3,'string'=>0);
$cell = self::xlsCell($row_number, $column_number);
$s = isset($styles[$cell_format]) ? $styles[$cell_format] : '0';
if (is_numeric($value)) {
fwrite($fd,'<c r="'.$cell.'" s="'.$s.'" t="n"><v>'.($value*1).'</v></c>');//int,float, etc
} else if ($cell_format=='date') {
fwrite($fd,'<c r="'.$cell.'" s="'.$s.'" t="n"><v>'.intval(self::convert_date_time($value)).'</v></c>');
} else if ($cell_format=='datetime') {
if ($value === '') {
fwrite($fd,'<c r="'.$cell.'" s="0"/>');
} else {
fwrite($fd,'<c r="'.$cell.'" s="'.$s.'" t="n"><v>'.self::convert_date_time($value).'</v></c>');
}
} else if ($value==''){
fwrite($fd,'<c r="'.$cell.'" s="'.$s.'"/>');
} else if ($value{0}=='='){
fwrite($fd,'<c r="'.$cell.'" s="'.$s.'" t="s"><f>'.self::xmlspecialchars($value).'</f></c>');
} else if ($value!==''){
fwrite($fd,'<c r="'.$cell.'" s="'.$s.'" t="s"><v>'.self::xmlspecialchars($this->setSharedString($value)).'</v></c>');
}
}
protected function writeStylesXML()
{
$tempfile = $this->tempFilename();
$fd = fopen($tempfile, "w+");
if ($fd===false) { self::log("write failed in ".__CLASS__."::".__FUNCTION__."."); return; }
fwrite($fd, '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'."\n");
fwrite($fd, '<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">');
fwrite($fd, '<numFmts count="4">');
fwrite($fd, '<numFmt formatCode="GENERAL" numFmtId="164"/>');
fwrite($fd, '<numFmt formatCode="[$$-1009]#,##0.00;[RED]\-[$$-1009]#,##0.00" numFmtId="165"/>');
fwrite($fd, '<numFmt formatCode="YYYY-MM-DD\ HH:MM:SS" numFmtId="166"/>');
fwrite($fd, '<numFmt formatCode="YYYY-MM-DD" numFmtId="167"/>');
fwrite($fd, '</numFmts>');
fwrite($fd, '<fonts count="4">');
fwrite($fd, '<font><name val="Arial"/><charset val="1"/><family val="2"/><sz val="10"/></font>');
fwrite($fd, '<font><name val="Arial"/><family val="0"/><sz val="10"/></font>');
fwrite($fd, '<font><name val="Arial"/><family val="0"/><sz val="10"/></font>');
fwrite($fd, '<font><name val="Arial"/><family val="0"/><sz val="10"/></font>');
fwrite($fd, '</fonts>');
fwrite($fd, '<fills count="2"><fill><patternFill patternType="none"/></fill><fill><patternFill patternType="gray125"/></fill></fills>');
fwrite($fd, '<borders count="1"><border diagonalDown="false" diagonalUp="false"><left/><right/><top/><bottom/><diagonal/></border></borders>');
fwrite($fd, '<cellStyleXfs count="15">');
fwrite($fd, '<xf applyAlignment="true" applyBorder="true" applyFont="true" applyProtection="true" borderId="0" fillId="0" fontId="0" numFmtId="164">');
fwrite($fd, '<alignment horizontal="general" indent="0" shrinkToFit="false" textRotation="0" vertical="bottom" wrapText="false"/>');
fwrite($fd, '<protection hidden="false" locked="true"/>');
fwrite($fd, '</xf>');
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="0"/>');
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="0"/>');
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="2" numFmtId="0"/>');
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="2" numFmtId="0"/>');
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
//fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="43"/>');
//fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="41"/>');
//fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="44"/>');
//fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="42"/>');
//fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="9"/>');
fwrite($fd, '</cellStyleXfs>');
fwrite($fd, '<cellXfs count="4">');
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="false" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="164" xfId="0"/>');
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="false" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="165" xfId="0"/>');
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="false" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="166" xfId="0"/>');
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="false" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="167" xfId="0"/>');
fwrite($fd, '</cellXfs>');
fwrite($fd, '<cellStyles count="1">');
fwrite($fd, '<cellStyle builtinId="0" customBuiltin="false" name="Normal" xfId="0"/>');
//fwrite($fd, '<cellStyle builtinId="3" customBuiltin="false" name="Comma" xfId="15"/>');
//fwrite($fd, '<cellStyle builtinId="6" customBuiltin="false" name="Comma [0]" xfId="16"/>');
//fwrite($fd, '<cellStyle builtinId="4" customBuiltin="false" name="Currency" xfId="17"/>');
//fwrite($fd, '<cellStyle builtinId="7" customBuiltin="false" name="Currency [0]" xfId="18"/>');
//fwrite($fd, '<cellStyle builtinId="5" customBuiltin="false" name="Percent" xfId="19"/>');
fwrite($fd, '</cellStyles>');
fwrite($fd, '</styleSheet>');
fclose($fd);
return $tempfile;
}
protected function setSharedString($v)
{
// Strip control characters which Excel does not seem to like...
$v = preg_replace('/[\x00-\x09\x0B\x0C\x0E-\x1F]/u', '', $v);
if (isset($this->shared_strings[$v]))
{
$string_value = $this->shared_strings[$v];
}
else
{
$string_value = count($this->shared_strings);
$this->shared_strings[$v] = $string_value;
}
$this->shared_string_count++;//non-unique count
return $string_value;
}
protected function writeSharedStringsXML()
{
$tempfile = $this->tempFilename();
$fd = fopen($tempfile, "w+");
if ($fd===false) { self::log("write failed in ".__CLASS__."::".__FUNCTION__."."); return; }
fwrite($fd,'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'."\n");
fwrite($fd,'<sst count="'.($this->shared_string_count).'" uniqueCount="'.count($this->shared_strings).'" xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">');
foreach($this->shared_strings as $s=>$c)
{
fwrite($fd,'<si><t>'.self::xmlspecialchars($s).'</t></si>');
}
fwrite($fd, '</sst>');
fclose($fd);
return $tempfile;
}
protected function buildAppXML()
{
$app_xml="";
$app_xml.='<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'."\n";
$app_xml.='<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"><TotalTime>0</TotalTime></Properties>';
return $app_xml;
}
protected function buildCoreXML()
{
$core_xml="";
$core_xml.='<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'."\n";
$core_xml.='<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">';
$core_xml.='<dcterms:created xsi:type="dcterms:W3CDTF">'.date("Y-m-d\TH:i:s.00\Z").'</dcterms:created>';//$date_time = '2013-07-25T15:54:37.00Z';
$core_xml.='<dc:creator>'.self::xmlspecialchars($this->author).'</dc:creator>';
$core_xml.='<cp:revision>0</cp:revision>';
$core_xml.='</cp:coreProperties>';
return $core_xml;
}
protected function buildRelationshipsXML()
{
$rels_xml="";
$rels_xml.='<?xml version="1.0" encoding="UTF-8"?>'."\n";
$rels_xml.='<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">';
$rels_xml.='<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>';
$rels_xml.='<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>';
$rels_xml.='<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>';
$rels_xml.="\n";
$rels_xml.='</Relationships>';
return $rels_xml;
}
protected function buildWorkbookXML()
{
$workbook_xml="";
$workbook_xml.='<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'."\n";
$workbook_xml.='<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">';
$workbook_xml.='<fileVersion appName="Calc"/><workbookPr backupFile="false" showObjects="all" date1904="false"/><workbookProtection/>';
$workbook_xml.='<bookViews><workbookView activeTab="0" firstSheet="0" showHorizontalScroll="true" showSheetTabs="true" showVerticalScroll="true" tabRatio="212" windowHeight="8192" windowWidth="16384" xWindow="0" yWindow="0"/></bookViews>';
$workbook_xml.='<sheets>';
foreach($this->sheets_meta as $i=>$sheet_meta) {
$workbook_xml.='<sheet name="'.self::xmlspecialchars($sheet_meta['sheetname']).'" sheetId="'.($i+1).'" state="visible" r:id="rId'.($i+2).'"/>';
}
$workbook_xml.='</sheets>';
$workbook_xml.='<calcPr iterateCount="100" refMode="A1" iterate="false" iterateDelta="0.001"/></workbook>';
return $workbook_xml;
}
protected function buildWorkbookRelsXML()
{
$wkbkrels_xml="";
$wkbkrels_xml.='<?xml version="1.0" encoding="UTF-8"?>'."\n";
$wkbkrels_xml.='<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">';
$wkbkrels_xml.='<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>';
foreach($this->sheets_meta as $i=>$sheet_meta) {
$wkbkrels_xml.='<Relationship Id="rId'.($i+2).'" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/'.($sheet_meta['xmlname']).'"/>';
}
if (!empty($this->shared_strings)) {
$wkbkrels_xml.='<Relationship Id="rId'.(count($this->sheets_meta)+2).'" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml"/>';
}
$wkbkrels_xml.="\n";
$wkbkrels_xml.='</Relationships>';
return $wkbkrels_xml;
}
protected function buildContentTypesXML()
{
$content_types_xml="";
$content_types_xml.='<?xml version="1.0" encoding="UTF-8"?>'."\n";
$content_types_xml.='<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">';
$content_types_xml.='<Override PartName="/_rels/.rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>';
$content_types_xml.='<Override PartName="/xl/_rels/workbook.xml.rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>';
foreach($this->sheets_meta as $i=>$sheet_meta) {
$content_types_xml.='<Override PartName="/xl/worksheets/'.($sheet_meta['xmlname']).'" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>';
}
if (!empty($this->shared_strings)) {
$content_types_xml.='<Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/>';
}
$content_types_xml.='<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>';
$content_types_xml.='<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>';
$content_types_xml.='<Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>';
$content_types_xml.='<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>';
$content_types_xml.="\n";
$content_types_xml.='</Types>';
return $content_types_xml;
}
//------------------------------------------------------------------
/*
* @param $row_number int, zero based
* @param $column_number int, zero based
* @return Cell label/coordinates, ex: A1, C3, AA42
* */
public static function xlsCell($row_number, $column_number)
{
$n = $column_number;
for($r = ""; $n >= 0; $n = intval($n / 26) - 1) {
$r = chr($n%26 + 0x41) . $r;
}
return $r . ($row_number+1);
}
//------------------------------------------------------------------
public static function log($string)
{
file_put_contents("php://stderr", date("Y-m-d H:i:s:").rtrim(is_array($string) ? json_encode($string) : $string)."\n");
}
//------------------------------------------------------------------
public static function xmlspecialchars($val)
{
return str_replace("'", "&#39;", htmlspecialchars($val));
}
//------------------------------------------------------------------
public static function array_first_key(array $arr)
{
reset($arr);
$first_key = key($arr);
return $first_key;
}
//------------------------------------------------------------------
public static function convert_date_time($date_input) //thanks to Excel::Writer::XLSX::Worksheet.pm (perl)
{
$days = 0; # Number of days since epoch
$seconds = 0; # Time expressed as fraction of 24h hours in seconds
$year=$month=$day=0;
$hour=$min =$sec=0;
$date_time = $date_input;
if (preg_match("/(\d{4})\-(\d{2})\-(\d{2})/", $date_time, $matches))
{
list($junk,$year,$month,$day) = $matches;
}
if (preg_match("/(\d{2}):(\d{2}):(\d{2})/", $date_time, $matches))
{
list($junk,$hour,$min,$sec) = $matches;
$seconds = ( $hour * 60 * 60 + $min * 60 + $sec ) / ( 24 * 60 * 60 );
}
//using 1900 as epoch, not 1904, ignoring 1904 special case
# Special cases for Excel.
if ("$year-$month-$day"=='1899-12-31') return $seconds ; # Excel 1900 epoch
if ("$year-$month-$day"=='1900-01-00') return $seconds ; # Excel 1900 epoch
if ("$year-$month-$day"=='1900-02-29') return 60 + $seconds ; # Excel false leapday
# We calculate the date by calculating the number of days since the epoch
# and adjust for the number of leap days. We calculate the number of leap
# days by normalising the year in relation to the epoch. Thus the year 2000
# becomes 100 for 4 and 100 year leapdays and 400 for 400 year leapdays.
$epoch = 1900;
$offset = 0;
$norm = 300;
$range = $year - $epoch;
# Set month days and check for leap year.
$leap = (($year % 400 == 0) || (($year % 4 == 0) && ($year % 100)) ) ? 1 : 0;
$mdays = array( 31, ($leap ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 );
# Some boundary checks
if($year < $epoch || $year > 9999) return 0;
if($month < 1 || $month > 12) return 0;
if($day < 1 || $day > $mdays[ $month - 1 ]) return 0;
# Accumulate the number of days since the epoch.
$days = $day; # Add days for current month
$days += array_sum( array_slice($mdays, 0, $month-1 ) ); # Add days for past months
$days += $range * 365; # Add days for past years
$days += intval( ( $range ) / 4 ); # Add leapdays
$days -= intval( ( $range + $offset ) / 100 ); # Subtract 100 year leapdays
$days += intval( ( $range + $offset + $norm ) / 400 ); # Add 400 year leapdays
$days -= $leap; # Already counted above
# Adjust for Excel erroneously treating 1900 as a leap year.
if ($days > 59) { $days++;}
return $days + $seconds;
}
//------------------------------------------------------------------
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2013 Combodo SARL
// Copyright (C) 2010-2014 Combodo SARL
//
// This file is part of iTop.
//
@@ -51,6 +51,9 @@ class XMLPage extends WebPage
{
if (!$this->m_bPassThrough)
{
// Get the unexpected output but do nothing with it
$sTrash = $this->ob_get_clean_safe();
$this->s_content = "<?xml version=\"1.0\" encoding=\"UTF-8\"?".">\n".trim($this->s_content);
$this->add_header("Content-Length: ".strlen($this->s_content));
foreach($this->a_headers as $s_header)
@@ -79,8 +82,7 @@ class XMLPage extends WebPage
}
else
{
$s_captured_output = ob_get_contents();
ob_end_clean();
$s_captured_output = $this->ob_get_clean_safe();
foreach($this->a_headers as $s_header)
{
header($s_header);
@@ -101,13 +103,4 @@ class XMLPage extends WebPage
public function table($aConfig, $aData, $aParams = array())
{
}
public function TrashUnexpectedOutput()
{
if (!$this->m_bPassThrough)
{
parent::TrashUnexpectedOutput();
}
}
}
?>

View File

@@ -91,6 +91,9 @@ abstract class AsyncTask extends DBObject
MetaModel::Init_AddAttribute(new AttributeExternalKey("event_id", array("targetclass"=>"Event", "jointype"=> "", "allowed_values"=>null, "sql"=>"event_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_SILENT, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeInteger("remaining_retries", array("allowed_values"=>null, "sql"=>"remaining_retries", "default_value"=>0, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeInteger("last_error_code", array("allowed_values"=>null, "sql"=>"last_error_code", "default_value"=>0, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("last_error", array("allowed_values"=>null, "sql"=>"last_error", "default_value"=>'', "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeDateTime("last_attempt", array("allowed_values"=>null, "sql"=>"last_attempt", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
}
/**
@@ -117,27 +120,34 @@ abstract class AsyncTask extends DBObject
{
try
{
$this->Set('status', 'running');
$this->Set('started', time());
$this->DBUpdate();
return self::OK;
if ($this->Get('status') == 'running')
{
return self::ALREADY_RUNNING;
}
else
{
$this->Set('status', 'running');
$this->Set('started', time());
$this->DBUpdate();
return self::OK;
}
}
catch(Exception $e)
{
// Corrupted task !! (for example: "Failed to reload object")
IssueLog::Error('Failed to process async task #'.$this->GetKey().' - reason: '.$e->getMessage().' - fatal error, deleting the task.');
if ($this->Get('event_id') != 0)
{
$oEventLog = MetaModel::GetObject('Event', $this->Get('event_id'));
$oEventLog->Set('message', 'Failed, corrupted data: '.$e->getMessage());
$oEventLog->DBUpdate();
if ($this->Get('event_id') != 0)
{
$oEventLog = MetaModel::GetObject('Event', $this->Get('event_id'));
$oEventLog->Set('message', 'Failed, corrupted data: '.$e->getMessage());
$oEventLog->DBUpdate();
}
$this->DBDelete();
return self::DELETED;
}
}
public function GetRetryDelay()
public function GetRetryDelay($iErrorCode = null)
{
$iRetryDelay = 600;
$aRetries = MetaModel::GetConfig()->Get('async_task_retries', array());
@@ -149,7 +159,7 @@ abstract class AsyncTask extends DBObject
return $iRetryDelay;
}
public function GetMaxRetries()
public function GetMaxRetries($iErrorCode = null)
{
$iMaxRetries = 0;
$aRetries = MetaModel::GetConfig()->Get('async_task_retries', array());
@@ -160,10 +170,16 @@ abstract class AsyncTask extends DBObject
}
}
/**
* Override to notify people that a task cannot be performed
*/
protected function OnDefinitiveFailure()
{
}
protected function OnInsert()
{
$this->Set('created', time());
$this->Set('remaining_retries', $this->GetMaxRetries());
}
/**
@@ -191,22 +207,7 @@ abstract class AsyncTask extends DBObject
}
catch(Exception $e)
{
$iRemaining = $this->Get('remaining_retries');
if ($iRemaining > 0)
{
$iRetryDelay = $this->GetRetryDelay();
IssueLog::Info('Failed to process async task #'.$this->GetKey().' - reason: '.$e->getMessage().' - remaining retries: '.$iRemaining.' - next retry in '.$iRetryDelay.'s');
$this->Set('remaining_retries', $iRemaining - 1);
$this->Set('status', 'planned');
$this->Set('started', null);
$this->Set('planned', time() + $iRetryDelay);
$this->DBUpdate();
}
else
{
IssueLog::Error('Failed to process async task #'.$this->GetKey().' - reason: '.$e->getMessage());
}
$this->HandleError($e->getMessage(), $e->getCode());
}
}
else
@@ -217,7 +218,56 @@ abstract class AsyncTask extends DBObject
return $bRet;
}
/**
* Overridable to extend the behavior in case of error (logging)
*/
protected function HandleError($sErrorMessage, $iErrorCode)
{
if ($this->Get('last_attempt') == '')
{
// First attempt
$this->Set('remaining_retries', $this->GetMaxRetries($iErrorCode));
}
$this->Set('last_error', $sErrorMessage);
$this->Set('last_error_code', $iErrorCode); // Note: can be ZERO !!!
$this->Set('last_attempt', time());
$iRemaining = $this->Get('remaining_retries');
if ($iRemaining > 0)
{
$iRetryDelay = $this->GetRetryDelay($iErrorCode);
IssueLog::Info('Failed to process async task #'.$this->GetKey().' - reason: '.$sErrorMessage.' - remaining retries: '.$iRemaining.' - next retry in '.$iRetryDelay.'s');
$this->Set('remaining_retries', $iRemaining - 1);
$this->Set('status', 'planned');
$this->Set('started', null);
$this->Set('planned', time() + $iRetryDelay);
}
else
{
IssueLog::Error('Failed to process async task #'.$this->GetKey().' - reason: '.$sErrorMessage);
$this->Set('status', 'error');
$this->Set('started', null);
$this->Set('planned', null);
$this->OnDefinitiveFailure();
}
$this->DBUpdate();
}
/**
* Throws an exception (message and code)
*/
abstract public function DoProcess();
/**
* Describes the error codes that DoProcess can return by the mean of exceptions
*/
static public function EnumErrorCodes()
{
return array();
}
}
/**

View File

@@ -480,6 +480,14 @@ abstract class AttributeDefinition
return (string)$sValue;
}
/**
* Override to differentiate a value displayed in the UI or in the history
*/
public function GetAsHTMLForHistory($sValue, $oHostObject = null, $bLocalize = true)
{
return $this->GetAsHTML($sValue, $oHostObject, $bLocalize);
}
public function GetAllowedValues($aArgs = array(), $sContains = '')
{
$oValSetDef = $this->GetValuesDef();
@@ -487,15 +495,18 @@ abstract class AttributeDefinition
return $oValSetDef->GetValues($aArgs, $sContains);
}
public function GetAsHTMLForHistory($sOldValue, $sNewValue, $sLabel = null)
/**
* Explain the change of the attribute (history)
*/
public function DescribeChangeAsHTML($sOldValue, $sNewValue, $sLabel = null)
{
if (is_null($sLabel))
{
$sLabel = $this->GetLabel();
}
$sNewValueHtml = $this->GetAsHTML($sNewValue);
$sOldValueHtml = $this->GetAsHTML($sOldValue);
$sNewValueHtml = $this->GetAsHTMLForHistory($sNewValue);
$sOldValueHtml = $this->GetAsHTMLForHistory($sOldValue);
if($this->IsExternalKey())
{
@@ -881,6 +892,106 @@ class AttributeLinkedSet extends AttributeDefinition
return $oSet;
}
/**
* Helper to get a value that will be JSON encoded
* The operation is the opposite to FromJSONToValue
*/
public function GetForJSON($value)
{
$aRet = array();
if (is_object($value) && ($value instanceof DBObjectSet))
{
$value->Rewind();
while ($oObj = $value->Fetch())
{
$sObjClass = get_class($oObj);
// Show only relevant information (hide the external key to the current object)
$aAttributes = array();
foreach(MetaModel::ListAttributeDefs($sObjClass) as $sAttCode => $oAttDef)
{
if ($sAttCode == 'finalclass')
{
if ($sObjClass == $this->GetLinkedClass())
{
// Simplify the output if the exact class could be determined implicitely
continue;
}
}
if ($sAttCode == $this->GetExtKeyToMe()) continue;
if ($oAttDef->IsExternalField()) continue;
if (!$oAttDef->IsDirectField()) continue;
if (!$oAttDef->IsScalar()) continue;
$attValue = $oObj->Get($sAttCode);
$aAttributes[$sAttCode] = $oAttDef->GetForJSON($attValue);
}
$aRet[] = $aAttributes;
}
}
return $aRet;
}
/**
* Helper to form a value, given JSON decoded data
* The operation is the opposite to GetForJSON
*/
public function FromJSONToValue($json)
{
$sTargetClass = $this->Get('linked_class');
$aLinks = array();
foreach($json as $aValues)
{
if (isset($aValues['finalclass']))
{
$sLinkClass = $aValues['finalclass'];
if (!is_subclass_of($sLinkClass, $sTargetClass))
{
throw new CoreException('Wrong class for link attribute specification', array('requested_class' => $sLinkClass, 'expected_class' => $sTargetClass));
}
}
elseif (MetaModel::IsAbstract($sTargetClass))
{
throw new CoreException('Missing finalclass for link attribute specification');
}
else
{
$sLinkClass = $sTargetClass;
}
$oLink = MetaModel::NewObject($sLinkClass);
foreach ($aValues as $sAttCode => $sValue)
{
$oLink->Set($sAttCode, $sValue);
}
// 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);
return $oSet;
}
public function Equals($val1, $val2)
{
if ($val1 === $val2) return true;
@@ -1982,7 +2093,7 @@ class AttributeCaseLog extends AttributeLongText
public function FromSQLToValue($aCols, $sPrefix = '')
{
if (!isset($aCols[$sPrefix]))
if (!array_key_exists($sPrefix, $aCols))
{
$sAvailable = implode(', ', array_keys($aCols));
throw new MissingColumnException("Missing column '$sPrefix' from {$sAvailable}");
@@ -3426,12 +3537,69 @@ class AttributeBlob extends AttributeDefinition
}
// Facilitate things: allow the user to Set the value from a string
// Facilitate things: allow administrators to upload a document
// from a CSV by specifying its path/URL
public function MakeRealValue($proposedValue, $oHostObj)
{
if (!is_object($proposedValue))
{
return new ormDocument($proposedValue, 'text/plain');
if (file_exists($proposedValue) && UserRights::IsAdministrator())
{
$sContent = file_get_contents($proposedValue);
$sExtension = strtolower(pathinfo($proposedValue, PATHINFO_EXTENSION));
$sMimeType = "application/x-octoet-stream";
$aKnownExtensions = array(
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12.xlsx',
'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'gif' => 'image/gif',
'png' => 'image/png',
'pdf' => 'application/pdf',
'doc' => 'application/msword',
'dot' => 'application/msword',
'xls' => 'application/vnd.ms-excel',
'ppt' => 'application/vnd.ms-powerpoint',
'vsd' => 'application/x-visio',
'vdx' => 'application/visio.drawing',
'odt' => 'application/vnd.oasis.opendocument.text',
'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
'odp' => 'application/vnd.oasis.opendocument.presentation',
'zip' => 'application/zip',
'txt' => 'text/plain',
'htm' => 'text/html',
'html' => 'text/html',
'exe' => 'application/octet-stream'
);
if (!array_key_exists($sExtension, $aKnownExtensions) && extension_loaded('fileinfo'))
{
$finfo = new finfo(FILEINFO_MIME);
$sMimeType = $finfo->file($proposedValue);
}
return new ormDocument($sContent, $sMimeType);
}
else
{
return new ormDocument($proposedValue, 'text/plain');
}
}
return $proposedValue;
}
@@ -3717,7 +3885,7 @@ class AttributeStopWatch extends AttributeDefinition
);
$aThresholds = array();
foreach ($this->ListThresholds() as $iThreshold => $aFoo)
foreach ($this->ListThresholds() as $iThreshold => $aDefinition)
{
$sThPrefix = '_'.$iThreshold;
$value->DefineThreshold(
@@ -3725,7 +3893,8 @@ class AttributeStopWatch extends AttributeDefinition
self::DateToSeconds($aCols[$sPrefix.$sThPrefix.'_deadline']),
(bool)($aCols[$sPrefix.$sThPrefix.'_passed'] == 1),
(bool)($aCols[$sPrefix.$sThPrefix.'_triggered'] == 1),
$aCols[$sPrefix.$sThPrefix.'_overrun']
$aCols[$sPrefix.$sThPrefix.'_overrun'],
array_key_exists('highlight', $aDefinition) ? $aDefinition['highlight'] : null
);
}
@@ -3923,19 +4092,17 @@ class AttributeStopWatch extends AttributeDefinition
return Dict::S('BooleanLabel:'.$sDictKey, 'def:'.$sDictKey);
}
public function GetSubItemAsHTMLForHistory($sItemCode, $sOldValue, $sNewValue, $sLabel)
public function GetSubItemAsHTMLForHistory($sItemCode, $sValue)
{
switch($sItemCode)
{
case 'timespent':
$sHtmlOld = (int)$sOldValue ? AttributeDuration::FormatDuration($sOldValue) : null;
$sHtmlNew = (int)$sNewValue ? AttributeDuration::FormatDuration($sNewValue) : null;
$sHtml = (int)$sValue ? Str::pure2html(AttributeDuration::FormatDuration($sValue)) : null;
break;
case 'started':
case 'laststart':
case 'stopped':
$sHtmlOld = (int)$sOldValue ? date(self::GetDateFormat(), (int)$sOldValue) : null;
$sHtmlNew = (int)$sNewValue ? date(self::GetDateFormat(), (int)$sNewValue) : null;
$sHtml = (int)$sValue ? date(self::GetDateFormat(), (int)$sValue) : null;
break;
default:
@@ -3949,26 +4116,21 @@ class AttributeStopWatch extends AttributeDefinition
switch($sThresholdCode)
{
case 'deadline':
$sHtmlOld = (int)$sOldValue ? date(self::GetDateFormat(true /*full*/), (int)$sOldValue) : null;
$sHtmlNew = (int)$sNewValue ? date(self::GetDateFormat(true /*full*/), (int)$sNewValue) : null;
$sHtml = (int)$sValue ? date(self::GetDateFormat(true /*full*/), (int)$sValue) : null;
break;
case 'passed':
$sHtmlOld = $this->GetBooleanLabel((int)$sOldValue);
$sHtmlNew = $this->GetBooleanLabel((int)$sNewValue);
$sHtml = $this->GetBooleanLabel((int)$sValue);
break;
case 'triggered':
$sHtmlOld = $this->GetBooleanLabel((int)$sOldValue);
$sHtmlNew = $this->GetBooleanLabel((int)$sNewValue);
$sHtml = $this->GetBooleanLabel((int)$sValue);
break;
case 'overrun':
$sHtmlOld = (int)$sOldValue > 0 ? AttributeDuration::FormatDuration((int)$sOldValue) : '';
$sHtmlNew = (int)$sNewValue > 0 ? AttributeDuration::FormatDuration((int)$sNewValue) : '';
$sHtml = (int)$sValue > 0 ? Str::pure2html(AttributeDuration::FormatDuration((int)$sValue)) : '';
}
}
}
}
$sRes = parent::GetAsHTMLForHistory($sHtmlOld, $sHtmlNew, $sLabel);
return $sRes;
return $sHtml;
}
static protected function GetDateFormat($bFull = false)
@@ -4283,6 +4445,13 @@ class AttributeSubItem extends AttributeDefinition
return $res;
}
public function GetAsHTMLForHistory($value, $oHostObject = null, $bLocalize = true)
{
$oParent = $this->GetTargetAttDef();
$res = $oParent->GetSubItemAsHTMLForHistory($this->Get('item_code'), $value);
return $res;
}
public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true)
{
$oParent = $this->GetTargetAttDef();
@@ -4297,15 +4466,6 @@ class AttributeSubItem extends AttributeDefinition
return $res;
}
public function GetAsHTMLForHistory($sOldValue, $sNewValue, $sLabel = null)
{
$sLabel = $this->GetLabel();
$oParent = $this->GetTargetAttDef();
$sValue = $oParent->GetSubItemAsHTMLForHistory($this->Get('item_code'), $sOldValue, $sNewValue, $sLabel);
return $sValue;
}
/**
* As of now, this function must be implemented to have the value in spreadsheet format
*/
@@ -4865,7 +5025,7 @@ class AttributeFriendlyName extends AttributeComputedFieldVoid
}
// Do not display friendly names in the history of change
public function GetAsHTMLForHistory($sOldValue, $sNewValue, $sLabel = null)
public function DescribeChangeAsHTML($sOldValue, $sNewValue, $sLabel = null)
{
return '';
}

View File

@@ -432,7 +432,7 @@ class BulkChange
break;
default:
$aErrors[$sAttCode] = Dict::Format('UI:CSVReport-Value-Issue-FoundMany', $oExtObjects->Count());
$aErrors[$sAttCode] = Dict::Format('UI:CSVReport-Value-Issue-FoundMany', $iCount);
$aResults[$sAttCode]= new CellStatus_Ambiguous($oTargetObj->Get($sAttCode), $iCount, $sOQL);
}
}

View File

@@ -55,7 +55,7 @@ class CMDBChangeOp extends DBObject
MetaModel::Init_AddAttribute(new AttributeExternalField("date", array("allowed_values"=>null, "extkey_attcode"=>"change", "target_attcode"=>"date")));
MetaModel::Init_AddAttribute(new AttributeExternalField("userinfo", array("allowed_values"=>null, "extkey_attcode"=>"change", "target_attcode"=>"userinfo")));
MetaModel::Init_AddAttribute(new AttributeString("objclass", array("allowed_values"=>null, "sql"=>"objclass", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("objkey", array("allowed_values"=>null, "sql"=>"objkey", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeInteger("objkey", array("allowed_values"=>null, "sql"=>"objkey", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_SetZListItems('details', array('change', 'date', 'userinfo')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('change', 'date', 'userinfo')); // Attributes to be displayed for the complete details
@@ -237,7 +237,7 @@ class CMDBChangeOpSetAttributeScalar extends CMDBChangeOpSetAttribute
$sAttName = $oAttDef->GetLabel();
$sNewValue = $this->Get('newvalue');
$sOldValue = $this->Get('oldvalue');
$sResult = $oAttDef->GetAsHTMLForHistory($sOldValue, $sNewValue);
$sResult = $oAttDef->DescribeChangeAsHTML($sOldValue, $sNewValue);
}
return $sResult;
}

View File

@@ -428,38 +428,38 @@ abstract class CMDBObject extends DBObject
}
public function DBInsert()
{
return $this->DBInsertTracked_Internal();
}
public function DBInsertTracked(CMDBChange $oChange, $bSkipStrongSecurity = null)
{
self::SetCurrentChange($oChange);
$this->CheckUserRights($bSkipStrongSecurity, UR_ACTION_MODIFY);
$ret = $this->DBInsertTracked_Internal();
return $ret;
}
public function DBInsertTrackedNoReload(CMDBChange $oChange, $bSkipStrongSecurity = null)
{
self::SetCurrentChange($oChange);
$this->CheckUserRights($bSkipStrongSecurity, UR_ACTION_MODIFY);
$ret = $this->DBInsertTracked_Internal(true);
return $ret;
}
/**
* To Be Obsoleted: DO NOT rely on an overload of this method since
* DBInsertTracked (resp. DBInsertTrackedNoReload) may call directly
* DBInsert (resp. DBInsertNoReload) in future versions of iTop.
* @param bool $bDoNotReload
* @return integer Identifier of the created object
*/
protected function DBInsertTracked_Internal($bDoNotReload = false)
{
if ($bDoNotReload)
{
$ret = parent::DBInsertNoReload();
$ret = $this->DBInsertNoReload();
}
else
{
$ret = parent::DBInsert();
$ret = $this->DBInsert();
}
return $ret;
}

View File

@@ -79,7 +79,7 @@ class CMDBSource
// Override the default port
$sServer = $aConnectInfo[0];
$iPort = (int)$aConnectInfo[1];
self::$m_oMysqli = new mysqli(self::$m_sDBHost, self::$m_sDBUser, self::$m_sDBPwd, '', $iPort);
self::$m_oMysqli = new mysqli($sServer, self::$m_sDBUser, self::$m_sDBPwd, '', $iPort);
}
else
{

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2014 Combodo SARL
//
// This file is part of iTop.
//
@@ -96,11 +96,19 @@ class DefaultWorkingTimeComputer implements iWorkingTimeComputer
*/
public function GetDeadline($oObject, $iDuration, DateTime $oStartDate)
{
if (class_exists('WorkingTimeRecorder'))
{
WorkingTimeRecorder::Trace(WorkingTimeRecorder::TRACE_DEBUG, __class__.'::'.__function__);
}
//echo "GetDeadline - default: ".$oStartDate->format('Y-m-d H:i:s')." + $iDuration<br/>\n";
// Default implementation: 24x7, no holidays: to compute the deadline, just add
// the specified duration to the given date/time
$oResult = clone $oStartDate;
$oResult->modify('+'.$iDuration.' seconds');
if (class_exists('WorkingTimeRecorder'))
{
WorkingTimeRecorder::SetValues($oStartDate->format('U'), $oResult->format('U'), $iDuration, WorkingTimeRecorder::COMPUTED_END);
}
return $oResult;
}
@@ -113,8 +121,17 @@ class DefaultWorkingTimeComputer implements iWorkingTimeComputer
*/
public function GetOpenDuration($oObject, DateTime $oStartDate, DateTime $oEndDate)
{
if (class_exists('WorkingTimeRecorder'))
{
WorkingTimeRecorder::Trace(WorkingTimeRecorder::TRACE_DEBUG, __class__.'::'.__function__);
}
//echo "GetOpenDuration - default: ".$oStartDate->format('Y-m-d H:i:s')." to ".$oEndDate->format('Y-m-d H:i:s')."<br/>\n";
return abs($oEndDate->format('U') - $oStartDate->format('U'));
$iDuration = abs($oEndDate->format('U') - $oStartDate->format('U'));
if (class_exists('WorkingTimeRecorder'))
{
WorkingTimeRecorder::SetValues($oStartDate->format('U'), $oEndDate->format('U'), $iDuration, WorkingTimeRecorder::COMPUTED_DURATION);
}
return $iDuration;
}
}

View File

@@ -769,6 +769,70 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'user_rights_legacy' => array(
'type' => 'bool',
'description' => 'Set to true to restore the buggy algorithm for the computation of user rights (within the same profile, ALLOW on the class itself has precedence on DENY of a parent class)',
'default' => false,
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'xlsx_exporter_cleanup_old_files_delay' => array(
'type' => 'int',
'description' => 'Delay (in seconds) for which to let the exported XLSX files on the server so that the user who initiated the export can download the result',
'default' => 86400,
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'xlsx_exporter_memory_limit' => array(
'type' => 'string',
'description' => 'Memory limit to use when (interactively) exporting data to Excel',
'default' => '2048M', // Huuuuuuge 2GB!
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'min_reload_interval' => array(
'type' => 'integer',
'description' => 'Minimum refresh interval (seconds) for dashboards, shortcuts, etc. Even if the interval is set programmatically, it is forced to that minimum',
'default' => 5, // In iTop 2.0.3, this was the hardcoded value
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'relations_max_depth' => array(
'type' => 'integer',
'description' => 'Maximum number of successive levels (depth) to explore when displaying the impact/depends on relations.',
'default' => 20, // In iTop 2.0.3, this was the hardcoded value
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'transaction_storage' => array(
'type' => 'string',
'description' => 'The type of mechanism to use for storing the unique identifiers for transactions (Session|File).',
'default' => 'Session',
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'transactions_enabled' => array(
'type' => 'bool',
'description' => 'Whether or not the whole mechanism to prevent multiple submissions of a page is enabled.',
'default' => true,
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'log_transactions' => array(
'type' => 'bool',
'description' => 'Whether or not to enable the debug log for the transactions.',
'default' => false,
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
);
public function IsProperty($sPropCode)
@@ -1422,6 +1486,8 @@ class Config
$aSettings['log_notification'] = $this->m_bLogNotification;
$aSettings['log_issue'] = $this->m_bLogIssue;
$aSettings['log_web_service'] = $this->m_bLogWebService;
$aSettings['log_queries'] = $this->m_bLogQueries;
$aSettings['query_cache_enabled'] = $this->m_bQueryCacheEnabled;
$aSettings['min_display_limit'] = $this->m_iMinDisplayLimit;
$aSettings['max_display_limit'] = $this->m_iMaxDisplayLimit;
$aSettings['standard_reload_interval'] = $this->m_iStandardReloadInterval;
@@ -1429,6 +1495,7 @@ class Config
$aSettings['secure_connection_required'] = $this->m_bSecureConnectionRequired;
$aSettings['default_language'] = $this->m_sDefaultLanguage;
$aSettings['allowed_login_types'] = $this->m_sAllowedLoginTypes;
$aSettings['ext_auth_variable'] = $this->m_sExtAuthVariable;
$aSettings['encryption_key'] = $this->m_sEncryptionKey;
$aSettings['csv_import_charsets'] = $this->m_aCharsets;
@@ -1494,6 +1561,8 @@ class Config
'log_notification' => $this->m_bLogNotification,
'log_issue' => $this->m_bLogIssue,
'log_web_service' => $this->m_bLogWebService,
'log_queries' => $this->m_bLogQueries,
'query_cache_enabled' => $this->m_bQueryCacheEnabled,
'secure_connection_required' => $this->m_bSecureConnectionRequired,
);
foreach($aBoolValues as $sKey => $bValue)
@@ -1532,6 +1601,7 @@ class Config
'db_collation' => $this->m_sDBCollation,
'default_language' => $this->m_sDefaultLanguage,
'allowed_login_types' => $this->m_sAllowedLoginTypes,
'ext_auth_variable' => $this->m_sExtAuthVariable,
'encryption_key' => $this->m_sEncryptionKey,
'csv_import_charsets' => $this->m_aCharsets,
);

View File

@@ -94,7 +94,9 @@ abstract class DBObject implements iDisplay
private $m_bFullyLoaded = false; // Compound objects can be partially loaded
private $m_aLoadedAtt = array(); // Compound objects can be partially loaded, array of sAttCode
protected $m_aModifiedAtt = array(); // list of (potentially) modified sAttCodes
protected $m_oMasterReplicaSet = null; // Set of SynchroReplica related to this object
protected $m_aSynchroData = null; // Set of Synch data related to this object
protected $m_sHighlightCode = null;
protected $m_aCallbacks = array();
// Use the MetaModel::NewObject to build an object (do we have to force it?)
public function __construct($aRow = null, $sClassAlias = '', $aAttToLoad = null, $aExtendedDataSpec = null)
@@ -337,7 +339,7 @@ abstract class DBObject implements iDisplay
if ($sAttCode == 'finalclass')
{
// Ignore it - this attribute is set upon object creation and that's it
return;
return false;
}
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
@@ -415,6 +417,9 @@ abstract class DBObject implements iDisplay
// Make sure we do not reload it anymore... before saving it
$this->RegisterAsDirty();
// This function is eligible as a lifecycle action: returning true upon success is a must
return true;
}
public function GetLabel($sAttCode)
@@ -557,6 +562,64 @@ abstract class DBObject implements iDisplay
{
return $this->m_aExtendedData;
}
/**
* Set the HighlightCode if the given code has a greater rank than the current HilightCode
* @param string $sCode
* @return void
*/
protected function SetHighlightCode($sCode)
{
$aHighlightScale = MetaModel::GetHighlightScale(get_class($this));
$fCurrentRank = 0.0;
if (($this->m_sHighlightCode !== null) && array_key_exists($this->m_sHighlightCode, $aHighlightScale))
{
$fCurrentRank = $aHighlightScale[$this->m_sHighlightCode]['rank'];
}
if (array_key_exists($sCode, $aHighlightScale))
{
$fRank = $aHighlightScale[$sCode]['rank'];
if ($fRank > $fCurrentRank)
{
$this->m_sHighlightCode = $sCode;
}
}
}
/**
* Get the current HighlightCode
* @return string The Hightlight code (null if none set, meaning rank = 0)
*/
protected function GetHighlightCode()
{
return $this->m_sHighlightCode;
}
protected function ComputeHighlightCode()
{
// First if the state defines a HiglightCode, apply it
$sState = $this->GetState();
if ($sState != '')
{
$sCode = MetaModel::GetHighlightCode(get_class($this), $sState);
$this->SetHighlightCode($sCode);
}
// The check for each StopWatch if a HighlightCode is effective
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode => $oAttDef)
{
if ($oAttDef instanceof AttributeStopWatch)
{
$oStopWatch = $this->Get($sAttCode);
$sCode = $oStopWatch->GetHighlightCode();
if ($sCode !== '')
{
$this->SetHighlightCode($sCode);
}
}
}
return $this->GetHighlightCode();
}
/**
* Updates the value of an external field by (re)loading the object
@@ -785,10 +848,27 @@ abstract class DBObject implements iDisplay
/**
* Get the icon representing this object
* @param boolean $bImgTag If true the result is a full IMG tag (or an emtpy string if no icon is defined)
* @return string Either the full IMG tag ($bImgTag == true) or just the path to the icon file
* @return string Either the full IMG tag ($bImgTag == true) or just the URL to the icon file
*/
public function GetIcon($bImgTag = true)
{
$sCode = $this->ComputeHighlightCode();
if($sCode != '')
{
$aHighlightScale = MetaModel::GetHighlightScale(get_class($this));
if (array_key_exists($sCode, $aHighlightScale))
{
$sIconUrl = $aHighlightScale[$sCode]['icon'];
if($bImgTag)
{
return "<img src=\"$sIconUrl\" style=\"vertical-align:middle\"/>";
}
else
{
return $sIconUrl;
}
}
}
return MetaModel::GetClassIcon(get_class($this), $bImgTag);
}
@@ -1102,54 +1182,51 @@ abstract class DBObject implements iDisplay
if ($this->InSyncScope())
{
$oReplicaSet = $this->GetMasterReplica();
if ($oReplicaSet->Count() > 0)
foreach ($this->GetSynchroData() as $iSourceId => $aSourceData)
{
while($aData = $oReplicaSet->FetchAssoc())
foreach ($aSourceData['replica'] as $oReplica)
{
$oDataSource = $aData['datasource'];
$oReplica = $aData['replica'];
$oDeletionPlan->AddToDelete($oReplica, DEL_SILENT);
}
$oDataSource = $aSourceData['source'];
if ($oDataSource->GetKey() == SynchroExecution::GetCurrentTaskId())
{
// The current task has the right to delete the object
continue;
}
$oReplica = reset($aSourceData['replica']); // Take the first one
if ($oReplica->Get('status_dest_creator') != 1)
{
// The object is not owned by the task
continue;
}
if ($oDataSource->GetKey() == SynchroExecution::GetCurrentTaskId())
{
// The current task has the right to delete the object
continue;
}
if ($oReplica->Get('status_dest_creator') != 1)
{
// The object is not owned by the task
continue;
}
$sLink = $oDataSource->GetName();
$sUserDeletePolicy = $oDataSource->Get('user_delete_policy');
switch($sUserDeletePolicy)
{
case 'nobody':
$this->m_aDeleteIssues[] = Dict::Format('Core:Synchro:TheObjectCannotBeDeletedByUser_Source', $sLink);
break;
$sLink = $oDataSource->GetName();
$sUserDeletePolicy = $oDataSource->Get('user_delete_policy');
switch($sUserDeletePolicy)
case 'administrators':
if (!UserRights::IsAdministrator())
{
case 'nobody':
$this->m_aDeleteIssues[] = Dict::Format('Core:Synchro:TheObjectCannotBeDeletedByUser_Source', $sLink);
break;
case 'administrators':
if (!UserRights::IsAdministrator())
{
$this->m_aDeleteIssues[] = Dict::Format('Core:Synchro:TheObjectCannotBeDeletedByUser_Source', $sLink);
}
break;
case 'everybody':
default:
// Ok
break;
}
break;
case 'everybody':
default:
// Ok
break;
}
}
}
}
final public function CheckToDelete(&$oDeletionPlan)
public function CheckToDelete(&$oDeletionPlan)
{
$this->MakeDeletionPlan($oDeletionPlan);
$oDeletionPlan->ComputeResults();
@@ -1508,6 +1585,15 @@ abstract class DBObject implements iDisplay
$oTrigger->DoActivate($this->ToArgs('this'));
}
// Callbacks registered with RegisterCallback
if (isset($this->m_aCallbacks[self::CALLBACK_AFTERINSERT]))
{
foreach ($this->m_aCallbacks[self::CALLBACK_AFTERINSERT] as $aCallBackData)
{
call_user_func_array($aCallBackData['callback'], $aCallBackData['params']);
}
}
$this->RecordObjCreation();
return $this->m_iKey;
@@ -1998,17 +2084,55 @@ abstract class DBObject implements iDisplay
// array('target_state'=>..., 'actions'=>array of handlers procs, 'user_restriction'=>TBD
$bSuccess = true;
foreach ($aTransitionDef['actions'] as $sActionHandler)
foreach ($aTransitionDef['actions'] as $actionHandler)
{
// std PHP spec
$aActionCallSpec = array($this, $sActionHandler);
if (!is_callable($aActionCallSpec))
if (is_string($actionHandler))
{
throw new CoreException("Unable to call action: ".get_class($this)."::$sActionHandler");
return;
// Old (pre-2.1.0 modules) action definition without any parameter
$aActionCallSpec = array($this, $actionHandler);
if (!is_callable($aActionCallSpec))
{
throw new CoreException("Unable to call action: ".get_class($this)."::$actionHandler");
return;
}
$bRet = call_user_func($aActionCallSpec, $sStimulusCode);
}
else // if (is_array($actionHandler))
{
// New syntax: 'verb' and typed parameters
$sAction = $actionHandler['verb'];
$aParams = array();
foreach($actionHandler['params'] as $aDefinition)
{
$sParamType = array_key_exists('type', $aDefinition) ? $aDefinition['type'] : 'string';
switch($sParamType)
{
case 'int':
$value = (int)$aDefinition['value'];
break;
case 'float':
$value = (float)$aDefinition['value'];
break;
case 'bool':
$value = (bool)$aDefinition['value'];
break;
case 'reference':
$value = ${$aDefinition['value']};
break;
case 'string':
default:
$value = (string)$aDefinition['value'];
}
$aParams[] = $value;
}
$aCallSpec = array($this, $sAction);
$bRet = call_user_func_array($aCallSpec, $aParams);
}
$bRet = call_user_func($aActionCallSpec, $sStimulusCode);
// if one call fails, the whole is considered as failed
if (!$bRet) $bSuccess = false;
}
@@ -2072,6 +2196,80 @@ abstract class DBObject implements iDisplay
$this->Set($sAttCode, $oSW);
}
/**
* Lifecycle action: Recover the default value (aka when an object is being created)
*/
public function Reset($sAttCode)
{
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
$this->Set($sAttCode, $oAttDef->GetDefaultValue());
return true;
}
/**
* Lifecycle action: Copy an attribute to another
*/
public function Copy($sDestAttCode, $sSourceAttCode)
{
$this->Set($sDestAttCode, $this->Get($sSourceAttCode));
return true;
}
/**
* Lifecycle action: Set the current date/time for the given attribute
*/
public function SetCurrentDate($sAttCode)
{
$this->Set($sAttCode, time());
return true;
}
/**
* Lifecycle action: Set the current logged in user for the given attribute
*/
public function SetCurrentUser($sAttCode)
{
$this->Set($sAttCode, UserRights::GetUserId());
return true;
}
/**
* Lifecycle action: Set the time elapsed since a reference point
*/
public function SetElapsedTime($sAttCode, $sRefAttCode, $sWorkingTimeComputer = null)
{
if (is_null($sWorkingTimeComputer))
{
$sWorkingTimeComputer = class_exists('SLAComputation') ? 'SLAComputation' : 'DefaultWorkingTimeComputer';
}
$oComputer = new $sWorkingTimeComputer();
$aCallSpec = array($oComputer, 'GetOpenDuration');
if (!is_callable($aCallSpec))
{
throw new CoreException("Unknown class/verb '$sWorkingTimeComputer/GetOpenDuration'");
}
$iStartTime = AttributeDateTime::GetAsUnixSeconds($this->Get($sRefAttCode));
$oStartDate = new DateTime('@'.$iStartTime); // setTimestamp not available in PHP 5.2
$oEndDate = new DateTime(); // now
if (class_exists('WorkingTimeRecorder'))
{
$sClass = get_class($this);
WorkingTimeRecorder::Start($this, time(), "DBObject-SetElapsedTime-$sAttCode-$sRefAttCode", 'Core:ExplainWTC:ElapsedTime', array("Class:$sClass/Attribute:$sAttCode"));
}
$iElapsed = call_user_func($aCallSpec, $this, $oStartDate, $oEndDate);
if (class_exists('WorkingTimeRecorder'))
{
WorkingTimeRecorder::End();
}
$this->Set($sAttCode, $iElapsed);
return true;
}
/*
* Create query parameters (SELECT ... WHERE service = :this->service_id)
* to be used with the APIs DBObjectSearch/DBObjectSet
@@ -2474,45 +2672,77 @@ abstract class DBObject implements iDisplay
}
/**
* WILL DEPRECATED SOON
* Caching relying on an object set is not efficient since 2.0.3
* Use GetSynchroData instead
*
* Get all the synchro replica related to this object
* @param none
* @return DBObjectSet Set with two columns: R=SynchroReplica S=SynchroDataSource
*/
public function GetMasterReplica()
{
if ($this->m_oMasterReplicaSet == null)
$sOQL = "SELECT replica,datasource FROM SynchroReplica AS replica JOIN SynchroDataSource AS datasource ON replica.sync_source_id=datasource.id WHERE replica.dest_class = :dest_class AND replica.dest_id = :dest_id";
$oReplicaSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), array() /* order by*/, array('dest_class' => get_class($this), 'dest_id' => $this->GetKey()));
return $oReplicaSet;
}
/**
* Get all the synchro data related to this object
* @param none
* @return array of data_source_id => array
* 'source' => $oSource,
* 'attributes' => array of $oSynchroAttribute
* 'replica' => array of $oReplica (though only one should exist, misuse of the data sync can have this consequence)
*/
public function GetSynchroData()
{
if ($this->m_aSynchroData == null)
{
//$aParentClasses = MetaModel::EnumParentClasses(get_class($this), ENUM_PARENT_CLASSES_ALL);
//$sClassesList = "'".implode("','", $aParentClasses)."'";
$sOQL = "SELECT replica,datasource FROM SynchroReplica AS replica JOIN SynchroDataSource AS datasource ON replica.sync_source_id=datasource.id WHERE replica.dest_class = :dest_class AND replica.dest_id = :dest_id";
$oReplicaSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), array() /* order by*/, array('dest_class' => get_class($this), 'dest_id' => $this->GetKey()));
$this->m_oMasterReplicaSet = $oReplicaSet;
$this->m_aSynchroData = array();
while($aData = $oReplicaSet->FetchAssoc())
{
$iSourceId = $aData['datasource']->GetKey();
if (!array_key_exists($iSourceId, $this->m_aSynchroData))
{
$aAttributes = array();
$oAttrSet = $aData['datasource']->Get('attribute_list');
while($oSyncAttr = $oAttrSet->Fetch())
{
$aAttributes[$oSyncAttr->Get('attcode')] = $oSyncAttr;
}
$this->m_aSynchroData[$iSourceId] = array(
'source' => $aData['datasource'],
'attributes' => $aAttributes,
'replica' => array()
);
}
// Assumption: $aData['datasource'] will not be null because the data source id is always set...
$this->m_aSynchroData[$iSourceId]['replica'][] = $aData['replica'];
}
}
else
{
$this->m_oMasterReplicaSet->Rewind();
}
return $this->m_oMasterReplicaSet;
return $this->m_aSynchroData;
}
public function GetSynchroReplicaFlags($sAttCode, &$aReason)
{
$iFlags = OPT_ATT_NORMAL;
$oSet = $this->GetMasterReplica();
while($aData = $oSet->FetchAssoc())
foreach ($this->GetSynchroData() as $iSourceId => $aSourceData)
{
if ($aData['datasource']->GetKey() == SynchroExecution::GetCurrentTaskId())
if ($iSourceId == SynchroExecution::GetCurrentTaskId())
{
// Ignore the current task (check to write => ok)
continue;
}
// Assumption: $aData['datasource'] will not be null because the data source id is always set...
$oReplica = $aData['replica'];
$oSource = $aData['datasource'];
$oAttrSet = $oSource->Get('attribute_list');
while($oSyncAttr = $oAttrSet->Fetch())
// Assumption: one replica - take the first one!
$oReplica = reset($aSourceData['replica']);
$oSource = $aSourceData['source'];
if (array_key_exists($sAttCode, $aSourceData['attributes']))
{
if (($oSyncAttr->Get('attcode') == $sAttCode) && ($oSyncAttr->Get('update') == 1) && ($oSyncAttr->Get('update_policy') == 'master_locked'))
$oSyncAttr = $aSourceData['attributes'][$sAttCode];
if (($oSyncAttr->Get('update') == 1) && ($oSyncAttr->Get('update_policy') == 'master_locked'))
{
$iFlags |= OPT_ATT_SLAVE;
$sUrl = $oSource->GetApplicationUrl($this, $oReplica);
@@ -2563,6 +2793,15 @@ abstract class DBObject implements iDisplay
public function GetHilightClass()
{
$sCode = $this->ComputeHighlightCode();
if($sCode != '')
{
$aHighlightScale = MetaModel::GetHighlightScale(get_class($this));
if (array_key_exists($sCode, $aHighlightScale))
{
return $aHighlightScale[$sCode]['color'];
}
}
return HILIGHT_CLASS_NONE;
}
@@ -2581,6 +2820,28 @@ abstract class DBObject implements iDisplay
}
$oPage->details($aValues);
}
const CALLBACK_AFTERINSERT = 0;
/**
* Register a call back that will be called when some internal event happens
*
* @param $iType string Any of the CALLBACK_x constants
* @param $callback callable Call specification like a function name, or array('<class>', '<method>') or array($object, '<method>')
* @param $aParameters Array Values that will be passed to the callback, after $this
*/
public function RegisterCallback($iType, $callback, $aParameters = array())
{
$sCallBackName = '';
if (!is_callable($callback, false, $sCallBackName))
{
throw new Exception('Registering an unknown/protected function or wrong syntax for the call spec: '.$sCallBackName);
}
$this->m_aCallbacks[$iType][] = array(
'callback' => $callback,
'params' => $aParameters
);
}
}

View File

@@ -53,9 +53,8 @@ class DBObjectSearch
public function __construct($sClass, $sClassAlias = null)
{
if (is_null($sClassAlias)) $sClassAlias = $sClass;
assert('is_string($sClass)');
assert('MetaModel::IsValidClass($sClass)'); // #@# could do better than an assert, or at least give the caller's reference
// => idee d'un assert avec call stack (autre utilisation = echec sur query SQL)
if(!is_string($sClass)) throw new Exception('DBObjectSearch::__construct called with a non-string parameter: $sClass = '.print_r($sClass, true));
if(!MetaModel::IsValidClass($sClass)) throw new Exception('DBObjectSearch::__construct called for an invalid class: "'.$sClass.'"');
$this->m_aSelectedClasses = array($sClassAlias => $sClass);
$this->m_aClasses = array($sClassAlias => $sClass);
@@ -449,7 +448,7 @@ class DBObjectSearch
public function AddCondition($sFilterCode, $value, $sOpCode = null)
{
MyHelpers::CheckKeyInArray('filter code', $sFilterCode, MetaModel::GetClassFilterDefs($this->GetClass()));
MyHelpers::CheckKeyInArray('filter code in class: '.$this->GetClass(), $sFilterCode, MetaModel::GetClassFilterDefs($this->GetClass()));
$oFilterDef = MetaModel::GetClassFilterDef($this->GetClass(), $sFilterCode);
$oField = new FieldExpression($sFilterCode, $this->GetClassAlias());
@@ -972,6 +971,9 @@ class DBObjectSearch
// Alternative to object mapping: the data are transfered directly into an array
// This is 10 times faster than creating a set of objects, and makes sense when optimization is required
/**
* @param hash $aOrderBy Array of '[<classalias>.]attcode' => bAscending
*/
public function ToDataArray($aColumns = array(), $aOrderBy = array(), $aArgs = array())
{
$sSQL = MetaModel::MakeSelectQuery($this, $aOrderBy, $aArgs);

View File

@@ -48,7 +48,7 @@ class DBObjectSet
* Create a new set based on a Search definition.
*
* @param DBObjectSearch $oFilter The search filter defining the objects which are part of the set (multiple columns/objects per row are supported)
* @param hash $aOrderBy
* @param hash $aOrderBy Array of '[<classalias>.]attcode' => bAscending
* @param hash $aArgs Values to substitute for the search/query parameters (if any). Format: param_name => value
* @param hash $aExtendedDataSpec
* @param int $iLimitCount Maximum number of rows to load (i.e. equivalent to MySQL's LIMIT start, count)
@@ -654,7 +654,11 @@ class DBObjectSet
else
{
// Pick the row from the objects added *in memory*
$oRetObj = $this->m_aAddedObjects[$this->m_iCurrRow - $this->m_iNumLoadedDBRows][$sRequestedClassAlias];
$aRetObjects = array();
foreach ($this->m_oFilter->GetSelectedClasses() as $sClassAlias => $sClass)
{
$aRetObjects[$sClassAlias] = $this->m_aAddedObjects[$this->m_iCurrRow - $this->m_iNumLoadedDBRows][$sClassAlias];
}
}
$this->m_iCurrRow++;
return $aRetObjects;
@@ -1094,8 +1098,11 @@ class DBObjectSet
{
foreach($aVals as $sCode => $oExpr)
{
$oScalarExpr = $oExpr->GetAsScalar($aScalarArgs);
$aConst[$sClassAlias][$sCode] = $oScalarExpr->GetValue();
if (is_object($oExpr)) // Array_merge_recursive creates an array when the same key is present multiple times... ignore them
{
$oScalarExpr = $oExpr->GetAsScalar($aScalarArgs);
$aConst[$sClassAlias][$sCode] = $oScalarExpr->GetValue();
}
}
}
return $aConst;
@@ -1149,5 +1156,3 @@ function HashCountComparison($a, $b) // Sort descending on 'count'
}
return ($a['count'] > $b['count']) ? -1 : 1;
}
?>

View File

@@ -177,6 +177,15 @@ class EMail
}
break;
case 'Null':
$oTransport = Swift_NullTransport::newInstance();
break;
case 'LogFile':
$oTransport = Swift_LogFileTransport::newInstance();
$oTransport->setLogFile(APPROOT.'log/mail.log');
break;
case 'PHPMail':
default:
$oTransport = Swift_MailTransport::newInstance();
@@ -396,4 +405,75 @@ class EMail
}
?>
/////////////////////////////////////////////////////////////////////////////////////
/**
* Extension to SwiftMailer: "debug" transport that pretends messages have been sent,
* but just log them to a file.
*
* @package Swift
* @author Denis Flaven
*/
class Swift_Transport_LogFileTransport extends Swift_Transport_NullTransport
{
protected $sLogFile;
/**
* Sends the given message.
*
* @param Swift_Mime_Message $message
* @param string[] $failedRecipients An array of failures by-reference
*
* @return int The number of sent emails
*/
public function send(Swift_Mime_Message $message, &$failedRecipients = null)
{
$hFile = @fopen($this->sLogFile, 'a');
if ($hFile)
{
$sTxt = "================== ".date('Y-m-d H:i:s')." ==================\n";
$sTxt .= $message->toString()."\n";
@fwrite($hFile, $sTxt);
@fclose($hFile);
}
return parent::send($message, $failedRecipients);
}
public function setLogFile($sFilename)
{
$this->sLogFile = $sFilename;
}
}
/**
* Pretends messages have been sent, but just log them to a file.
*
* @package Swift
* @author Denis Flaven
*/
class Swift_LogFileTransport extends Swift_Transport_LogFileTransport
{
/**
* Create a new LogFileTransport.
*/
public function __construct()
{
call_user_func_array(
array($this, 'Swift_Transport_LogFileTransport::__construct'),
Swift_DependencyContainer::getInstance()
->createDependenciesFor('transport.null')
);
}
/**
* Create a new LogFileTransport instance.
*
* @return Swift_LogFileTransport
*/
public static function newInstance()
{
return new self();
}
}

View File

@@ -351,7 +351,7 @@ class EventLoginUsage extends Event
MetaModel::Init_Params($aParams);
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 AttributeExternalKey("user_id", array("targetclass"=>"User", "jointype"=> "", "allowed_values"=>null, "sql"=>"user_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_SILENT, "depends_on"=>array())));
$aZList = array('date', 'user_id');
if (MetaModel::IsValidAttCode('Contact', 'name'))
{

View File

@@ -299,14 +299,11 @@ class BinaryExpression extends Expression
{
$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
else if ($this->m_sOperator == 'AND')
{
$aResult = array_merge($this->m_oRightExpr->ListConstantFields(), $this->m_oLeftExpr->ListConstantFields()) ;
// Strictly, this should be done only for the AND operator
$aResult = array_merge_recursive($this->m_oRightExpr->ListConstantFields(), $this->m_oLeftExpr->ListConstantFields());
}
return $aResult;
}

View File

@@ -59,8 +59,11 @@ class FileLog
$hLogFile = @fopen($this->m_sFile, 'a');
if ($hLogFile !== false)
{
flock($hLogFile, LOCK_EX);
$sDate = date('Y-m-d H:i:s');
fwrite($hLogFile, "$sDate | $sText\n");
fflush($hLogFile);
flock($hLogFile, LOCK_UN);
fclose($hLogFile);
}
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2013 Combodo SARL
// Copyright (C) 2010-2014 Combodo SARL
//
// This file is part of iTop.
//
@@ -279,6 +279,7 @@ abstract class MetaModel
private static $m_aChildClasses = array(); // array of ("classname" => array of "childclass")
private static $m_aClassParams = array(); // array of ("classname" => array of class information)
private static $m_aHighlightScales = array(); // array of ("classname" => array of highlightscale information)
static public function GetParentPersistentClass($sRefClass)
{
@@ -835,6 +836,10 @@ abstract class MetaModel
final static public function GetAttributeDef($sClass, $sAttCode)
{
self::_check_subclass($sClass);
if (!isset(self::$m_aAttribDefs[$sClass][$sAttCode]))
{
throw new Exception("Unknown attribute $sAttCode from class $sClass");
}
return self::$m_aAttribDefs[$sClass][$sAttCode];
}
@@ -1908,6 +1913,50 @@ abstract class MetaModel
self::$m_aTransitions[$sTargetClass][$sStateCode] = array();
}
public static function Init_DefineHighlightScale($aHighlightScale)
{
$sTargetClass = self::GetCallersPHPClass("Init");
self::$m_aHighlightScales[$sTargetClass] = $aHighlightScale;
}
public static function GetHighlightScale($sTargetClass)
{
$aScale = array();
$aParentScale = array();
$sParentClass = self::GetParentPersistentClass($sTargetClass);
if (!empty($sParentClass))
{
// inherit the scale from the parent class
$aParentScale = self::GetHighlightScale($sParentClass);
}
if (array_key_exists($sTargetClass, self::$m_aHighlightScales))
{
$aScale = self::$m_aHighlightScales[$sTargetClass];
}
return array_merge($aParentScale, $aScale); // Merge both arrays, the values from the last one have precedence
}
public static function GetHighlightCode($sTargetClass, $sStateCode)
{
$sCode = '';
if ( array_key_exists($sTargetClass, self::$m_aStates)
&& array_key_exists($sStateCode, self::$m_aStates[$sTargetClass])
&& array_key_exists('highlight', self::$m_aStates[$sTargetClass][$sStateCode]) )
{
$sCode = self::$m_aStates[$sTargetClass][$sStateCode]['highlight']['code'];
}
else
{
// Check the parent's definition
$sParentClass = self::GetParentPersistentClass($sTargetClass);
if (!empty($sParentClass))
{
$sCode = self::GetHighlightCode($sParentClass, $sStateCode);
}
}
return $sCode;
}
public static function Init_OverloadStateAttribute($sStateCode, $sAttCode, $iFlags)
{
// Warning: this is not sufficient: the flags have to be copied to the states that are inheriting from this state
@@ -2122,6 +2171,10 @@ abstract class MetaModel
{
$aScalarArgs[$sArgName] = (string) $value;
}
elseif (is_null($value))
{
$aScalarArgs[$sArgName] = null;
}
}
}
// Add standard contextual arguments
@@ -2169,6 +2222,9 @@ abstract class MetaModel
}
/**
* @param hash $aOrderBy Array of '[<classalias>.]attcode' => bAscending
*/
public static function MakeSelectQuery(DBObjectSearch $oFilter, $aOrderBy = array(), $aArgs = array(), $aAttToLoad = null, $aExtendedDataSpec = null, $iLimitCount = 0, $iLimitStart = 0, $bGetCount = false)
{
// Check the order by specification, and prefix with the class alias
@@ -2179,32 +2235,44 @@ abstract class MetaModel
$aOrderSpec = array();
foreach ($aOrderBy as $sFieldAlias => $bAscending)
{
if ($sFieldAlias != 'id')
{
MyHelpers::CheckValueInArray('field name in ORDER BY spec', $sFieldAlias, self::GetAttributesList($sClass));
}
if (!is_bool($bAscending))
{
throw new CoreException("Wrong direction in ORDER BY spec, found '$bAscending' and expecting a boolean value");
}
if (self::IsValidAttCode($sClass, $sFieldAlias))
$iDotPos = strpos($sFieldAlias, '.');
if ($iDotPos === false)
{
$oAttDef = self::GetAttributeDef($sClass, $sFieldAlias);
foreach($oAttDef->GetOrderBySQLExpressions($sClassAlias) as $sSQLExpression)
$sAttClass = $sClass;
$sAttClassAlias = $sClassAlias;
$sAttCode = $sFieldAlias;
}
else
{
$sAttClassAlias = substr($sFieldAlias, 0, $iDotPos);
$sAttClass = $oFilter->GetClassName($sAttClassAlias);
$sAttCode = substr($sFieldAlias, $iDotPos + 1);
}
if ($sAttCode != 'id')
{
MyHelpers::CheckValueInArray('field name in ORDER BY spec', $sAttCode, self::GetAttributesList($sAttClass));
$oAttDef = self::GetAttributeDef($sAttClass, $sAttCode);
foreach($oAttDef->GetOrderBySQLExpressions($sAttClassAlias) as $sSQLExpression)
{
$aOrderSpec[$sSQLExpression] = $bAscending;
}
}
else
{
$aOrderSpec['`'.$sClassAlias.$sFieldAlias.'`'] = $bAscending;
$aOrderSpec['`'.$sAttClassAlias.$sAttCode.'`'] = $bAscending;
}
// Make sure that the columns used for sorting are present in the loaded columns
if (!is_null($aAttToLoad) && !isset($aAttToLoad[$sClassAlias][$sFieldAlias]))
if (!is_null($aAttToLoad) && !isset($aAttToLoad[$sAttClassAlias][$sAttCode]))
{
$aAttToLoad[$sClassAlias][$sFieldAlias] = MetaModel::GetAttributeDef($sClass, $sFieldAlias);
$aAttToLoad[$sAttClassAlias][$sAttCode] = MetaModel::GetAttributeDef($sAttClass, $sAttCode);
}
}
@@ -3011,7 +3079,7 @@ abstract class MetaModel
$sExtAttCode = $oAtt->GetExtAttCode();
// Translate mainclass.extfield => remoteclassalias.remotefieldcode
$oRemoteAttDef = self::GetAttributeDef($sKeyClass, $sExtAttCode);
foreach ($oRemoteAttDef->GetSQLExpressions() as $sColID => $sRemoteAttExpr)
foreach ($oRemoteAttDef->GetSQLExpressions() as $sColId => $sRemoteAttExpr)
{
$aTranslateNow[$sTargetAlias][$sAttCode.$sColId] = new FieldExpression($sExtAttCode, $sKeyClassAlias);
//echo "<p><b>aTranslateNow[$sTargetAlias][$sAttCode.$sColId] = new FieldExpression($sExtAttCode, $sKeyClassAlias);</b></p>\n";
@@ -3386,6 +3454,33 @@ abstract class MetaModel
{
// Do nothing...
}
else if ($oAttDef instanceof AttributeStopWatch)
{
$aThresholds = $oAttDef->ListThresholds();
if (is_array($aThresholds))
{
foreach($aThresholds as $iPercent => $aDef)
{
if (array_key_exists('highlight', $aDef))
{
if(!array_key_exists('code', $aDef['highlight']))
{
$aErrors[$sClass][] = "The 'code' element is missing for the 'highlight' property of the $iPercent% threshold in the attribute: '$sAttCode'.";
$aSugFix[$sClass][] = "Add a 'code' entry specifying the value of the highlight code for this threshold.";
}
else
{
$aScale = self::GetHighlightScale($sClass);
if (!array_key_exists($aDef['highlight']['code'], $aScale))
{
$aErrors[$sClass][] = "'{$aDef['highlight']['code']}' is not a valid value for the 'code' element of the $iPercent% threshold in the attribute: '$sAttCode'.";
$aSugFix[$sClass][] = "The possible highlight codes for this class are: ".implode(', ', array_keys($aScale)).".";
}
}
}
}
}
}
else // standard attributes
{
// Check that the default values definition is a valid object!
@@ -3483,17 +3578,46 @@ abstract class MetaModel
}
}
// Lifcycle - check that the action handlers are defined
// Lifecycle - check that the action handlers are defined
foreach (self::EnumStates($sClass) as $sStateCode => $aStateDef)
{
foreach(self::EnumTransitions($sClass, $sStateCode) as $sStimulusCode => $aTransitionDef)
{
foreach ($aTransitionDef['actions'] as $sActionHandler)
foreach ($aTransitionDef['actions'] as $actionHandler)
{
if (!method_exists($sClass, $sActionHandler))
if (is_string($actionHandler))
{
$aErrors[$sClass][] = "Unknown function '$sActionHandler' in transition [$sStateCode/$sStimulusCode] for state attribute '$sStateAttCode'";
$aSugFix[$sClass][] = "Specify a function which prototype is in the form [public function $sActionHandler(\$sStimulusCode){return true;}]";
if (!method_exists($sClass, $actionHandler))
{
$aErrors[$sClass][] = "Unknown function '$sActionHandler' in transition [$sStateCode/$sStimulusCode] for state attribute '$sStateAttCode'";
$aSugFix[$sClass][] = "Specify a function which prototype is in the form [public function $sActionHandler(\$sStimulusCode){return true;}]";
}
}
else // if(is_array($actionHandler))
{
$sActionHandler = $actionHandler['verb'];
if (!method_exists($sClass, $sActionHandler))
{
$aErrors[$sClass][] = "Unknown function '$sActionHandler' in transition [$sStateCode/$sStimulusCode] for state attribute '$sStateAttCode'";
$aSugFix[$sClass][] = "Specify a function which prototype is in the form [public function $sActionHandler(...){return true;}]";
}
}
}
}
if (array_key_exists('highlight', $aStateDef))
{
if(!array_key_exists('code', $aStateDef['highlight']))
{
$aErrors[$sClass][] = "The 'code' element is missing for the 'highlight' property of state: '$sStateCode'.";
$aSugFix[$sClass][] = "Add a 'code' entry specifying the value of the highlight code for this state.";
}
else
{
$aScale = self::GetHighlightScale($sClass);
if (!array_key_exists($aStateDef['highlight']['code'], $aScale))
{
$aErrors[$sClass][] = "'{$aStateDef['highlight']['code']}' is not a valid value for the 'code' element in the 'highlight' property of state: '$sStateCode'.";
$aSugFix[$sClass][] = "The possible highlight codes for this class are: ".implode(', ', array_keys($aScale)).".";
}
}
}
@@ -3681,7 +3805,7 @@ abstract class MetaModel
}
public static function DBCreate()
public static function DBCreate($aCallback = null)
{
// Note: we have to check if the DB does exist, because we may share the DB
// with other applications (in which case the DB does exist, not the tables with the given prefix)
@@ -3689,18 +3813,24 @@ abstract class MetaModel
{
CMDBSource::CreateDB(self::$m_sDBName);
}
self::DBCreateTables();
self::DBCreateTables($aCallback);
self::DBCreateViews();
}
protected static function DBCreateTables()
protected static function DBCreateTables($aCallback = null)
{
list($aErrors, $aSugFix, $aCondensedQueries) = self::DBCheckFormat();
//$sSQL = implode('; ', $aCondensedQueries); Does not work - multiple queries not allowed
foreach($aCondensedQueries as $sQuery)
{
$fStart = microtime(true);
CMDBSource::CreateTable($sQuery);
$fDuration = microtime(true) - $fStart;
if ($aCallback != null)
{
call_user_func($aCallback, $sQuery, $fDuration);
}
}
}
@@ -4718,6 +4848,7 @@ abstract class MetaModel
self::$m_aStates = $result['m_aStates'];
self::$m_aStimuli = $result['m_aStimuli'];
self::$m_aTransitions = $result['m_aTransitions'];
self::$m_aHighlightScales = $result['m_aHighlightScales'];
}
$oKPI->ComputeAndReport('Metamodel APC (fetch + read)');
}
@@ -4754,6 +4885,7 @@ abstract class MetaModel
$aCache['m_aStates'] = self::$m_aStates; // array of ("classname" => array of "statecode"=>array('label'=>..., attribute_inherit=> attribute_list=>...))
$aCache['m_aStimuli'] = self::$m_aStimuli; // array of ("classname" => array of ("stimuluscode"=>array('label'=>...)))
$aCache['m_aTransitions'] = self::$m_aTransitions; // array of ("classname" => array of ("statcode_from"=>array of ("stimuluscode" => array('target_state'=>..., 'actions'=>array of handlers procs, 'user_restriction'=>TBD)))
$aCache['m_aHighlightScales'] = self::$m_aHighlightScales; // array of ("classname" => array of higlightcodes)))
apc_store($sOqlAPCCacheId, $aCache);
$oKPI->ComputeAndReport('Metamodel APC (store)');
}
@@ -4815,7 +4947,15 @@ abstract class MetaModel
if (!file_exists($sFile))
{
$sConfigFile = self::$m_oConfig->GetLoadedFile();
throw new CoreException('Wrong filename in configuration file', array('file' => $sConfigFile, 'module' => $sModuleType, 'filename' => $sFile));
if (strlen($sConfigFile) > 0)
{
throw new CoreException('Include: wrong file name in configuration file', array('config file' => $sConfigFile, 'section' => $sModuleType, 'filename' => $sFile));
}
else
{
// The configuration is in memory only
throw new CoreException('Include: wrong file name in configuration file (in memory)', array('section' => $sModuleType, 'filename' => $sFile));
}
}
// Note: We do not expect the modules to output characters while loading them.
@@ -5292,9 +5432,11 @@ abstract class MetaModel
{
// Some environment parameters are objects, we just need scalars
if (is_object($replace)) continue;
$aSearches[] = '$'.$sSearch.'$';
$aReplacements[] = (string) $replace;
else
{
$aSearches[] = '$'.$sSearch.'$';
$aReplacements[] = (string) $replace;
}
}
return str_replace($aSearches, $aReplacements, $aInput);
}
@@ -5394,5 +5536,3 @@ MetaModel::RegisterZList("preview", array("description"=>"All attributes visible
MetaModel::RegisterZList("standard_search", array("description"=>"List of criteria for the standard search", "type"=>"filters"));
MetaModel::RegisterZList("advanced_search", array("description"=>"List of criteria for the advanced search", "type"=>"filters"));
?>

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2013 Combodo SARL
// Copyright (C) 2013-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -24,7 +24,7 @@
* Relies on MySQL locks because the API sem_get is not always present in the
* installed PHP.
*
* @copyright Copyright (C) 2013 Combodo SARL
* @copyright Copyright (C) 2013-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
class iTopMutex
@@ -37,8 +37,19 @@ class iTopMutex
public function __construct($sName, $sDBHost = null, $sDBUser = null, $sDBPwd = null)
{
// Compute the name of a lock for mysql
// Note: the name is server-wide!!!
// Note: names are server-wide!!! So let's make the name specific to this iTop instance
$oConfig = utils::GetConfig(); // Will return an empty config when called during the setup
$sDBName = $oConfig->GetDBName();
$sDBSubname = $oConfig->GetDBSubname();
$this->sName = 'itop.'.$sName;
if (substr($sName, -strlen($sDBName.$sDBSubname)) != $sDBName.$sDBSubname)
{
// If the name supplied already ends with the expected suffix
// don't add it twice, since the setup may try to detect an already
// running cron job by its mutex, without knowing if the config already exists or not
$this->sName .= $sDBName.$sDBSubname;
}
$this->bLocked = false; // Not yet locked
if (!array_key_exists($this->sName, self::$aAcquiredLocks))
@@ -48,7 +59,6 @@ class iTopMutex
// It is a MUST to create a dedicated session each time a lock is required, because
// using GET_LOCK anytime on the same session will RELEASE the current and unique session lock (known issue)
$oConfig = utils::GetConfig();
$sDBHost = is_null($sDBHost) ? $oConfig->GetDBHost() : $sDBHost;
$sDBUser = is_null($sDBUser) ? $oConfig->GetDBUser() : $sDBUser;
$sDBPwd = is_null($sDBPwd) ? $oConfig->GetDBPwd() : $sDBPwd;
@@ -121,7 +131,43 @@ class iTopMutex
$this->bLocked = true;
self::$aAcquiredLocks[$this->sName]++;
}
return ($res === '1');
if (($res !== '1') && ($res !== '0'))
{
$sMsg = 'GET_LOCK('.$this->sName.', 0) returned: '.var_export($res, true).'. Expected values are: 0, 1 or null';
IssueLog::Error($sMsg);
throw new Exception($sMsg);
}
return ($res !== '0');
}
/**
* Check if the mutex is locked WITHOUT TRYING TO ACQUIRE IT
* @returns bool True if the mutex is in use, false otherwise
*/
public function IsLocked()
{
if ($this->bLocked)
{
return true; // Already acquired
}
if (self::$aAcquiredLocks[$this->sName] > 0)
{
return true;
}
$res = $this->QueryToScalar("SELECT IS_FREE_LOCK('".$this->sName."')"); // IS_FREE_LOCK detects some error cases that IS_USED_LOCK do not detect
if (is_null($res))
{
$sMsg = "MySQL Error, IS_FREE_LOCK('".$this->sName."') returned null. Error (".mysqli_errno($this->hDBLink).") = '".mysqli_error($this->hDBLink)."'";
IssueLog::Error($sMsg);
throw new Exception($sMsg);
}
else if ($res == '1')
{
// Lock is free
return false;
}
return true;
}
/**

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
@@ -23,7 +23,7 @@ define('CASELOG_SEPARATOR', "\n".'========== %1$s : %2$s (%3$d) ============'."\
/**
* Class to store a "case log" in a structured way, keeping track of its successive entries
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2015 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
class ormCaseLog {
@@ -384,7 +384,7 @@ class ormCaseLog {
}
public function AddLogEntryFromJSON($oJson)
public function AddLogEntryFromJSON($oJson, $bCheckUserId = true)
{
$sText = isset($oJson->message) ? $oJson->message : '';
@@ -394,16 +394,24 @@ class ormCaseLog {
{
throw new Exception("Only administrators can set the user id", RestResult::UNAUTHORIZED);
}
try
if ($bCheckUserId && ($oJson->user_id != 0))
{
$oUser = RestUtils::FindObjectFromKey('User', $oJson->user_id);
try
{
$oUser = RestUtils::FindObjectFromKey('User', $oJson->user_id);
}
catch(Exception $e)
{
throw new Exception('user_id: '.$e->getMessage(), $e->getCode());
}
$iUserId = $oUser->GetKey();
$sOnBehalfOf = $oUser->GetFriendlyName();
}
catch(Exception $e)
else
{
throw new Exception('user_id: '.$e->getMessage(), $e->getCode());
$iUserId = $oJson->user_id;
$sOnBehalfOf = $oJson->user_login;
}
$iUserId = $oUser->GetKey();
$sOnBehalfOf = $oUser->GetFriendlyName();
}
else
{

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2013 Combodo SARL
// Copyright (C) 2010-2014 Combodo SARL
//
// This file is part of iTop.
//
@@ -62,12 +62,13 @@ class ormStopWatch
return (string) $this->iTimeSpent;
}
public function DefineThreshold($iPercent, $tDeadline = null, $bPassed = false, $bTriggered = false, $iOverrun = null)
public function DefineThreshold($iPercent, $tDeadline = null, $bPassed = false, $bTriggered = false, $iOverrun = null, $aHighlightDef = null)
{
$this->aThresholds[$iPercent] = array(
'deadline' => $tDeadline, // unix time (seconds)
'triggered' => $bTriggered,
'overrun' => $iOverrun
'overrun' => $iOverrun,
'highlight' => $aHighlightDef, // array('code' => string, 'persistent' => boolean)
);
}
@@ -81,6 +82,26 @@ class ormStopWatch
return $this->iTimeSpent;
}
/**
* Get the working elapsed time since the start of the stop watch
* even if it is currently running
* @param oAttDef AttributeDefinition Attribute hosting the stop watch
* @param oObject Hosting object (used for query parameters)
*/
public function GetElapsedTime($oAttDef, $oObject)
{
if (is_null($this->iLastStart))
{
return $this->GetTimeSpent();
}
else
{
$iElapsed = $this->ComputeDuration($oObject, $oAttDef, $this->iLastStart, time());
return $this->iTimeSpent + $iElapsed;
}
}
public function GetStartDate()
{
return $this->iStarted;
@@ -129,6 +150,10 @@ class ormStopWatch
{
$bRet = true;
}
if (isset($aThresholdData['overrun']) && ($aThresholdData['overrun'] > 0))
{
$bRet = true;
}
}
return $bRet;
}
@@ -143,6 +168,31 @@ class ormStopWatch
return false;
}
}
public function GetHighlightCode()
{
$sCode = '';
// Process the thresholds in ascending order
$aPercents = array();
foreach($this->aThresholds as $iPercent => $aDefs)
{
$aPercents[] = $iPercent;
}
sort($aPercents, SORT_NUMERIC);
foreach($aPercents as $iPercent)
{
$aDefs = $this->aThresholds[$iPercent];
if (array_key_exists('highlight', $aDefs) && is_array($aDefs['highlight']) && $this->IsThresholdPassed($iPercent))
{
// If persistant or SW running...
if (($aDefs['highlight']['persistent'] == true) || (($aDefs['highlight']['persistent'] == false) && !is_null($this->iLastStart)))
{
$sCode = $aDefs['highlight']['code'];
}
}
}
return $sCode;
}
public function GetAsHTML($oAttDef, $oHostObject = null)
{
@@ -281,7 +331,7 @@ class ormStopWatch
* It is the responsibility of the caller to compute the deadlines
* (to avoid computing twice for the same result)
*/
public function Start($oObject, $oAttDef)
public function Start($oObject, $oAttDef, $iNow = null)
{
if (!is_null($this->iLastStart))
{
@@ -289,11 +339,16 @@ class ormStopWatch
return false;
}
if (is_null($iNow))
{
$iNow = time();
}
if (is_null($this->iStarted))
{
$this->iStarted = time();
$this->iStarted = $iNow;
}
$this->iLastStart = time();
$this->iLastStart = $iNow;
$this->iStopped = null;
return true;
@@ -309,8 +364,9 @@ class ormStopWatch
// Currently stopped - do nothing
return false;
}
$iDurationGoal = $this->ComputeGoal($oObject, $oAttDef);
$iComputationRefTime = time();
foreach ($this->aThresholds as $iPercent => &$aThresholdData)
{
if (is_null($iDurationGoal))
@@ -321,9 +377,20 @@ class ormStopWatch
else
{
$iThresholdDuration = round($iPercent * $iDurationGoal / 100);
if (class_exists('WorkingTimeRecorder'))
{
$sClass = get_class($oObject);
$sAttCode = $oAttDef->GetCode();
WorkingTimeRecorder::Start($oObject, $iComputationRefTime, "ormStopWatch-Deadline-$iPercent-$sAttCode", 'Core:ExplainWTC:StopWatch-Deadline', array("Class:$sClass/Attribute:$sAttCode", $iPercent));
}
$aThresholdData['deadline'] = $this->ComputeDeadline($oObject, $oAttDef, $this->iLastStart, $iThresholdDuration - $this->iTimeSpent);
// OR $aThresholdData['deadline'] = $this->ComputeDeadline($oObject, $oAttDef, $this->iStarted, $iThresholdDuration);
if (class_exists('WorkingTimeRecorder'))
{
WorkingTimeRecorder::End();
}
}
if (is_null($aThresholdData['deadline']) || ($aThresholdData['deadline'] > time()))
{
@@ -353,8 +420,18 @@ class ormStopWatch
return false;
}
if (class_exists('WorkingTimeRecorder'))
{
$sClass = get_class($oObject);
$sAttCode = $oAttDef->GetCode();
WorkingTimeRecorder::Start($oObject, time(), "ormStopWatch-TimeSpent-$sAttCode", 'Core:ExplainWTC:StopWatch-TimeSpent', array("Class:$sClass/Attribute:$sAttCode"), true /*cumulative*/);
}
$iElapsed = $this->ComputeDuration($oObject, $oAttDef, $this->iLastStart, time());
$this->iTimeSpent = $this->iTimeSpent + $iElapsed;
if (class_exists('WorkingTimeRecorder'))
{
WorkingTimeRecorder::End();
}
foreach ($this->aThresholds as $iPercent => &$aThresholdData)
{
@@ -424,9 +501,44 @@ class CheckStopWatchThresholds implements iBackgroundProcess
{
$sVerb = $aActionData['verb'];
$aParams = $aActionData['params'];
$sParams = implode(', ', $aParams);
$aValues = array();
foreach($aParams as $def)
{
if (is_string($def))
{
// Old method (pre-2.1.0) non typed parameters
$aValues[] = $def;
}
else // if(is_array($def))
{
$sParamType = array_key_exists('type', $def) ? $def['type'] : 'string';
switch($sParamType)
{
case 'int':
$value = (int)$def['value'];
break;
case 'float':
$value = (float)$def['value'];
break;
case 'bool':
$value = (bool)$def['value'];
break;
case 'reference':
$value = ${$def['value']};
break;
case 'string':
default:
$value = (string)$def['value'];
}
$aValues[] = $value;
}
}
$aCallSpec = array($oObj, $sVerb);
call_user_func_array($aCallSpec, $aParams);
call_user_func_array($aCallSpec, $aValues);
}
// Mark the threshold as "triggered"

View File

@@ -342,6 +342,7 @@ class lnkTriggerAction extends cmdbAbstractObject
"db_key_field" => "link_id",
"db_finalclass_field" => "",
"display_template" => "",
"is_link" => true,
);
MetaModel::Init_Params($aParams);
MetaModel::Init_AddAttribute(new AttributeExternalKey("action_id", array("targetclass"=>"Action", "jointype"=> '', "allowed_values"=>null, "sql"=>"action_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array())));

View File

@@ -234,12 +234,19 @@ abstract class User extends cmdbAbstractObject
$aChanges = $this->ListChanges();
if (array_key_exists('login', $aChanges))
{
$sNewLogin = $aChanges['login'];
$oSearch = DBObjectSearch::FromOQL_AllData("SELECT User WHERE login = :newlogin");
$oSet = new DBObjectSet($oSearch, array(), array('newlogin' => $sNewLogin));
if ($oSet->Count() > 0)
if (strcasecmp($this->Get('login'), $this->GetOriginal('login')) !== 0)
{
$this->m_aCheckIssues[] = Dict::Format('Class:User/Error:LoginMustBeUnique', $sNewLogin);
$sNewLogin = $aChanges['login'];
$oSearch = DBObjectSearch::FromOQL_AllData("SELECT User WHERE login = :newlogin");
if (!$this->IsNew())
{
$oSearch->AddCondition('id', $this->GetKey(), '!=');
}
$oSet = new DBObjectSet($oSearch, array(), array('newlogin' => $sNewLogin));
if ($oSet->Count() > 0)
{
$this->m_aCheckIssues[] = Dict::Format('Class:User/Error:LoginMustBeUnique', $sNewLogin);
}
}
}
// Check that this user has at least one profile assigned
@@ -248,7 +255,6 @@ abstract class User extends cmdbAbstractObject
{
$this->m_aCheckIssues[] = Dict::Format('Class:User/Error:AtLeastOneProfileIsNeeded');
}
}
function GetGrantAsHtml($sClass, $iAction)
@@ -341,6 +347,29 @@ abstract class User extends cmdbAbstractObject
}
}
}
public function CheckToDelete(&$oDeletionPlan)
{
if (MetaModel::GetConfig()->Get('demo_mode'))
{
// Users deletion is NOT allowed in demo mode
$oDeletionPlan->AddToDelete($this, null);
$oDeletionPlan->SetDeletionIssues($this, array('deletion not allowed in demo mode.'), true);
$oDeletionPlan->ComputeResults();
return false;
}
return parent::CheckToDelete($oDeletionPlan);
}
protected function DBDeleteSingleObject()
{
if (MetaModel::GetConfig()->Get('demo_mode'))
{
// Users deletion is NOT allowed in demo mode
return;
}
parent::DBDeleteSingleObject();
}
}
/**
@@ -371,7 +400,7 @@ abstract class UserInternal extends User
MetaModel::Init_InheritAttributes();
// When set, this token allows for password reset
MetaModel::Init_AddAttribute(new AttributeString("reset_pwd_token", array("allowed_values"=>null, "sql"=>"reset_pwd_token", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeOneWayPassword("reset_pwd_token", array("allowed_values"=>null, "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
// Display lists
MetaModel::Init_SetZListItems('details', array('contactid', 'first_name', 'email', 'login', 'language', 'profile_list', 'allowed_org_list')); // Attributes to be displayed for the complete details
@@ -1274,8 +1303,9 @@ class CAS_SelfRegister implements iSelfRegister
}
else
{
// No membership required, anybody will pass
$bFound = true;
// No membership: no way to create the user that should exist prior to authentication
phpCAS::log("User ".phpCAS::getUser().": missing user account in iTop (or iTop badly configured, Cf setting cas_memberof)");
$bFound = false;
}
if (!$bFound)

View File

@@ -100,6 +100,9 @@ class ValueSetObjects extends ValueSetDefinition
private $m_bAllowAllData;
private $m_aModifierProperties;
/**
* @param hash $aOrderBy Array of '[<classalias>.]attcode' => bAscending
*/
public function __construct($sFilterExp, $sValueAttCode = '', $aOrderBy = array(), $bAllowAllData = false, $aModifierProperties = array())
{
$this->m_sContains = '';
@@ -415,5 +418,3 @@ class ValueSetEnumClasses extends ValueSetEnum
return true;
}
}
?>

View File

@@ -206,6 +206,11 @@ legend.transparent {
padding-left:14px;
background: url(../images/mini-arrow-orange.gif) no-repeat left;
}
.ui-widget-content td a.cke_toolbox_collapser {
padding-left: 0;
}
p a:hover, td a:hover {
text-decoration:underline;
color:#EB8F00;
@@ -1416,3 +1421,7 @@ div.ui-dialog-header {
padding-bottom: 10px;
padding-top: 7px;
}
.form_field_error {
border: 1px solid #933;
background: #fcc;
}

View File

@@ -22,7 +22,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s %2$s</format>
<attributes>
<attribute id="item_class"/>
<attribute id="temp_id"/>

View File

@@ -17,7 +17,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="ref"/>
</attributes>
@@ -457,7 +456,7 @@
<hidden/>
</attribute>
<attribute id="agent_id">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="agent_email">
<hidden/>
@@ -466,13 +465,13 @@
<mandatory/>
</attribute>
<attribute id="supervisor_id">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="manager_group_id">
<mandatory/>
</attribute>
<attribute id="manager_id">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="outage">
<hidden/>
@@ -1205,7 +1204,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="ref"/>
</attributes>
@@ -1306,7 +1304,7 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_assign">
<stimulus>ev_assign</stimulus>
<target>assigned</target>
<actions/>
@@ -1488,7 +1486,7 @@
<hidden/>
</attribute>
<attribute id="agent_id">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="agent_email">
<hidden/>
@@ -1497,13 +1495,13 @@
<mandatory/>
</attribute>
<attribute id="supervisor_id">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="manager_group_id">
<mandatory/>
</attribute>
<attribute id="manager_id">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="outage">
<hidden/>
@@ -1513,7 +1511,7 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_plan">
<stimulus>ev_plan</stimulus>
<target>plannedscheduled</target>
<actions/>
@@ -1589,7 +1587,7 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_implement">
<stimulus>ev_implement</stimulus>
<target>implemented</target>
<actions/>
@@ -1796,12 +1794,12 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_monitor">
<stimulus>ev_monitor</stimulus>
<target>monitored</target>
<actions/>
</transition>
<transition>
<transition id="ev_finish">
<stimulus>ev_finish</stimulus>
<target>closed</target>
<actions>
@@ -1879,7 +1877,7 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_finish">
<stimulus>ev_finish</stimulus>
<target>closed</target>
<actions>
@@ -2166,7 +2164,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="ref"/>
</attributes>
@@ -2465,7 +2462,7 @@
<hidden/>
</attribute>
<attribute id="agent_id">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="agent_email">
<hidden/>
@@ -2474,13 +2471,13 @@
<mandatory/>
</attribute>
<attribute id="supervisor_id">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="manager_group_id">
<mandatory/>
</attribute>
<attribute id="manager_id">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="outage">
<hidden/>
@@ -3124,7 +3121,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="ref"/>
</attributes>
@@ -3248,12 +3244,12 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_validate">
<stimulus>ev_validate</stimulus>
<target>validated</target>
<actions/>
</transition>
<transition>
<transition id="ev_reject">
<stimulus>ev_reject</stimulus>
<target>rejected</target>
<actions/>
@@ -3339,7 +3335,7 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_assign">
<stimulus>ev_assign</stimulus>
<target>assigned</target>
<actions/>
@@ -3422,7 +3418,7 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_reopen">
<stimulus>ev_reopen</stimulus>
<target>new</target>
<actions/>
@@ -3471,7 +3467,7 @@
<hidden/>
</attribute>
<attribute id="agent_id">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="agent_email">
<hidden/>
@@ -3480,13 +3476,13 @@
<mandatory/>
</attribute>
<attribute id="supervisor_id">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="manager_group_id">
<mandatory/>
</attribute>
<attribute id="manager_id">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="outage">
<hidden/>
@@ -3502,7 +3498,7 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_plan">
<stimulus>ev_plan</stimulus>
<target>plannedscheduled</target>
<actions/>
@@ -3589,12 +3585,12 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_approve">
<stimulus>ev_approve</stimulus>
<target>approved</target>
<actions/>
</transition>
<transition>
<transition id="ev_notapprove">
<stimulus>ev_notapprove</stimulus>
<target>notapproved</target>
<actions/>
@@ -3680,7 +3676,7 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_implement">
<stimulus>ev_implement</stimulus>
<target>implemented</target>
<actions/>
@@ -3763,7 +3759,7 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_replan">
<stimulus>ev_replan</stimulus>
<target>plannedscheduled</target>
<actions/>
@@ -3849,12 +3845,12 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_monitor">
<stimulus>ev_monitor</stimulus>
<target>monitored</target>
<actions/>
</transition>
<transition>
<transition id="ev_finish">
<stimulus>ev_finish</stimulus>
<target>closed</target>
<actions>
@@ -3944,7 +3940,7 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_finish">
<stimulus>ev_finish</stimulus>
<target>closed</target>
<actions>
@@ -4246,7 +4242,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="ref"/>
</attributes>
@@ -4353,7 +4348,7 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_assign">
<stimulus>ev_assign</stimulus>
<target>assigned</target>
<actions/>
@@ -4547,7 +4542,7 @@
<hidden/>
</attribute>
<attribute id="agent_id">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="agent_email">
<hidden/>
@@ -4556,13 +4551,13 @@
<mandatory/>
</attribute>
<attribute id="supervisor_id">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="manager_group_id">
<mandatory/>
</attribute>
<attribute id="manager_id">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="outage">
<hidden/>
@@ -4578,7 +4573,7 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_plan">
<stimulus>ev_plan</stimulus>
<target>plannedscheduled</target>
<actions/>
@@ -4660,12 +4655,12 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_approve">
<stimulus>ev_approve</stimulus>
<target>approved</target>
<actions/>
</transition>
<transition>
<transition id="ev_notapprove">
<stimulus>ev_notapprove</stimulus>
<target>notapproved</target>
<actions/>
@@ -4746,7 +4741,7 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_implement">
<stimulus>ev_implement</stimulus>
<target>implemented</target>
<actions/>
@@ -4823,7 +4818,7 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_replan">
<stimulus>ev_replan</stimulus>
<target>plannedscheduled</target>
<actions/>
@@ -4903,12 +4898,12 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_monitor">
<stimulus>ev_monitor</stimulus>
<target>monitored</target>
<actions/>
</transition>
<transition>
<transition id="ev_finish">
<stimulus>ev_finish</stimulus>
<target>closed</target>
<actions>
@@ -4992,7 +4987,7 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_finish">
<stimulus>ev_finish</stimulus>
<target>closed</target>
<actions>

View File

@@ -17,7 +17,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="name"/>
</attributes>
@@ -116,7 +115,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="name"/>
</attributes>
@@ -289,7 +287,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field>finalclass</db_final_class_field>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="name"/>
</attributes>
@@ -487,7 +484,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s %2$s</format>
<attributes>
<attribute id="first_name"/>
<attribute id="name"/>
@@ -626,7 +622,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="name"/>
</attributes>
@@ -745,7 +740,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="team_id"/>
</attributes>
@@ -865,7 +859,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field>finalclass</db_final_class_field>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="name"/>
</attributes>
@@ -1031,7 +1024,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="name"/>
</attributes>
@@ -1140,7 +1132,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="name"/>
</attributes>
@@ -1248,7 +1239,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="name"/>
</attributes>
@@ -1371,7 +1361,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="name"/>
</attributes>
@@ -1540,7 +1529,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s / %2$s</format>
<attributes>
<attribute id="ip"/>
<attribute id="ip_mask"/>
@@ -1700,7 +1688,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="name"/>
</attributes>
@@ -1821,7 +1808,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field>finalclass</db_final_class_field>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="name"/>
</attributes>
@@ -1887,7 +1873,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="name"/>
</attributes>
@@ -1952,7 +1937,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="name"/>
</attributes>
@@ -2018,7 +2002,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="patch_id"/>
</attributes>
@@ -2110,7 +2093,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field>finalclass</db_final_class_field>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="name"/>
</attributes>
@@ -2297,7 +2279,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s - %2$s</format>
<attributes>
<attribute id="name"/>
<attribute id="device_id_friendlyname"/>
@@ -2496,7 +2477,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s - %2$s</format>
<attributes>
<attribute id="name"/>
<attribute id="device_id_friendlyname"/>
@@ -2645,7 +2625,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s - %2$s</format>
<attributes>
<attribute id="name"/>
<attribute id="device_id_friendlyname"/>
@@ -2785,7 +2764,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s - %2$s</format>
<attributes>
<attribute id="name"/>
<attribute id="db_server_instance_id_friendlyname"/>
@@ -2945,7 +2923,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="name"/>
</attributes>
@@ -3092,7 +3069,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="name"/>
</attributes>
@@ -3222,7 +3198,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="name"/>
</attributes>
@@ -3373,7 +3348,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s - %2$s</format>
<attributes>
<attribute id="device_id_friendlyname"/>
<attribute id="name"/>
@@ -3737,7 +3711,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="name"/>
</attributes>
@@ -3905,7 +3878,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="name"/>
</attributes>
@@ -4097,7 +4069,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="name"/>
</attributes>
@@ -4220,7 +4191,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="name"/>
</attributes>
@@ -4374,7 +4344,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="name"/>
</attributes>
@@ -4562,7 +4531,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="name"/>
</attributes>
@@ -4772,7 +4740,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="name"/>
</attributes>
@@ -4993,7 +4960,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="name"/>
</attributes>
@@ -5201,7 +5167,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="name"/>
</attributes>
@@ -5345,7 +5310,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="group_id"/>
</attributes>
@@ -5452,7 +5416,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="ci_id"/>
</attributes>
@@ -5565,7 +5528,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="ci_id"/>
</attributes>
@@ -5682,7 +5644,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="solution_id"/>
</attributes>
@@ -5789,7 +5750,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="solution_id"/>
</attributes>

View File

@@ -17,7 +17,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="ref"/>
</attributes>
@@ -54,7 +53,7 @@
<read_only/>
</attribute>
<attribute id="description">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="start_date">
<read_only/>
@@ -63,28 +62,28 @@
<mandatory/>
</attribute>
<attribute id="org_id">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="service_id">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="servicesubcategory_id">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="product">
<must_prompt/>
</attribute>
<attribute id="impact">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="urgency">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="priority">
<read_only/>
</attribute>
<attribute id="workgroup_id">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="agent_email">
<hidden/>
@@ -130,7 +129,7 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_assign">
<stimulus>ev_assign</stimulus>
<target>assigned</target>
<actions>
@@ -139,7 +138,7 @@
</action>
</actions>
</transition>
<transition>
<transition id="ev_timeout">
<stimulus>ev_timeout</stimulus>
<target>escalated_tto</target>
<actions/>
@@ -204,7 +203,7 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_assign">
<stimulus>ev_assign</stimulus>
<target>assigned</target>
<actions>
@@ -284,17 +283,17 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_reassign">
<stimulus>ev_reassign</stimulus>
<target>assigned</target>
<actions/>
</transition>
<transition>
<transition id="ev_reassign">
<stimulus>ev_timeout</stimulus>
<target>escalated_ttr</target>
<actions/>
</transition>
<transition>
<transition id="ev_reassign">
<stimulus>ev_resolve</stimulus>
<target>resolved</target>
<actions>
@@ -377,12 +376,12 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_reassign">
<stimulus>ev_reassign</stimulus>
<target>escalated_ttr</target>
<actions/>
</transition>
<transition>
<transition id="ev_resolve">
<stimulus>ev_resolve</stimulus>
<target>resolved</target>
<actions>
@@ -546,12 +545,12 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_reassign">
<stimulus>ev_reassign</stimulus>
<target>assigned</target>
<actions/>
</transition>
<transition>
<transition id="ev_close">
<stimulus>ev_close</stimulus>
<target>closed</target>
<actions>
@@ -1042,7 +1041,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="ticket_id"/>
</attributes>

View File

@@ -14,7 +14,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="name"/>
</attributes>
@@ -224,7 +223,6 @@
<db_key_field>link_id</db_key_field>
<db_final_class_field/>
<naming>
<format>lnkInfraError</format>
<attributes/>
</naming>
<display_template/>
@@ -326,7 +324,6 @@
<db_key_field>link_id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="link_type"/>
</attributes>

View File

@@ -17,7 +17,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="ref"/>
</attributes>
@@ -213,7 +212,7 @@
<read_only/>
</attribute>
<attribute id="description">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="ticket_log">
<hidden/>
@@ -222,28 +221,28 @@
<read_only/>
</attribute>
<attribute id="org_id">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="service_id">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="servicesubcategory_id">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="product">
<must_prompt/>
</attribute>
<attribute id="impact">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="urgency">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="priority">
<read_only/>
</attribute>
<attribute id="workgroup_id">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="agent_id">
<hidden/>
@@ -268,7 +267,7 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_assign">
<stimulus>ev_assign</stimulus>
<target>assigned</target>
<actions>
@@ -324,12 +323,12 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_reassign">
<stimulus>ev_reassign</stimulus>
<target>assigned</target>
<actions/>
</transition>
<transition>
<transition id="ev_resolve">
<stimulus>ev_resolve</stimulus>
<target>resolved</target>
<actions>
@@ -398,12 +397,12 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_reassign">
<stimulus>ev_reassign</stimulus>
<target>assigned</target>
<actions/>
</transition>
<transition>
<transition id="ev_close">
<stimulus>ev_close</stimulus>
<target>closed</target>
<actions>
@@ -539,7 +538,11 @@
3 => 3,
),
);
$iPriority = $aPriorities[(int)$this->Get('impact')][(int)$this->Get('urgency')];
$iPriority = 1;
if (isset($aPriorities[(int)$this->Get('impact')][(int)$this->Get('urgency')]))
{
$iPriority = $aPriorities[(int)$this->Get('impact')][(int)$this->Get('urgency')];
}
return $iPriority;
}]]></code>
</method>
@@ -753,7 +756,7 @@
<parent>ProblemManagement</parent>
<definition>
<layout>DashboardLayoutTwoCols</layout>
<title></title>
<title/>
<cells>
<cell id="0">
<rank>0</rank>

View File

@@ -37,7 +37,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="ref"/>
</attributes>
@@ -91,7 +90,7 @@
<read_only/>
</attribute>
<attribute id="description">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="start_date">
<read_only/>
@@ -100,28 +99,28 @@
<mandatory/>
</attribute>
<attribute id="org_id">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="service_id">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="servicesubcategory_id">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="product">
<must_prompt/>
</attribute>
<attribute id="impact">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="urgency">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="priority">
<read_only/>
</attribute>
<attribute id="workgroup_id">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="agent_email">
<hidden/>
@@ -173,7 +172,7 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_assign">
<stimulus>ev_assign</stimulus>
<target>assigned</target>
<actions>
@@ -182,7 +181,7 @@
</action>
</actions>
</transition>
<transition>
<transition id="ev_timeout">
<stimulus>ev_timeout</stimulus>
<target>escalated_tto</target>
<actions/>
@@ -253,7 +252,7 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_assign">
<stimulus>ev_assign</stimulus>
<target>assigned</target>
<actions>
@@ -339,17 +338,17 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_reassign">
<stimulus>ev_reassign</stimulus>
<target>assigned</target>
<actions/>
</transition>
<transition>
<transition id="ev_timeout">
<stimulus>ev_timeout</stimulus>
<target>escalated_ttr</target>
<actions/>
</transition>
<transition>
<transition id="ev_resolve">
<stimulus>ev_resolve</stimulus>
<target>resolved</target>
<actions>
@@ -361,7 +360,7 @@
</action>
</actions>
</transition>
<transition>
<transition id="ev_freeze">
<stimulus>ev_freeze</stimulus>
<target>frozen</target>
<actions/>
@@ -443,12 +442,12 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_reassign">
<stimulus>ev_reassign</stimulus>
<target>escalated_ttr</target>
<actions/>
</transition>
<transition>
<transition id="ev_resolve">
<stimulus>ev_resolve</stimulus>
<target>resolved</target>
<actions>
@@ -536,17 +535,17 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_timeout">
<stimulus>ev_timeout</stimulus>
<target>escalated_ttr</target>
<actions/>
</transition>
<transition>
<transition id="ev_assign">
<stimulus>ev_assign</stimulus>
<target>assigned</target>
<actions/>
</transition>
<transition>
<transition id="ev_resolve">
<stimulus>ev_resolve</stimulus>
<target>resolved</target>
<actions/>
@@ -641,12 +640,12 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_reassign">
<stimulus>ev_reassign</stimulus>
<target>assigned</target>
<actions/>
</transition>
<transition>
<transition id="ev_close">
<stimulus>ev_close</stimulus>
<target>closed</target>
<actions>

View File

@@ -17,7 +17,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field>finalclass</db_final_class_field>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="name"/>
</attributes>
@@ -201,7 +200,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="name"/>
</attributes>
@@ -376,7 +374,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="name"/>
</attributes>
@@ -567,7 +564,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="customer_contract_id"/>
</attributes>
@@ -670,7 +666,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="contract_id"/>
</attributes>
@@ -777,7 +772,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="contract_id"/>
</attributes>
@@ -886,7 +880,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="contract_id"/>
</attributes>
@@ -996,7 +989,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="contract_id"/>
</attributes>
@@ -1091,7 +1083,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="name"/>
</attributes>
@@ -1271,7 +1262,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="name"/>
</attributes>
@@ -1372,7 +1362,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="name"/>
</attributes>
@@ -1455,7 +1444,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="name"/>
</attributes>
@@ -1588,7 +1576,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="sla_id"/>
</attributes>
@@ -1723,7 +1710,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="service_id"/>
</attributes>
@@ -1832,7 +1818,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="service_id"/>
</attributes>
@@ -1941,7 +1926,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="service_id"/>
</attributes>

View File

@@ -17,7 +17,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field>finalclass</db_final_class_field>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="ref"/>
</attributes>
@@ -162,7 +161,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="ref"/>
</attributes>
@@ -422,7 +420,7 @@
<read_only/>
</attribute>
<attribute id="description">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="start_date">
<read_only/>
@@ -431,28 +429,28 @@
<mandatory/>
</attribute>
<attribute id="org_id">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="service_id">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="servicesubcategory_id">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="product">
<must_prompt/>
</attribute>
<attribute id="impact">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="urgency">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="priority">
<read_only/>
</attribute>
<attribute id="workgroup_id">
<must_change/>
<must_prompt/>
</attribute>
<attribute id="agent_email">
<hidden/>
@@ -498,7 +496,7 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_assign">
<stimulus>ev_assign</stimulus>
<target>assigned</target>
<actions>
@@ -507,7 +505,7 @@
</action>
</actions>
</transition>
<transition>
<transition id="ev_timeout">
<stimulus>ev_timeout</stimulus>
<target>escalated_tto</target>
<actions/>
@@ -572,7 +570,7 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_assign">
<stimulus>ev_assign</stimulus>
<target>assigned</target>
<actions>
@@ -652,17 +650,17 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_reassign">
<stimulus>ev_reassign</stimulus>
<target>assigned</target>
<actions/>
</transition>
<transition>
<transition id="ev_timeout">
<stimulus>ev_timeout</stimulus>
<target>escalated_ttr</target>
<actions/>
</transition>
<transition>
<transition id="ev_resolve">
<stimulus>ev_resolve</stimulus>
<target>resolved</target>
<actions>
@@ -745,12 +743,12 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_reassign">
<stimulus>ev_reassign</stimulus>
<target>escalated_ttr</target>
<actions/>
</transition>
<transition>
<transition id="ev_resolve">
<stimulus>ev_resolve</stimulus>
<target>resolved</target>
<actions>
@@ -914,12 +912,12 @@
</attribute>
</flags>
<transitions>
<transition>
<transition id="ev_reassign">
<stimulus>ev_reassign</stimulus>
<target>assigned</target>
<actions/>
</transition>
<transition>
<transition id="ev_close">
<stimulus>ev_close</stimulus>
<target>closed</target>
<actions>
@@ -1157,7 +1155,11 @@
3 => 3,
),
);
$iPriority = $aPriorities[(int)$this->Get('impact')][(int)$this->Get('urgency')];
$iPriority = 1;
if (isset($aPriorities[(int)$this->Get('impact')][(int)$this->Get('urgency')]))
{
$iPriority = $aPriorities[(int)$this->Get('impact')][(int)$this->Get('urgency')];
}
return $iPriority;
}]]></code>
</method>
@@ -1484,7 +1486,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="ticket_id"/>
</attributes>
@@ -1567,7 +1568,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="ticket_id"/>
</attributes>
@@ -1677,7 +1677,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="ticket_id"/>
</attributes>

View File

@@ -0,0 +1,30 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* @author Erik Bøg <erik@boegmoeller.dk>
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @licence http://opensource.org/licenses/AGPL-3.0
*/
Dict::Add('DA DA', 'Danish', 'Dansk', array(
'Class:UserExternal' => 'Extern Bruger',
'Class:UserExternal+' => 'Bruger udenfor iTop',
));
?>

View File

@@ -0,0 +1,32 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* @author Erik Bøg <erik@boegmoeller.dk>
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @licence http://opensource.org/licenses/AGPL-3.0
*/
Dict::Add('DA DA', 'Danish', 'Dansk', array(
'Class:UserLDAP' => 'LDAP-Bruger',
'Class:UserLDAP+' => 'Bruger der godkendes via LDAP',
'Class:UserLDAP/Attribute:password' => 'Password',
'Class:UserLDAP/Attribute:password+' => 'Brugerens password',
));
?>

View File

@@ -0,0 +1,32 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* @author Erik Bøg <erik@boegmoeller.dk>
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @licence http://opensource.org/licenses/AGPL-3.0
*/
Dict::Add('DA DA', 'Danish', 'Dansk', array(
'Class:UserLocal' => 'iTop-Bruger',
'Class:UserLocal+' => 'Bruger der godkendes af iTop',
'Class:UserLocal/Attribute:password' => 'Password',
'Class:UserLocal/Attribute:password+' => 'Brugerens password',
));
?>

View File

@@ -0,0 +1,40 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* @author Erik Bøg <erik@boegmoeller.dk>
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @licence http://opensource.org/licenses/AGPL-3.0
*/
Dict::Add('DA DA', 'Danish', 'Dansk', array(
'Attachments:TabTitle_Count' => 'Vedhæftninger (%1$d)',
'Attachments:EmptyTabTitle' => 'Vedhæftninger',
'Attachments:FieldsetTitle' => 'Vedhæftninger',
'Attachments:DeleteBtn' => 'Slet',
'Attachments:History_File_Added' => 'Vedhæftning %1$s tilføjet.',
'Attachments:History_File_Removed' => 'Vedhæftning %1$s fjernet.',
'Attachments:AddAttachment' => 'Vedhæft: ',
'Attachments:UploadNotAllowedOnThisSystem' => 'Upload IKKE tilladt i dette system.',
'Attachment:Max_Go' => '(Maksimal størrelse: %1$s GB)',
'Attachment:Max_Mo' => '(Maksimal størrelse: %1$s MB)',
'Attachment:Max_Ko' => '(Maksimal størrelse: %1$s KB)',
'Attachments:NoAttachment' => 'Intet vedhæftet. ',
));
?>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0">
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.1">
<classes>
<class id="Attachment" _delta="define">
<parent>DBObject</parent>
@@ -22,7 +22,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s %2$s</format>
<attributes>
<attribute id="item_class"/>
<attribute id="temp_id"/>
@@ -37,20 +36,20 @@
</reconciliation>
<indexes>
<index id="1">
<attributes>
<attribute id="temp_id"/>
</attributes>
<attributes>
<attribute id="temp_id"/>
</attributes>
</index>
<index id="2">
<attributes>
<attribute id="item_class"/>
<attribute id="item_id"/>
</attributes>
<attributes>
<attribute id="item_class"/>
<attribute id="item_id"/>
</attributes>
</index>
<index id="3">
<attributes>
<attribute id="item_org_id"/>
</attributes>
<attributes>
<attribute id="item_org_id"/>
</attributes>
</index>
</indexes>
</properties>

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
@@ -250,6 +250,7 @@ EOF
$('#attachment_plugin').trigger('remove_attachment', [att_id]);
return false; // Do not submit the form !
}
function ajaxFileUpload()
{
//starting setting some animation when the ajax starts and completes
@@ -347,7 +348,7 @@ EOF
$sIcon = utils::GetAbsoluteUrlAppRoot().AttachmentPlugIn::GetFileIcon($sFileName);
$sDownloadLink = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=download_document&class=Attachment&id='.$iAttId.'&field=contents';
$sPreview = $oDoc->IsPreviewAvailable() ? 'true' : 'false';
$oPage->add('<div class="attachment" id="display_attachment_'.$iAttId.'"><a data-preview="'.$sPreview.'" href="'.$sDownloadLink.'"><img src="'.$sIcon.'"><br/>'.$sFileName.'<input id="attachment_'+data.result.att_id+'" type="hidden" name="attachments[]" value="'.$iAttId.'"/></a><br/>&nbsp;<input id="btn_remove_'.$iAttId.'" type="button" class="btn_hidden" value="Delete" onClick="RemoveAttachment('.$iAttId.');"/>&nbsp;</div>');
$oPage->add('<div class="attachment" id="display_attachment_'.$iAttId.'"><a data-preview="'.$sPreview.'" href="'.$sDownloadLink.'"><img src="'.$sIcon.'"><br/>'.$sFileName.'<input id="attachment_'.$iAttId.'" type="hidden" name="attachments[]" value="'.$iAttId.'"/></a><br/>&nbsp;<input id="btn_remove_'.$iAttId.'" type="button" class="btn_hidden" value="Delete" onClick="RemoveAttachment('.$iAttId.');"/>&nbsp;</div>');
$oPage->add_ready_script("$('#attachment_plugin').trigger('add_attachment', [$iAttId, '".addslashes($sFileName)."']);");
}
}
@@ -368,6 +369,7 @@ $oPage->add_ready_script(
url: GetAbsoluteUrlModulesRoot()+'itop-attachments/ajax.attachment.php',
formData: { operation: 'add', temp_id: '$sTempId', obj_class: '$sClass' },
dataType: 'json',
pasteZone: null, // Don't accept files via Chrome's copy/paste
done: function (e, data) {
if(typeof(data.result.error) != 'undefined')
{
@@ -383,7 +385,7 @@ $oPage->add_ready_script(
{
$('#display_attachment_'+data.result.att_id).hover( function() { $(this).children(':button').toggleClass('btn_hidden'); } );
}
$('#attachment_plugin').trigger('add_attachment', [data.result.att_id, data.msg]);
$('#attachment_plugin').trigger('add_attachment', [data.result.att_id, data.result.msg]);
}
}
},
@@ -440,7 +442,6 @@ EOF
);
$oPage->p('<span style="display:none;" id="attachment_loading">Loading, please wait...</span>');
$oPage->p('<input type="hidden" id="attachment_plugin" name="attachment_plugin"/>');
$oPage->add('</fieldset>');
if ($this->m_bDeleteEnabled)
{
$oPage->add_ready_script('$(".attachment").hover( function() {$(this).children(":button").toggleClass("btn_hidden"); } );');
@@ -466,9 +467,12 @@ EOF
$oPage->add('<div class="attachment" id="attachment_'.$iAttId.'"><a data-preview="'.$sPreview.'" href="'.$sDownloadLink.'"><img src="'.$sIcon.'"><br/>'.$sFileName.'</a><input type="hidden" name="attachments[]" value="'.$iAttId.'"/><br/>&nbsp;&nbsp;</div>');
}
}
$oPage->add('</span>');
}
$oPage->add('</fieldset>');
$sPreviewNotAvailable = addslashes(Dict::S('Attachments:PreviewNotAvailable'));
$oPage->add_ready_script("$(document).tooltip({ items: '.attachment a', position: { my: 'left top', at: 'right top', using: function( position, feedback ) { $( this ).css( position ); }}, content: function() { if ($(this).attr('data-preview') == 'true') { return('<img style=\"max-width:290px\" src=\"'+$(this).attr('href')+'\"></img>');} else { return '$sPreviewNotAvailable'; }}});");
$iMaxWidth = MetaModel::GetModuleSetting('itop-attachments', 'preview_max_width', 290);
$oPage->add_ready_script("$(document).tooltip({ items: '.attachment a', position: { my: 'left top', at: 'right top', using: function( position, feedback ) { $( this ).css( position ); }}, content: function() { if ($(this).attr('data-preview') == 'true') { return('<img style=\"max-width:{$iMaxWidth}px\" src=\"'+$(this).attr('href')+'\"></img>');} else { return '$sPreviewNotAvailable'; }}});");
}
protected static function UpdateAttachments($oObject, $oChange = null)

View File

@@ -19,11 +19,11 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-attachments/1.0.0',
'itop-attachments/2.1.0',
array(
// Identification
//
'label' => 'Tickets attachments',
'label' => 'Tickets Attachments',
'category' => 'business',
// Setup
@@ -64,6 +64,7 @@ SetupWebPage::AddModule(
'settings' => array(
'allowed_classes' => array('Ticket'), // List of classes for which to manage "Attachments"
'position' => 'relations', // Where to display the attachments: relations | properties
'preview_max_width' => 290,
),
)
);

View File

@@ -0,0 +1,48 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* @author LinProfs <info@linprofs.com>
*
* Linux & Open Source Professionals
* http://www.linprofs.com
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @licence http://opensource.org/licenses/AGPL-3.0
*/
Dict::Add('NL NL', 'Dutch', 'Nederlands', array(
'Attachments:TabTitle_Count' => 'Bijlagen (%1$d)',
'Attachments:EmptyTabTitle' => 'Bijlagen',
'Attachments:FieldsetTitle' => 'Bijlagen',
'Attachments:DeleteBtn' => 'Verwijder',
'Attachments:History_File_Added' => 'Bijlage %1$s toegevoegd.',
'Attachments:History_File_Removed' => 'Bijlage %1$s verwijderd.',
'Attachments:AddAttachment' => 'Voeg bijlage toe: ',
'Attachments:UploadNotAllowedOnThisSystem' => 'Bestand upload is NIET toegestaan op dit systeem.',
'Attachment:Max_Go' => '(Maximum bestandsgrootte: %1$s Go)',
'Attachment:Max_Mo' => '(Maximum bestandsgrootte: %1$s Mo)',
'Attachment:Max_Ko' => '(Maximum bestandsgrootte: %1$s Ko)',
'Attachments:NoAttachment' => 'Geen bijlage. ',
'Attachments:PreviewNotAvailable' => 'Een voorbeeld is niet beschikbaar voor dit type bijlage.',
'Class:Attachment' => 'Bijlage',
'Class:Attachment+' => '',
));
?>

View File

@@ -0,0 +1,181 @@
<?php
// Copyright (C) 2013-2016 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Backup from an interactive session
*
* @copyright Copyright (C) 2013-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
if (!defined('__DIR__')) define('__DIR__', dirname(__FILE__));
require_once(__DIR__.'/../../approot.inc.php');
require_once(APPROOT.'/application/application.inc.php');
require_once(APPROOT.'/application/webpage.class.inc.php');
require_once(APPROOT.'/application/ajaxwebpage.class.inc.php');
require_once(APPROOT.'core/mutex.class.inc.php');
try
{
$sOperation = utils::ReadParam('operation', '');
switch($sOperation)
{
case 'backup':
require_once(APPROOT.'/application/startup.inc.php');
require_once(APPROOT.'/application/loginwebpage.class.inc.php');
LoginWebPage::DoLogin(true); // Check user rights and prompt if needed (must be admin)
$oPage = new ajax_page("");
$oPage->no_cache();
$oPage->SetContentType('text/html');
if (utils::GetConfig()->Get('demo_mode'))
{
$oPage->add("<div data-error-stimulus=\"Error\">Sorry, iTop is in <b>demonstration mode</b>: the feature is disabled.</div>");
}
else
{
try
{
set_time_limit(0);
$oBB = new BackupExec(APPROOT.'data/backups/manual/', 0 /*iRetentionCount*/);
$sRes = $oBB->Process(time() + 36000); // 10 hours to complete should be sufficient!
}
catch (Exception $e)
{
$oPage->p('Error: '.$e->getMessage());
}
}
$oPage->output();
break;
case 'restore_get_token':
require_once(APPROOT.'/application/startup.inc.php');
require_once(APPROOT.'/application/loginwebpage.class.inc.php');
LoginWebPage::DoLogin(true); // Check user rights and prompt if needed (must be admin)
$oPage = new ajax_page("");
$oPage->no_cache();
$oPage->SetContentType('text/html');
$sEnvironment = utils::ReadParam('environment', 'production', false, 'raw_data');
$oRestoreMutex = new iTopMutex('restore.'.$sEnvironment);
if (!$oRestoreMutex->IsLocked())
{
$sFile = utils::ReadParam('file', '', false, 'raw_data');
$sToken = str_replace(' ', '', (string)microtime());
$sTokenFile = APPROOT.'/data/restore.'.$sToken.'.tok';
file_put_contents($sTokenFile, $sFile);
$oPage->add_ready_script(
<<<EOF
$("#restore_token").val('$sToken');
EOF
);
}
else
{
$oPage->p(Dict::S('bkp-restore-running'));
}
$oPage->output();
break;
case 'restore_exec':
require_once(APPROOT."setup/runtimeenv.class.inc.php");
require_once(APPROOT.'/application/utils.inc.php');
require_once(APPROOT.'/setup/backup.class.inc.php');
require_once(dirname(__FILE__).'/dbrestore.class.inc.php');
IssueLog::Enable(APPROOT.'log/error.log');
$oPage = new ajax_page("");
$oPage->no_cache();
$oPage->SetContentType('text/html');
if (utils::GetConfig()->Get('demo_mode'))
{
$oPage->add("<div data-error-stimulus=\"Error\">Sorry, iTop is in <b>demonstration mode</b>: the feature is disabled.</div>");
}
else
{
$sEnvironment = utils::ReadParam('environment', 'production', false, 'raw_data');
$oRestoreMutex = new iTopMutex('restore.'.$sEnvironment);
IssueLog::Info("Backup Restore - Acquiring the LOCK 'restore.$sEnvironment'");
$oRestoreMutex->Lock();
IssueLog::Info('Backup Restore - LOCK acquired, executing...');
try
{
set_time_limit(0);
// Get the file and destroy the token (single usage)
$sToken = utils::ReadParam('token', '', false, 'raw_data');
$sTokenFile = APPROOT.'/data/restore.'.$sToken.'.tok';
$sFile = file_get_contents($sTokenFile);
unlink($sTokenFile);
$sMySQLBinDir = utils::ReadParam('mysql_bindir', '', false, 'raw_data');
$sDBHost = utils::ReadParam('db_host', '', false, 'raw_data');
$sDBUser = utils::ReadParam('db_user', '', false, 'raw_data');
$sDBPwd = utils::ReadParam('db_pwd', '', false, 'raw_data');
$sDBName = utils::ReadParam('db_name', '', false, 'raw_data');
$sDBSubName = utils::ReadParam('db_subname', '', false, 'raw_data');
$oDBRS = new DBRestore($sDBHost, $sDBUser, $sDBPwd, $sDBName, $sDBSubName);
$oDBRS->SetMySQLBinDir($sMySQLBinDir);
$sBackupDir = APPROOT.'data/backups/';
$sBackupFile = $sBackupDir.$sFile;
$sRes = $oDBRS->RestoreFromZip($sBackupFile, $sEnvironment);
IssueLog::Info('Backup Restore - Done, releasing the LOCK');
$oRestoreMutex->Unlock();
}
catch (Exception $e)
{
$oRestoreMutex->Unlock();
$oPage->p('Error: '.$e->getMessage());
}
}
$oPage->output();
break;
case 'download':
require_once(APPROOT.'/application/startup.inc.php');
require_once(APPROOT.'/application/loginwebpage.class.inc.php');
LoginWebPage::DoLogin(true); // Check user rights and prompt if needed (must be admin)
if (utils::GetConfig()->Get('demo_mode'))
{
throw new Exception('iTop is in demonstration mode: the feature is disabled');
}
$sFile = utils::ReadParam('file', '', false, 'raw_data');
$oBackup = new DBBackupScheduled();
$sBackupDir = APPROOT.'data/backups/';
$oBackup->DownloadBackup($sBackupDir.$sFile);
break;
}
}
catch (Exception $e)
{
IssueLog::Error($e->getMessage());
}
?>

View File

@@ -0,0 +1,54 @@
# Parameters file for backup.php and check-backup.php
#
# Usage:
# backup.php --param_file=<this file>[,<another one>]
# or
# http://.../itop-backup/backup.php?param_file=<this file>[,<another one>]
#
# If a parameter is given both in the file and in the arguments,
# then the value given as argument is retained
#
# Note: most of the default values provided here should work fine
# if you have created sample data with the setup program
# MySQL coming with Easy PHP (Windows)
mysql_bindir = C:\Program Files\EasyPHP-5.3.6.0\mysql\bin
# Authentication
auth_user = admin
auth_pwd = admin
# Target file - path and filename (optional)
#
# Formatting rules:
# %Y-%m-%d => 2011-01-25... see PHP documentation of strftime()
# Placeholders:
# __HOST__ MySQL server
# __DB__ Database name
# __SUBNAME__ Tables prefix
#
backup_file = /var/log/__DB__-%Y-%m-%d
# Check thresholds (check-backup.php)
#
check_size_min = 20000 # bytes
check_size_reduction_max = 10 # percentage
# Ticket creation (check-backup.php)
#
# If the backup has failed, a ticket will be created
# This process relies on the SOAP service "CreateIncident"
#
# Root URL of an instance of iTop, into which the ticket will be created
check_ticket_itop = http://localhost/myiTop
# Any of the above paramaters are mandatory
check_ticket_login = admin # must have the right to create an Incident Ticket
check_ticket_pwd = admin
check_ticket_title = Backup check failed
check_ticket_customer = Demo
check_ticket_service = Computers and peripherals
check_ticket_service_subcategory = Repair
check_ticket_workgroup = Hardware support
check_ticket_impacted_server = dbserver1.demo.com

View File

@@ -0,0 +1,200 @@
<?php
// Copyright (C) 2014 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
if (!defined('__DIR__')) define('__DIR__', dirname(__FILE__));
require_once(__DIR__.'/../../approot.inc.php');
require_once(APPROOT.'application/application.inc.php');
require_once(APPROOT.'application/webpage.class.inc.php');
require_once(APPROOT.'application/csvpage.class.inc.php');
require_once(APPROOT.'application/clipage.class.inc.php');
require_once(APPROOT.'application/ajaxwebpage.class.inc.php');
require_once(APPROOT.'core/log.class.inc.php');
require_once(APPROOT.'application/startup.inc.php');
class MyDBBackup extends DBBackup
{
protected function LogInfo($sMsg)
{
$this->oPage->p($sMsg);
}
protected function LogError($sMsg)
{
$this->oPage->p('Error: '.$sMsg);
ToolsLog::Error($sMsg);
}
protected $oPage;
public function __construct($oPage)
{
$this->oPage = $oPage;
parent::__construct();
}
}
/**
* Checks if a parameter (possibly empty) was specified when calling this page
*/
function CheckParam($sParamName)
{
global $argv;
if (isset($_REQUEST[$sParamName])) return true; // HTTP parameter either GET or POST
if (!is_array($argv)) return false;
foreach($argv as $sArg)
{
if ($sArg == '--'.$sParamName) return true; // Empty command line parameter, long unix style
if ($sArg == $sParamName) return true; // Empty command line parameter, Windows style
if ($sArg == '-'.$sParamName) return true; // Empty command line parameter, short unix style
if (preg_match('/^--'.$sParamName.'=(.*)$/', $sArg, $aMatches)) return true; // Command parameter with a value
}
return false;
}
function Usage($oP)
{
$oP->p('Perform a backup of the iTop database by running mysqldump');
$oP->p('Parameters:');
if (utils::IsModeCLI())
{
$oP->p('auth_user: login, must be administrator');
$oP->p('auth_pwd: ...');
}
$oP->p('backup_file [optional]: name of the file to store the backup into. Follows the PHP strftime format spec. The following placeholders are available: __HOST__, __DB__, __SUBNAME__');
$oP->p('simulate [optional]: set to check the name of the file that would be created');
$oP->p('mysql_bindir [optional]: specify the path for mysqldump');
if (utils::IsModeCLI())
{
$oP->p('Example: php -q backup.php --auth_user=admin --auth_pwd=myPassw0rd');
$oP->p('Known limitation: the current directory must be the directory of backup.php');
}
else
{
$oP->p('Example: .../backup.php?backup_file=/tmp/backup.__DB__-__SUBNAME__.%Y-%m');
}
}
function ExitError($oP, $sMessage)
{
ToolsLog::Error($sMessage);
$oP->p($sMessage);
$oP->output();
exit;
}
function ReadMandatoryParam($oP, $sParam)
{
$sValue = utils::ReadParam($sParam, null, true /* Allow CLI */, 'raw_data');
if (is_null($sValue))
{
ExitError($oP, "ERROR: Missing argument '$sParam'");
}
return trim($sValue);
}
/////////////////////////////////
// Main program
set_time_limit(0);
if (utils::IsModeCLI())
{
$oP = new CLIPage("iTop - Database Backup");
}
else
{
$oP = new WebPage("iTop - Database Backup");
}
try
{
utils::UseParamFile();
}
catch(Exception $e)
{
ExitError($oP, $e->GetMessage());
}
if (utils::IsModeCLI())
{
$oP->p(date('Y-m-d H:i:s')." - running backup utility");
$sAuthUser = ReadMandatoryParam($oP, 'auth_user');
$sAuthPwd = ReadMandatoryParam($oP, 'auth_pwd');
$bDownloadBackup = false;
if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd))
{
UserRights::Login($sAuthUser); // Login & set the user's language
}
else
{
ExitError($oP, "Access restricted or wrong credentials ('$sAuthUser')");
}
}
else
{
require_once(APPROOT.'application/loginwebpage.class.inc.php');
LoginWebPage::DoLogin(); // Check user rights and prompt if needed
$bDownloadBackup = utils::ReadParam('download', false);
}
if (!UserRights::IsAdministrator())
{
ExitError($oP, "Access restricted to administors");
}
if (CheckParam('?') || CheckParam('h') || CheckParam('help'))
{
Usage($oP);
$oP->output();
exit;
}
$sDefaultBackupFileName = SetupUtils::GetTmpDir().'/'."__DB__-%Y-%m-%d";
$sBackupFile = utils::ReadParam('backup_file', $sDefaultBackupFileName, true, 'raw_data');
// Interpret strftime specifications (like %Y) and database placeholders
$oBackup = new MyDBBackup($oP);
$oBackup->SetMySQLBinDir(MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'mysql_bindir', ''));
$sBackupFile = $oBackup->MakeName($sBackupFile);
$sZipArchiveFile = $sBackupFile.'.zip';
$bSimulate = utils::ReadParam('simulate', false, true);
$res = false;
if ($bSimulate)
{
$oP->p("Simulate: would create file '$sZipArchiveFile'");
}
elseif (MetaModel::GetConfig()->Get('demo_mode'))
{
$oP->p("Sorry, iTop is in demonstration mode: the feature is disabled");
}
else
{
$oBackup->CreateZip($sZipArchiveFile);
}
if ($res && $bDownloadBackup)
{
$oBackup->DownloadBackup($sZipArchiveFile);
}
$oP->output();
?>

View File

@@ -0,0 +1,267 @@
<?php
// Copyright (C) 2014 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
// Purpose: check that the backup has been successfully executed
// this script is aimed at being invoked in CLI mode only
// Developer's notes:
// Duplicated code: sys_get_temp_dir, the computation of the target filename, etc.
// Recommended usage in CRON
// /usr/bin/php -q /var/www/combodo/modules/itop-backup/backup.php --backup_file=/home/backups/combodo-crm-%Y-%m-%d
if (!defined('__DIR__')) define('__DIR__', dirname(__FILE__));
require_once(__DIR__.'/../../approot.inc.php');
require_once(APPROOT.'/application/utils.inc.php');
require_once(APPROOT.'/core/config.class.inc.php');
function ReadMandatoryParam($sParam)
{
$value = utils::ReadParam($sParam, null, true /* Allow CLI */, 'raw_data');
if (is_null($value))
{
throw new Exception("Missing argument '$sParam'");
}
return $value;
}
if (!function_exists('sys_get_temp_dir'))
{
// Based on http://www.phpit.net/
// article/creating-zip-tar-archives-dynamically-php/2/
function sys_get_temp_dir()
{
// Try to get from environment variable
if (!empty($_ENV['TMP']))
{
return realpath($_ENV['TMP']);
}
else if (!empty($_ENV['TMPDIR']))
{
return realpath($_ENV['TMPDIR']);
}
else if (!empty($_ENV['TEMP']))
{
return realpath($_ENV['TEMP']);
}
// Detect by creating a temporary file
else
{
// Try to use system's temporary directory
// as random name shouldn't exist
$temp_file = tempnam(md5(uniqid(rand(), TRUE)), '');
if ($temp_file)
{
$temp_dir = realpath(dirname($temp_file));
unlink($temp_file);
return $temp_dir;
}
else
{
return FALSE;
}
}
}
}
function MakeArchiveFileName($iRefTime = null)
{
$sDefaultBackupFileName = sys_get_temp_dir().'/'."__DB__-%Y-%m-%d";
$sBackupFile = utils::ReadParam('backup_file', $sDefaultBackupFileName, true, 'raw_data');
$oConfig = new Config(APPCONF.'production/config-itop.php');
$sBackupFile = str_replace('__HOST__', $oConfig->GetDBHost(), $sBackupFile);
$sBackupFile = str_replace('__DB__', $oConfig->GetDBName(), $sBackupFile);
$sBackupFile = str_replace('__SUBNAME__', $oConfig->GetDBSubName(), $sBackupFile);
if (is_null($iRefTime))
{
$sBackupFile = strftime($sBackupFile);
}
else
{
$sBackupFile = strftime($sBackupFile, $iRefTime);
}
return $sBackupFile.'.zip';
}
function RaiseAlarm($sMessage)
{
echo "$sMessage\n";
try
{
$sTicketLogin = ReadMandatoryParam('check_ticket_login');
$sTicketPwd = ReadMandatoryParam('check_ticket_pwd');
$sTicketTitle = ReadMandatoryParam('check_ticket_title');
$sTicketCustomer = ReadMandatoryParam('check_ticket_customer');
$sTicketService = ReadMandatoryParam('check_ticket_service');
$sTicketSubcategory = ReadMandatoryParam('check_ticket_service_subcategory');
$sTicketWorkgroup = ReadMandatoryParam('check_ticket_workgroup');
$sTicketImpactedServer = ReadMandatoryParam('check_ticket_impacted_server');
}
catch (Exception $e)
{
echo "The ticket could not be created: ".$e->GetMessage()."\n";
return;
}
$sMessage = "Server: [[Server:".$sTicketImpactedServer."]]\n".$sMessage;
require_once(APPROOT.'webservices/itopsoaptypes.class.inc.php');
//$sItopRootDefault = 'http'.((isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS']!='off')) ? 's' : '').'://'.$_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT'].dirname($_SERVER['SCRIPT_NAME']).'/../..';
//$sItopRoot = utils::ReadParam('check_ticket_itop', $sItopRootDefault);
$sItopRoot = ReadMandatoryParam('check_ticket_itop');
$sWsdlUri = $sItopRoot.'/webservices/itop.wsdl.php';
//$sWsdlUri .= '?service_category=';
$aSOAPMapping = SOAPMapping::GetMapping();
ini_set("soap.wsdl_cache_enabled","0");
$oSoapClient = new SoapClient(
$sWsdlUri,
array(
'trace' => 1,
'classmap' => $aSOAPMapping, // defined in itopsoaptypes.class.inc.php
)
);
try
{
$oRes = $oSoapClient->CreateIncidentTicket
(
$sTicketLogin, /* login */
$sTicketPwd, /* password */
$sTicketTitle, /* title */
$sMessage, /* description */
null, /* caller */
new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', $sTicketCustomer))), /* customer */
new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', $sTicketService))), /* service */
new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', $sTicketSubcategory))), /* service subcategory */
'', /* product */
new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', $sTicketWorkgroup))), /* workgroup */
array(
new SOAPLinkCreationSpec(
'Server',
array(new SOAPSearchCondition('name', $sTicketImpactedServer)),
array()
),
), /* impacted cis */
'1', /* impact */
'1' /* urgency */
);
}
catch(Exception $e)
{
echo "The ticket could not be created: SOAP Exception = '".$e->getMessage()."'\n";
}
//echo "<pre>\n";
//print_r($oRes);
//echo "</pre>\n";
if ($oRes->status)
{
$sTicketName = $oRes->result[0]->values[1]->value;
echo "Created ticket: $sTicketName\n";
}
else
{
echo "ERROR: Failed to create the ticket in target iTop ($sItopRoot)\n";
foreach ($oRes->errors->messages as $oMessage)
{
echo $oMessage->text."\n";
}
}
}
//////////
// Main
try
{
utils::UseParamFile();
}
catch(Exception $e)
{
echo "Error: ".$e->GetMessage()."\n";
exit;
}
$sZipArchiveFile = MakeArchiveFileName();
echo date('Y-m-d H:i:s')." - Checking file: $sZipArchiveFile\n";
if (file_exists($sZipArchiveFile))
{
if ($aStat = stat($sZipArchiveFile))
{
$iSize = (int) $aStat['size'];
$iMIN = utils::ReadParam('check_size_min', 0);
if ($iSize > $iMIN)
{
echo "Found the archive\n";
$sOldArchiveFile = MakeArchiveFileName(time() - 86400); // yesterday's archive
if (file_exists($sOldArchiveFile))
{
if ($aOldStat = stat($sOldArchiveFile))
{
echo "Comparing its size with older file: $sOldArchiveFile\n";
$iOldSize = (int) $aOldStat['size'];
$fVariationPercent = 100 * ($iSize - $iOldSize) / $iOldSize;
$sVariation = round($fVariationPercent, 2)." percent(s)";
$iREDUCTIONMAX = utils::ReadParam('check_size_reduction_max');
if ($fVariationPercent < -$iREDUCTIONMAX)
{
RaiseAlarm("Backup file '$sZipArchiveFile' changed by $sVariation, expecting a reduction limited to $iREDUCTIONMAX percents of the original size");
}
elseif ($fVariationPercent < 0)
{
echo "Size variation: $sVariation (the maximum allowed reduction is $iREDUCTIONMAX) \n";
}
else
{
echo "The archive grew by: $sVariation\n";
}
}
}
}
else
{
RaiseAlarm("Backup file '$sZipArchiveFile' too small (Found: $iSize, while expecting $iMIN bytes)");
}
}
else
{
RaiseAlarm("Failed to stat backup file '$sZipArchiveFile'");
}
}
else
{
RaiseAlarm("Missing backup file '$sZipArchiveFile'");
}
?>

View File

@@ -0,0 +1,129 @@
<?php
// Copyright (C) 2014 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
class DBRestore extends DBBackup
{
protected function LogInfo($sMsg)
{
//IssueLog::Info('non juste info: '.$sMsg);
}
protected function LogError($sMsg)
{
IssueLog::Error($sMsg);
}
protected function LoadDatabase($sDataFile)
{
$this->LogInfo("Loading data onto $this->sDBHost/$this->sDBName(suffix:'$this->sDBSubName')");
// Just to check the connection to the DB (more accurate than getting the retcode of mysql)
$oMysqli = $this->DBConnect();
$sHost = self::EscapeShellArg($this->sDBHost);
$sUser = self::EscapeShellArg($this->sDBUser);
$sPwd = self::EscapeShellArg($this->sDBPwd);
$sDBName = self::EscapeShellArg($this->sDBName);
if (empty($this->sMySQLBinDir))
{
$sMySQLExe = 'mysql';
}
else
{
$sMySQLExe = '"'.$this->sMySQLBinDir.'/mysql"';
}
if (is_null($this->iDBPort))
{
$sPortOption = '';
}
else
{
$sPortOption = '--port='.$this->iDBPort.' ';
}
$sDataFileEscaped = self::EscapeShellArg($sDataFile);
$sCommand = "$sMySQLExe --default-character-set=utf8 --host=$sHost $sPortOption --user=$sUser --password=$sPwd $sDBName <$sDataFileEscaped 2>&1";
$sCommandDisplay = "$sMySQLExe --default-character-set=utf8 --host=$sHost $sPortOption --user=xxxx --password=xxxx $sDBName <$sDataFileEscaped 2>&1";
// Now run the command for real
$this->LogInfo("Executing command: $sCommandDisplay");
$aOutput = array();
$iRetCode = 0;
exec($sCommand, $aOutput, $iRetCode);
foreach($aOutput as $sLine)
{
$this->LogInfo("mysql said: $sLine");
}
if ($iRetCode != 0)
{
$this->LogError("Failed to execute: $sCommandDisplay. The command returned:$iRetCode");
foreach($aOutput as $sLine)
{
$this->LogError("mysql said: $sLine");
}
if (count($aOutput) == 1)
{
$sMoreInfo = trim($aOutput[0]);
}
else
{
$sMoreInfo = "Check the log file '".realpath(APPROOT.'/log/error.log')."' for more information.";
}
throw new BackupException("Failed to execute mysql: ".$sMoreInfo);
}
}
public function RestoreFromZip($sZipFile, $sEnvironment = 'production')
{
$this->LogInfo("Starting restore of ".basename($sZipFile));
$oZip = new ZipArchive();
$res = $oZip->open($sZipFile);
// Load the database
//
$sDataDir = tempnam(SetupUtils::GetTmpDir(), 'itop-');
unlink($sDataDir); // I need a directory, not a file...
SetupUtils::builddir($sDataDir); // Here is the directory
$oZip->extractTo($sDataDir, 'itop-dump.sql');
$sDataFile = $sDataDir.'/itop-dump.sql';
$this->LoadDatabase($sDataFile);
unlink($sDataFile);
// Update the code
//
$sDeltaFile = APPROOT.'data/'.$sEnvironment.'.delta.xml';
if ($oZip->locateName('delta.xml') !== false)
{
// Extract and rename delta.xml => <env>.delta.xml;
file_put_contents($sDeltaFile, $oZip->getFromName('delta.xml'));
}
else
{
@unlink($sDeltaFile);
}
$sConfigFile = APPROOT.'conf/'.$sEnvironment.'/config-itop.php';
@chmod($sConfigFile, 0770); // Allow overwriting the file
$oZip->extractTo(APPROOT.'conf/'.$sEnvironment, 'config-itop.php');
@chmod($sConfigFile, 0444); // Read-only
$oEnvironment = new RunTimeEnvironment($sEnvironment);
$oEnvironment->CompileFrom($sEnvironment);
}
}

View File

@@ -0,0 +1,48 @@
<?php
/**
* Localized data
*
* @copyright Copyright (C) 2013 Combodo
* @license http://opensource.org/licenses/AGPL-3.0
* @author Robert Jaehne <robert.jaehne@itomig.de>
*/
Dict::Add('DE DE', 'German', 'Deutsch', array(
'bkp-backup-running' => 'Backup wird durchgeführt. Bitte warten ...',
'bkp-restore-running' => 'Wiederherstellung läuft. Bitte warten ...',
'Menu:BackupStatus' => 'Geplante Backups',
'bkp-status-title' => 'Geplante Backups',
'bkp-status-checks' => 'Einstellungen und Prüfungen',
'bkp-mysqldump-ok' => 'mysqldump ist vorhanden: %1$s',
'bkp-mysqldump-notfound' => 'mysqldump wurde nicht gefunden: %1$s - Stellen sie sicher, das er eingespielt und im Pfad verfügbar ist oder editieren sie die Konfigurationsdatei um das MySQL bindir anzupassen.',
'bkp-mysqldump-issue' => 'mysqldump konnte nicht eingespielt werden (retcode=%1$d): Stellen sie sicher, das er eingespielt und im Pfad verfügbar ist oder editieren sie die Konfigurationsdatei um das MySQL bindir anzupassen.',
'bkp-missing-dir' => 'Zielverzeichniss %1$s nicht gefunden',
'bkp-free-disk-space' => '<b>%1$s frei</b> in %2$s',
'bkp-dir-not-writeable' => '%1$s ist nicht schreibbar',
'bkp-wrong-format-spec' => 'Die verwendete Definition zur Formatierung von Dateinamen ist nicht korrekt (%1$s). Die Standard-Definition %2$s wird verwendet',
'bkp-name-sample' => 'Backup-Dateien werden abhängig von Datum, Zeit und Datenbank-Identifier erstellt. Beispiel: %1$s',
'bkp-week-days' => 'Backups werden <b>jeden %1$s um %2$s durchgeführt</b>',
'bkp-retention' => 'Mindestens <b>%1$d Backups werden im Zielverzeichniss vorgehalten</b>',
'bkp-next-to-delete' => 'Wird gelöscht, wenn das nächste Backup angelegt wird (unter Einstellungen "Menge vorhalten")',
'bkp-table-file' => 'Datei',
'bkp-table-file+' => 'Nur Dateien mit der Endung .zip werden als Backup-Dateien berücksichtigt.',
'bkp-table-size' => 'Grösse',
'bkp-table-size+' => '',
'bkp-table-actions' => 'Aktionen',
'bkp-table-actions+' => '',
'bkp-status-backups-auto' => 'Geplante Backups',
'bkp-status-backups-manual' => 'Manuelle Backups',
'bkp-status-backups-none' => 'Kein Backup vorhanden',
'bkp-next-backup' => 'Das nächste Backup wird am <b>%1$s</b> (%2$s) um %3$s durchgeführt',
'bkp-button-backup-now' => 'Backup läuft!',
'bkp-button-restore-now' => 'Wiederherstellen!',
'bkp-confirm-backup' => 'Bitte bestätigen sie, dass sie jetzt ein Backup erstellen wollen now.',
'bkp-confirm-restore' => 'Bitte bestätigen sie, dass sie mit Backup %1$s eine Wiederherstellung durchführen wollen.',
'bkp-wait-backup' => 'Bitte warten, bis das Backup abgeschlossen ist ...',
'bkp-wait-restore' => 'Bitte warten, bis die Wiederherstellung abgeschlossen ist ...',
'bkp-success-restore' => 'Wiederherstellung erfolgreich.',
));

View File

@@ -0,0 +1,46 @@
<?php
/**
* Localized data
*
* @copyright Copyright (C) 2013 Combodo
* @license http://opensource.org/licenses/AGPL-3.0
*/
Dict::Add('EN US', 'English', 'English', array(
'bkp-backup-running' => 'A backup is running. Please wait...',
'bkp-restore-running' => 'A restore is running. Please wait...',
'Menu:BackupStatus' => 'Scheduled Backups',
'bkp-status-title' => 'Scheduled Backups',
'bkp-status-checks' => 'Settings and checks',
'bkp-mysqldump-ok' => 'mysqldump is present: %1$s',
'bkp-mysqldump-notfound' => 'mysqldump could not be found: %1$s - Please make sure it is installed and in the path, or edit the configuration file to tune mysql_bindir.',
'bkp-mysqldump-issue' => 'mysqldump could not be executed (retcode=%1$d): Please make sure it is installed and in the path, or edit the configuration file to tune mysql_bindir',
'bkp-missing-dir' => 'The target directory %1$s count not be found',
'bkp-free-disk-space' => '<b>%1$s free</b> in %2$s',
'bkp-dir-not-writeable' => '%1$s is not writeable',
'bkp-wrong-format-spec' => 'The current specification to format the file names is wrong (%1$s). A default specification will apply: %2$s',
'bkp-name-sample' => 'Backup files are named depending on DB identifiers, date and time. Example: %1$s',
'bkp-week-days' => 'Backups will occur <b>every %1$s at %2$s</b>',
'bkp-retention' => 'At most <b>%1$d backup files will be kept</b> in the target directory.',
'bkp-next-to-delete' => 'Will be deleted when the next backup occurs (see the setting "retention_count")',
'bkp-table-file' => 'File',
'bkp-table-file+' => 'Only files having the extension .zip are considered as being backup files',
'bkp-table-size' => 'Size',
'bkp-table-size+' => '',
'bkp-table-actions' => 'Actions',
'bkp-table-actions+' => '',
'bkp-status-backups-auto' => 'Scheduled backups',
'bkp-status-backups-manual' => 'Manual backups',
'bkp-status-backups-none' => 'No backup yet',
'bkp-next-backup' => 'The next backup will occur on <b>%1$s</b> (%2$s) at %3$s',
'bkp-button-backup-now' => 'Backup now!',
'bkp-button-restore-now' => 'Restore!',
'bkp-confirm-backup' => 'Please confirm that you do request the backup to occur right now.',
'bkp-confirm-restore' => 'Please confirm that you do want to restore the backup %1$s.',
'bkp-wait-backup' => 'Please wait for the backup to complete...',
'bkp-wait-restore' => 'Please wait for the restore to complete...',
'bkp-success-restore' => 'Restore successfully completed.',
));
?>

View File

@@ -0,0 +1,46 @@
<?php
/**
* Localized data
*
* @copyright Copyright (C) 2013 Combodo
* @license http://opensource.org/licenses/AGPL-3.0
*/
Dict::Add('FR FR', 'French', 'Français', array(
'bkp-backup-running' => 'Une sauvegarde est en cours. Veuillez patienter...',
'bkp-restore-running' => 'Une restauration des données est en cours. Veuillez patienter...',
'Menu:BackupStatus' => 'Sauvegarde automatique',
'bkp-status-title' => 'Sauvegarde automatique',
'bkp-status-checks' => 'Réglages et vérifications',
'bkp-mysqldump-ok' => 'mysqldump est installé: %1$s',
'bkp-mysqldump-notfound' => 'mysqldump n\'a pas été trouvé: %1$s - Veuillez vous assurer que les outils mysql sont installés et qu\'ils sont accessibles en ligne de commande, ou bien éditez le fichier de configuration pour en donner le chemin via mysql_bindir.',
'bkp-mysqldump-issue' => 'mysqldump n\'a pas pu être exécuté (code de retour: %1$d). Veuillez vérifier que les outils mysql sont installés et qu\'ils sont accessibles en ligne de commande, ou bien éditez le fichier de configuration pour en donner le chemin via mysql_bindir.',
'bkp-missing-dir' => 'Le répertoire cible \'%1$s\' n\'existe pas ou ne peut pas être lu.',
'bkp-free-disk-space' => 'Vous disposez de <b>%1$s d\'espace disque</b> sur %2$s',
'bkp-dir-not-writeable' => 'Le répertoire cible \'%1$s\' n\'est pas accessible en écriture.',
'bkp-wrong-format-spec' => 'La spécification de format pour le nom des sauvegarde est incorrecte (%1$s). La spécification par défaut sera appliquée: %2$s',
'bkp-name-sample' => 'Les fichiers de sauvegardes seront nommés en fonction de la base, la date et l\'heure. Par exemple: %1$s',
'bkp-week-days' => 'Les sauvegardes seront effectuées <b>tous les %1$s à %2$s</b>',
'bkp-retention' => 'Au plus <b>%1$d fichiers de sauvegardes seront conservés</b> dans le répertoire cible.',
'bkp-next-to-delete' => 'Sera effacé lors de la prochaine sauvegarde (Cf. réglage "retention_count")',
'bkp-table-file' => 'Fichier',
'bkp-table-file+' => 'Seuls les fichiers ayant l\'extension .zip sont considérés comme étant des fichiers de sauvegarde',
'bkp-table-size' => 'Taille',
'bkp-table-size+' => '',
'bkp-table-actions' => 'Actions',
'bkp-table-actions+' => '',
'bkp-status-backups-auto' => 'Sauvegardes automatiques',
'bkp-status-backups-manual' => 'Sauvegardes manuelles',
'bkp-status-backups-none' => 'Aucune sauvegarde n\'a été faite jusqu\' à présent.',
'bkp-next-backup' => 'La prochaine sauvegarde aura lieu <b>%1$s</b> (%2$s) à %3$s',
'bkp-button-backup-now' => 'Sauvegarder maintenant !',
'bkp-button-restore-now' => 'Restaurer !',
'bkp-confirm-backup' => 'Veuillez confirmer que vous souhaiter effectuer une sauvegarde maintenant.',
'bkp-confirm-restore' => 'Veuillez confirmer que vous souhaiter effectuer la restauration de \'%1$s\' maintenant.',
'bkp-wait-backup' => 'Sauvegarde en cours...',
'bkp-wait-restore' => 'Restauration des données en cours...',
'bkp-success-restore' => 'Restauration des données terminée.',
));
?>

View File

@@ -0,0 +1,286 @@
<?php
// Copyright (C) 2014-2016 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
require_once(APPROOT.'setup/setuputils.class.inc.php');
require_once(APPROOT.'setup/backup.class.inc.php');
require_once(APPROOT.'core/mutex.class.inc.php');
define('BACKUP_DEFAULT_FORMAT', '__DB__-%Y-%m-%d_%H_%M');
class BackupHandler extends ModuleHandlerAPI
{
public static function OnMetaModelStarted()
{
try
{
$oRestoreMutex = new iTopMutex('restore.'.utils::GetCurrentEnvironment());
if ($oRestoreMutex->IsLocked())
{
IssueLog::Info(__class__.'::'.__function__.' A user is trying to use iTop while a restore is running. The requested page is in read-only mode.');
MetaModel::GetConfig()->Set('access_mode', ACCESS_READONLY, 'itop-backup');
MetaModel::GetConfig()->Set('access_message', ' - '.dict::S('bkp-restore-running'), 'itop-backup');
}
}
catch(Exception $e)
{
IssueLog::Error(__class__.'::'.__function__.' Failed to check if a backup/restore is running: '.$e->getMessage());
}
}
}
class DBBackupScheduled extends DBBackup
{
protected function LogInfo($sMsg)
{
static $bDebug = null;
if ($bDebug == null)
{
$bDebug = MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'debug', false);
}
if ($bDebug)
{
echo $sMsg."\n";
}
}
protected function LogError($sMsg)
{
static $bDebug = null;
if ($bDebug == null)
{
$bDebug = MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'debug', false);
}
IssueLog::Error($sMsg);
if ($bDebug)
{
echo 'Error: '.$sMsg."\n";
}
}
/**
* List and order by date the backups in the given directory
* Note: the algorithm is currently based on the file modification date... because there is no "creation date" in general
*/
public function ListFiles($sBackupDir)
{
$aFiles = array();
$aTimes = array();
foreach(glob($sBackupDir.'*.zip') as $sFilePath)
{
$aFiles[] = $sFilePath;
$aTimes[] = filemtime($sFilePath); // unix time
}
array_multisort($aTimes, $aFiles);
return $aFiles;
}
}
class BackupExec implements iScheduledProcess
{
protected $sBackupDir;
protected $iRetentionCount;
/**
* Constructor
* @param sBackupDir string Target directory, defaults to APPROOT/data/backups/auto
* @param iRetentionCount int Rotation (default to the value given in the configuration file 'retentation_count') set to 0 to disable this feature
*/
public function __construct($sBackupDir = null, $iRetentionCount = null)
{
if (is_null($sBackupDir))
{
$this->sBackupDir = APPROOT.'data/backups/auto/';
}
else
{
$this->sBackupDir = $sBackupDir;
}
if (is_null($iRetentionCount))
{
$this->iRetentionCount = MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'retention_count', 5);
}
else
{
$this->iRetentionCount = $iRetentionCount;
}
}
public function Process($iUnixTimeLimit)
{
$oMutex = new iTopMutex('backup.'.utils::GetCurrentEnvironment());
$oMutex->Lock();
try
{
// Make sure the target directory exists
SetupUtils::builddir($this->sBackupDir);
$oBackup = new DBBackupScheduled();
// Eliminate files exceeding the retention setting
//
if ($this->iRetentionCount > 0)
{
$aFiles = $oBackup->ListFiles($this->sBackupDir);
while (count($aFiles) >= $this->iRetentionCount)
{
$sFileToDelete = array_shift($aFiles);
unlink($sFileToDelete);
if (file_exists($sFileToDelete))
{
// Ok, do not loop indefinitely on this
break;
}
}
}
// Do execute the backup
//
$oBackup->SetMySQLBinDir(MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'mysql_bindir', ''));
$sBackupFile = MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'file_name_format', '__DB__-%Y-%m-%d_%H_%M');
$sName = $oBackup->MakeName($sBackupFile);
if ($sName == '')
{
$sName = $oBackup->MakeName(BACKUP_DEFAULT_FORMAT);
}
$sZipFile = $this->sBackupDir.$sName.'.zip';
$sSourceConfigFile = APPCONF.utils::GetCurrentEnvironment().'/'.ITOP_CONFIG_FILE;
$oBackup->CreateZip($sZipFile, $sSourceConfigFile);
}
catch (Exception $e)
{
$oMutex->Unlock();
throw $e;
}
$oMutex->Unlock();
return "Created the backup: $sZipFile";
}
/*
Interpret current setting for the week days
@returns array of int (monday = 1)
*/
public function InterpretWeekDays()
{
static $aWEEKDAYTON = array('monday' => 1, 'tuesday' => 2, 'wednesday' => 3, 'thursday' => 4, 'friday' => 5, 'saturday' => 6, 'sunday' => 7);
$aDays = array();
$sWeekDays = MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'week_days', 'monday, tuesday, wednesday, thursday, friday');
if ($sWeekDays != '')
{
$aWeekDaysRaw = explode(',', $sWeekDays);
foreach ($aWeekDaysRaw as $sWeekDay)
{
$sWeekDay = strtolower(trim($sWeekDay));
if (array_key_exists($sWeekDay, $aWEEKDAYTON))
{
$aDays[] = $aWEEKDAYTON[$sWeekDay];
}
else
{
throw new Exception("'itop-backup: wrong format for setting 'week_days' (found '$sWeekDay')");
}
}
}
if (count($aDays) == 0)
{
throw new Exception("'itop-backup: missing setting 'week_days'");
}
$aDays = array_unique($aDays);
sort($aDays);
return $aDays;
}
/*
Gives the exact time at which the process must be run next time
@returns DateTime
*/
public function GetNextOccurrence()
{
$bEnabled = MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'enabled', true);
if (!$bEnabled)
{
$oRet = new DateTime('3000-01-01');
}
else
{
// 1st - Interpret the list of days as ordered numbers (monday = 1)
//
$aDays = $this->InterpretWeekDays();
// 2nd - Find the next active week day
//
$sBackupTime = MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'time', '23:30');
if (!preg_match('/[0-2][0-9]:[0-5][0-9]/', $sBackupTime))
{
throw new Exception("'itop-backup: wrong format for setting 'time' (found '$sBackupTime')");
}
$oNow = new DateTime();
$iNextPos = false;
for ($iDay = $oNow->format('N') ; $iDay <= 7 ; $iDay++)
{
$iNextPos = array_search($iDay, $aDays);
if ($iNextPos !== false)
{
if (($iDay > $oNow->format('N')) || ($oNow->format('H:i') < $sBackupTime))
{
break;
}
}
}
// 3rd - Compute the result
//
if ($iNextPos === false)
{
// Jump to the first day within the next week
$iFirstDayOfWeek = $aDays[0];
$iDayMove = $oNow->format('N') - $iFirstDayOfWeek;
$oRet = clone $oNow;
$oRet->modify('-'.$iDayMove.' days');
$oRet->modify('+1 weeks');
}
else
{
$iNextDayOfWeek = $aDays[$iNextPos];
$iMove = $iNextDayOfWeek - $oNow->format('N');
$oRet = clone $oNow;
$oRet->modify('+'.$iMove.' days');
}
list($sHours, $sMinutes) = explode(':', $sBackupTime);
$oRet->setTime((int)$sHours, (int) $sMinutes);
}
return $oRet;
}
}
class ItopBackup extends ModuleHandlerAPI
{
public static function OnMenuCreation()
{
if (UserRights::IsAdministrator())
{
$oAdminMenu = new MenuGroup('AdminTools', 80 /* fRank */);
new WebPageMenuNode('BackupStatus', utils::GetAbsoluteUrlModulePage('itop-backup', 'status.php'), $oAdminMenu->GetIndex(), 15 /* fRank */);
}
}
}

View File

@@ -0,0 +1,60 @@
<?php
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-backup/2.1.1',
array(
// Identification
//
'label' => 'Backup utilities',
'category' => 'Application management',
// Setup
//
'dependencies' => array(
),
'mandatory' => true,
'visible' => false,
// Components
//
'datamodel' => array(
'main.itop-backup.php',
//'model.itop-backup.php',
),
'webservice' => array(
//'webservices.itop-backup.php',
),
'dictionary' => array(
'en.dict.itop-backup.php',
'fr.dict.itop-backup.php',
//'de.dict.itop-backup.php',
),
'data.struct' => array(
//'data.struct.itop-backup.xml',
),
'data.sample' => array(
//'data.sample.itop-backup.xml',
),
// Documentation
//
'doc.manual_setup' => '',
'doc.more_information' => '',
// Default settings
//
'settings' => array(
'mysql_bindir' => '',
'week_days' => 'monday, tuesday, wednesday, thursday, friday',
'time' => '23:30',
//'file_name_format' => '__DB__-%Y-%m-%d_%H_%M',
'retention_count' => 5,
'enabled' => true,
'debug' => false
),
)
);
?>

View File

@@ -0,0 +1,401 @@
<?php
// Copyright (C) 2016 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Monitor the backup
*
* @copyright Copyright (C) 2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
if (!defined('__DIR__')) define('__DIR__', dirname(__FILE__));
require_once(__DIR__.'/../../approot.inc.php');
require_once(APPROOT.'application/application.inc.php');
require_once(APPROOT.'application/itopwebpage.class.inc.php');
require_once(APPROOT.'application/startup.inc.php');
require_once(APPROOT.'application/loginwebpage.class.inc.php');
/////////////////////////////////////////////////////////////////////
// Main program
//
LoginWebPage::DoLogin(true); // Check user rights and prompt if needed (must be admin)
//$sOperation = utils::ReadParam('operation', 'menu');
//$oAppContext = new ApplicationContext();
$oP = new iTopWebPage(Dict::S('bkp-status-title'));
$oP->set_base(utils::GetAbsoluteUrlAppRoot().'pages/');
try
{
$oP->add("<h1>".Dict::S('bkp-status-title')."</h1>");
if (MetaModel::GetConfig()->Get('demo_mode'))
{
$oP->add("<div class=\"header_message message_info\">iTop is in <b>demonstration mode</b>: the feature is disabled.</div>");
}
$sImgOk = '<img src="../images/validation_ok.png"> ';
$sImgError = '<img src="../images/validation_error.png"> ';
$oP->add("<fieldset>");
$oP->add("<legend>".Dict::S('bkp-status-checks')."</legend>");
// Availability of mysqldump
//
$sMySQLBinDir = MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'mysql_bindir', '');
$sMySQLBinDir = utils::ReadParam('mysql_bindir', $sMySQLBinDir, true);
if (empty($sMySQLBinDir))
{
$sMySQLDump = 'mysqldump';
}
else
{
//echo 'Info - Found mysql_bindir: '.$sMySQLBinDir;
$sMySQLDump = '"'.$sMySQLBinDir.'/mysqldump"';
}
$sCommand = "$sMySQLDump -V 2>&1";
$aOutput = array();
$iRetCode = 0;
exec($sCommand, $aOutput, $iRetCode);
if ($iRetCode == 0)
{
$sMySqlDump = $sImgOk.Dict::Format("bkp-mysqldump-ok", $aOutput[0]);
}
elseif ($iRetCode == 1)
{
$sMySqlDump = $sImgError.Dict::Format("bkp-mysqldump-notfound", implode(' ', $aOutput));
}
else
{
$sMySqlDump = $sImgError.Dict::Format("bkp-mysqldump-issue", $iRetCode);
}
foreach($aOutput as $sLine)
{
//echo 'Info - mysqldump -V said: '.$sLine;
}
$oP->p($sMySqlDump);
// Destination directory
//
// Make sure the target directory exists and is writeable
$sBackupDir = APPROOT.'data/backups/';
SetupUtils::builddir($sBackupDir);
if (!is_dir($sBackupDir))
{
$oP->p($sImgError.Dict::Format('bkp-missing-dir', $sBackupDir));
}
else
{
$oP->p(Dict::Format('bkp-free-disk-space', SetupUtils::HumanReadableSize(SetupUtils::CheckDiskSpace($sBackupDir)), $sBackupDir));
if (!is_writable($sBackupDir))
{
$oP->p($sImgError.Dict::Format('bkp-dir-not-writeable', $sBackupDir));
}
}
$sBackupDirAuto = $sBackupDir.'auto/';
SetupUtils::builddir($sBackupDirAuto);
$sBackupDirManual = $sBackupDir.'manual/';
SetupUtils::builddir($sBackupDirManual);
// Wrong format
//
$sBackupFile = MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'file_name_format', BACKUP_DEFAULT_FORMAT);
$oBackup = new DBBackupScheduled();
$sZipName = $oBackup->MakeName($sBackupFile);
if ($sZipName == '')
{
$oP->p($sImgError.Dict::Format('bkp-wrong-format-spec', $sBackupFile, BACKUP_DEFAULT_FORMAT));
}
else
{
$oP->p(Dict::Format('bkp-name-sample', $sZipName));
}
// Week Days
//
$aWeekDayToString = array(
1 => Dict::S('DayOfWeek-Monday'),
2 => Dict::S('DayOfWeek-Tuesday'),
3 => Dict::S('DayOfWeek-Wednesday'),
4 => Dict::S('DayOfWeek-Thursday'),
5 => Dict::S('DayOfWeek-Friday'),
6 => Dict::S('DayOfWeek-Saturday'),
7 => Dict::S('DayOfWeek-Sunday')
);
$aDayLabels = array();
$oBackupExec = new BackupExec();
foreach ($oBackupExec->InterpretWeekDays() as $iDay)
{
$aDayLabels[] = $aWeekDayToString[$iDay];
}
$sDays = implode(', ', $aDayLabels);
$sBackupTime = MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'time', '23:30');
$oP->p(Dict::Format('bkp-week-days', $sDays, $sBackupTime));
$iRetention = MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'retention_count', 5);
$oP->p(Dict::Format('bkp-retention', $iRetention));
$oP->add("</fieldset>");
// List of backups
//
$aFiles = $oBackup->ListFiles($sBackupDirAuto);
$aFilesToDelete = array();
while (count($aFiles) > $iRetention - 1)
{
$aFilesToDelete[] = array_shift($aFiles);
}
$oRestoreMutex = new iTopMutex('restore.'.utils::GetCurrentEnvironment());
if ($oRestoreMutex->IsLocked())
{
$sDisableRestore = 'disabled="disabled"';
}
else
{
$sDisableRestore = '';
}
// 1st table: list the backups made in the background
//
$aDetails = array();
foreach ($oBackup->ListFiles($sBackupDirAuto) as $sBackupFile)
{
$sFileName = basename($sBackupFile);
$sFilePath = 'auto/'.$sFileName;
if (MetaModel::GetConfig()->Get('demo_mode'))
{
$sName = $sFileName;
}
else
{
$sAjax = utils::GetAbsoluteUrlModulePage('itop-backup', 'ajax.backup.php', array('operation' => 'download', 'file' => $sFilePath));
$sName = "<a href=\"$sAjax\">".$sFileName.'</a>';
}
$sSize = SetupUtils::HumanReadableSize(filesize($sBackupFile));
$sConfirmRestore = addslashes(Dict::Format('bkp-confirm-restore', $sFileName));
$sFileEscaped = addslashes($sFilePath);
$sRestoreBtn = '<button class="restore" onclick="LaunchRestoreNow(\''.$sFileEscaped.'\', \''.$sConfirmRestore.'\');" '.$sDisableRestore.'>'.Dict::S('bkp-button-restore-now').'</button>';
if (in_array($sBackupFile, $aFilesToDelete))
{
$aDetails[] = array('file' => $sName.' <span class="next_to_delete" title="'.Dict::S('bkp-next-to-delete').'">*</span>', 'size' => $sSize, 'actions' => $sRestoreBtn);
}
else
{
$aDetails[] = array('file' => $sName, 'size' => $sSize, 'actions' => $sRestoreBtn);
}
}
$aConfig = array(
'file' => array('label' => Dict::S('bkp-table-file'), 'description' => Dict::S('bkp-table-file+')),
'size' => array('label' => Dict::S('bkp-table-size'), 'description' => Dict::S('bkp-table-size+')),
'actions' => array('label' => Dict::S('bkp-table-actions'), 'description' => Dict::S('bkp-table-actions+')),
);
$oP->add("<fieldset>");
$oP->add("<legend>".Dict::S('bkp-status-backups-auto')."</legend>");
if (count($aDetails) > 0)
{
$oP->add('<div style="max-height:400px; overflow: auto;">');
$oP->table($aConfig, array_reverse($aDetails));
$oP->add('</div>');
}
else
{
$oP->p(Dict::S('bkp-status-backups-none'));
}
$oP->add("</fieldset>");
// 2nd table: list the backups made manually
//
$aDetails = array();
foreach ($oBackup->ListFiles($sBackupDirManual) as $sBackupFile)
{
$sFileName = basename($sBackupFile);
$sFilePath = 'manual/'.$sFileName;
if (MetaModel::GetConfig()->Get('demo_mode'))
{
$sName = $sFileName;
}
else
{
$sAjax = utils::GetAbsoluteUrlModulePage('itop-backup', 'ajax.backup.php', array('operation' => 'download', 'file' => $sFilePath));
$sName = "<a href=\"$sAjax\">".$sFileName.'</a>';
}
$sSize = SetupUtils::HumanReadableSize(filesize($sBackupFile));
$sConfirmRestore = addslashes(Dict::Format('bkp-confirm-restore', $sFileName));
$sFileEscaped = addslashes($sFilePath);
$sRestoreBtn = '<button class="restore" onclick="LaunchRestoreNow(\''.$sFileEscaped.'\', \''.$sConfirmRestore.'\');" '.$sDisableRestore.'>'.Dict::S('bkp-button-restore-now').'</button>';
$aDetails[] = array('file' => $sName, 'size' => $sSize, 'actions' => $sRestoreBtn);
}
$aConfig = array(
'file' => array('label' => Dict::S('bkp-table-file'), 'description' => Dict::S('bkp-table-file+')),
'size' => array('label' => Dict::S('bkp-table-size'), 'description' => Dict::S('bkp-table-size+')),
'actions' => array('label' => Dict::S('bkp-table-actions'), 'description' => Dict::S('bkp-table-actions+')),
);
$oP->add("<fieldset>");
$oP->add("<legend>".Dict::S('bkp-status-backups-manual')."</legend>");
if (count($aDetails) > 0)
{
$oP->add('<div style="max-height:400px; overflow: auto;">');
$oP->table($aConfig, array_reverse($aDetails));
$oP->add('</div>');
}
else
{
$oP->p(Dict::S('bkp-status-backups-none'));
}
$oP->add("</fieldset>");
// Ongoing operation ?
//
$oBackupMutex = new iTopMutex('backup.'.utils::GetCurrentEnvironment());
if ($oBackupMutex->IsLocked())
{
$oP->p(Dict::S('bkp-backup-running'));
}
$oRestoreMutex = new iTopMutex('restore.'.utils::GetCurrentEnvironment());
if ($oRestoreMutex->IsLocked())
{
$oP->p(Dict::S('bkp-restore-running'));
}
// Do backup now
//
$oBackupExec = new BackupExec();
$oNext = $oBackupExec->GetNextOccurrence();
$oP->p(Dict::Format('bkp-next-backup', $aWeekDayToString[$oNext->Format('N')], $oNext->Format('Y-m-d'), $oNext->Format('H:i')));
$oP->p('<button onclick="LaunchBackupNow();">'.Dict::S('bkp-button-backup-now').'</button>');
$oP->add('<div id="backup_success" class="header_message message_ok" style="display: none;"></div>');
$oP->add('<div id="backup_errors" class="header_message message_error" style="display: none;"></div>');
$oP->add('<input type="hidden" name="restore_token" id="restore_token"/>');
$sConfirmBackup = addslashes(Dict::S('bkp-confirm-backup'));
$sPleaseWaitBackup = addslashes(Dict::S('bkp-wait-backup'));
$sPleaseWaitRestore = addslashes(Dict::S('bkp-wait-restore'));
$sRestoreDone = addslashes(Dict::S('bkp-success-restore'));
$sMySQLBinDir = addslashes(MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'mysql_bindir', ''));
$sDBHost = addslashes(MetaModel::GetConfig()->GetDBHost());
$sDBUser = addslashes(MetaModel::GetConfig()->GetDBUser());
$sDBPwd = addslashes(MetaModel::GetConfig()->GetDBPwd());
$sDBName = addslashes(MetaModel::GetConfig()->GetDBName());
$sDBSubName = addslashes(MetaModel::GetConfig()->GetDBSubName());
$sEnvironment = addslashes(utils::GetCurrentEnvironment());
$oP->add_script(
<<<EOF
function LaunchBackupNow()
{
$('#backup_success').hide();
$('#backup_errors').hide();
if (confirm('$sConfirmBackup'))
{
$.blockUI({ message: '<h1><img src="../images/indicator.gif" /> $sPleaseWaitBackup</h1>' });
var oParams = {};
oParams.operation = 'backup';
$.post(GetAbsoluteUrlModulePage('itop-backup', 'ajax.backup.php'), oParams, function(data){
if (data.search(/error|exceptio|notice|warning/i) != -1)
{
$('#backup_errors').html(data);
$('#backup_errors').show();
}
else
{
window.location.reload();
}
$.unblockUI();
});
}
}
function LaunchRestoreNow(sBackupFile, sConfirmationMessage)
{
if (confirm(sConfirmationMessage))
{
$.blockUI({ message: '<h1><img src="../images/indicator.gif" /> $sPleaseWaitRestore</h1>' });
$('#backup_success').hide();
$('#backup_errors').hide();
var oParams = {};
oParams.operation = 'restore_get_token';
oParams.file = sBackupFile;
$.post(GetAbsoluteUrlModulePage('itop-backup', 'ajax.backup.php'), oParams, function(data){
// Get the value of restore_token
$('#backup_errors').append(data);
var oParams = {};
oParams.operation = 'restore_exec';
oParams.token = $("#restore_token").val();
oParams.mysql_bindir = '$sMySQLBinDir';
oParams.db_host = '$sDBHost';
oParams.db_user = '$sDBUser';
oParams.db_pwd = '$sDBPwd';
oParams.db_name = '$sDBName';
oParams.db_subname = '$sDBSubName';
oParams.environment = '$sEnvironment';
if (oParams.token.length > 0)
{
$.post(GetAbsoluteUrlModulePage('itop-backup', 'ajax.backup.php'), oParams, function(data){
if (data.search(/error|exceptio|notice|warning/i) != -1)
{
$('#backup_success').hide();
$('#backup_errors').html(data);
$('#backup_errors').show();
}
else
{
$('#backup_errors').hide();
$('#backup_success').html('$sRestoreDone');
$('#backup_success').show();
}
$.unblockUI();
});
}
else
{
$('button.restore').attr('disabled', 'disabled');
$.unblockUI();
}
});
}
}
EOF
);
if (MetaModel::GetConfig()->Get('demo_mode'))
{
$oP->add_ready_script("$('button').attr('disabled', 'disabled').attr('title', 'Disabled in demonstration mode')");
}
}
catch(Exception $e)
{
$oP->p('<b>'.$e->getMessage().'</b>');
}
$oP->output();
?>

View File

@@ -1,9 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0">
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.1">
<classes>
<class id="lnkVirtualDeviceToVolume" _delta="define">
<parent>cmdbAbstractObject</parent>
<properties>
<is_link>1</is_link>
<category>bizmodel,configmgmt</category>
<abstract>false</abstract>
<key_type>autoincrement</key_type>
@@ -11,7 +12,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="volume_id"/>
</attributes>
@@ -91,4 +91,4 @@
</presentation>
</class>
</classes>
</itop_design>
</itop_design>

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-bridge-virtualization-storage/2.0.0',
'itop-bridge-virtualization-storage/2.1.0',
array(
// Identification
//

View File

@@ -0,0 +1,247 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* @author Erik Bøg <erik@boegmoeller.dk>
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @licence http://opensource.org/licenses/AGPL-3.0
*/
Dict::Add('DA DA', 'Danish', 'Dansk', array(
'Class:RoutineChange' => 'Rutine Ændring',
'Class:RoutineChange+' => '',
'Class:RoutineChange/Stimulus:ev_assign' => 'Tildel',
'Class:RoutineChange/Stimulus:ev_assign+' => '',
'Class:RoutineChange/Stimulus:ev_reopen' => 'Genåben',
'Class:RoutineChange/Stimulus:ev_reopen+' => '',
'Class:RoutineChange/Stimulus:ev_plan' => 'Planlæg',
'Class:RoutineChange/Stimulus:ev_plan+' => '',
'Class:RoutineChange/Stimulus:ev_approve' => 'Godkend',
'Class:RoutineChange/Stimulus:ev_approve+' => '',
'Class:RoutineChange/Stimulus:ev_replan' => 'Genplanlæg',
'Class:RoutineChange/Stimulus:ev_replan+' => '',
'Class:RoutineChange/Stimulus:ev_notapprove' => 'Godkend Ikke',
'Class:RoutineChange/Stimulus:ev_notapprove+' => '',
'Class:RoutineChange/Stimulus:ev_implement' => 'Implementer',
'Class:RoutineChange/Stimulus:ev_implement+' => '',
'Class:RoutineChange/Stimulus:ev_monitor' => 'Overvåg',
'Class:RoutineChange/Stimulus:ev_monitor+' => '',
'Class:RoutineChange/Stimulus:ev_finish' => 'Afslut',
'Class:RoutineChange/Stimulus:ev_finish+' => '',
'Class:NormalChange' => 'Normal Ændring',
'Class:NormalChange+' => '',
'Class:NormalChange/Attribute:acceptance_date' => 'Dato for accept',
'Class:NormalChange/Attribute:acceptance_date+' => '',
'Class:NormalChange/Attribute:acceptance_comment' => 'Kommentar til accept',
'Class:NormalChange/Attribute:acceptance_comment+' => '',
'Class:NormalChange/Stimulus:ev_validate' => 'Valider',
'Class:NormalChange/Stimulus:ev_validate+' => '',
'Class:NormalChange/Stimulus:ev_reject' => 'Afslå',
'Class:NormalChange/Stimulus:ev_reject+' => '',
'Class:NormalChange/Stimulus:ev_assign' => 'Tildel',
'Class:NormalChange/Stimulus:ev_assign+' => '',
'Class:NormalChange/Stimulus:ev_reopen' => 'Genåben',
'Class:NormalChange/Stimulus:ev_reopen+' => '',
'Class:NormalChange/Stimulus:ev_plan' => 'Planlæg',
'Class:NormalChange/Stimulus:ev_plan+' => '',
'Class:NormalChange/Stimulus:ev_approve' => 'Godkend',
'Class:NormalChange/Stimulus:ev_approve+' => '',
'Class:NormalChange/Stimulus:ev_replan' => 'Genplanlæg',
'Class:NormalChange/Stimulus:ev_replan+' => '',
'Class:NormalChange/Stimulus:ev_notapprove' => 'Godkent Ikke',
'Class:NormalChange/Stimulus:ev_notapprove+' => '',
'Class:NormalChange/Stimulus:ev_implement' => 'Implementer',
'Class:NormalChange/Stimulus:ev_implement+' => '',
'Class:NormalChange/Stimulus:ev_monitor' => 'Overvåg',
'Class:NormalChange/Stimulus:ev_monitor+' => '',
'Class:NormalChange/Stimulus:ev_finish' => 'Afslut',
'Class:NormalChange/Stimulus:ev_finish+' => '',
'Class:EmergencyChange' => 'Emergency Ændring',
'Class:EmergencyChange+' => '',
'Class:EmergencyChange/Stimulus:ev_assign' => 'Tildel',
'Class:EmergencyChange/Stimulus:ev_assign+' => '',
'Class:EmergencyChange/Stimulus:ev_reopen' => 'Genåben',
'Class:EmergencyChange/Stimulus:ev_reopen+' => '',
'Class:EmergencyChange/Stimulus:ev_plan' => 'Planlæg',
'Class:EmergencyChange/Stimulus:ev_plan+' => '',
'Class:EmergencyChange/Stimulus:ev_approve' => 'Godkend',
'Class:EmergencyChange/Stimulus:ev_approve+' => '',
'Class:EmergencyChange/Stimulus:ev_replan' => 'Genplanlæg',
'Class:EmergencyChange/Stimulus:ev_replan+' => '',
'Class:EmergencyChange/Stimulus:ev_notapprove' => 'Godkend Ikke',
'Class:EmergencyChange/Stimulus:ev_notapprove+' => '',
'Class:EmergencyChange/Stimulus:ev_implement' => 'Implementer',
'Class:EmergencyChange/Stimulus:ev_implement+' => '',
'Class:EmergencyChange/Stimulus:ev_monitor' => 'Overvåg',
'Class:EmergencyChange/Stimulus:ev_monitor+' => '',
'Class:EmergencyChange/Stimulus:ev_finish' => 'Afslut',
'Class:EmergencyChange/Stimulus:ev_finish+' => '',
'Menu:ChangeManagement' => 'Change Management',
'Menu:Change:Overview' => 'Oversigt',
'Menu:Change:Overview+' => '',
'Menu:NewChange' => 'Ny Change',
'Menu:NewChange+' => 'Opret en ny Change ticket',
'Menu:SearchChanges' => 'Søg efter Changes',
'Menu:SearchChanges+' => 'Søg efter Change Tickets',
'Menu:Change:Shortcuts' => 'Genveje',
'Menu:Change:Shortcuts+' => '',
'Menu:WaitingAcceptance' => 'Changes, som afventer accept',
'Menu:WaitingAcceptance+' => '',
'Menu:WaitingApproval' => 'Changes, som afventer godkendelse',
'Menu:WaitingApproval+' => '',
'Menu:Changes' => 'Offene Changes',
'Menu:Changes+' => 'Alle åbne Changes',
'Menu:MyChanges' => 'Mine Changes',
'Menu:MyChanges+' => 'Changes som er tildelt mig',
'UI-ChangeManagementOverview-ChangeByCategory-last-7-days' => 'Changes de sidste 7 dage, efter kategori',
'UI-ChangeManagementOverview-Last-7-days' => 'Antal Changes de sidste 7 dage',
'UI-ChangeManagementOverview-ChangeByDomain-last-7-days' => 'Changes de sidste 7 dage efter type',
'UI-ChangeManagementOverview-ChangeByStatus-last-7-days' => 'Changes de sidste 7 dage efter status',
'Class:Change' => 'Change',
'Class:Change+' => '',
'Class:Change/Attribute:status' => 'Status',
'Class:Change/Attribute:status+' => '',
'Class:Change/Attribute:status/Value:new' => 'Ny',
'Class:Change/Attribute:status/Value:new+' => '',
'Class:Change/Attribute:status/Value:validated' => 'Valideret',
'Class:Change/Attribute:status/Value:validated+' => '',
'Class:Change/Attribute:status/Value:rejected' => 'Afslået',
'Class:Change/Attribute:status/Value:rejected+' => '',
'Class:Change/Attribute:status/Value:assigned' => 'Tildelt',
'Class:Change/Attribute:status/Value:assigned+' => '',
'Class:Change/Attribute:status/Value:plannedscheduled' => 'Planlagt og tidssat',
'Class:Change/Attribute:status/Value:plannedscheduled+' => '',
'Class:Change/Attribute:status/Value:approved' => 'Godkendt',
'Class:Change/Attribute:status/Value:approved+' => '',
'Class:Change/Attribute:status/Value:notapproved' => 'Ikke Godkendt',
'Class:Change/Attribute:status/Value:notapproved+' => '',
'Class:Change/Attribute:status/Value:implemented' => 'Implementeret',
'Class:Change/Attribute:status/Value:implemented+' => '',
'Class:Change/Attribute:status/Value:monitored' => 'Overvåget',
'Class:Change/Attribute:status/Value:monitored+' => '',
'Class:Change/Attribute:status/Value:closed' => 'Lukket',
'Class:Change/Attribute:status/Value:closed+' => '',
'Class:Change/Attribute:reason' => 'Årsag',
'Class:Change/Attribute:reason+' => '',
'Class:Change/Attribute:requestor_id' => 'Rekvirent',
'Class:Change/Attribute:requestor_id+' => '',
'Class:Change/Attribute:requestor_email' => 'Rekvirent e-mail',
'Class:Change/Attribute:requestor_email+' => '',
'Class:Change/Attribute:creation_date' => 'Oprettet dato',
'Class:Change/Attribute:creation_date+' => '',
'Class:Change/Attribute:impact' => 'Indvirkning',
'Class:Change/Attribute:impact+' => '',
'Class:Change/Attribute:supervisor_group_id' => 'Supervisor-Team',
'Class:Change/Attribute:supervisor_group_id+' => '',
'Class:Change/Attribute:supervisor_group_name' => 'Supervisor-Teamnavn',
'Class:Change/Attribute:supervisor_group_name+' => '',
'Class:Change/Attribute:supervisor_id' => 'Supervisor',
'Class:Change/Attribute:supervisor_id+' => '',
'Class:Change/Attribute:supervisor_email' => 'Supervisor e-mail',
'Class:Change/Attribute:supervisor_email+' => '',
'Class:Change/Attribute:manager_group_id' => 'Manager-Team',
'Class:Change/Attribute:manager_group_id+' => '',
'Class:Change/Attribute:manager_group_name' => 'Manager-Team',
'Class:Change/Attribute:manager_group_name+' => '',
'Class:Change/Attribute:manager_id' => 'Manager',
'Class:Change/Attribute:manager_id+' => '',
'Class:Change/Attribute:manager_email' => 'Manager',
'Class:Change/Attribute:manager_email+' => '',
'Class:Change/Attribute:outage' => 'Nedetid',
'Class:Change/Attribute:outage+' => '',
'Class:Change/Attribute:outage/Value:no' => 'Nej',
'Class:Change/Attribute:outage/Value:no+' => '',
'Class:Change/Attribute:outage/Value:yes' => 'Ja',
'Class:Change/Attribute:outage/Value:yes+' => '',
'Class:Change/Attribute:fallback' => 'Fallback-Plan',
'Class:Change/Attribute:fallback+' => '',
'Class:Change/Attribute:parent_id' => 'Parent Change',
'Class:Change/Attribute:parent_id+' => '',
'Class:Change/Attribute:parent_name' => 'Parent Change Ref',
'Class:Change/Attribute:parent_name+' => '',
'Class:Change/Attribute:related_request_list' => 'Relaterede Requests',
'Class:Change/Attribute:related_request_list+' => '',
'Class:Change/Attribute:related_problems_list' => 'Relaterede Problemer',
'Class:Change/Attribute:related_problems_list+' => '',
'Class:Change/Attribute:child_changes_list' => 'Afledte Changes',
'Class:Change/Attribute:child_changes_list+' => '',
'Class:Change/Attribute:parent_id_friendlyname' => 'Parent Friendly Name',
'Class:Change/Attribute:parent_id_friendlyname+' => '',
'Class:Change/Attribute:parent_id_finalclass_recall' => 'Change-Type',
'Class:Change/Attribute:parent_id_finalclass_recall+' => '',
'Class:Change/Stimulus:ev_validate' => 'Valider',
'Class:Change/Stimulus:ev_validate+' => '',
'Class:Change/Stimulus:ev_reject' => 'Afslå',
'Class:Change/Stimulus:ev_reject+' => '',
'Class:Change/Stimulus:ev_assign' => 'Tildel',
'Class:Change/Stimulus:ev_assign+' => '',
'Class:Change/Stimulus:ev_reopen' => 'Genåben',
'Class:Change/Stimulus:ev_reopen+' => '',
'Class:Change/Stimulus:ev_plan' => 'Planlæg',
'Class:Change/Stimulus:ev_plan+' => '',
'Class:Change/Stimulus:ev_approve' => 'Godkend',
'Class:Change/Stimulus:ev_approve+' => '',
'Class:Change/Stimulus:ev_replan' => 'Genplanlæg',
'Class:Change/Stimulus:ev_replan+' => '',
'Class:Change/Stimulus:ev_notapprove' => 'Afslå',
'Class:Change/Stimulus:ev_notapprove+' => '',
'Class:Change/Stimulus:ev_implement' => 'Implementer',
'Class:Change/Stimulus:ev_implement+' => '',
'Class:Change/Stimulus:ev_monitor' => 'Overvåg',
'Class:Change/Stimulus:ev_monitor+' => '',
'Class:Change/Stimulus:ev_finish' => 'Afslut',
'Class:Change/Stimulus:ev_finish+' => '',
'Class:RoutineChange/Stimulus:ev_validate' => 'Valider',
'Class:RoutineChange/Stimulus:ev_validate+' => '',
'Class:RoutineChange/Stimulus:ev_reject' => 'Afslå',
'Class:RoutineChange/Stimulus:ev_reject+' => '',
'Class:ApprovedChange' => 'Godkendte Changes',
'Class:ApprovedChange+' => '',
'Class:ApprovedChange/Attribute:approval_date' => 'Dato for godkendelse',
'Class:ApprovedChange/Attribute:approval_date+' => '',
'Class:ApprovedChange/Attribute:approval_comment' => 'Kommentar til godkendelsen',
'Class:ApprovedChange/Attribute:approval_comment+' => '',
'Class:ApprovedChange/Stimulus:ev_validate' => 'Valider',
'Class:ApprovedChange/Stimulus:ev_validate+' => '',
'Class:ApprovedChange/Stimulus:ev_reject' => 'Afslå',
'Class:ApprovedChange/Stimulus:ev_reject+' => '',
'Class:ApprovedChange/Stimulus:ev_assign' => 'Tildel',
'Class:ApprovedChange/Stimulus:ev_assign+' => '',
'Class:ApprovedChange/Stimulus:ev_reopen' => 'Genåben',
'Class:ApprovedChange/Stimulus:ev_reopen+' => '',
'Class:ApprovedChange/Stimulus:ev_plan' => 'Planlæg',
'Class:ApprovedChange/Stimulus:ev_plan+' => '',
'Class:ApprovedChange/Stimulus:ev_approve' => 'Godkend',
'Class:ApprovedChange/Stimulus:ev_approve+' => '',
'Class:ApprovedChange/Stimulus:ev_replan' => 'Genplanlæg',
'Class:ApprovedChange/Stimulus:ev_replan+' => '',
'Class:ApprovedChange/Stimulus:ev_notapprove' => 'Tilbagekald godkendelse',
'Class:ApprovedChange/Stimulus:ev_notapprove+' => '',
'Class:ApprovedChange/Stimulus:ev_implement' => 'Implementer',
'Class:ApprovedChange/Stimulus:ev_implement+' => '',
'Class:ApprovedChange/Stimulus:ev_monitor' => 'Overvåg',
'Class:ApprovedChange/Stimulus:ev_monitor+' => '',
'Class:ApprovedChange/Stimulus:ev_finish' => 'Afslut',
'Class:ApprovedChange/Stimulus:ev_finish+' => '',
'Class:EmergencyChange/Stimulus:ev_validate' => 'Valider',
'Class:EmergencyChange/Stimulus:ev_validate+' => '',
'Class:EmergencyChange/Stimulus:ev_reject' => 'Afslå',
'Class:EmergencyChange/Stimulus:ev_reject+' => '',
));
?>

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-change-mgmt-itil/2.0.0',
'itop-change-mgmt-itil/2.1.0',
array(
// Identification
//

View File

@@ -0,0 +1,115 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* @author Erik Bøg <erik@boegmoeller.dk>
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @licence http://opensource.org/licenses/AGPL-3.0
*/
Dict::Add('DA DA', 'Danish', 'Dansk', array(
'Class:Change' => 'Change',
'Class:Change+' => '',
'Class:Change/Attribute:status' => 'Status',
'Class:Change/Attribute:status+' => 'Status for emnet',
'Class:Change/Attribute:status/Value:new' => 'Ny',
'Class:Change/Attribute:status/Value:new+' => 'Opret ny',
'Class:Change/Attribute:status/Value:assigned' => 'Tildelt',
'Class:Change/Attribute:status/Value:assigned+' => '',
'Class:Change/Attribute:status/Value:planned' => 'Planlagt',
'Class:Change/Attribute:status/Value:planned+' => '',
'Class:Change/Attribute:status/Value:rejected' => 'Afslået',
'Class:Change/Attribute:status/Value:rejected+' => '',
'Class:Change/Attribute:status/Value:approved' => 'Godkendt',
'Class:Change/Attribute:status/Value:approved+' => '',
'Class:Change/Attribute:status/Value:closed' => 'Lukket',
'Class:Change/Attribute:status/Value:closed+' => '',
'Class:Change/Attribute:category' => 'Kategori',
'Class:Change/Attribute:category+' => '',
'Class:Change/Attribute:category/Value:application' => 'Applikation',
'Class:Change/Attribute:category/Value:application+' => '',
'Class:Change/Attribute:category/Value:hardware' => 'Hardware',
'Class:Change/Attribute:category/Value:hardware+' => '',
'Class:Change/Attribute:category/Value:network' => 'Netværk',
'Class:Change/Attribute:category/Value:network+' => '',
'Class:Change/Attribute:category/Value:other' => 'Andet',
'Class:Change/Attribute:category/Value:other+' => '',
'Class:Change/Attribute:category/Value:software' => 'Software',
'Class:Change/Attribute:category/Value:software+' => '',
'Class:Change/Attribute:category/Value:system' => 'System',
'Class:Change/Attribute:category/Value:system+' => '',
'Class:Change/Attribute:reject_reason' => 'Årsag til afslag',
'Class:Change/Attribute:reject_reason+' => '',
'Class:Change/Attribute:changemanager_id' => 'Change Manager',
'Class:Change/Attribute:changemanager_id+' => '',
'Class:Change/Attribute:parent_id' => 'Parent Change',
'Class:Change/Attribute:parent_id+' => '',
'Class:Change/Attribute:creation_date' => 'Oprettelsesdato',
'Class:Change/Attribute:creation_date+' => '',
'Class:Change/Attribute:approval_date' => 'Godkendelsesdato',
'Class:Change/Attribute:approval_date+' => '',
'Class:Change/Attribute:fallback_plan' => 'Fallback-Plan',
'Class:Change/Attribute:fallback_plan+' => '',
'Class:Change/Attribute:related_request_list' => 'Relaterede Requests',
'Class:Change/Attribute:related_request_list+' => '',
'Class:Change/Attribute:child_changes_list' => 'Afledte Changes',
'Class:Change/Attribute:child_changes_list+' => '',
'Class:Change/Stimulus:ev_assign' => 'Tildel',
'Class:Change/Stimulus:ev_assign+' => '',
'Class:Change/Stimulus:ev_plan' => 'Planlæg',
'Class:Change/Stimulus:ev_plan+' => '',
'Class:Change/Stimulus:ev_reject' => 'Afslåp',
'Class:Change/Stimulus:ev_reject+' => '',
'Class:Change/Stimulus:ev_reopen' => 'Genåben',
'Class:Change/Stimulus:ev_reopen+' => '',
'Class:Change/Stimulus:ev_approve' => 'Godkend',
'Class:Change/Stimulus:ev_approve+' => '',
'Class:Change/Stimulus:ev_finish' => 'Luk',
'Class:Change/Stimulus:ev_finish+' => '',
'Menu:ChangeManagement' => 'Change Management',
'Menu:Change:Overview' => 'Oversigt',
'Menu:Change:Overview+' => '',
'Menu:NewChange' => 'Ny Change',
'Menu:NewChange+' => '',
'Menu:SearchChanges' => 'Søg efter Changes',
'Menu:SearchChanges+' => '',
'Menu:Change:Shortcuts' => 'Genveje',
'Menu:Change:Shortcuts+' => '',
'Menu:WaitingAcceptance' => 'Changes der afventer accept',
'Menu:WaitingAcceptance+' => '',
'Menu:WaitingApproval' => 'Changes der afventer godkendelse',
'Menu:WaitingApproval+' => '',
'Menu:Changes' => 'Åbne Changes',
'Menu:Changes+' => '',
'Menu:MyChanges' => 'Changes tildelt til mig',
'Menu:MyChanges+' => '',
'UI-ChangeManagementOverview-ChangeByCategory-last-7-days' => 'Changes de sidste 7 dage efter kategori',
'UI-ChangeManagementOverview-Last-7-days' => 'Antal Changes i de sidste 7 dage',
'UI-ChangeManagementOverview-ChangeByDomain-last-7-days' => 'Changes de sidste 7 dage efter type',
'UI-ChangeManagementOverview-ChangeByStatus-last-7-days' => 'Changes de sidste 7 dage efter status',
'Class:Change/Attribute:changemanager_email' => 'Change Manager Email',
'Class:Change/Attribute:changemanager_email+' => '',
'Class:Change/Attribute:parent_name' => 'Parent Change ref',
'Class:Change/Attribute:parent_name+' => '',
'Class:Change/Attribute:related_incident_list' => 'Relaterede Incidents',
'Class:Change/Attribute:related_incident_list+' => '',
'Class:Change/Attribute:parent_id_friendlyname' => 'Parent Change Friendly Name',
'Class:Change/Attribute:parent_id_friendlyname+' => '',
));
?>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0">
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.1">
<classes>
<class id="Change" _delta="define">
<parent>Ticket</parent>
@@ -17,7 +17,6 @@
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="ref"/>
</attributes>
@@ -38,12 +37,12 @@
<fields>
<field id="status" xsi:type="AttributeEnum">
<values>
<value>new</value>
<value>assigned</value>
<value>planned</value>
<value>approved</value>
<value>closed</value>
<value>rejected</value>
<value id="new">new</value>
<value id="assigned">assigned</value>
<value id="planned">planned</value>
<value id="approved">approved</value>
<value id="closed">closed</value>
<value id="rejected">rejected</value>
</values>
<sql>status</sql>
<default_value>new</default_value>
@@ -51,12 +50,12 @@
</field>
<field id="category" xsi:type="AttributeEnum">
<values>
<value>hardware</value>
<value>software</value>
<value>system</value>
<value>network</value>
<value>application</value>
<value>other</value>
<value id="hardware">hardware</value>
<value id="software">software</value>
<value id="system">system</value>
<value id="network">network</value>
<value id="application">application</value>
<value id="other">other</value>
</values>
<sql>category</sql>
<default_value>hardware</default_value>
@@ -107,8 +106,8 @@
</field>
<field id="outage" xsi:type="AttributeEnum">
<values>
<value>yes</value>
<value>no</value>
<value id="yes">yes</value>
<value id="no">no</value>
</values>
<sql>outage</sql>
<default_value>no</default_value>
@@ -145,6 +144,23 @@
</field>
</fields>
<lifecycle>
<highlight_scale>
<item id="approved">
<rank>1</rank>
<color>HIGHLIGHT_CLASS_NONE</color>
<icon>images/change-approved.png</icon>
</item>
<item id="rejected">
<rank>2</rank>
<color>HIGHLIGHT_CLASS_NONE</color>
<icon>images/change-rejected.png</icon>
</item>
<item id="closed">
<rank>3</rank>
<color>HIGHLIGHT_CLASS_NONE</color>
<icon>images/change-closed.png</icon>
</item>
</highlight_scale>
<attribute>status</attribute>
<stimuli>
<stimulus id="ev_assign" xsi:type="StimulusUserAction"/>
@@ -201,21 +217,15 @@
</attribute>
</flags>
<transitions>
<transition>
<stimulus>ev_assign</stimulus>
<transition id="ev_assign">
<target>assigned</target>
<actions/>
</transition>
</transitions>
</state>
<state id="assigned">
<inherit_flags_from>new</inherit_flags_from>
<flags>
<attribute id="ref">
<read_only/>
</attribute>
<attribute id="org_id">
<mandatory/>
</attribute>
<attribute id="team_id">
<mandatory/>
<must_prompt/>
@@ -224,57 +234,22 @@
<mandatory/>
<must_prompt/>
</attribute>
<attribute id="title">
<mandatory/>
</attribute>
<attribute id="description">
<mandatory/>
</attribute>
<attribute id="last_update">
<read_only/>
</attribute>
<attribute id="close_date">
<hidden/>
</attribute>
<attribute id="reject_reason">
<hidden/>
</attribute>
<attribute id="changemanager_id">
<mandatory/>
<must_prompt/>
</attribute>
<attribute id="creation_date">
<read_only/>
</attribute>
<attribute id="approval_date">
<hidden/>
</attribute>
<attribute id="outage">
<hidden/>
</attribute>
<attribute id="caller_id"/>
</flags>
<transitions>
<transition>
<stimulus>ev_plan</stimulus>
<transition id="ev_plan">
<target>planned</target>
<actions/>
</transition>
</transitions>
</state>
<state id="planned">
<inherit_flags_from>assigned</inherit_flags_from>
<flags>
<attribute id="ref">
<read_only/>
</attribute>
<attribute id="org_id">
<mandatory/>
</attribute>
<attribute id="title">
<mandatory/>
</attribute>
<attribute id="description">
<mandatory/>
</attribute>
<attribute id="start_date">
<mandatory/>
<must_prompt/>
@@ -283,21 +258,6 @@
<mandatory/>
<must_prompt/>
</attribute>
<attribute id="last_update">
<read_only/>
</attribute>
<attribute id="close_date">
<hidden/>
</attribute>
<attribute id="reject_reason">
<hidden/>
</attribute>
<attribute id="creation_date">
<read_only/>
</attribute>
<attribute id="approval_date">
<hidden/>
</attribute>
<attribute id="fallback_plan">
<mandatory/>
<must_prompt/>
@@ -305,43 +265,61 @@
<attribute id="caller_id">
<mandatory/>
</attribute>
<attribute id="team_id">
<mandatory/>
</attribute>
<attribute id="agent_id">
<mandatory/>
</attribute>
<attribute id="changemanager_id">
<mandatory/>
</attribute>
<attribute id="outage">
<mandatory/>
<must_prompt/>
</attribute>
</flags>
<transitions>
<transition>
<stimulus>ev_reject</stimulus>
<transition id="ev_reject">
<target>rejected</target>
<actions/>
</transition>
<transition>
<stimulus>ev_approve</stimulus>
<transition id="ev_approve">
<target>approved</target>
<actions>
<action>
<verb>SetApprovalDate</verb>
<verb>SetCurrentDate</verb>
<params>
<param xsi:type="attcode">approval_date</param>
</params>
</action>
<action>
<verb>ResetRejectReason</verb>
<verb>Reset</verb>
<params>
<param xsi:type="attcode">reject_reason</param>
</params>
</action>
</actions>
</transition>
</transitions>
</state>
<state id="rejected">
<highlight>
<code>rejected</code>
</highlight>
<inherit_flags_from>assigned</inherit_flags_from>
<flags>
<attribute id="ref">
<attribute id="start_date">
<read_only/>
</attribute>
<attribute id="end_date">
<read_only/>
</attribute>
<attribute id="private_log">
<read_only/>
<read_only/>
</attribute>
<attribute id="caller_id">
<read_only/>
</attribute>
<attribute id="fallback_plan">
<read_only/>
</attribute>
<attribute id="category">
<read_only/>
</attribute>
<attribute id="parent_id">
<read_only/>
</attribute>
<attribute id="org_id">
@@ -353,34 +331,13 @@
<attribute id="description">
<read_only/>
</attribute>
<attribute id="last_update">
<read_only/>
</attribute>
<attribute id="close_date">
<hidden/>
</attribute>
<attribute id="start_date">
<read_only/>
</attribute>
<attribute id="end_date">
<read_only/>
</attribute>
<attribute id="private_log">
<read_only/>
</attribute>
<attribute id="reject_reason">
<mandatory/>
<must_prompt/>
</attribute>
<attribute id="creation_date">
<read_only/>
</attribute>
<attribute id="approval_date">
<read_only/>
</attribute>
<attribute id="caller_id">
<read_only/>
</attribute>
<attribute id="team_id">
<read_only/>
</attribute>
@@ -390,35 +347,26 @@
<attribute id="changemanager_id">
<read_only/>
</attribute>
<attribute id="private_log">
<read_only/>
</attribute>
<attribute id="fallback_plan">
<read_only/>
</attribute>
<attribute id="category">
<read_only/>
</attribute>
<attribute id="parent_id">
<read_only/>
</attribute>
<attribute id="outage">
<read_only/>
</attribute>
</flags>
<transitions>
<transition>
<stimulus>ev_reopen</stimulus>
<transition id="ev_reopen">
<target>assigned</target>
<actions/>
</transition>
</transitions>
</state>
<state id="approved">
<highlight>
<code>approved</code>
</highlight>
<inherit_flags_from>planned</inherit_flags_from>
<flags>
<attribute id="ref">
<read_only/>
</attribute>
<attribute id="private_log"/>
<attribute id="category"/>
<attribute id="parent_id"/>
<attribute id="org_id">
<read_only/>
</attribute>
@@ -428,21 +376,12 @@
<attribute id="description">
<read_only/>
</attribute>
<attribute id="last_update">
<read_only/>
</attribute>
<attribute id="close_date">
<hidden/>
</attribute>
<attribute id="start_date">
<read_only/>
</attribute>
<attribute id="end_date">
<read_only/>
</attribute>
<attribute id="private_log">
<normal/>
</attribute>
<attribute id="reject_reason">
<read_only/>
</attribute>
@@ -464,78 +403,37 @@
<attribute id="changemanager_id">
<read_only/>
</attribute>
<attribute id="fallback_plan">
<normal/>
</attribute>
<attribute id="category">
<normal/>
</attribute>
<attribute id="parent_id">
<normal/>
</attribute>
<attribute id="fallback_plan"/>
<attribute id="outage">
<read_only/>
</attribute>
</flags>
<transitions>
<transition>
<stimulus>ev_finish</stimulus>
<transition id="ev_finish">
<target>closed</target>
<actions>
<action>
<verb>SetClosureDate</verb>
<verb>SetCurrentDate</verb>
<params>
<param xsi:type="attcode">close_date</param>
</params>
</action>
</actions>
</transition>
</transitions>
</state>
<state id="closed">
<highlight>
<code>closed</code>
</highlight>
<inherit_flags_from>approved</inherit_flags_from>
<flags>
<attribute id="ref">
<read_only/>
</attribute>
<attribute id="org_id">
<read_only/>
</attribute>
<attribute id="title">
<read_only/>
</attribute>
<attribute id="description">
<read_only/>
</attribute>
<attribute id="last_update">
<read_only/>
</attribute>
<attribute id="close_date">
<read_only/>
</attribute>
<attribute id="start_date">
<read_only/>
</attribute>
<attribute id="end_date">
<read_only/>
</attribute>
<attribute id="reject_reason">
<read_only/>
</attribute>
<attribute id="creation_date">
<read_only/>
</attribute>
<attribute id="approval_date">
<read_only/>
</attribute>
<attribute id="caller_id">
<read_only/>
</attribute>
<attribute id="team_id">
<read_only/>
</attribute>
<attribute id="agent_id">
<read_only/>
</attribute>
<attribute id="changemanager_id">
<read_only/>
</attribute>
<attribute id="private_log">
<read_only/>
</attribute>
@@ -548,9 +446,6 @@
<attribute id="parent_id">
<read_only/>
</attribute>
<attribute id="outage">
<read_only/>
</attribute>
</flags>
<transitions/>
</state>
@@ -558,6 +453,10 @@
</lifecycle>
<methods>
<method id="SetApprovalDate">
<comment><![CDATA[/**
* To be deprecated: use SetCurrentDate() instead
* @return void
*/]]></comment>
<static>false</static>
<access>public</access>
<type>LifecycleAction</type>
@@ -568,6 +467,10 @@
}]]></code>
</method>
<method id="ResetRejectReason">
<comment><![CDATA[/**
* To be deprecated: use SetCurrentDate() instead
* @return void
*/]]></comment>
<static>false</static>
<access>public</access>
<type>LifecycleAction</type>
@@ -578,6 +481,10 @@
}]]></code>
</method>
<method id="SetClosureDate">
<comment><![CDATA[/**
* To be deprecated: use SetCurrentDate() instead
* @return void
*/]]></comment>
<static>false</static>
<access>public</access>
<type>LifecycleAction</type>
@@ -633,63 +540,6 @@
$this->Set('last_update', time());
}]]></code>
</method>
<method id="GetIcon">
<comment>/**&#13;
* Get the icon representing this object&#13;
* @param boolean $bImgTag If true the result is a full IMG tag (or an emtpy string if no icon is defined)&#13;
* @return string Either the full IMG tag ($bImgTag == true) or just the path to the icon file&#13;
*/</comment>
<static>false</static>
<access>public</access>
<type>Overload-DBObject</type>
<code><![CDATA[ public function GetIcon($bImgTag = true)
{
$sStatus = $this->Get('status');
switch($this->GetState())
{
case 'approved':
case 'implemented':
case 'monitored':
$sIcon = self::MakeIconFromName('change-approved.png');
break;
case 'rejected':
case 'notapproved':
$sIcon = self::MakeIconFromName('change-rejected.png');
break;
case 'closed':
$sIcon = self::MakeIconFromName('change-closed.png');
break;
default:
$sIcon = MetaModel::GetClassIcon(get_class($this), $bImgTag);
}
return $sIcon;
}]]></code>
</method>
<method id="MakeIconFromName">
<static>true</static>
<access>protected</access>
<type>Overload-DBObject</type>
<code><![CDATA[ protected static function MakeIconFromName($sIconName, $bImgTag = true)
{
$sIcon = '';
if ($sIconName != '')
{
$sPath = '../env-'.utils::GetCurrentEnvironment().'/itop-change-mgmt/images/'.$sIconName;
if ($bImgTag)
{
$sIcon = "<img src=\"$sPath\" style=\"vertical-align:middle;\"/>";
}
else
{
$sIcon = $sPath;
}
}
return $sIcon;
}]]></code>
</method>
</methods>
<presentation>
<details>

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-change-mgmt/2.0.0',
'itop-change-mgmt/2.1.0',
array(
// Identification
//

View File

@@ -0,0 +1,146 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* @author LinProfs <info@linprofs.com>
*
* Linux & Open Source Professionals
* http://www.linprofs.com
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @licence http://opensource.org/licenses/AGPL-3.0
*/
Dict::Add('NL NL', 'Dutch', 'Nederlands', array(
'Menu:ChangeManagement' => 'Change management',
'Menu:Change:Overview' => 'Overzicht',
'Menu:Change:Overview+' => '',
'Menu:NewChange' => 'Nieuwe change',
'Menu:NewChange+' => 'Maak een nieuwe change ticket aan',
'Menu:SearchChanges' => 'Zoek voor changes',
'Menu:SearchChanges+' => 'Zoek voor change tickets',
'Menu:Change:Shortcuts' => 'Snelkoppelingen',
'Menu:Change:Shortcuts+' => '',
'Menu:WaitingAcceptance' => 'Changes die nog acceptatie vereisen',
'Menu:WaitingAcceptance+' => '',
'Menu:WaitingApproval' => 'Changes die nog goedkeuring vereisen',
'Menu:WaitingApproval+' => '',
'Menu:Changes' => 'Open changes',
'Menu:Changes+' => 'Alle open changes',
'Menu:MyChanges' => 'Changes toegewezen aan mij',
'Menu:MyChanges+' => 'Changes toegewezen door mij (als Agent)',
'UI-ChangeManagementOverview-ChangeByCategory-last-7-days' => 'Changes per categorie van de afgelopen 7 dagen',
'UI-ChangeManagementOverview-Last-7-days' => 'Aantal changes van de afgelopen 7 dagen',
'UI-ChangeManagementOverview-ChangeByDomain-last-7-days' => 'Changes per domein van de afgelopen 7 dagen',
'UI-ChangeManagementOverview-ChangeByStatus-last-7-days' => 'Changes per status van de afgelopen 7 dagen',
));
// Dictionnay conventions
// Class:<class_name>
// Class:<class_name>+
// Class:<class_name>/Attribute:<attribute_code>
// Class:<class_name>/Attribute:<attribute_code>+
// Class:<class_name>/Attribute:<attribute_code>/Value:<value>
// Class:<class_name>/Attribute:<attribute_code>/Value:<value>+
// Class:<class_name>/Stimulus:<stimulus_code>
// Class:<class_name>/Stimulus:<stimulus_code>+
//
// Class: Change
//
Dict::Add('NL NL', "Dutch", "Nederlands", array(
'Class:Change' => 'Change',
'Class:Change+' => '',
'Class:Change/Attribute:status' => 'Status',
'Class:Change/Attribute:status+' => '',
'Class:Change/Attribute:status/Value:new' => 'Nieuw',
'Class:Change/Attribute:status/Value:new+' => '',
'Class:Change/Attribute:status/Value:assigned' => 'Toegewezen',
'Class:Change/Attribute:status/Value:assigned+' => '',
'Class:Change/Attribute:status/Value:planned' => 'Gepland',
'Class:Change/Attribute:status/Value:planned+' => '',
'Class:Change/Attribute:status/Value:rejected' => 'Rejected',
'Class:Change/Attribute:status/Value:rejected+' => '',
'Class:Change/Attribute:status/Value:approved' => 'Goedgekeurd',
'Class:Change/Attribute:status/Value:approved+' => '',
'Class:Change/Attribute:status/Value:closed' => 'Gesloten',
'Class:Change/Attribute:status/Value:closed+' => '',
'Class:Change/Attribute:category' => 'Categorie',
'Class:Change/Attribute:category+' => '',
'Class:Change/Attribute:category/Value:application' => 'applicatie',
'Class:Change/Attribute:category/Value:application+' => 'applicatie',
'Class:Change/Attribute:category/Value:hardware' => 'hardware',
'Class:Change/Attribute:category/Value:hardware+' => 'hardware',
'Class:Change/Attribute:category/Value:network' => 'netwerk',
'Class:Change/Attribute:category/Value:network+' => 'netwerk',
'Class:Change/Attribute:category/Value:other' => 'anders',
'Class:Change/Attribute:category/Value:other+' => 'anders',
'Class:Change/Attribute:category/Value:software' => 'software',
'Class:Change/Attribute:category/Value:software+' => 'software',
'Class:Change/Attribute:category/Value:system' => 'systeem',
'Class:Change/Attribute:category/Value:system+' => 'systeem',
'Class:Change/Attribute:reject_reason' => 'Reden van afwijzing',
'Class:Change/Attribute:reject_reason+' => '',
'Class:Change/Attribute:changemanager_id' => 'Change manager',
'Class:Change/Attribute:changemanager_id+' => '',
'Class:Change/Attribute:changemanager_email' => 'Change manager email',
'Class:Change/Attribute:changemanager_email+' => '',
'Class:Change/Attribute:parent_id' => 'Hoofd change',
'Class:Change/Attribute:parent_id+' => '',
'Class:Change/Attribute:parent_name' => 'Hoofd change ref',
'Class:Change/Attribute:parent_name+' => '',
'Class:Change/Attribute:creation_date' => 'Creatie datum',
'Class:Change/Attribute:creation_date+' => '',
'Class:Change/Attribute:approval_date' => 'Goedkeuring datum',
'Class:Change/Attribute:approval_date+' => '',
'Class:Change/Attribute:fallback_plan' => 'Backup plan',
'Class:Change/Attribute:fallback_plan+' => '',
'Class:Change/Attribute:related_request_list' => 'Gerelateerde verzoeken',
'Class:Change/Attribute:related_request_list+' => 'Alle gebruikersverzoeken gelinkt aan deze change',
'Class:Change/Attribute:related_incident_list' => 'Gerelateerde incidenten',
'Class:Change/Attribute:related_incident_list+' => 'Alle incidenten die gelinkt zijn aan deze change',
'Class:Change/Attribute:related_problems_list' => 'Gerelateerde problemen',
'Class:Change/Attribute:related_problems_list+' => 'Alle problemen gelinkt aan deze change',
'Class:Change/Attribute:child_changes_list' => 'Sub changes',
'Class:Change/Attribute:child_changes_list+' => 'Alle sub changes gelinkt aan deze change',
'Class:Change/Attribute:parent_id_friendlyname' => 'Hoofd change friendly name',
'Class:Change/Attribute:parent_id_friendlyname+' => '',
'Class:Change/Stimulus:ev_assign' => 'Wijs toe',
'Class:Change/Stimulus:ev_assign+' => '',
'Class:Change/Stimulus:ev_plan' => 'Plan',
'Class:Change/Stimulus:ev_plan+' => '',
'Class:Change/Stimulus:ev_reject' => 'Wijs af',
'Class:Change/Stimulus:ev_reject+' => '',
'Class:Change/Stimulus:ev_reopen' => 'Heropen',
'Class:Change/Stimulus:ev_reopen+' => '',
'Class:Change/Stimulus:ev_approve' => 'Keur goed',
'Class:Change/Stimulus:ev_approve+' => '',
'Class:Change/Stimulus:ev_finish' => 'Sluit',
'Class:Change/Stimulus:ev_finish+' => '',
'Class:Change/Attribute:outage' => 'Storing',
'Class:Change/Attribute:outage+' => '',
'Class:Change/Attribute:outage/Value:no' => 'Nee',
'Class:Change/Attribute:outage/Value:no+' => '',
'Class:Change/Attribute:outage/Value:yes' => 'Yes',
'Class:Change/Attribute:outage/Value:yes+' => '',
));
?>

File diff suppressed because it is too large Load Diff

View File

@@ -289,8 +289,8 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
'Class:Model/Attribute:type/Value:Tablet+' => '',
'Class:Model/Attribute:type/Value:TapeLibrary' => 'Tape-Library',
'Class:Model/Attribute:type/Value:TapeLibrary+' => '',
'Class:Model/Attribute:type/Value:Telephone' => 'Telefon',
'Class:Model/Attribute:type/Value:Telephone+' => '',
'Class:Model/Attribute:type/Value:Phone' => 'Telefon',
'Class:Model/Attribute:type/Value:Phone+' => '',
'Class:Model/Attribute:physicaldevices_list' => 'Phyische Geräte',
'Class:Model/Attribute:physicaldevices_list+' => '',
'Class:NetworkDeviceType' => 'Netzerkgerätetyp',
@@ -1019,10 +1019,14 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
'Class:lnkGroupToCI/Attribute:group_name+' => '',
'Class:lnkGroupToCI/Attribute:ci_name' => 'Name',
'Class:lnkGroupToCI/Attribute:ci_name+' => '',
'Menu:DataAdministration' => 'Data Management',
'Menu:DataAdministration+' => 'Data Management',
'Menu:Catalogs' => 'Kataloge',
'Menu:Catalogs+' => 'Datentypen',
'Menu:Audit' => 'Audit',
'Menu:Audit+' => 'Audit',
'Menu:CSVImport' => 'CSV-Import',
'Menu:CSVImport+' => 'Massenerstellung oder -aktualisierung',
'Menu:Organization' => 'Organisationen',
'Menu:Organization+' => 'Alle Organisationen',
'Menu:Application' => 'Anwendungen',

View File

@@ -33,7 +33,7 @@ Dict::Add('EN US', 'English', 'English', array(
'Relation:impacts/Description' => 'Elements impacted by',
'Relation:impacts/VerbUp' => 'Impact...',
'Relation:impacts/VerbDown' => 'Elements impacted by...',
'Relation:depends on/Description' => 'Elements this element depends on',
'Relation:depends on/Description' => 'Elements impacting',
'Relation:depends on/VerbUp' => 'Depends on...',
'Relation:depends on/VerbDown' => 'Impacts...',
));
@@ -1327,8 +1327,8 @@ Dict::Add('EN US', 'English', 'English', array(
'Class:Model/Attribute:type/Value:Tablet+' => 'Tablet',
'Class:Model/Attribute:type/Value:TapeLibrary' => 'Tape Library',
'Class:Model/Attribute:type/Value:TapeLibrary+' => 'Tape Library',
'Class:Model/Attribute:type/Value:Telephone' => 'Telephone',
'Class:Model/Attribute:type/Value:Telephone+' => 'Telephone',
'Class:Model/Attribute:type/Value:Phone' => 'Telephone',
'Class:Model/Attribute:type/Value:Phone+' => 'Telephone',
'Class:Model/Attribute:physicaldevices_list' => 'Physical devices',
'Class:Model/Attribute:physicaldevices_list+' => 'All the physical devices corresponding to this model',
));
@@ -1770,10 +1770,14 @@ Dict::Add('EN US', 'English', 'English', array(
//
Dict::Add('EN US', 'English', 'English', array(
'Menu:DataAdministration' => 'Data administration',
'Menu:DataAdministration+' => 'Data administration',
'Menu:Catalogs' => 'Catalogs',
'Menu:Catalogs+' => 'Data types',
'Menu:Audit' => 'Audit',
'Menu:Audit+' => 'Audit',
'Menu:CSVImport' => 'CSV import',
'Menu:CSVImport+' => 'Bulk creation or update',
'Menu:Organization' => 'Organizations',
'Menu:Organization+' => 'All organizations',
'Menu:Application' => 'Applications',

View File

@@ -1331,8 +1331,8 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
'Class:Model/Attribute:type/Value:Tablet+' => 'Tableta',
'Class:Model/Attribute:type/Value:TapeLibrary' => 'Librería de Cinta',
'Class:Model/Attribute:type/Value:TapeLibrary+' => 'Librería de Cinta',
'Class:Model/Attribute:type/Value:Telephone' => 'Teléfono',
'Class:Model/Attribute:type/Value:Telephone+' => 'Teléfono',
'Class:Model/Attribute:type/Value:Phone' => 'Teléfono',
'Class:Model/Attribute:type/Value:Phone+' => 'Teléfono',
'Class:Model/Attribute:physicaldevices_list' => 'Dispositivo Físico',
'Class:Model/Attribute:physicaldevices_list+' => 'Dispositivo Físico',
));
@@ -1768,10 +1768,14 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
//
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
'Menu:DataAdministration' => 'Administración de Datos',
'Menu:DataAdministration+' => 'Administración de Datos',
'Menu:Catalogs' => 'Catálogos',
'Menu:Catalogs+' => 'Tipos de Datos',
'Menu:Audit' => 'Auditoría',
'Menu:Audit+' => 'Auditoría',
'Menu:CSVImport' => 'Importar CSV',
'Menu:CSVImport+' => 'Creación o Actualización Másiva',
'Menu:Organization' => 'Organizaciones',
'Menu:Organization+' => 'Organizaciones',
'Menu:Application' => 'Aplicaciones',

View File

@@ -1269,8 +1269,8 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Class:Model/Attribute:type/Value:Tablet+' => 'Tablette',
'Class:Model/Attribute:type/Value:TapeLibrary' => 'Bandothèque',
'Class:Model/Attribute:type/Value:TapeLibrary+' => 'Bandothèque',
'Class:Model/Attribute:type/Value:Telephone' => 'Téléphone',
'Class:Model/Attribute:type/Value:Telephone+' => 'Téléphone',
'Class:Model/Attribute:type/Value:Phone' => 'Téléphone',
'Class:Model/Attribute:type/Value:Phone+' => 'Téléphone',
'Class:Model/Attribute:physicaldevices_list' => 'Matériels',
'Class:Model/Attribute:physicaldevices_list+' => '',
));
@@ -1717,10 +1717,14 @@ Dict::Add('FR FR', 'French', 'Français', array(
Dict::Add('FR FR', 'French', 'Français', array(
'Menu:DataAdministration' => 'Administration des données',
'Menu:DataAdministration+' => 'Administration des données',
'Menu:Catalogs' => 'Catalogues',
'Menu:Catalogs+' => 'Types de données',
'Menu:Audit' => 'Audit',
'Menu:Audit+' => 'Audit',
'Menu:CSVImport' => 'Import CSV',
'Menu:CSVImport+' => 'Import ou mise à jour en masse',
'Menu:Organization' => 'Organisations',
'Menu:Organization+' => 'Toutes les organisations',
'Menu:Application' => 'Logiciels',
@@ -1817,7 +1821,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Relation:impacts/Description' => 'Eléments impactés par',
'Relation:impacts/VerbUp' => 'Impacte...',
'Relation:impacts/VerbDown' => 'Dépend de...',
'Relation:depends on/Description' => 'Eléments dont dépend cet élément',
'Relation:depends on/Description' => 'Eléments dont dépend',
'Relation:depends on/VerbUp' => 'Dépend de...',
'Relation:depends on/VerbDown' => 'Impacte...',
'Menu:ConfigManagement:Typology' => 'Configuration des typologies',

View File

@@ -641,10 +641,14 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', array(
'Class:Subnet/Tab:FreeIPs' => 'Szabad IP-k',
'Class:Subnet/Tab:FreeIPs-count' => 'Szabad IP-k: %1$s',
'Class:Subnet/Tab:FreeIPs-explain' => '10 szabad IP cím kivonata',
'Menu:DataAdministration' => 'Adat adminisztráció',
'Menu:DataAdministration+' => '',
'Menu:Catalogs' => 'Katalógusok',
'Menu:Catalogs+' => '',
'Menu:Audit' => 'Audit',
'Menu:Audit+' => '',
'Menu:CSVImport' => 'CSV import',
'Menu:CSVImport+' => '',
'Menu:Organization' => 'Szervezet',
'Menu:Organization+' => '',
'Menu:Application' => 'Alkalmazások',

View File

@@ -641,10 +641,14 @@ Dict::Add('IT IT', 'Italian', 'Italiano', array(
'Class:Subnet/Tab:FreeIPs' => 'IP liberi',
'Class:Subnet/Tab:FreeIPs-count' => 'IP liberi: %1$s',
'Class:Subnet/Tab:FreeIPs-explain' => 'Qui c\'è un estratto di 10 indirizzi IP liberi',
'Menu:DataAdministration' => 'Dati di amministrazione',
'Menu:DataAdministration+' => '',
'Menu:Catalogs' => 'Cataloghi',
'Menu:Catalogs+' => 'Tipi di dato',
'Menu:Audit' => 'Audit',
'Menu:Audit+' => 'Audit',
'Menu:CSVImport' => 'Importazione CSV',
'Menu:CSVImport+' => '',
'Menu:Organization' => 'Organizzazioni',
'Menu:Organization+' => 'Tutte le organizzazioni',
'Menu:Application' => 'Applicazioni',

View File

@@ -294,8 +294,8 @@ Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:Model/Attribute:type/Value:Tablet+' => 'タブレット',
'Class:Model/Attribute:type/Value:TapeLibrary' => 'テープライブラリ',
'Class:Model/Attribute:type/Value:TapeLibrary+' => 'テープライブラリ',
'Class:Model/Attribute:type/Value:Telephone' => '電話',
'Class:Model/Attribute:type/Value:Telephone+' => '電話',
'Class:Model/Attribute:type/Value:Phone' => '電話',
'Class:Model/Attribute:type/Value:Phone+' => '電話',
'Class:Model/Attribute:physicaldevices_list' => '物理デバイス',
'Class:Model/Attribute:physicaldevices_list+' => '',
'Class:NetworkDeviceType' => 'ネットワークデバイスタイプ',
@@ -1012,10 +1012,14 @@ Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:lnkGroupToCI/Attribute:group_name+' => '',
'Class:lnkGroupToCI/Attribute:ci_name' => '名前',
'Class:lnkGroupToCI/Attribute:ci_name+' => '',
'Menu:DataAdministration' => 'データ管理',
'Menu:DataAdministration+' => 'データ管理',
'Menu:Catalogs' => 'カタログ',
'Menu:Catalogs+' => 'データタイプ',
'Menu:Audit' => '監査',
'Menu:Audit+' => '監査',
'Menu:CSVImport' => 'CSV インポート',
'Menu:CSVImport+' => '一括作成/一括更新',
'Menu:Organization' => '組織',
'Menu:Organization+' => '全組織',
'Menu:Application' => 'アプリケーション',
@@ -1085,6 +1089,8 @@ Dict::Add('JA JP', 'Japanese', '日本語', array(
'Menu:Typology+' => 'トポロジー構成',
'Menu:Software' => 'ソフトウエアカタログ',
'Menu:Software+' => 'ソフトウエアカタログ',
'Menu:OSVersion' => 'OS バージョン',
'Menu:OSVersion+' => '',
'UI_WelcomeMenu_AllConfigItems' => 'サマリー',
'Menu:ConfigManagement:Typology' => '分類構成',
'Server:baseinfo' => '基本情報',

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-config-mgmt/2.0.0',
'itop-config-mgmt/2.1.0',
array(
// Identification
//
@@ -89,6 +89,7 @@ if (!class_exists('ConfigMgmtInstaller'))
self::RenameEnumValueInDB('Software', 'type', 'Webserver', 'WebServer');
self::RenameEnumValueInDB('Model', 'type', 'SANswitch', 'SANSwitch');
self::RenameEnumValueInDB('Model', 'type', 'IpPhone', 'IPPhone');
self::RenameEnumValueInDB('Model', 'type', 'Telephone', 'Phone');
self::RenameClassInDB('DBserver', 'DBServer');
self::RenameClassInDB('OSfamily', 'OSFamily');
self::RenameClassInDB('OSversion', 'OSVersion');

File diff suppressed because it is too large Load Diff

View File

@@ -1322,8 +1322,8 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array(
'Class:Model/Attribute:type/Value:Tablet+' => 'Tablet',
'Class:Model/Attribute:type/Value:TapeLibrary' => 'Tape Library',
'Class:Model/Attribute:type/Value:TapeLibrary+' => 'Tape Library',
'Class:Model/Attribute:type/Value:Telephone' => 'Telefone',
'Class:Model/Attribute:type/Value:Telephone+' => 'Telefone',
'Class:Model/Attribute:type/Value:Phone' => 'Telefone',
'Class:Model/Attribute:type/Value:Phone+' => 'Telefone',
'Class:Model/Attribute:physicaldevices_list' => 'Dispositivo físico',
'Class:Model/Attribute:physicaldevices_list+' => 'Todos os dispositivos físicos correspondentes a esse modelo',
));
@@ -1764,17 +1764,20 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array(
//
Dict::Add('PT BR', 'Brazilian', 'Brazilian', array(
'Menu:DataAdministration' => 'Administração Dados',
'Menu:DataAdministration+' => 'Administração Dados',
'Menu:Catalogs' => 'Catálogos',
'Menu:Catalogs+' => 'Tipos dados',
'Menu:Audit' => 'Auditoria',
'Menu:Audit+' => 'Auditoria',
'Menu:CSVImport' => 'Importar CSV',
'Menu:CSVImport+' => 'Criação ou atualização em massa',
'Menu:Organization' => 'Organizações',
'Menu:Organization+' => 'Todas organizações',
'Menu:Application' => 'Applicações',
'Menu:Application+' => 'Todas aplicações',
'Menu:DBServer' => 'Serviços Banco de Dados',
'Menu:DBServer+' => 'Serviços Banco de Dados',
'Menu:Audit' => 'Auditoria',
'Menu:ConfigManagement' => 'Gerenciamento Configurações',
'Menu:ConfigManagement+' => 'Gerenciamento Configurações',
'Menu:ConfigManagementOverview' => 'Visão geral',

View File

@@ -1322,8 +1322,8 @@ Dict::Add('RU RU', 'Russian', 'Русский', array(
'Class:Model/Attribute:type/Value:Tablet+' => 'Планшет',
'Class:Model/Attribute:type/Value:TapeLibrary' => 'Ленточная библиотека',
'Class:Model/Attribute:type/Value:TapeLibrary+' => 'Ленточная библиотека',
'Class:Model/Attribute:type/Value:Telephone' => 'Телефон',
'Class:Model/Attribute:type/Value:Telephone+' => 'Телефон',
'Class:Model/Attribute:type/Value:Phone' => 'Телефон',
'Class:Model/Attribute:type/Value:Phone+' => 'Телефон',
'Class:Model/Attribute:physicaldevices_list' => 'Устройства',
'Class:Model/Attribute:physicaldevices_list+' => 'Устройства',
));
@@ -1765,10 +1765,14 @@ Dict::Add('RU RU', 'Russian', 'Русский', array(
//
Dict::Add('RU RU', 'Russian', 'Русский', array(
'Menu:DataAdministration' => 'Административные данные',
'Menu:DataAdministration+' => 'Административные данные',
'Menu:Catalogs' => 'Каталоги',
'Menu:Catalogs+' => 'Каталоги',
'Menu:Audit' => 'Аудит',
'Menu:Audit+' => 'Аудит',
'Menu:CSVImport' => 'Импорт CSV',
'Menu:CSVImport+' => 'Пакетное создание или обновление',
'Menu:Organization' => 'Организации',
'Menu:Organization+' => 'Все организации',
'Menu:Application' => 'Приложения',

View File

@@ -990,10 +990,14 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array(
//
Dict::Add('TR TR', 'Turkish', 'Türkçe', array(
'Menu:DataAdministration' => 'Veri Yönetimi',
'Menu:DataAdministration+' => 'Veri Yönetimi',
'Menu:Catalogs' => 'Kataloglar',
'Menu:Catalogs+' => 'Veri tipleri',
'Menu:Audit' => 'Denetleme',
'Menu:Audit+' => 'Denetleme',
'Menu:CSVImport' => 'CSV dışardan al',
'Menu:CSVImport+' => 'Çoklu yaratım veya güncelleme',
'Menu:Organization' => 'Kurumlar',
'Menu:Organization+' => 'Tüm Kurumlar',
'Menu:Application' => 'Uygulamalar',

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