├── locales ├── cs_CZ.mo ├── en_GB.mo ├── fr_FR.mo ├── pt_BR.mo ├── pt_PT.mo ├── glpi.pot ├── en_GB.po ├── pt_PT.po ├── fr_FR.po ├── pt_BR.po └── cs_CZ.po ├── seasonality.png ├── screenshots ├── seasonality.png └── seasonality_ticket.png ├── README.md ├── tools ├── update_mo.pl ├── update_po.pl └── extract_template.sh ├── .github └── workflows │ ├── updatepot.yml │ ├── generatemo.yml │ └── release.yml ├── install └── sql │ ├── empty-1.4.0.sql │ └── empty.sql ├── scripts ├── seasonality_load_scripts.js └── seasonality.js ├── front ├── seasonality.php ├── seasonality.form.php └── item.form.php ├── ajax ├── ticket.php └── loadscripts.php ├── lib └── daterangepicker │ ├── jquery.comiseo.daterangepicker.css │ ├── jquery.comiseo.daterangepicker.min.js │ └── moment.min.js ├── inc ├── seasonalityinjection.class.php ├── iteminjection.class.php ├── profile.class.php ├── seasonality.class.php └── item.class.php ├── hook.php ├── seasonality.xml ├── setup.php └── LICENSE /locales/cs_CZ.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InfotelGLPI/seasonality/master/locales/cs_CZ.mo -------------------------------------------------------------------------------- /locales/en_GB.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InfotelGLPI/seasonality/master/locales/en_GB.mo -------------------------------------------------------------------------------- /locales/fr_FR.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InfotelGLPI/seasonality/master/locales/fr_FR.mo -------------------------------------------------------------------------------- /locales/pt_BR.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InfotelGLPI/seasonality/master/locales/pt_BR.mo -------------------------------------------------------------------------------- /locales/pt_PT.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InfotelGLPI/seasonality/master/locales/pt_PT.mo -------------------------------------------------------------------------------- /seasonality.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InfotelGLPI/seasonality/master/seasonality.png -------------------------------------------------------------------------------- /screenshots/seasonality.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InfotelGLPI/seasonality/master/screenshots/seasonality.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # seasonality 2 | Plugin seasonality for GLPI 3 | 4 | Ce plugin a été archivé. 5 | 6 | This plugin has been archived. 7 | -------------------------------------------------------------------------------- /screenshots/seasonality_ticket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InfotelGLPI/seasonality/master/screenshots/seasonality_ticket.png -------------------------------------------------------------------------------- /tools/update_mo.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | #!/usr/bin/perl -w 3 | 4 | if (@ARGV!=0){ 5 | print "USAGE update_mo.pl\n\n"; 6 | 7 | exit(); 8 | } 9 | 10 | 11 | opendir(DIRHANDLE,'locales')||die "ERROR: can not read current directory\n"; 12 | foreach (readdir(DIRHANDLE)){ 13 | if ($_ ne '..' && $_ ne '.'){ 14 | 15 | if(!(-l "$dir/$_")){ 16 | if (index($_,".po",0)==length($_)-3) { 17 | $lang=$_; 18 | $lang=~s/\.po//; 19 | 20 | `msgfmt locales/$_ -o locales/$lang.mo`; 21 | } 22 | } 23 | 24 | } 25 | } 26 | closedir DIRHANDLE; 27 | 28 | # 29 | # 30 | -------------------------------------------------------------------------------- /.github/workflows/updatepot.yml: -------------------------------------------------------------------------------- 1 | name: Update POT 2 | on: 3 | push: 4 | branches: [ master ] 5 | paths-ignore: 6 | - 'locales/**' 7 | 8 | env: 9 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 10 | jobs: 11 | run: 12 | 13 | name: Update POT 14 | 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout repo 18 | uses: actions/checkout@v2 19 | 20 | - name: install xgettext 21 | 22 | run: sudo apt-get install gettext; 23 | - name: Update POT 24 | run: sh tools/extract_template.sh; 25 | 26 | 27 | - name: Commit changes 28 | uses: EndBug/add-and-commit@v5.1.0 29 | with: 30 | message: "Update POT" 31 | - name: Push changes 32 | 33 | uses: actions-go/push@v1 34 | 35 | -------------------------------------------------------------------------------- /tools/update_po.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | #!/usr/bin/perl -w 3 | 4 | if (@ARGV!=2){ 5 | print "USAGE update_po.pl transifex_login transifex_password\n\n"; 6 | 7 | exit(); 8 | } 9 | $user = $ARGV[0]; 10 | $password = $ARGV[1]; 11 | 12 | opendir(DIRHANDLE,'locales')||die "ERROR: can not read current directory\n"; 13 | foreach (readdir(DIRHANDLE)){ 14 | if ($_ ne '..' && $_ ne '.'){ 15 | 16 | if(!(-l "$dir/$_")){ 17 | if (index($_,".po",0)==length($_)-3) { 18 | $lang=$_; 19 | $lang=~s/\.po//; 20 | 21 | `wget --user=$user --password=$password --output-document=locales/$_ http://www.transifex.com/api/2/project/GLPI_seasonality/resource/glpi/translation/$lang/?file=$_`; 22 | } 23 | } 24 | 25 | } 26 | } 27 | closedir DIRHANDLE; 28 | 29 | # 30 | # 31 | -------------------------------------------------------------------------------- /.github/workflows/generatemo.yml: -------------------------------------------------------------------------------- 1 | name: Generate MO 2 | on: 3 | push: 4 | branches: [ master ] 5 | paths: 6 | - '**.po' 7 | env: 8 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 9 | jobs: 10 | run: 11 | 12 | name: Generate mo 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout repo 16 | uses: actions/checkout@v2 17 | 18 | - name: Setup Perl environment 19 | # You may pin to the exact commit or the version. 20 | # uses: shogo82148/actions-setup-perl@8d2e3d59a9516b785ed32169d48a4888eaa9b514 21 | uses: shogo82148/actions-setup-perl@v1.7.2 22 | - name: msgfmt 23 | # You may pin to the exact commit or the version. 24 | # uses: whtsky/msgfmt-action@6b2181f051b002182d01a1e1f1aff216230c5a4d 25 | uses: whtsky/msgfmt-action@20190305 26 | - name: Generate mo 27 | run: perl tools/update_mo.pl; 28 | 29 | - name: Commit changes 30 | uses: EndBug/add-and-commit@v5.1.0 31 | with: 32 | 33 | message: "Generate mo" 34 | - name: Push changes 35 | 36 | uses: actions-go/push@v1 37 | 38 | -------------------------------------------------------------------------------- /tools/extract_template.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | soft='GLPI - Seasonality plugin' 4 | version='1.1.0' 5 | email='glpi-translation@gna.org' 6 | copyright='INDEPNET Development Team' 7 | 8 | #xgettext *.php */*.php -copyright-holder='$copyright' --package-name=$soft --package-version=$version --msgid-bugs-address=$email -o locales/en_GB.po -L PHP --from-code=UTF-8 --force-po -i --keyword=_n:1,2 --keyword=__ --keyword=_e 9 | 10 | # Only strings with domain specified are extracted (use Xt args of keyword param to set number of args needed) 11 | 12 | xgettext *.php */*.php --copyright-holder='Seasonality Development Team' --package-name='GLPI - Seasonality plugin' --package-version='1.0.0' -o locales/glpi.pot -L PHP --add-comments=TRANS --from-code=UTF-8 --force-po \ 13 | --keyword=_n:1,2,4t --keyword=__s:1,2t --keyword=__:1,2t --keyword=_e:1,2t --keyword=_x:1c,2,3t \ 14 | --keyword=_ex:1c,2,3t --keyword=_nx:1c,2,3,5t --keyword=_sx:1c,2,3t 15 | 16 | ### for using tx : 17 | ##tx set --execute --auto-local -r GLPI_ocsinventoryng.glpi_ocsinventoryng-version-100 'locales/.po' --source-lang en --source-file locales/glpi.pot 18 | ## tx push -s 19 | ## tx pull -a 20 | 21 | 22 | -------------------------------------------------------------------------------- /install/sql/empty-1.4.0.sql: -------------------------------------------------------------------------------- 1 | -- 2 | -- Structure de la table 'glpi_plugin_seasonality_seasonalities' 3 | -- 4 | -- 5 | DROP TABLE IF EXISTS `glpi_plugin_seasonality_seasonalities`; 6 | CREATE TABLE `glpi_plugin_seasonality_seasonalities` ( 7 | `id` int(11) NOT NULL auto_increment, -- id 8 | `name` varchar(255) DEFAULT NULL, 9 | `entities_id` varchar(255) DEFAULT NULL, 10 | `is_recursive` tinyint(1) NOT NULL DEFAULT 0, 11 | `begin_date` datetime DEFAULT NULL, 12 | `end_date` datetime DEFAULT NULL, 13 | `urgency` int(11) NOT NULL DEFAULT 0, 14 | `periodicity` varchar(255) DEFAULT NULL, 15 | PRIMARY KEY (`id`), 16 | KEY `entities_id` (`entities_id`) 17 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; 18 | 19 | -- 20 | -- Structure de la table 'glpi_plugin_seasonality_items' 21 | -- 22 | -- 23 | DROP TABLE IF EXISTS `glpi_plugin_seasonality_items`; 24 | CREATE TABLE `glpi_plugin_seasonality_items` ( 25 | `id` int(11) NOT NULL auto_increment, -- id 26 | `plugin_seasonality_seasonalities_id` int(11) NOT NULL DEFAULT 0, 27 | `itilcategories_id` int(11) NOT NULL DEFAULT 0, 28 | PRIMARY KEY (`id`), 29 | UNIQUE KEY `unicity` (`plugin_seasonality_seasonalities_id`,`itilcategories_id`), 30 | KEY `itilcategories_id` (`itilcategories_id`) 31 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; -------------------------------------------------------------------------------- /install/sql/empty.sql: -------------------------------------------------------------------------------- 1 | -- 2 | -- Structure de la table 'glpi_plugin_seasonality_seasonalities' 3 | -- 4 | -- 5 | DROP TABLE IF EXISTS `glpi_plugin_seasonality_seasonalities`; 6 | CREATE TABLE `glpi_plugin_seasonality_seasonalities` ( 7 | `id` int(11) NOT NULL auto_increment, -- id 8 | `name` varchar(255) DEFAULT NULL, 9 | `entities_id` varchar(255) DEFAULT NULL, 10 | `is_recursive` tinyint(1) NOT NULL DEFAULT 0, 11 | `begin_date` datetime DEFAULT NULL, 12 | `end_date` datetime DEFAULT NULL, 13 | `urgency` int(11) NOT NULL DEFAULT 0, 14 | `periodicity` varchar(255) DEFAULT NULL, 15 | PRIMARY KEY (`id`), 16 | KEY `entities_id` (`entities_id`) 17 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; 18 | 19 | -- 20 | -- Structure de la table 'glpi_plugin_seasonality_items' 21 | -- 22 | -- 23 | DROP TABLE IF EXISTS `glpi_plugin_seasonality_items`; 24 | CREATE TABLE `glpi_plugin_seasonality_items` ( 25 | `id` int(11) NOT NULL auto_increment, -- id 26 | `plugin_seasonality_seasonalities_id` int(11) NOT NULL DEFAULT 0, 27 | `itilcategories_id` int(11) NOT NULL DEFAULT 0, 28 | PRIMARY KEY (`id`), 29 | UNIQUE KEY `unicity` (`plugin_seasonality_seasonalities_id`,`itilcategories_id`), 30 | KEY `itilcategories_id` (`itilcategories_id`) 31 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; -------------------------------------------------------------------------------- /scripts/seasonality_load_scripts.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Load plugin scripts on page start 4 | */ 5 | (function ($) { 6 | $.fn.seasonality_load_scripts = function () { 7 | 8 | init(); 9 | 10 | // Start the plugin 11 | function init() { 12 | // $(document).ready(function () { 13 | var path = 'plugins/seasonality/'; 14 | var url = window.location.href.replace(/front\/.*/, path); 15 | if (window.location.href.indexOf('plugins') > 0) { 16 | url = window.location.href.replace(/plugins\/.*/, path); 17 | } 18 | 19 | // Send data 20 | $.ajax({ 21 | url: url + 'ajax/loadscripts.php', 22 | type: "POST", 23 | dataType: "html", 24 | data: 'action=load', 25 | success: function (response, opts) { 26 | var scripts, scriptsFinder = /]*>([\s\S]+?)<\/script>/gi; 27 | while (scripts = scriptsFinder.exec(response)) { 28 | eval(scripts[1]); 29 | } 30 | } 31 | }); 32 | // }); 33 | } 34 | 35 | return this; 36 | } 37 | }(jQuery)); 38 | 39 | $(document).seasonality_load_scripts(); 40 | -------------------------------------------------------------------------------- /front/seasonality.php: -------------------------------------------------------------------------------- 1 | . 27 | -------------------------------------------------------------------------- 28 | */ 29 | 30 | include ('../../../inc/includes.php'); 31 | 32 | if ($_SESSION['glpiactiveprofile']['interface'] == 'central') { 33 | Html::header(PluginSeasonalitySeasonality::getTypeName(1), '', "helpdesk", "pluginseasonalityseasonality", "seasonality"); 34 | } else { 35 | Html::helpHeader(PluginSeasonalitySeasonality::getTypeName(1)); 36 | } 37 | 38 | Search::show('PluginSeasonalitySeasonality'); 39 | 40 | if ($_SESSION['glpiactiveprofile']['interface'] == 'central') { 41 | Html::footer(); 42 | } else { 43 | Html::helpFooter(); 44 | } -------------------------------------------------------------------------------- /ajax/ticket.php: -------------------------------------------------------------------------------- 1 | . 27 | -------------------------------------------------------------------------- 28 | */ 29 | 30 | include ('../../../inc/includes.php'); 31 | 32 | Session::checkLoginUser(); 33 | Html::header_nocache(); 34 | 35 | if (!isset($_POST['tickets_id']) || empty($_POST['tickets_id'])){ 36 | $_POST['tickets_id'] = 0; 37 | } 38 | 39 | switch($_POST['action']){ 40 | case 'changeUrgency': 41 | header("Content-Type: text/html; charset=UTF-8"); 42 | $item = new PluginSeasonalityItem(); 43 | 44 | echo json_encode($item->getUrgencyFromCategory($_POST['itilcategories_id'], $_POST['tickets_id'], $_POST['date'], $_POST['type'], $_POST['entities_id'])); 45 | break; 46 | } -------------------------------------------------------------------------------- /locales/glpi.pot: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR Seasonality Development Team 3 | # This file is distributed under the same license as the GLPI - Seasonality plugin package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: GLPI - Seasonality plugin 1.0.0\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2020-12-04 11:23+0000\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=CHARSET\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" 20 | 21 | #: setup.php:82 inc/item.class.php:724 inc/profile.class.php:44 22 | #: inc/profile.class.php:133 inc/seasonality.class.php:43 23 | msgid "Seasonality" 24 | msgid_plural "Seasonalities" 25 | msgstr[0] "" 26 | msgstr[1] "" 27 | 28 | #: inc/item.class.php:43 29 | msgid "Seasonality item" 30 | msgid_plural "Seasonality items" 31 | msgstr[0] "" 32 | msgstr[1] "" 33 | 34 | #: inc/item.class.php:173 35 | msgid "Add a category" 36 | msgstr "" 37 | 38 | #: inc/item.class.php:611 39 | msgid "Add seasonality" 40 | msgstr "" 41 | 42 | #: inc/item.class.php:612 43 | msgid "Delete seasonality" 44 | msgstr "" 45 | 46 | #: inc/item.class.php:757 47 | msgid "Cannot add two seasonalities with same date interval" 48 | msgstr "" 49 | 50 | #: inc/seasonality.class.php:72 51 | msgid "Date range" 52 | msgstr "" 53 | 54 | #: inc/seasonality.class.php:81 55 | msgid "Clear" 56 | msgstr "" 57 | 58 | #: inc/seasonality.class.php:83 59 | msgid "Select date range..." 60 | msgstr "" 61 | 62 | #: inc/seasonality.class.php:89 63 | msgid "Today" 64 | msgstr "" 65 | 66 | #: inc/seasonality.class.php:93 67 | msgid "Tomorrow" 68 | msgstr "" 69 | 70 | #: inc/seasonality.class.php:97 71 | msgid "Next 7 Days" 72 | msgstr "" 73 | 74 | #: inc/seasonality.class.php:101 75 | msgid "Next Week" 76 | msgstr "" 77 | -------------------------------------------------------------------------------- /ajax/loadscripts.php: -------------------------------------------------------------------------------- 1 | . 27 | -------------------------------------------------------------------------- 28 | */ 29 | 30 | include ('../../../inc/includes.php'); 31 | 32 | //Html::header_nocache(); 33 | Session::checkLoginUser(); 34 | header("Content-Type: text/html; charset=UTF-8"); 35 | 36 | if (isset($_POST['action'])) { 37 | switch ($_POST['action']) { 38 | case "load" : 39 | if (strpos($_SERVER['HTTP_REFERER'], "ticket.form.php") !== false 40 | || strpos($_SERVER['HTTP_REFERER'], "helpdesk.public.php") !== false 41 | || strpos($_SERVER['HTTP_REFERER'], "tracking.injector.php") !== false) { 42 | 43 | $rand = mt_rand(); 44 | 45 | $params = ['root_doc' => $CFG_GLPI['root_doc']]; 46 | 47 | echo ""; 51 | } 52 | break; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /locales/en_GB.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR Seasonality Development Team 3 | # This file is distributed under the same license as the GLPI - Seasonality plugin package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: GLPI - Seasonality plugin 1.0.0\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2018-07-10 09:03+0200\n" 11 | "PO-Revision-Date: 2019-02-25 10:35+0100\n" 12 | "MIME-Version: 1.0\n" 13 | "Content-Type: text/plain; charset=UTF-8\n" 14 | "Content-Transfer-Encoding: 8bit\n" 15 | "Language: en_GB\n" 16 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 17 | "Last-Translator: \n" 18 | "Language-Team: \n" 19 | "X-Generator: Poedit 2.0.9\n" 20 | 21 | #: setup.php:80 inc/item.class.php:720 inc/profile.class.php:44 22 | #: inc/profile.class.php:133 inc/seasonality.class.php:43 23 | msgid "Seasonality" 24 | msgid_plural "Seasonalities" 25 | msgstr[0] "Seasonality" 26 | msgstr[1] "Seasonalities" 27 | 28 | #: inc/item.class.php:43 29 | msgid "Seasonality item" 30 | msgid_plural "Seasonality items" 31 | msgstr[0] "Seasonality item" 32 | msgstr[1] "Seasonality items" 33 | 34 | #: inc/item.class.php:171 35 | msgid "Add a category" 36 | msgstr "Add a category" 37 | 38 | #: inc/item.class.php:607 39 | msgid "Add seasonality" 40 | msgstr "Add seasonality" 41 | 42 | #: inc/item.class.php:608 43 | msgid "Delete seasonality" 44 | msgstr "Delete seasonality" 45 | 46 | #: inc/item.class.php:753 47 | msgid "Cannot add two seasonalities with same date interval" 48 | msgstr "Cannot add two seasonalities with same date interval" 49 | 50 | #: inc/seasonality.class.php:72 51 | msgid "Date range" 52 | msgstr "Date range" 53 | 54 | #: inc/seasonality.class.php:81 55 | msgid "Clear" 56 | msgstr "Clear" 57 | 58 | #: inc/seasonality.class.php:83 59 | msgid "Select date range..." 60 | msgstr "Select date range..." 61 | 62 | #: inc/seasonality.class.php:89 63 | msgid "Today" 64 | msgstr "Today" 65 | 66 | #: inc/seasonality.class.php:93 67 | msgid "Tomorrow" 68 | msgstr "Tomorrow" 69 | 70 | #: inc/seasonality.class.php:97 71 | msgid "Next 7 Days" 72 | msgstr "Next 7 Days" 73 | 74 | #: inc/seasonality.class.php:101 75 | msgid "Next Week" 76 | msgstr "Next Week" 77 | -------------------------------------------------------------------------------- /locales/pt_PT.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR Seasonality Development Team 3 | # This file is distributed under the same license as the GLPI - Seasonality plugin package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | # Translators: 7 | # Emperium EmperiuM , 2018 8 | # 9 | msgid "" 10 | msgstr "" 11 | "Project-Id-Version: GLPI - Seasonality plugin 1.0.0\n" 12 | "Report-Msgid-Bugs-To: \n" 13 | "POT-Creation-Date: 2018-07-10 09:03+0200\n" 14 | "PO-Revision-Date: 2019-02-25 10:36+0100\n" 15 | "Last-Translator: Emperium EmperiuM , 2018\n" 16 | "Language-Team: Portuguese (Portugal) (https://www.transifex.com/tsmr/teams/88214/pt_PT/)\n" 17 | "MIME-Version: 1.0\n" 18 | "Content-Type: text/plain; charset=UTF-8\n" 19 | "Content-Transfer-Encoding: 8bit\n" 20 | "Language: pt_PT\n" 21 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 22 | "X-Generator: Poedit 2.0.9\n" 23 | 24 | #: setup.php:80 inc/item.class.php:720 inc/profile.class.php:44 25 | #: inc/profile.class.php:133 inc/seasonality.class.php:43 26 | msgid "Seasonality" 27 | msgid_plural "Seasonalities" 28 | msgstr[0] "" 29 | msgstr[1] "" 30 | 31 | #: inc/item.class.php:43 32 | msgid "Seasonality item" 33 | msgid_plural "Seasonality items" 34 | msgstr[0] "" 35 | msgstr[1] "" 36 | 37 | #: inc/item.class.php:171 38 | msgid "Add a category" 39 | msgstr "Adicionar uma categoria " 40 | 41 | #: inc/item.class.php:607 42 | msgid "Add seasonality" 43 | msgstr "" 44 | 45 | #: inc/item.class.php:608 46 | msgid "Delete seasonality" 47 | msgstr "" 48 | 49 | #: inc/item.class.php:753 50 | msgid "Cannot add two seasonalities with same date interval" 51 | msgstr "" 52 | 53 | #: inc/seasonality.class.php:72 54 | msgid "Date range" 55 | msgstr "" 56 | 57 | #: inc/seasonality.class.php:81 58 | msgid "Clear" 59 | msgstr "" 60 | 61 | #: inc/seasonality.class.php:83 62 | msgid "Select date range..." 63 | msgstr "" 64 | 65 | #: inc/seasonality.class.php:89 66 | msgid "Today" 67 | msgstr "" 68 | 69 | #: inc/seasonality.class.php:93 70 | msgid "Tomorrow" 71 | msgstr "" 72 | 73 | #: inc/seasonality.class.php:97 74 | msgid "Next 7 Days" 75 | msgstr "" 76 | 77 | #: inc/seasonality.class.php:101 78 | msgid "Next Week" 79 | msgstr "" 80 | -------------------------------------------------------------------------------- /front/seasonality.form.php: -------------------------------------------------------------------------------- 1 | . 27 | -------------------------------------------------------------------------- 28 | */ 29 | 30 | include ('../../../inc/includes.php'); 31 | 32 | if (empty($_GET["id"])) { 33 | $_GET["id"] = ""; 34 | } 35 | 36 | $item = new PluginSeasonalitySeasonality(); 37 | 38 | if (isset($_POST["add"])) { 39 | // Check add rights for fields 40 | $item->check(-1, CREATE, $_POST); 41 | $newID = $item->add($_POST); 42 | 43 | if ($_SESSION['glpibackcreated']) { 44 | Html::redirect($item->getFormURL()."?id=".$newID); 45 | } else { 46 | Html::back(); 47 | } 48 | 49 | } elseif (isset($_POST["update"])) { 50 | // Check update rights for fields 51 | $item->check($_POST['id'], UPDATE, $_POST); 52 | $item->update($_POST); 53 | Html::back(); 54 | 55 | } elseif (isset($_POST["purge"])) { 56 | // Check delete rights for fields 57 | $item->check($_POST['id'], PURGE, $_POST); 58 | $item->delete($_POST, 1); 59 | $item->redirectToList(); 60 | 61 | } else { 62 | $item->checkGlobal(READ); 63 | Html::header(PluginSeasonalitySeasonality::getTypeName(1), '', "helpdesk", "pluginseasonalityseasonality", "seasonality"); 64 | $item->display($_GET); 65 | Html::footer(); 66 | } -------------------------------------------------------------------------------- /front/item.form.php: -------------------------------------------------------------------------------- 1 | . 27 | -------------------------------------------------------------------------- 28 | */ 29 | 30 | include ('../../../inc/includes.php'); 31 | 32 | if (empty($_GET["id"])) { 33 | $_GET["id"] = ""; 34 | } 35 | 36 | $item = new PluginSeasonalityItem(); 37 | 38 | if (isset($_POST["add"])) { 39 | // Check add rights for fields 40 | $item->check(-1, CREATE, $_POST); 41 | $newID = $item->add($_POST); 42 | Html::back(); 43 | 44 | } elseif (isset($_POST["update"])) { 45 | // Check update rights for fields 46 | $item->check($_POST['id'], UPDATE, $_POST); 47 | $item->update($_POST); 48 | Html::back(); 49 | 50 | } elseif (isset($_POST["delete"])) { 51 | // Check delete rights for fields 52 | $item->check($_POST['id'], PURGE, $_POST); 53 | $item->delete($_POST, 1); 54 | Html::back(); 55 | 56 | } else { 57 | $seasonality = new PluginSeasonalitySeasonality(); 58 | $seasonality->checkGlobal(READ); 59 | Html::header(PluginSeasonalitySeasonality::getTypeName(1), '', "helpdesk", "pluginseasonalityseasonality", "seasonality"); 60 | 61 | $item->getFromDB($_GET['id']); 62 | $_GET['id'] = $item->fields['plugin_seasonality_seasonalities_id']; 63 | $seasonality->display($_GET); 64 | Html::footer(); 65 | } -------------------------------------------------------------------------------- /lib/daterangepicker/jquery.comiseo.daterangepicker.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2014 Tamble, Inc. 3 | * Licensed under MIT (https://github.com/tamble/jquery-ui-daterangepicker/raw/master/LICENSE.txt) 4 | */ 5 | 6 | .comiseo-daterangepicker-triggerbutton.ui-button { 7 | text-align: left; 8 | min-width: 18em; 9 | } 10 | 11 | .comiseo-daterangepicker { 12 | position: absolute; 13 | padding: 5px; 14 | } 15 | 16 | .comiseo-daterangepicker-mask { 17 | margin: 0; 18 | padding: 0; 19 | position: fixed; 20 | left: 0; 21 | top: 0; 22 | height: 100%; 23 | width: 100%; 24 | /* required for IE */ 25 | background-color: #fff; 26 | opacity: 0; 27 | filter: alpha(opacity = 0); 28 | } 29 | 30 | .comiseo-daterangepicker-presets, 31 | .comiseo-daterangepicker-calendar { 32 | display: table-cell; 33 | vertical-align: top; 34 | height: 230px; 35 | } 36 | 37 | .comiseo-daterangepicker-right .comiseo-daterangepicker-presets { 38 | padding: 2px 7px 7px 2px; 39 | } 40 | 41 | .comiseo-daterangepicker-left .comiseo-daterangepicker-presets { 42 | padding: 2px 2px 7px 7px; 43 | } 44 | 45 | .ui-menu { 46 | white-space: nowrap; 47 | } 48 | 49 | .comiseo-daterangepicker .ui-widget-content, 50 | .comiseo-daterangepicker .ui-datepicker .ui-state-highlight { 51 | border-width: 0; 52 | } 53 | 54 | .comiseo-daterangepicker > .comiseo-daterangepicker-main.ui-widget-content { 55 | border-bottom-width: 1px; 56 | } 57 | 58 | .comiseo-daterangepicker .ui-datepicker .ui-datepicker-today .ui-state-highlight { 59 | border-width: 1px; 60 | } 61 | 62 | .comiseo-daterangepicker-right .comiseo-daterangepicker-calendar { 63 | border-left-width: 1px; 64 | padding-left: 5px; 65 | } 66 | 67 | .comiseo-daterangepicker-left .comiseo-daterangepicker-calendar { 68 | border-right-width: 1px; 69 | padding-right: 5px; 70 | } 71 | 72 | .comiseo-daterangepicker-right .comiseo-daterangepicker-buttonpanel { 73 | float: left; 74 | } 75 | 76 | .comiseo-daterangepicker-left .comiseo-daterangepicker-buttonpanel { 77 | float: right; 78 | } 79 | 80 | .comiseo-daterangepicker-buttonpanel > button { 81 | margin-top: 6px; 82 | } 83 | 84 | .comiseo-daterangepicker-right .comiseo-daterangepicker-buttonpanel > button { 85 | margin-right: 6px; 86 | } 87 | 88 | .comiseo-daterangepicker-left .comiseo-daterangepicker-buttonpanel > button { 89 | margin-left: 6px; 90 | } 91 | 92 | /* themeable styles */ 93 | .comiseo-daterangepicker-calendar .ui-state-highlight a.ui-state-default { 94 | background: #b0c4de; 95 | color: #fff; 96 | } -------------------------------------------------------------------------------- /locales/fr_FR.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR Seasonality Development Team 3 | # This file is distributed under the same license as the GLPI - Seasonality plugin package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | # Translators: 7 | # Xavier CAILLAUD , 2020 8 | # 9 | #, fuzzy 10 | msgid "" 11 | msgstr "" 12 | "Project-Id-Version: GLPI - Seasonality plugin 1.0.0\n" 13 | "Report-Msgid-Bugs-To: \n" 14 | "POT-Creation-Date: 2019-02-25 10:33+0100\n" 15 | "PO-Revision-Date: 2020-11-02 16:18+0000\n" 16 | "Last-Translator: Xavier CAILLAUD , 2020\n" 17 | "Language-Team: French (France) (https://www.transifex.com/infotelGLPI/teams/88214/fr_FR/)\n" 18 | "MIME-Version: 1.0\n" 19 | "Content-Type: text/plain; charset=UTF-8\n" 20 | "Content-Transfer-Encoding: 8bit\n" 21 | "Language: fr_FR\n" 22 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" 23 | 24 | #: setup.php:82 inc/item.class.php:724 inc/profile.class.php:44 25 | #: inc/profile.class.php:133 inc/seasonality.class.php:43 26 | msgid "Seasonality" 27 | msgid_plural "Seasonalities" 28 | msgstr[0] "Saisonnalité" 29 | msgstr[1] "Saisonnalités" 30 | 31 | #: inc/item.class.php:43 32 | msgid "Seasonality item" 33 | msgid_plural "Seasonality items" 34 | msgstr[0] "Elément de saisonnalité" 35 | msgstr[1] "Eléments de saisonnalité" 36 | 37 | #: inc/item.class.php:173 38 | msgid "Add a category" 39 | msgstr "Ajouter une catégorie" 40 | 41 | #: inc/item.class.php:611 42 | msgid "Add seasonality" 43 | msgstr "Ajouter une saisonnalité" 44 | 45 | #: inc/item.class.php:612 46 | msgid "Delete seasonality" 47 | msgstr "Supprimer une saisonnalité" 48 | 49 | #: inc/item.class.php:757 50 | msgid "Cannot add two seasonalities with same date interval" 51 | msgstr "" 52 | "Ajout impossible, une saisonnalité existe déja sur cet intervalle de date" 53 | 54 | #: inc/seasonality.class.php:72 55 | msgid "Date range" 56 | msgstr "Intervalle de dates" 57 | 58 | #: inc/seasonality.class.php:81 59 | msgid "Clear" 60 | msgstr "Effacer" 61 | 62 | #: inc/seasonality.class.php:83 63 | msgid "Select date range..." 64 | msgstr "Sélectionner un intervalle de dates" 65 | 66 | #: inc/seasonality.class.php:89 67 | msgid "Today" 68 | msgstr "Ajourd'hui" 69 | 70 | #: inc/seasonality.class.php:93 71 | msgid "Tomorrow" 72 | msgstr "Demain" 73 | 74 | #: inc/seasonality.class.php:97 75 | msgid "Next 7 Days" 76 | msgstr "Les 7 prochains jours" 77 | 78 | #: inc/seasonality.class.php:101 79 | msgid "Next Week" 80 | msgstr "La semaine suivante" 81 | -------------------------------------------------------------------------------- /locales/pt_BR.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR Seasonality Development Team 3 | # This file is distributed under the same license as the GLPI - Seasonality plugin package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | # Translators: 7 | # Rodrigo de Almeida Sottomaior Macedo , 2020 8 | # 9 | #, fuzzy 10 | msgid "" 11 | msgstr "" 12 | "Project-Id-Version: GLPI - Seasonality plugin 1.0.0\n" 13 | "Report-Msgid-Bugs-To: \n" 14 | "POT-Creation-Date: 2019-02-25 10:33+0100\n" 15 | "PO-Revision-Date: 2020-11-02 16:18+0000\n" 16 | "Last-Translator: Rodrigo de Almeida Sottomaior Macedo , 2020\n" 17 | "Language-Team: Portuguese (Brazil) (https://www.transifex.com/infotelGLPI/teams/88214/pt_BR/)\n" 18 | "MIME-Version: 1.0\n" 19 | "Content-Type: text/plain; charset=UTF-8\n" 20 | "Content-Transfer-Encoding: 8bit\n" 21 | "Language: pt_BR\n" 22 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" 23 | 24 | #: setup.php:82 inc/item.class.php:724 inc/profile.class.php:44 25 | #: inc/profile.class.php:133 inc/seasonality.class.php:43 26 | msgid "Seasonality" 27 | msgid_plural "Seasonalities" 28 | msgstr[0] "Sazonalidade" 29 | msgstr[1] "Sazonalidades" 30 | 31 | #: inc/item.class.php:43 32 | msgid "Seasonality item" 33 | msgid_plural "Seasonality items" 34 | msgstr[0] "Item de Sazonalidade" 35 | msgstr[1] "Itens de Sazonalidades" 36 | 37 | #: inc/item.class.php:173 38 | msgid "Add a category" 39 | msgstr "Adicionar uma categoria" 40 | 41 | #: inc/item.class.php:611 42 | msgid "Add seasonality" 43 | msgstr "Adicionar sazonalidade" 44 | 45 | #: inc/item.class.php:612 46 | msgid "Delete seasonality" 47 | msgstr "Apagar Sazonalidade" 48 | 49 | #: inc/item.class.php:757 50 | msgid "Cannot add two seasonalities with same date interval" 51 | msgstr "" 52 | "Não é possível adicionar duas sazonalidades com o mesmo intervalo de datas" 53 | 54 | #: inc/seasonality.class.php:72 55 | msgid "Date range" 56 | msgstr "Período" 57 | 58 | #: inc/seasonality.class.php:81 59 | msgid "Clear" 60 | msgstr "Limpar" 61 | 62 | #: inc/seasonality.class.php:83 63 | msgid "Select date range..." 64 | msgstr "Selecionar período..." 65 | 66 | #: inc/seasonality.class.php:89 67 | msgid "Today" 68 | msgstr "Hoje" 69 | 70 | #: inc/seasonality.class.php:93 71 | msgid "Tomorrow" 72 | msgstr "Amanhã" 73 | 74 | #: inc/seasonality.class.php:97 75 | msgid "Next 7 Days" 76 | msgstr "Próximos 7 dias" 77 | 78 | #: inc/seasonality.class.php:101 79 | msgid "Next Week" 80 | msgstr "Próxima Semana" 81 | -------------------------------------------------------------------------------- /locales/cs_CZ.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR Seasonality Development Team 3 | # This file is distributed under the same license as the GLPI - Seasonality plugin package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | # Translators: 7 | # Xavier CAILLAUD , 2020 8 | # 9 | #, fuzzy 10 | msgid "" 11 | msgstr "" 12 | "Project-Id-Version: GLPI - Seasonality plugin 1.0.0\n" 13 | "Report-Msgid-Bugs-To: \n" 14 | "POT-Creation-Date: 2019-02-25 10:33+0100\n" 15 | "PO-Revision-Date: 2020-11-02 16:18+0000\n" 16 | "Last-Translator: Xavier CAILLAUD , 2020\n" 17 | "Language-Team: Czech (Czech Republic) (https://www.transifex.com/infotelGLPI/teams/88214/cs_CZ/)\n" 18 | "MIME-Version: 1.0\n" 19 | "Content-Type: text/plain; charset=UTF-8\n" 20 | "Content-Transfer-Encoding: 8bit\n" 21 | "Language: cs_CZ\n" 22 | "Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n" 23 | 24 | #: setup.php:82 inc/item.class.php:724 inc/profile.class.php:44 25 | #: inc/profile.class.php:133 inc/seasonality.class.php:43 26 | msgid "Seasonality" 27 | msgid_plural "Seasonalities" 28 | msgstr[0] "Sezonnost" 29 | msgstr[1] "Sezonnosti" 30 | msgstr[2] "Sezonností" 31 | msgstr[3] "Sezonností" 32 | 33 | #: inc/item.class.php:43 34 | msgid "Seasonality item" 35 | msgid_plural "Seasonality items" 36 | msgstr[0] "Položka sezonnosti" 37 | msgstr[1] "Položky sezonnosti" 38 | msgstr[2] "Položek sezonnosti" 39 | msgstr[3] "Položek sezonnosti" 40 | 41 | #: inc/item.class.php:173 42 | msgid "Add a category" 43 | msgstr "Přidat kategorii" 44 | 45 | #: inc/item.class.php:611 46 | msgid "Add seasonality" 47 | msgstr "Přidat sezonnost" 48 | 49 | #: inc/item.class.php:612 50 | msgid "Delete seasonality" 51 | msgstr "Smazat sezonnost" 52 | 53 | #: inc/item.class.php:757 54 | msgid "Cannot add two seasonalities with same date interval" 55 | msgstr "Není možné přidat dvě sezonnosti pro stejné období" 56 | 57 | #: inc/seasonality.class.php:72 58 | msgid "Date range" 59 | msgstr "Období" 60 | 61 | #: inc/seasonality.class.php:81 62 | msgid "Clear" 63 | msgstr "Vyčistit" 64 | 65 | #: inc/seasonality.class.php:83 66 | msgid "Select date range..." 67 | msgstr "Vybrat období…" 68 | 69 | #: inc/seasonality.class.php:89 70 | msgid "Today" 71 | msgstr "Dnes" 72 | 73 | #: inc/seasonality.class.php:93 74 | msgid "Tomorrow" 75 | msgstr "Zítra" 76 | 77 | #: inc/seasonality.class.php:97 78 | msgid "Next 7 Days" 79 | msgstr "Následujících 7 dní" 80 | 81 | #: inc/seasonality.class.php:101 82 | msgid "Next Week" 83 | msgstr "Příští týden" 84 | -------------------------------------------------------------------------------- /inc/seasonalityinjection.class.php: -------------------------------------------------------------------------------- 1 | . 27 | -------------------------------------------------------------------------- 28 | */ 29 | 30 | if (!defined('GLPI_ROOT')){ 31 | die("Sorry. You can't access directly to this file"); 32 | } 33 | 34 | class PluginSeasonalitySeasonalityInjection extends PluginSeasonalitySeasonality 35 | implements PluginDatainjectionInjectionInterface { 36 | 37 | static function getTable($classname = null) { 38 | 39 | $parenttype = get_parent_class(); 40 | return $parenttype::getTable(); 41 | 42 | } 43 | 44 | function isPrimaryType() { 45 | return true; 46 | } 47 | 48 | function connectedTo() { 49 | return []; 50 | } 51 | 52 | function getOptions($primary_type = '') { 53 | 54 | $tab = Search::getOptions(get_parent_class($this)); 55 | 56 | //$blacklist = PluginDatainjectionCommonInjectionLib::getBlacklistedOptions(); 57 | //Remove some options because some fields cannot be imported 58 | $notimportable = []; 59 | $options['ignore_fields'] = $notimportable; 60 | 61 | $tab = PluginDatainjectionCommonInjectionLib::addToSearchOptions($tab, $options, $this); 62 | 63 | return $tab; 64 | } 65 | 66 | /** 67 | * Standard method to add an object into glpi 68 | * WILL BE INTEGRATED INTO THE CORE IN 0.80 69 | * @param values fields to add into glpi 70 | * @param options options used during creation 71 | * @return an array of IDs of newly created objects : for example array(Computer=>1, Networkport=>10) 72 | */ 73 | function addOrUpdateObject($values=[], $options=[]) { 74 | 75 | $lib = new PluginDatainjectionCommonInjectionLib($this,$values,$options); 76 | $lib->processAddOrUpdate(); 77 | return $lib->getInjectionResults(); 78 | } 79 | 80 | } -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | 2 | on: 3 | push: 4 | # Sequence of patterns matched against refs/tags 5 | tags: 6 | - '*.*.*' # Push events to matching ex:20.15.10 7 | 8 | name: Create release with tag 9 | env: 10 | TAG_VALUE: ${GITHUB_REF/refs\/tags\//} 11 | jobs: 12 | build: 13 | name: Upload Release Asset 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v2 18 | - name: Build project # This would actually build your project, using zip for an example artifact 19 | id: build_ 20 | env: 21 | GITHUB_NAME: ${{ github.event.repository.name }} 22 | 23 | 24 | run: sudo apt-get install libxml-xpath-perl;echo $(xpath -e '/root/versions/version[num="'${GITHUB_REF/refs\/tags\//}'"]/compatibility/text()' $GITHUB_NAME.xml);echo ::set-output name=version_glpi::$(xpath -e '/root/versions/version[num="'${GITHUB_REF/refs\/tags\//}'"]/compatibility/text()' $GITHUB_NAME.xml); rm -rf $GITHUB_NAME.xml tools wiki screenshots test .git .github ISSUE_TEMPLATE.md TODO.txt $GITHUB_NAME.png;cd ..; tar -zcvf glpi-$GITHUB_NAME-${GITHUB_REF/refs\/tags\//}.tar.gz $GITHUB_NAME;ls -al;echo ::set-output name=tag::${GITHUB_REF/refs\/tags\//};echo ${{ steps.getxml.outputs.info }}; 25 | # run: rm -rf $GITHUB_NAME.xml tools wiki screenshots test ISSUE_TEMPLATE.md TODO.txt $GITHUB_NAME.png; tar -zcvf glpi-$GITHUB_NAME-$GITHUB_TAG.tar.gz $GITHUB_NAME 26 | - name: Create Release 27 | id: create_release 28 | uses: actions/create-release@v1 29 | env: 30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | with: 32 | tag_name: ${{ github.ref }} 33 | release_name: | 34 | GLPI ${{ steps.build_.outputs.version_glpi }} : Version ${{ github.ref }} disponible / available 35 | body : Version ${{ steps.build_.outputs.tag }} released for GLPI ${{ steps.build_.outputs.version_glpi }} 36 | draft: false 37 | prerelease: true 38 | - name: Upload Release Asset 39 | id: upload-release-asset 40 | uses: actions/upload-release-asset@v1 41 | env: 42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 43 | GITHUB_NAME: ${{ github.event.repository.name }} 44 | with: 45 | upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps 46 | asset_path: /home/runner/work/${{ github.event.repository.name }}/glpi-${{ github.event.repository.name }}-${{ steps.build_.outputs.tag }}.tar.gz 47 | asset_name: glpi-${{ github.event.repository.name }}-${{ steps.build_.outputs.tag }}.tar.gz 48 | asset_content_type: application/zip 49 | 50 | -------------------------------------------------------------------------------- /hook.php: -------------------------------------------------------------------------------- 1 | . 27 | -------------------------------------------------------------------------- 28 | */ 29 | 30 | function plugin_seasonality_install() { 31 | global $DB; 32 | 33 | include_once (GLPI_ROOT . "/plugins/seasonality/inc/profile.class.php"); 34 | 35 | // Table sql creation 36 | if (!$DB->tableExists("glpi_plugin_seasonality_seasonalities")) { 37 | $DB->runFile(GLPI_ROOT . "/plugins/seasonality/install/sql/empty-1.4.0.sql"); 38 | } 39 | 40 | PluginSeasonalityProfile::createFirstAccess($_SESSION['glpiactiveprofile']['id']); 41 | return true; 42 | } 43 | 44 | // Uninstall process for plugin : need to return true if succeeded 45 | function plugin_seasonality_uninstall() { 46 | global $DB; 47 | 48 | // Plugin tables deletion 49 | $tables = ["glpi_plugin_seasonality_seasonalities", 50 | "glpi_plugin_seasonality_items"]; 51 | 52 | foreach ($tables as $table) 53 | $DB->query("DROP TABLE IF EXISTS `$table`;"); 54 | 55 | return true; 56 | } 57 | 58 | function plugin_seasonality_postinit() { 59 | } 60 | 61 | function plugin_seasonality_getAddSearchOptions($itemtype) { 62 | $tab = []; 63 | 64 | if ($itemtype == 'ITILCategory') { 65 | $item = new PluginSeasonalityItem(); 66 | $tab = $item->getAddSearchOptions(); 67 | } 68 | 69 | return $tab; 70 | } 71 | 72 | function plugin_seasonality_MassiveActions($type) { 73 | 74 | switch($type){ 75 | case 'ITILCategory': 76 | $item = new PluginSeasonalityItem(); 77 | $output = $item->massiveActions($type); 78 | return $output; 79 | } 80 | } 81 | 82 | function plugin_datainjection_populate_seasonality() { 83 | global $INJECTABLE_TYPES; 84 | 85 | $INJECTABLE_TYPES['PluginSeasonalityItemInjection'] = 'seasonality'; 86 | $INJECTABLE_TYPES['PluginSeasonalitySeasonalityInjection'] = 'seasonality'; 87 | } -------------------------------------------------------------------------------- /seasonality.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Seasonality 4 | seasonality 5 | stable 6 | https://raw.githubusercontent.com/InfotelGLPI/seasonality/master/seasonality.png 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | https://github.com/InfotelGLPI/seasonality 20 | https://github.com/InfotelGLPI/seasonality/releases 21 | https://github.com/InfotelGLPI/seasonality/issues 22 | https://raw.githubusercontent.com/InfotelGLPI/seasonality/master/README.md 23 | 24 | Ludovic Dupont 25 | Infotel 26 | 27 | 28 | 29 | 1.5.0 30 | 9.4 31 | 32 | 33 | 1.4.1 34 | 9.3 35 | 36 | 37 | 1.4.0 38 | 9.3 39 | 40 | 41 | 1.3.0 42 | 9.2 43 | 44 | 45 | 1.2.0 46 | 9.1 47 | 48 | 49 | 1.1.0 50 | 0.90 51 | 52 | 53 | 54 | cs_CZ 55 | fr_FR 56 | en_GB 57 | 58 | 59 | 60 | 61 | ticket 62 | categorie 63 | 64 | 65 | ticket 66 | category 67 | 68 | 69 | požadavek 70 | kategorie 71 | 72 | 73 | 74 | https://raw.githubusercontent.com/InfotelGLPI/seasonality/master/screenshots/seasonality.png 75 | https://raw.githubusercontent.com/InfotelGLPI/seasonality/master/screenshots/seasonality_ticket.png 76 | 77 | 78 | -------------------------------------------------------------------------------- /inc/iteminjection.class.php: -------------------------------------------------------------------------------- 1 | . 27 | -------------------------------------------------------------------------- 28 | */ 29 | 30 | if (!defined('GLPI_ROOT')){ 31 | die("Sorry. You can't access directly to this file"); 32 | } 33 | 34 | class PluginSeasonalityItemInjection extends PluginSeasonalityItem 35 | implements PluginDatainjectionInjectionInterface { 36 | 37 | static function getTable($classname = null) { 38 | 39 | $parenttype = get_parent_class(); 40 | return $parenttype::getTable(); 41 | 42 | } 43 | 44 | function isPrimaryType() { 45 | return true; 46 | } 47 | 48 | function connectedTo() { 49 | return []; 50 | } 51 | 52 | function getOptions($primary_type = '') { 53 | 54 | $tab = Search::getOptions(get_parent_class($this)); 55 | 56 | //$blacklist = PluginDatainjectionCommonInjectionLib::getBlacklistedOptions(); 57 | //Remove some options because some fields cannot be imported 58 | $notimportable = []; 59 | $options['ignore_fields'] = $notimportable; 60 | $options['displaytype'] = ["relation" => [180]]; 61 | 62 | $tab = PluginDatainjectionCommonInjectionLib::addToSearchOptions($tab, $options, $this); 63 | 64 | return $tab; 65 | } 66 | 67 | /** 68 | * Standard method to add an object into glpi 69 | * WILL BE INTEGRATED INTO THE CORE IN 0.80 70 | * @param values fields to add into glpi 71 | * @param options options used during creation 72 | * @return an array of IDs of newly created objects : for example array(Computer=>1, Networkport=>10) 73 | */ 74 | function addOrUpdateObject($values=[], $options=[]) { 75 | 76 | $lib = new PluginDatainjectionCommonInjectionLib($this,$values,$options); 77 | $lib->processAddOrUpdate(); 78 | return $lib->getInjectionResults(); 79 | } 80 | 81 | function customDataAlreadyInDB($injectionClass, $values, $options){ 82 | 83 | if (isset($values['PluginSeasonalityItem']['plugin_seasonality_seasonalities_id']) && $values['PluginSeasonalityItem']['plugin_seasonality_seasonalities_id']) { 84 | return true; 85 | } 86 | 87 | return false; 88 | } 89 | 90 | function customimport($toinject, $add, $rights){ 91 | 92 | $item = new PluginSeasonalityItem(); 93 | return $item->add(['itilcategories_id' => $toinject['items_id'], 94 | 'plugin_seasonality_seasonalities_id' => $toinject['plugin_seasonality_seasonalities_id']]); 95 | } 96 | } -------------------------------------------------------------------------------- /setup.php: -------------------------------------------------------------------------------- 1 | . 27 | -------------------------------------------------------------------------- 28 | */ 29 | 30 | define('PLUGIN_SEASONALITY_VERSION', '1.5.0'); 31 | 32 | // Init the hooks of the plugins -Needed 33 | function plugin_init_seasonality() { 34 | global $PLUGIN_HOOKS, $CFG_GLPI; 35 | 36 | $PLUGIN_HOOKS['csrf_compliant']['seasonality'] = true; 37 | $PLUGIN_HOOKS['change_profile']['seasonality'] = ['PluginSeasonalityProfile', 'changeProfile']; 38 | 39 | if (Session::getLoginUserID()) { 40 | Plugin::registerClass('PluginSeasonalityProfile', ['addtabon' => 'Profile']); 41 | 42 | // Display a menu entry 43 | if (Session::haveRight("plugin_seasonality", READ)) { 44 | $PLUGIN_HOOKS['use_massive_action']['seasonality'] = 1; 45 | $PLUGIN_HOOKS['menu_toadd']['seasonality'] = ['helpdesk' => 'PluginSeasonalitySeasonality']; 46 | Plugin::registerClass('PluginSeasonalityItem', ['addtabon' => 'ITILCategory']); 47 | Plugin::registerClass('PluginSeasonalityItem', ['addtabon' => 'PluginSeasonalitySeasonality']); 48 | } 49 | 50 | // Add specific files to add to the header : javascript or css 51 | $PLUGIN_HOOKS['add_javascript']['seasonality'] = ["lib/daterangepicker/jquery.comiseo.daterangepicker.min.js", 52 | "lib/daterangepicker/moment.min.js"]; 53 | 54 | if (strpos($_SERVER['REQUEST_URI'], "ticket.form.php") !== false 55 | || strpos($_SERVER['REQUEST_URI'], "helpdesk.public.php") !== false 56 | || strpos($_SERVER['REQUEST_URI'], "tracking.injector.php") !== false) { 57 | $PLUGIN_HOOKS['javascript']['seasonality'][] = '/plugins/seasonality/scripts/seasonality.js'; 58 | $PLUGIN_HOOKS['javascript']['seasonality'][] = '/plugins/seasonality/scripts/seasonality_load_scripts.js'; 59 | 60 | Html::requireJs('seasonality'); 61 | } 62 | 63 | // Css 64 | $PLUGIN_HOOKS['add_css']['seasonality'] = ["lib/daterangepicker/jquery.comiseo.daterangepicker.css"]; 65 | 66 | // Purge 67 | $PLUGIN_HOOKS['pre_item_purge']['seasonality'] = [ 68 | 'PluginSeasonalitySeasonality' => ['PluginSeasonalityItem', 'purgeItem'], 69 | 'Profile' => ['PluginSeasonalityProfile', 'purgeProfiles'] 70 | ]; 71 | 72 | $PLUGIN_HOOKS['plugin_datainjection_populate']['seasonality'] = 'plugin_datainjection_populate_seasonality'; 73 | } 74 | // End init, when all types are registered 75 | $PLUGIN_HOOKS['post_init']['seasonality'] = 'plugin_seasonality_postinit'; 76 | } 77 | 78 | // Get the name and the version of the plugin - Needed 79 | function plugin_version_seasonality() { 80 | 81 | return [ 82 | 'name' => _n('Seasonality', 'Seasonalities', 2, 'seasonality'), 83 | 'version' => PLUGIN_SEASONALITY_VERSION, 84 | 'license' => 'GPLv2+', 85 | 'author' => "Infotel & Ludovic Dupont", 86 | 'homepage' => 'https://github.com/InfotelGLPI/seasonality', 87 | 'requirements' => [ 88 | 'glpi' => [ 89 | 'min' => '9.4', 90 | 'dev' => false 91 | ] 92 | ]]; 93 | } 94 | 95 | // Optional : check prerequisites before install : may print errors or add to message after redirect 96 | function plugin_seasonality_check_prerequisites() { 97 | if (version_compare(GLPI_VERSION, '9.4', 'lt') 98 | || version_compare(GLPI_VERSION, '9.5', 'ge')) { 99 | if (method_exists('Plugin', 'messageIncompatible')) { 100 | echo Plugin::messageIncompatible('core', '9.4'); 101 | } 102 | return false; 103 | } 104 | return true; 105 | } 106 | 107 | // Uninstall process for plugin : need to return true if succeeded : may display messages or add to message after redirect 108 | function plugin_seasonality_check_config() { 109 | return true; 110 | } -------------------------------------------------------------------------------- /lib/daterangepicker/jquery.comiseo.daterangepicker.min.js: -------------------------------------------------------------------------------- 1 | /*! https://github.com/tamble/jquery-ui-daterangepicker 2 | * Copyright (c) 2015 Tamble, Inc. Licensed MIT 3 | */ 4 | 5 | (function(a,F,T){function P(c,b,e){function g(b){d.button("option","label",b)}var d,h;h="drp_autogen"+z++;a('label[for="'+c.attr("id")+'"]').attr("for",h);d=a('').addClass(b+"-triggerbutton").attr({title:c.attr("title"),tabindex:c.attr("tabindex"),id:h}).button({icons:{secondary:e.icon},label:e.initialText});return{getElement:function(){return d},getLabel:function(){return d.button("option","label")},setLabel:g,reset:function(){c.val("").change();g(e.initialText)},enforceOptions:function(){d.button("option", 6 | {icons:{secondary:e.icon},label:e.initialText})}}}function G(c,b,e){var g,d;(function(){g=a("
").addClass(c+"-presets");d=a("
    ");a.each(b.presetRanges,function(){a('
  • '+this.text+"
  • ").data("dateStart",this.dateStart).data("dateEnd",this.dateEnd).click(e).appendTo(d)});g.append(d);d.menu().data("ui-menu").delay=0})();return{getElement:function(){return g}}}function Q(c,b){function e(e,d){var c=a.datepicker.parseDate(b.datepickerOptions.dateFormat||a.datepicker._defaults.dateFormat, 7 | e);!f.start||f.end?(f.start=c,f.end=null):c",{"class":c+"-calendar ui-widget-content"});q.datepicker(a.extend({},b.datepickerOptions,{beforeShowDay:g,onSelect:e}));d();return{getElement:function(){return q},scrollToRangeStart:function(){f.start&&q.datepicker("setDate",f.start)},getRange:function(){return f},setRange:function(b){f=b;h()},refresh:h,reset:function(){f={start:null,end:null};h()},enforceOptions:function(){q.datepicker("option",a.extend({},b.datepickerOptions, 9 | {beforeShowDay:g,onSelect:e}))}}}function R(c,b,e){var g,d,h,q;d=a('').text(b.applyButtonText).button();h=a('').text(b.clearButtonText).button();q=a('').text(b.cancelButtonText).button();g=a("
    ").addClass(c+"-buttonpanel").append(d).append(h).append(q);e&&(d.click(e.onApply),h.click(e.onClear),q.click(e.onCancel)); 10 | return{getElement:function(){return g},enforceOptions:function(){d.button("option","label",b.applyButtonText);h.button("option","label",b.clearButtonText);q.button("option","label",b.cancelButtonText)}}}function S(c,b){function e(){if(b.autoFitCalendars){var k=a(F).width(),l=p.outerWidth(!0),c=r.getElement(),d=c.datepicker("option","numberOfMonths"),f=d;if(l>k){for(;1k;)c.datepicker("option","numberOfMonths",--d);d!==f&&(e.monthWidth=(l-p.outerWidth(!0))/(f-d))}else for(;d=e.monthWidth;)c.datepicker("option","numberOfMonths",++d);I();C=!1}}function g(){m.getElement().click(J);m.getElement().keydown(z);x.click(s);a(F).resize(function(){t?e():C=!0})}function d(k){var l=b.dateFormat;return a.datepicker.formatDate(l,k.start)+(+k.end!==+k.start?b.rangeSplitter+a.datepicker.formatDate(l,k.end):"")}function h(k){var l=b.altFormat,c={};c.start=a.datepicker.formatDate(l,k.start);c.end=a.datepicker.formatDate(l,k.end);return JSON.stringify(c)}function q(k){var c= 12 | b.altFormat,d=null;if(k)try{d=JSON.parse(k,function(b,k){return b?a.datepicker.parseDate(c,k):k})}catch(e){}return d}function f(){var b=B();b?(m.setLabel(d(b)),r.setRange(b)):r.reset()}function A(a){var l=a||r.getRange();if(l.start&&(l.end||(l.end=l.start),a&&r.setRange(l),m.setLabel(d(l)),c.val(h(l)).change(),b.onChange))b.onChange()}function B(){return q(c.val())}function H(){m.reset();r.reset();c.val("")}function K(){var c=a(this),d=c.data("dateStart")().startOf("day").toDate(),c=c.data("dateEnd")().startOf("day").toDate(); 13 | r.setRange({start:d,end:c});b.applyOnMenuSelect&&(s(),A());return!1}function I(){p.position({my:"left top",at:"left bottom"+(0>b.verticalOffset?b.verticalOffset:"+"+b.verticalOffset),of:m.getElement(),collision:"flipfit flipfit",using:function(c,a){var d=u,e,f=a.element.top+a.element.height/2,h=a.target.top+a.target.height/2,g=v;u=a.element.left+a.element.width/2>a.target.left+a.target.width/2?L:M;u!==d&&(b.mirrorOnCollision&&(e=u===M?w:r,p.children().first().append(e.getElement())),p.removeClass(n+ 14 | "-"+y[d]),p.addClass(n+"-"+y[u]));p.css({left:c.left,top:c.top});v=f>h?N:O;v!==g&&(null!==g&&m.getElement().removeClass(n+"-"+y[g]),m.getElement().addClass(n+"-"+y[v]));d=v===N&&a.element.top-a.target.top!==a.target.height+b.verticalOffset||v===O&&a.target.top-a.element.top!==a.element.height+b.verticalOffset;m.getElement().toggleClass(n+"-vfit",d)}})}function z(b){switch(b.which){case a.ui.keyCode.UP:case a.ui.keyCode.DOWN:b.preventDefault();b.stopPropagation();D();break;case a.ui.keyCode.ESCAPE:b.preventDefault(); 15 | b.stopPropagation();s();break;case a.ui.keyCode.TAB:s()}}function D(){t||(m.getElement().addClass(n+"-active"),x.show(),t=!0,C&&e(),r.scrollToRangeStart(),p.show(),I());if(b.onOpen)b.onOpen()}function s(){t&&(p.hide(),x.hide(),m.getElement().removeClass(n+"-active"),t=!1);if(b.onClose)b.onClose()}function J(){t?s():D()}var n="comiseo-daterangepicker",p,x,m,w,r,E,t=!1,C=!1,M=0,L=1,O=2,N=3,y=["left","right","top","bottom"],u=L,v=null;(function(){m=P(c,n,b);w=G(n,b,K);r=Q(n,b);e.numberOfMonths=b.datepickerOptions.numberOfMonths; 16 | e.numberOfMonths instanceof Array&&(b.autoFitCalendars=!1);E=R(n,b,{onApply:function(){s();A()},onClear:function(){s();H()},onCancel:function(){s();f()}});p=a("
    ",{"class":n+" "+n+"-"+y[u]+" ui-widget ui-widget-content ui-corner-all ui-front"}).append(a("
    ",{"class":n+"-main ui-widget-content"}).append(w.getElement()).append(r.getElement())).append(a('
    ').append(E.getElement())).hide();c.hide().after(m.getElement());x=a("
    ",{"class":"ui-front "+ 17 | n+"-mask"}).hide();a("body").append(x).append(p);e();f();g()})();return{toggle:J,destroy:function(){p.remove();m.getElement().remove();c.show()},open:D,close:s,setRange:A,getRange:B,clearRange:H,reset:f,enforceOptions:function(){var a=w;w=G(n,b,K);a.getElement().replaceWith(w.getElement());r.enforceOptions();E.enforceOptions();m.enforceOptions();(a=B())&&m.setLabel(d(a))},getContainer:function(){return p}}}var z=0;a.widget("comiseo.daterangepicker",{version:"0.4.1",options:{presetRanges:[{text:"Today", 18 | dateStart:function(){return moment()},dateEnd:function(){return moment()}},{text:"Yesterday",dateStart:function(){return moment().subtract("days",1)},dateEnd:function(){return moment().subtract("days",1)}},{text:"Last 7 Days",dateStart:function(){return moment().subtract("days",6)},dateEnd:function(){return moment()}},{text:"Last Week (Mo-Su)",dateStart:function(){return moment().subtract("days",7).isoWeekday(1)},dateEnd:function(){return moment().subtract("days",7).isoWeekday(7)}},{text:"Month to Date", 19 | dateStart:function(){return moment().startOf("month")},dateEnd:function(){return moment()}},{text:"Previous Month",dateStart:function(){return moment().subtract("month",1).startOf("month")},dateEnd:function(){return moment().subtract("month",1).endOf("month")}},{text:"Year to Date",dateStart:function(){return moment().startOf("year")},dateEnd:function(){return moment()}}],verticalOffset:0,initialText:"Select date range...",icon:"ui-icon-triangle-1-s",applyButtonText:"Apply",clearButtonText:"Clear", 20 | cancelButtonText:"Cancel",rangeSplitter:" - ",dateFormat:"M d, yy",altFormat:"yy-mm-dd",mirrorOnCollision:!0,applyOnMenuSelect:!0,autoFitCalendars:!0,onOpen:null,onClose:null,onChange:null,datepickerOptions:{numberOfMonths:3,maxDate:0}},_create:function(){this._dateRangePicker=S(this.element,this.options)},_destroy:function(){this._dateRangePicker.destroy()},_setOptions:function(a){this._super(a);this._dateRangePicker.enforceOptions()},open:function(){this._dateRangePicker.open()},close:function(){this._dateRangePicker.close()}, 21 | setRange:function(a){this._dateRangePicker.setRange(a)},getRange:function(){return this._dateRangePicker.getRange()},clearRange:function(){this._dateRangePicker.clearRange()},widget:function(){return this._dateRangePicker.getContainer()}})})(jQuery,window); 22 | -------------------------------------------------------------------------------- /inc/profile.class.php: -------------------------------------------------------------------------------- 1 | . 27 | -------------------------------------------------------------------------- 28 | */ 29 | 30 | if (!defined('GLPI_ROOT')) { 31 | die("Sorry. You can't access directly to this file"); 32 | } 33 | 34 | /** 35 | * Class PluginSeasonalityProfile 36 | * 37 | * This class manages the profile rights of the plugin 38 | * 39 | * @package Seasonality 40 | */ 41 | class PluginSeasonalityProfile extends Profile { 42 | 43 | static function getTypeName($nb=0) { 44 | return __('Seasonality', 'seasonality'); 45 | } 46 | 47 | /** 48 | * Get tab name for item 49 | * 50 | * @param CommonGLPI $item 51 | * @param type $withtemplate 52 | * @return string 53 | */ 54 | function getTabNameForItem(CommonGLPI $item, $withtemplate=0) { 55 | 56 | if ($item->getType()=='Profile') { 57 | return PluginSeasonalityProfile::getTypeName(2); 58 | } 59 | return ''; 60 | } 61 | 62 | /** 63 | * display tab content for item 64 | * 65 | * @global type $CFG_GLPI 66 | * @param CommonGLPI $item 67 | * @param type $tabnum 68 | * @param type $withtemplate 69 | * @return boolean 70 | */ 71 | static function displayTabContentForItem(CommonGLPI $item, $tabnum=1, $withtemplate=0) { 72 | global $CFG_GLPI; 73 | 74 | if ($item->getType()=='Profile') { 75 | $ID = $item->getID(); 76 | $prof = new self(); 77 | 78 | self::addDefaultProfileInfos($ID, 79 | ['plugin_seasonality' => 0]); 80 | $prof->showForm($ID); 81 | } 82 | 83 | return true; 84 | } 85 | 86 | /** 87 | * show profile form 88 | * 89 | * @param type $ID 90 | * @param type $options 91 | * @return boolean 92 | */ 93 | function showForm ($profiles_id=0, $openform=TRUE, $closeform=TRUE) { 94 | 95 | echo "
    "; 96 | if (($canedit = Session::haveRightsOr(self::$rightname, [CREATE, UPDATE, PURGE])) 97 | && $openform) { 98 | $profile = new Profile(); 99 | echo "
    "; 100 | } 101 | 102 | $profile = new Profile(); 103 | $profile->getFromDB($profiles_id); 104 | 105 | $rights = $this->getAllRights(); 106 | $profile->displayRightsChoiceMatrix($rights, ['canedit' => $canedit, 107 | 'default_class' => 'tab_bg_2', 108 | 'title' => __('General')]); 109 | 110 | if ($canedit 111 | && $closeform) { 112 | echo "
    "; 113 | echo Html::hidden('id', ['value' => $profiles_id]); 114 | echo Html::submit(_sx('button', 'Save'), ['name' => 'update']); 115 | echo "
    \n"; 116 | Html::closeForm(); 117 | } 118 | echo "
    "; 119 | 120 | $this->showLegend(); 121 | } 122 | 123 | /** 124 | * Get all rights 125 | * 126 | * @param type $all 127 | * @return array 128 | */ 129 | static function getAllRights($all = false) { 130 | 131 | $rights = [ 132 | ['itemtype' => 'PluginSeasonalitySeasonality', 133 | 'label' => __('Seasonality', 'seasonality'), 134 | 'field' => 'plugin_seasonality' 135 | ] 136 | ]; 137 | 138 | return $rights; 139 | } 140 | 141 | /** 142 | * Init profiles 143 | * 144 | **/ 145 | 146 | static function translateARight($old_right) { 147 | switch ($old_right) { 148 | case '': 149 | return 0; 150 | case 'r' : 151 | return READ; 152 | case 'w': 153 | return ALLSTANDARDRIGHT; 154 | case '0': 155 | case '1': 156 | return $old_right; 157 | 158 | default : 159 | return 0; 160 | } 161 | } 162 | 163 | 164 | /** 165 | * @since 0.85 166 | * Migration rights from old system to the new one for one profile 167 | * @param $profiles_id the profile ID 168 | */ 169 | static function migrateOneProfile($profiles_id) { 170 | global $DB; 171 | //Cannot launch migration if there's nothing to migrate... 172 | if (!$DB->tableExists('glpi_plugin_seasonality_profiles')) { 173 | return true; 174 | } 175 | 176 | foreach ($DB->request('glpi_plugin_seasonality_profiles', 177 | "`profiles_id`='$profiles_id'") as $profile_data) { 178 | 179 | $matching = ['seasonality' => 'plugin_seasonality']; 180 | $current_rights = ProfileRight::getProfileRights($profiles_id, array_values($matching)); 181 | foreach ($matching as $old => $new) { 182 | if (!isset($current_rights[$old])) { 183 | $query = "UPDATE `glpi_profilerights` 184 | SET `rights`='".self::translateARight($profile_data[$old])."' 185 | WHERE `name`='$new' AND `profiles_id`='$profiles_id'"; 186 | $DB->query($query); 187 | } 188 | } 189 | } 190 | } 191 | 192 | /** 193 | * Initialize profiles, and migrate it necessary 194 | */ 195 | static function initProfile() { 196 | global $DB; 197 | $profile = new self(); 198 | $dbu = new DbUtils(); 199 | //Add new rights in glpi_profilerights table 200 | foreach ($profile->getAllRights(true) as $data) { 201 | if ($dbu->countElementsInTable("glpi_profilerights", 202 | ["name" => $data['field']]) == 0) { 203 | ProfileRight::addProfileRights([$data['field']]); 204 | } 205 | } 206 | 207 | //Migration old rights in new ones 208 | foreach ($DB->request("SELECT `id` FROM `glpi_profiles`") as $prof) { 209 | self::migrateOneProfile($prof['id']); 210 | } 211 | foreach ($DB->request("SELECT * 212 | FROM `glpi_profilerights` 213 | WHERE `profiles_id`='".$_SESSION['glpiactiveprofile']['id']."' 214 | AND `name` LIKE '%plugin_seasonality%'") as $prof) { 215 | $_SESSION['glpiactiveprofile'][$prof['name']] = $prof['rights']; 216 | } 217 | } 218 | 219 | /** 220 | * Initialize profiles, and migrate it necessary 221 | */ 222 | static function changeProfile() { 223 | global $DB; 224 | 225 | foreach ($DB->request("SELECT * 226 | FROM `glpi_profilerights` 227 | WHERE `profiles_id`='".$_SESSION['glpiactiveprofile']['id']."' 228 | AND `name` LIKE '%plugin_seasonality%'") as $prof) { 229 | $_SESSION['glpiactiveprofile'][$prof['name']] = $prof['rights']; 230 | } 231 | 232 | } 233 | 234 | static function createFirstAccess($profiles_id) { 235 | 236 | $rights = ['plugin_seasonality' => ALLSTANDARDRIGHT]; 237 | 238 | self::addDefaultProfileInfos($profiles_id, 239 | $rights, true); 240 | 241 | } 242 | 243 | /** 244 | * @param $profile 245 | **/ 246 | static function addDefaultProfileInfos($profiles_id, $rights, $drop_existing = false) { 247 | 248 | $dbu = new DbUtils(); 249 | $profileRight = new ProfileRight(); 250 | foreach ($rights as $right => $value) { 251 | if ($dbu->countElementsInTable('glpi_profilerights', 252 | ["profiles_id" => $profiles_id, "name" => $right]) && $drop_existing) { 253 | $profileRight->deleteByCriteria(['profiles_id' => $profiles_id, 'name' => $right]); 254 | } 255 | if (!$dbu->countElementsInTable('glpi_profilerights', 256 | ["profiles_id" => $profiles_id, "name" => $right])) { 257 | $myright['profiles_id'] = $profiles_id; 258 | $myright['name'] = $right; 259 | $myright['rights'] = $value; 260 | $profileRight->add($myright); 261 | 262 | //Add right to the current session 263 | $_SESSION['glpiactiveprofile'][$right] = $value; 264 | } 265 | } 266 | } 267 | 268 | } -------------------------------------------------------------------------------- /scripts/seasonality.js: -------------------------------------------------------------------------------- 1 | (function ($) { 2 | $.fn.seasonality = function (options) { 3 | var object = this; 4 | init(); 5 | 6 | /** 7 | * Start the plugin 8 | */ 9 | function init() { 10 | object.params = new Array(); 11 | object.params['root_doc'] = ''; 12 | 13 | if (options != undefined) { 14 | $.each(options, function (index, val) { 15 | if (val != undefined && val != null) { 16 | object.params[index] = val; 17 | } 18 | }); 19 | } 20 | // object.initValues = new Array(); 21 | } 22 | 23 | /** 24 | * Add elements to ticket or central 25 | */ 26 | this.addelements = function () { 27 | $(document).ready(function () { 28 | // Get tickets_id 29 | var tickets_id = object.urlParam(window.location.href, 'id'); 30 | // CENTRAL 31 | if (location.pathname.indexOf('ticket.form.php') > 0 && !object.isIE()) { 32 | // Launched on each complete Ajax load 33 | $(document).ajaxComplete(function (event, xhr, option) { 34 | // We execute the code only if the central form display request is done 35 | if (option.url != undefined && (option.url.indexOf('loadscripts.php') > 0)) { 36 | // Delay the execution (ajax requestcomplete event fired before dom loading) 37 | setTimeout(function () { 38 | var itilcategoriesIdElm = $('select[name="itilcategories_id"], input[name="itilcategories_id"]'); 39 | 40 | // Unbind existing on_change 41 | if (tickets_id == 0 || tickets_id == undefined){ 42 | itilcategoriesIdElm.off( "change" ); 43 | } 44 | 45 | // object.saveCurrentTicket(); 46 | object.loadUrgency(tickets_id, false); 47 | 48 | // On change itilcategory 49 | itilcategoriesIdElm.on('change', function () { 50 | object.loadUrgency(tickets_id, true); 51 | }); 52 | }, 1860); 53 | } 54 | }, this); 55 | 56 | // POST ONLY 57 | } else if (location.pathname.indexOf('helpdesk.public.php') > 0 || location.pathname.indexOf('tracking.injector.php') > 0 || object.isIE()){ 58 | var itilcategoriesIdElm = $('select[name="itilcategories_id"], input[name="itilcategories_id"]'); 59 | 60 | // Unbind existing on_change 61 | if (tickets_id == 0 || tickets_id == undefined){ 62 | itilcategoriesIdElm.off( "change" ); 63 | } 64 | 65 | // object.saveCurrentTicket(); 66 | object.loadUrgency(tickets_id, false); 67 | 68 | // On change itilcategory 69 | itilcategoriesIdElm.on('change', function () { 70 | object.loadUrgency(tickets_id, true); 71 | }); 72 | } 73 | }); 74 | } 75 | 76 | // /** 77 | // * Save curent ticket 78 | // */ 79 | // this.saveCurrentTicket = function (){ 80 | // // Save current values 81 | // object.initValues['urgency'] = $("select[name='urgency'], input[name='urgency']").val(); 82 | // object.initValues['impact'] = $("select[name='impact'], input[name='impact']").val() 83 | // object.initValues['priority'] = $("select[name='priority'], input[name='priority']").val(); 84 | // } 85 | // 86 | /** 87 | * Restore curent ticket 88 | */ 89 | this.restoreCurrentTicket = function (json){ 90 | // restore current values 91 | $("select[name='urgency'], input[name='urgency']").select2("val", json.default_urgency); 92 | $('select[name="impact"], input[name="impact"]').select2("val", json.default_impact); 93 | $('select[name="priority"], input[name="priority"]').select2("val", json.default_priority); 94 | } 95 | 96 | /** 97 | * Load urgency 98 | * 99 | * @param string tickets_id 100 | */ 101 | this.loadUrgency = function (tickets_id, reload){ 102 | var root_doc = object.params['root_doc']; 103 | var itilcategoriesIdElm = $("select[name='itilcategories_id'], input[name='itilcategories_id']"); 104 | var date = $("input[name='date']"); 105 | var urgence_block = $("select[name='urgency'], input[name='urgency']"); 106 | var type = $("select[name='type'], input[name='type']"); 107 | var entities_id = $("input[name='entities_id']"); 108 | 109 | if (urgence_block.length != 0) { 110 | $.ajax({ 111 | url: root_doc + '/plugins/seasonality/ajax/ticket.php', 112 | type: "POST", 113 | dataType: "json", 114 | data: { 115 | action : 'changeUrgency', 116 | itilcategories_id : (itilcategoriesIdElm.length != 0) ? itilcategoriesIdElm.val() : '0', 117 | date : (date.length != 0) ? date.val() : '0', 118 | tickets_id : tickets_id, 119 | type : (type.length != 0) ? type.val() : '1', 120 | entities_id : (entities_id.length != 0) ? entities_id.val() : '0', 121 | }, 122 | success: function (json, opts) { 123 | if ($('#seasonalities_link').length != 0) { 124 | $('#seasonalities_link').remove(); 125 | } 126 | 127 | if (!json.error) { 128 | var priorityElm = $('select[name="priority"], input[name="priority"]'); 129 | var impactElm = $('select[name="impact"], input[name="impact"]'); 130 | 131 | // Update urgency 132 | urgence_block.val(json.urgency_id); 133 | urgence_block.select2("val", json.urgency_id); 134 | var requesterText = urgence_block[0].nextSibling; 135 | if (requesterText.nodeValue != null) { 136 | $(requesterText).remove(); 137 | urgence_block.parent().append(json.urgency_name); 138 | } 139 | 140 | // Append seasonality link after category 141 | itilcategoriesIdElm.parent().append(json.seasonalities_link); 142 | 143 | // Update priority 144 | $.ajax({ 145 | url: root_doc + '/ajax/priority.php', 146 | type: "POST", 147 | dataType: "html", 148 | data: { 149 | urgency: json.urgency_id, 150 | impact: impactElm.val(), 151 | priority: (priorityElm.length != 0) ? priorityElm.attr('id') : '0' 152 | }, 153 | success: function (response, opts) { 154 | $('span[id^="change_priority_"]').html(response); 155 | if ((tickets_id == 0 || tickets_id == undefined) && reload) { 156 | $('form[name="form_ticket"], form[name="helpdeskform"]').submit(); 157 | } 158 | } 159 | }); 160 | } else { 161 | if (!json.template) { 162 | object.restoreCurrentTicket(json); 163 | } 164 | if ((tickets_id == 0 || tickets_id == undefined) && reload) { 165 | $('form[name="form_ticket"], form[name="helpdeskform"]').submit(); 166 | } 167 | } 168 | } 169 | }); 170 | } 171 | } 172 | 173 | /** 174 | * Get url parameter 175 | * 176 | * @param string url 177 | * @param string name 178 | */ 179 | this.urlParam = function (url, name) { 180 | var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(url); 181 | if (results != null) { 182 | return results[1] || 0; 183 | } 184 | } 185 | 186 | /** 187 | * Is IE navigator 188 | */ 189 | this.isIE = function () { 190 | var ua = window.navigator.userAgent; 191 | var msie = ua.indexOf("MSIE "); 192 | 193 | if (msie > 0 || !!navigator.userAgent.match(/Trident.*rv\:11\./)) // If Internet Explorer, return version number 194 | return true; 195 | 196 | return false; 197 | } 198 | 199 | return this; 200 | } 201 | }(jQuery)); 202 | -------------------------------------------------------------------------------- /inc/seasonality.class.php: -------------------------------------------------------------------------------- 1 | . 27 | -------------------------------------------------------------------------- 28 | */ 29 | 30 | if (!defined('GLPI_ROOT')) { 31 | die("Sorry. You can't access directly to this file"); 32 | } 33 | 34 | class PluginSeasonalitySeasonality extends CommonDBTM { 35 | 36 | static $rightname = 'plugin_seasonality'; 37 | 38 | /** 39 | * functions mandatory 40 | * getTypeName(), canCreate(), canView() 41 | * */ 42 | static function getTypeName($nb=0) { 43 | return _n('Seasonality', 'Seasonalities', $nb, 'seasonality'); 44 | } 45 | 46 | /** 47 | * Show form 48 | * 49 | * @param $ID integer ID of the item 50 | * @param $options array options used 51 | */ 52 | function showForm($ID, $options=[]) { 53 | 54 | $this->initForm($ID, $options); 55 | $this->showFormHeader($options); 56 | 57 | echo ""; 58 | echo "".__('Name')." *"; 59 | echo ""; 60 | Html::autocompletionTextField($this, "name", ['value' => $this->fields['name']]); 61 | echo ""; 62 | echo ""; 63 | echo __('Urgency')." *"; 64 | echo ""; 65 | echo ""; 66 | Ticket::dropdownUrgency(['value' => $this->fields["urgency"]]); 67 | echo ""; 68 | echo ""; 69 | 70 | echo ""; 71 | echo ""; 72 | echo __('Date range', 'seasonality')." *"; 73 | echo ""; 74 | echo ""; 75 | echo ""; 76 | // Init date range 77 | $JS = "$(function() { 78 | $('#seasonality_date_range').daterangepicker({ 79 | dateFormat : '".$this->getDateFormat()."', 80 | applyButtonText : '"._sx('button', 'Post')."', 81 | clearButtonText : '".__('Clear', 'seasonality')."', 82 | cancelButtonText: '"._sx('button', 'Cancel')."', 83 | initialText : '".__('Select date range...', 'seasonality')."', 84 | datepickerOptions: { 85 | minDate: new Date(".strtotime($this->fields['begin_date']."-12 MONTH")."*1000), 86 | maxDate: new Date(".strtotime($this->fields['begin_date']."+12 MONTH")."*1000) 87 | }, 88 | presetRanges: [{ 89 | text: '".addslashes(__('Today', 'seasonality'))."', 90 | dateStart: function() { return moment() }, 91 | dateEnd: function() { return moment() } 92 | }, { 93 | text: '".addslashes(__('Tomorrow', 'seasonality'))."', 94 | dateStart: function() { return moment().add('days', 1) }, 95 | dateEnd: function() { return moment().add('days', 1) } 96 | }, { 97 | text: '".addslashes(__('Next 7 Days', 'seasonality'))."', 98 | dateStart: function() { return moment() }, 99 | dateEnd: function() { return moment().add('days', 6) } 100 | }, { 101 | text: '".addslashes(__('Next Week', 'seasonality'))."', 102 | dateStart: function() { return moment().add('weeks', 1).startOf('week') }, 103 | dateEnd: function() { return moment().add('weeks', 1).endOf('week') } 104 | }], 105 | });"; 106 | 107 | // Predefined dates 108 | if (!empty($this->fields['begin_date']) && !empty($this->fields['end_date'])) { 109 | $JS .= " var start = new Date(".strtotime($this->fields['begin_date'])."*1000); 110 | var end = new Date(".strtotime($this->fields['end_date'])."*1000); 111 | $('#seasonality_date_range').daterangepicker('setRange', {start: start, end: end});"; 112 | } 113 | 114 | $JS .= "});"; 115 | echo Html::scriptBlock($JS); 116 | echo ""; 117 | echo ""; 118 | echo __('Recurrent'); 119 | echo ""; 120 | echo ""; 121 | Dropdown::showYesNo('periodicity', $this->fields['periodicity']); 122 | echo ""; 123 | echo ""; 124 | 125 | $this->showFormButtons($options); 126 | 127 | return true; 128 | } 129 | 130 | /** 131 | * Compute next creation date of a ticket 132 | * 133 | * New parameter in version 0.84 : $calendars_id 134 | * 135 | * @param $begin_date datetime Begin date of the recurrent ticket 136 | * @param $end_date datetime End date of the recurrent ticket 137 | * @param $periodicity timestamp Periodicity of creation 138 | * @param $date_ticket datetime Date of opening the ticket 139 | * 140 | * @return datetime next creation date 141 | **/ 142 | function computeNextCreationDate($begin_date, $end_date, $periodicity, $date_ticket) { 143 | 144 | if (empty($begin_date) || ($begin_date == 'NULL')) { 145 | return 'NULL'; 146 | } 147 | 148 | $dates = [$begin_date, $end_date]; 149 | 150 | if ($periodicity > 0) { 151 | $yearDiff = date('Y', strtotime($end_date)) - date('Y', strtotime($begin_date)); 152 | $ticketYear = date('Y', strtotime($date_ticket)); 153 | $begin_date = ($ticketYear - $yearDiff) . "-" . date('m-d', strtotime($begin_date)); 154 | $end_date = ($ticketYear) . "-" . date('m-d', strtotime($end_date)); 155 | 156 | $begin_date_begin = ($ticketYear) . "-" . date('m-d', strtotime($begin_date)); 157 | $end_date_begin = ($ticketYear + $yearDiff) . "-" . date('m-d', strtotime($end_date)); 158 | 159 | if (($begin_date < $date_ticket && $date_ticket < $end_date) || ($begin_date_begin < $date_ticket && $date_ticket < $end_date_begin)) { 160 | return true; 161 | } else { 162 | return false; 163 | } 164 | }else{ 165 | if ($begin_date <= $date_ticket && $end_date >= $date_ticket) { 166 | return true; 167 | }else{ 168 | return false; 169 | } 170 | } 171 | } 172 | 173 | /** 174 | * Get date format 175 | * 176 | * @return string 177 | */ 178 | function getDateFormat(){ 179 | switch ($_SESSION['glpidate_format']) { 180 | case 1 : 181 | return 'dd-mm-yy'; 182 | case 2 : 183 | return 'mm-dd-yy'; 184 | default : 185 | return 'yy-mm-dd'; 186 | } 187 | } 188 | 189 | /** 190 | * Actions done before add 191 | * 192 | * @param type $input 193 | * @return type 194 | */ 195 | function prepareInputForAdd($input) { 196 | if (isset($input['date_range'])) { 197 | $dates = json_decode(stripslashes($input['date_range']), true); 198 | 199 | $input['begin_date'] = $dates['start']; 200 | $input['end_date'] = $dates['end']; 201 | } 202 | if (!$this->checkMandatoryFields($input)) { 203 | return false; 204 | } 205 | 206 | return $input; 207 | } 208 | 209 | /** 210 | * Actions done before update 211 | * 212 | * @param type $input 213 | * @return type 214 | */ 215 | function prepareInputForUpdate($input) { 216 | if (isset($input['date_range'])) { 217 | $dates = json_decode(stripslashes($input['date_range']), true); 218 | 219 | $input['begin_date'] = $dates['start']; 220 | $input['end_date'] = $dates['end']; 221 | } 222 | if (!$this->checkMandatoryFields($input)) { 223 | return false; 224 | } 225 | 226 | return $input; 227 | } 228 | 229 | /** 230 | * Add search options for an item 231 | * 232 | * @return array 233 | */ 234 | function rawSearchOptions(){ 235 | 236 | $tab = parent::rawSearchOptions(); 237 | 238 | $tab[] = [ 239 | 'id' => '3', 240 | 'table' => 'glpi_entities', 241 | 'field' => 'name', 242 | 'name' => __('Entity'), 243 | 'datatype' => 'dropdown' 244 | ]; 245 | 246 | $tab[] = [ 247 | 'id' => '4', 248 | 'table' => $this->getTable(), 249 | 'field' => 'is_recursive', 250 | 'name' => __('Recursive'), 251 | 'datatype' => 'bool' 252 | ]; 253 | 254 | $tab[] = [ 255 | 'id' => '5', 256 | 'table' => $this->getTable(), 257 | 'field' => 'begin_date', 258 | 'name' => __('Begin date'), 259 | 'datatype' => 'datetime' 260 | ]; 261 | 262 | $tab[] = [ 263 | 'id' => '6', 264 | 'table' => $this->getTable(), 265 | 'field' => 'end_date', 266 | 'name' => __('End date'), 267 | 'datatype' => 'datetime' 268 | ]; 269 | 270 | $tab[] = [ 271 | 'id' => '7', 272 | 'table' => 'glpi_itilcategories', 273 | 'field' => 'name', 274 | 'name' => __('Category'), 275 | 'datatype' => 'dropdown', 276 | 'forcegroupby' => true, 277 | 'massiveaction' => false, 278 | 'joinparams' => [ 279 | 'beforejoin' => [ 280 | 'table' => 'glpi_plugin_seasonality_items', 281 | 'joinparams' => [ 282 | 'jointype' => 'child' 283 | ] 284 | ] 285 | ] 286 | ]; 287 | 288 | $tab[] = [ 289 | 'id' => '8', 290 | 'table' => $this->getTable(), 291 | 'field' => 'periodicity', 292 | 'name' => __('Recurrent'), 293 | 'datatype' => 'bool' 294 | ]; 295 | 296 | $tab[] = [ 297 | 'id' => '9', 298 | 'table' => $this->getTable(), 299 | 'field' => 'urgency', 300 | 'name' => __('Urgency'), 301 | 'datatype' => 'specific', 302 | 'searchtype' => 'equals', 303 | 'massiveaction' => true 304 | ]; 305 | 306 | return $tab; 307 | } 308 | 309 | /** 310 | * @param $field 311 | * @param $name (default '') 312 | * @param $values (default '') 313 | * @param $options array 314 | * */ 315 | static function getSpecificValueToSelect($field, $name = '', $values = '', array $options = []) { 316 | 317 | if (!is_array($values)) { 318 | $values = [$field => $values]; 319 | } 320 | $options['display'] = false; 321 | 322 | switch ($field) { 323 | case 'urgency' : 324 | $options['value'] = $values[$field]; 325 | return Ticket::dropdownUrgency(['name' => $name,'value' => $values[$field], 'display' => false]); 326 | } 327 | return parent::getSpecificValueToSelect($field, $name, $values, $options); 328 | } 329 | 330 | /** 331 | * @param $field 332 | * @param $values 333 | * @param $options array 334 | * */ 335 | static function getSpecificValueToDisplay($field, $values, array $options = []) { 336 | if (!is_array($values)) { 337 | $values = [$field => $values]; 338 | } 339 | switch ($field) { 340 | case 'urgency': 341 | return Ticket::getUrgencyName($values[$field]); 342 | } 343 | return parent::getSpecificValueToDisplay($field, $values, $options); 344 | } 345 | 346 | /** 347 | * checkMandatoryFields 348 | * 349 | * @param type $input 350 | * @return boolean 351 | */ 352 | function checkMandatoryFields($input){ 353 | $msg = []; 354 | $checkKo = false; 355 | 356 | $mandatory_fields = ['end_date' => __('End date'), 357 | 'begin_date' => __('Begin date'), 358 | 'name' => __('Name'), 359 | 'urgency' => __('Urgency')]; 360 | 361 | foreach ($input as $key => $value) { 362 | if (array_key_exists($key, $mandatory_fields)) { 363 | if (empty($value)) { 364 | $msg[] = $mandatory_fields[$key]; 365 | $checkKo = true; 366 | } 367 | } 368 | } 369 | 370 | if ($checkKo) { 371 | Session::addMessageAfterRedirect(sprintf(__("Mandatory fields are not filled. Please correct: %s"), implode(', ', $msg)), true, ERROR); 372 | return false; 373 | } 374 | return true; 375 | } 376 | 377 | /** 378 | * Menu content for headers 379 | */ 380 | static function getMenuContent() { 381 | $plugin_page = PluginSeasonalitySeasonality::getSearchURL(false); 382 | $menu = []; 383 | //Menu entry in helpdesk 384 | $menu['title'] = PluginSeasonalitySeasonality::getTypeName(2); 385 | $menu['page'] = $plugin_page; 386 | $menu['links']['search'] = $plugin_page; 387 | 388 | // Main 389 | $menu['options']['seasonality']['title'] = PluginSeasonalitySeasonality::getTypeName(1); 390 | $menu['options']['seasonality']['page'] = PluginSeasonalitySeasonality::getSearchURL(false); 391 | $menu['options']['seasonality']['links']['add'] = PluginSeasonalitySeasonality::getFormURL(false); 392 | $menu['options']['seasonality']['links']['search'] = PluginSeasonalitySeasonality::getSearchURL(false); 393 | 394 | return $menu; 395 | } 396 | 397 | 398 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | 341 | -------------------------------------------------------------------------------- /inc/item.class.php: -------------------------------------------------------------------------------- 1 | . 27 | -------------------------------------------------------------------------- 28 | */ 29 | 30 | if (!defined('GLPI_ROOT')) { 31 | die("Sorry. You can't access directly to this file"); 32 | } 33 | 34 | class PluginSeasonalityItem extends CommonDBTM { 35 | 36 | static $rightname = 'plugin_seasonality'; 37 | 38 | /** 39 | * functions mandatory 40 | * getTypeName(), canCreate(), canView() 41 | * */ 42 | static function getTypeName($nb=0) { 43 | return _n('Seasonality item', 'Seasonality items', $nb, 'seasonality'); 44 | } 45 | 46 | /** 47 | * Purge item 48 | * 49 | * @param type $item 50 | */ 51 | static function purgeItem($item) { 52 | switch ($item->getType()) { 53 | case 'PluginSeasonalitySeasonality': 54 | $temp = new self(); 55 | $temp->deleteByCriteria([ 56 | 'plugin_seasonality_seasonalities_id' => $item->getField("id") 57 | ], 1); 58 | } 59 | } 60 | 61 | /** 62 | * Display tab for each tickets 63 | * 64 | * @param CommonGLPI $item 65 | * @param int $withtemplate 66 | * @return array|string 67 | */ 68 | function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) { 69 | 70 | if (!$withtemplate) { 71 | switch ($item->getType()) { 72 | case 'ITILCategory': 73 | return PluginSeasonalitySeasonality::getTypeName(2); 74 | case 'PluginSeasonalitySeasonality': 75 | return _n('Ticket category', 'Ticket categories', 2); 76 | } 77 | } 78 | 79 | return ''; 80 | } 81 | 82 | /** 83 | * Display content for each users 84 | * 85 | * @static 86 | * @param CommonGLPI $item 87 | * @param int $tabnum 88 | * @param int $withtemplate 89 | * @return bool|true 90 | */ 91 | static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0) { 92 | $seasonality = new self(); 93 | 94 | switch ($item->getType()) { 95 | case 'ITILCategory': 96 | $seasonality->showForItem($item); 97 | break; 98 | case 'PluginSeasonalitySeasonality': 99 | $seasonality->showForSeasonality($item); 100 | break; 101 | } 102 | 103 | return true; 104 | } 105 | 106 | /** 107 | * Show for item 108 | * 109 | * @param $ID integer ID of the item 110 | * @param $options array options used 111 | */ 112 | function showForItem(ITILCategory $item) { 113 | 114 | $canedit = $item->can($item->fields['id'], UPDATE) && $this->canCreate(); 115 | 116 | $plugin_seasonality_seasonalities_id = 0; 117 | 118 | $data = $this->getItemsForCategory($item->fields['id']); 119 | $used = []; 120 | foreach ($data as $val) { 121 | $used[] = $val['plugin_seasonality_seasonalities_id']; 122 | } 123 | 124 | echo ""; 125 | echo "
    "; 126 | echo ""; 127 | echo ""; 128 | echo ""; 129 | echo ""; 134 | echo ""; 135 | 136 | if ($canedit) { 137 | echo ""; 138 | echo ""; 141 | echo ""; 142 | } 143 | echo "
    ".PluginSeasonalitySeasonality::getTypeName(1)."
    ".PluginSeasonalitySeasonality::getTypeName(1)." *"; 130 | Dropdown::show('PluginSeasonalitySeasonality', ['used' => $used, 131 | 'entity' => $item->getField('entities_id'), 132 | 'entity_sons' => $item->getField('is_recursive')]); 133 | echo "
    "; 139 | echo ""; 140 | echo "
    "; 144 | 145 | echo ""; 146 | 147 | Html::closeForm(); 148 | 149 | // Show seasonality list 150 | $this->showSeasonalityList($item); 151 | 152 | return true; 153 | } 154 | 155 | /** 156 | * Show for category add for seasonaltity 157 | * 158 | * @param $ID integer ID of the item 159 | * @param $options array options used 160 | */ 161 | function showForSeasonality(PluginSeasonalitySeasonality $seasonality) { 162 | 163 | $canedit = $seasonality->can($seasonality->fields['id'], UPDATE) && $this->canCreate(); 164 | 165 | $data = $this->getItems($seasonality->fields['id']); 166 | $used = []; 167 | foreach ($data as $val) { 168 | $used[] = $val['itilcategories_id']; 169 | } 170 | 171 | echo ""; 172 | echo "
    "; 173 | echo ""; 174 | echo ""; 175 | echo ""; 176 | echo ""; 181 | echo ""; 182 | 183 | if ($canedit) { 184 | echo ""; 185 | echo ""; 189 | echo ""; 190 | } 191 | echo "
    ".__('Add a category', 'seasonality')."
    ".__('Category')." *"; 177 | Dropdown::show('ITILCategory', ['entity' => $seasonality->getField('entities_id'), 178 | 'entity_sons' => $seasonality->getField('is_recursive'), 179 | 'used' => $used]); 180 | echo "
    "; 186 | echo ""; 187 | echo ""; 188 | echo "
    "; 192 | 193 | Html::closeForm(); 194 | 195 | // Show category list 196 | $this->showCategoryList($seasonality); 197 | 198 | return true; 199 | } 200 | 201 | /** 202 | * Function show categories for item 203 | * 204 | * @param type $item 205 | * @return boolean 206 | */ 207 | function showCategoryList(PluginSeasonalitySeasonality $item) { 208 | 209 | $canedit = ($item->can($item->fields['id'], UPDATE) && $this->canCreate()); 210 | 211 | $rand = mt_rand(); 212 | 213 | if (isset($_GET["start"])) { 214 | $start = $_GET["start"]; 215 | } else { 216 | $start = 0; 217 | } 218 | $data = $this->getItems($item->fields['id'], $start); 219 | 220 | if (!empty($data)) { 221 | echo "
    "; 222 | if ($canedit) { 223 | Html::openMassiveActionsForm('mass'.__CLASS__.$rand); 224 | $massiveactionparams = ['item' => __CLASS__, 'container' => 'mass'.__CLASS__.$rand]; 225 | Html::showMassiveActions($massiveactionparams); 226 | } 227 | $dbu = new DbUtils(); 228 | Html::printAjaxPager(__('Category'), $start, 229 | $dbu->countElementsInTable($this->getTable(), 230 | ["plugin_seasonality_seasonalities_id" => $item->fields['id']])); 231 | echo ""; 232 | echo ""; 233 | echo ""; 238 | echo ""; 239 | echo ""; 240 | 241 | foreach ($data as $field) { 242 | echo ""; 243 | echo ""; 248 | // Data 249 | $item = new ITILCategory(); 250 | $item->getFromDB($field['itilcategories_id']); 251 | echo ""; 252 | echo ""; 253 | } 254 | echo "
    "; 234 | if ($canedit) { 235 | echo Html::getCheckAllAsCheckbox('mass'.__CLASS__.$rand); 236 | } 237 | echo "".__('Name')."
    "; 244 | if ($canedit) { 245 | Html::showMassiveActionCheckBox(__CLASS__, $field['id']); 246 | } 247 | echo "".$item->getLink()."
    "; 255 | if ($canedit) { 256 | $massiveactionparams['ontop'] = false; 257 | Html::showMassiveActions($massiveactionparams); 258 | Html::closeForm(); 259 | } 260 | echo "
    "; 261 | } 262 | } 263 | 264 | /** 265 | * Function show seasonality for item 266 | * 267 | * @param type $item 268 | * @return boolean 269 | */ 270 | function showSeasonalityList(ITILCategory $item) { 271 | 272 | $canedit = ($item->can($item->fields['id'], UPDATE) && $this->canCreate()); 273 | 274 | $rand = mt_rand(); 275 | 276 | if (isset($_GET["start"])) { 277 | $start = $_GET["start"]; 278 | } else { 279 | $start = 0; 280 | } 281 | $data = $this->getItemsForCategory($item->fields['id'], $start); 282 | 283 | if (!empty($data)) { 284 | echo "
    "; 285 | if ($canedit) { 286 | Html::openMassiveActionsForm('mass'.__CLASS__.$rand); 287 | $massiveactionparams = ['item' => __CLASS__, 'container' => 'mass'.__CLASS__.$rand]; 288 | Html::showMassiveActions($massiveactionparams); 289 | } 290 | 291 | $dbu = new DbUtils(); 292 | Html::printAjaxPager(PluginSeasonalitySeasonality::getTypeName(2), 293 | $start, 294 | $dbu->countElementsInTable($this->getTable(), 295 | ["itilcategories_id" => $item->fields['id']])); 296 | echo ""; 297 | echo ""; 298 | echo ""; 303 | echo ""; 304 | echo ""; 305 | echo ""; 306 | echo ""; 307 | echo ""; 308 | echo ""; 309 | 310 | foreach ($data as $field) { 311 | echo ""; 312 | echo ""; 317 | // Data 318 | $item = new PluginSeasonalitySeasonality(); 319 | $item->getFromDB($field['plugin_seasonality_seasonalities_id']); 320 | echo ""; 321 | echo ""; 322 | echo ""; 323 | echo ""; 324 | echo ""; 325 | echo ""; 326 | } 327 | echo "
    "; 299 | if ($canedit) { 300 | echo Html::getCheckAllAsCheckbox('mass'.__CLASS__.$rand); 301 | } 302 | echo "".__('Name')."".__('Urgency')."".__('Begin date')."".__('End date')."".__('Recurrent')."
    "; 313 | if ($canedit) { 314 | Html::showMassiveActionCheckBox(__CLASS__, $field['assocID']); 315 | } 316 | echo "".$item->getLink()."".Ticket::getUrgencyName($field["urgency"])."".Html::convDate($field['begin_date'])."".Html::convDate($field['end_date'])."".Dropdown::getYesNo($field['periodicity'])."
    "; 328 | if ($canedit) { 329 | $massiveactionparams['ontop'] = false; 330 | Html::showMassiveActions($massiveactionparams); 331 | Html::closeForm(); 332 | } 333 | echo "
    "; 334 | } 335 | } 336 | 337 | /** 338 | * Function get items 339 | * 340 | * @global type $DB 341 | * @param type $id 342 | * @param type $start 343 | * @return type 344 | */ 345 | function getItems($id, $start=0){ 346 | global $DB; 347 | 348 | $output = []; 349 | 350 | $query = "SELECT `".$this->getTable()."`.* 351 | FROM ".$this->getTable()." 352 | WHERE `".$this->getTable()."`.`plugin_seasonality_seasonalities_id` = '".Toolbox::cleanInteger($id)."' 353 | LIMIT ".intval($start).",".intval($_SESSION['glpilist_limit']); 354 | 355 | $result = $DB->query($query); 356 | if ($DB->numrows($result)) { 357 | while ($data = $DB->fetch_assoc($result)) { 358 | $output[$data['id']] = $data; 359 | } 360 | } 361 | 362 | return $output; 363 | } 364 | 365 | /** 366 | * Function get items 367 | * 368 | * @global type $DB 369 | * @param type $id 370 | * @param type $start 371 | * @param type $condition 372 | * @return type 373 | */ 374 | function getItemsForCategory($id, $start=0, $condition='1'){ 375 | global $DB; 376 | 377 | $output = []; 378 | 379 | $query = "SELECT `".$this->getTable()."`.*, 380 | `".$this->getTable()."`.id as assocID, 381 | `glpi_plugin_seasonality_seasonalities`.* 382 | FROM ".$this->getTable()." 383 | LEFT JOIN `glpi_plugin_seasonality_seasonalities` 384 | ON (`glpi_plugin_seasonality_seasonalities`.`id` = `".$this->getTable()."`.`plugin_seasonality_seasonalities_id`) 385 | WHERE `".$this->getTable()."`.`itilcategories_id` = '".Toolbox::cleanInteger($id)."' 386 | AND $condition 387 | LIMIT ".intval($start).",".intval($_SESSION['glpilist_limit']); 388 | 389 | $result = $DB->query($query); 390 | if ($DB->numrows($result)) { 391 | while ($data = $DB->fetch_assoc($result)) { 392 | $output[$data['id']] = $data; 393 | } 394 | } 395 | 396 | return $output; 397 | } 398 | 399 | 400 | /** 401 | * Get urgency from ticket category 402 | * 403 | * @param type $itilcategories_id 404 | * @param type $tickets_id 405 | * @param type $date 406 | * @param type $type 407 | * @param type $entities_id 408 | * @return type 409 | */ 410 | function getUrgencyFromCategory($itilcategories_id, $tickets_id, $date, $type, $entities_id) { 411 | 412 | // Default values 413 | $error = 1; 414 | $urgency_name = null; 415 | $urgency_id = 0; 416 | $seasonalities_link = null; 417 | $seasonality = new PluginSeasonalitySeasonality(); 418 | $default_urgency = 3; 419 | $default_impact = 3; 420 | $default_priority = Ticket::computePriority(3, 3); 421 | 422 | $ticket = new Ticket(); 423 | 424 | // If template load urgency, DO NOT load seasonality 425 | if ($tickets_id > 0) { 426 | $ticket->getFromDB($tickets_id); 427 | $tt = $ticket->getTicketTemplateToUse(0, $ticket->fields['type'], $ticket->fields['itilcategories_id'], $ticket->fields['entities_id']); 428 | } else { 429 | $tt = $ticket->getTicketTemplateToUse(0, $type, $itilcategories_id, $entities_id); 430 | } 431 | if (isset($tt->predefined) && count($tt->predefined)) { 432 | if (isset($tt->predefined['impact'])) { 433 | $default_impact = $tt->predefined['impact']; 434 | } 435 | if (isset($tt->predefined['urgency'])) { 436 | $default_urgency = $tt->predefined['urgency']; 437 | } 438 | if (isset($tt->predefined['priority'])) { 439 | $default_priority = $tt->predefined['priority']; 440 | } 441 | } 442 | 443 | if (empty($date)) { 444 | $date = date('Y-m-d'); 445 | } 446 | 447 | // Load ticket values if possible 448 | if ($tickets_id > 0) { 449 | if ($itilcategories_id == 0) { 450 | $itilcategories_id = $ticket->fields['itilcategories_id']; 451 | } 452 | if (empty($date)) { 453 | $date = $ticket->fields['date']; 454 | } 455 | if (isset($ticket->fields['urgency'])) { 456 | $default_urgency = $ticket->fields['urgency']; 457 | } 458 | if (isset($ticket->fields['impact'])) { 459 | $default_impact = $ticket->fields['impact']; 460 | } 461 | if (isset($ticket->fields['priority'])) { 462 | $default_priority = $ticket->fields['priority']; 463 | } 464 | } 465 | 466 | // Find correct seasonality for category 467 | $datas = $this->getItemsForCategory($itilcategories_id); 468 | if (!empty($datas)) { 469 | foreach($datas as $data){ 470 | if ($seasonality->computeNextCreationDate($data['begin_date'], $data['end_date'], $data['periodicity'], $date)) { 471 | $urgency_name = Ticket::getUrgencyName($data["urgency"]); 472 | $urgency_id = $data["urgency"]; 473 | 474 | $seasonality->getFromDB($data["plugin_seasonality_seasonalities_id"]); 475 | if ($_SESSION['glpiactiveprofile']['interface'] == 'central' && self::canUpdate()) { 476 | $seasonalities_link = ""; 477 | } else { 478 | $seasonalities_link = ""; 479 | } 480 | $error = 0; 481 | break; 482 | } 483 | } 484 | } 485 | 486 | return ['error' => $error, 487 | 'template' => 0, 488 | 'seasonalities_link' => $seasonalities_link, 489 | 'urgency_id' => $urgency_id, 490 | 'urgency_name' => $urgency_name, 491 | 'default_urgency' => $default_urgency, 492 | 'default_impact' => $default_impact, 493 | 'default_priority' => $default_priority]; 494 | } 495 | 496 | /** 497 | * Show form 498 | * 499 | * @param $ID integer ID of the item 500 | * @param $options array options used 501 | */ 502 | function showForm($ID=0, $options=[]){ 503 | $this->getFromDB($ID); 504 | 505 | $seasonality = new PluginSeasonalitySeasonality(); 506 | $seasonality->showForm($this->getID(), $options); 507 | } 508 | 509 | /** 510 | * Actions done before add 511 | * 512 | * @param type $input 513 | * @return type 514 | */ 515 | function prepareInputForAdd($input) { 516 | if (!$this->checkMandatoryFields($input) || !$this->checkItemDate($input)) { 517 | return false; 518 | } 519 | 520 | return $input; 521 | } 522 | 523 | 524 | 525 | /** 526 | * Actions done before update 527 | * 528 | * @param type $input 529 | * @return type 530 | */ 531 | function prepareInputForUpdate($input) { 532 | if (!$this->checkMandatoryFields($input)) { 533 | return false; 534 | } 535 | 536 | return $input; 537 | } 538 | 539 | /** 540 | * Add search options for an item 541 | * 542 | * @return array 543 | */ 544 | function getAddSearchOptions(){ 545 | $tab = []; 546 | 547 | $tab[] = [ 548 | 'id' => '180', 549 | 'table'=> 'glpi_plugin_seasonality_seasonalities', 550 | 'field' => 'name', 551 | 'name' => self::getTypeName(2), 552 | 'datatype' => 'dropdown', 553 | 'forcegroupby' => true, 554 | 'massiveaction' => false, 555 | 'displaytype' => 'relation', 556 | 'relationclass' => 'PluginSeasonalityItem', 557 | 'joinparams' => ['beforejoin' 558 | => ['table' => 'glpi_plugin_seasonality_items', 559 | 'joinparams' => ['jointype' => 'child']]] 560 | ]; 561 | 562 | return $tab; 563 | } 564 | 565 | /** 566 | * Add search options for an item 567 | * 568 | * @return array 569 | */ 570 | function rawSearchOptions(){ 571 | $tab = []; 572 | 573 | $tab[] = [ 574 | 'id' => '3', 575 | 'table' => $this->getTable(), 576 | 'field' => 'itilcategories_id', 577 | 'name' => 'Itilcategories id', 578 | 'datatype' => 'dropdown', 579 | 'massiveaction' => false, 580 | 'search' => false 581 | ]; 582 | $tab[] = [ 583 | 'id' => '4', 584 | 'table' => $this->getTable(), 585 | 'field' => 'plugin_seasonality_seasonalities_id', 586 | 'name' => 'Seasonalities id', 587 | 'datatype' => 'dropdown', 588 | 'massiveaction' => false, 589 | 'search' => false 590 | ]; 591 | 592 | return $tab; 593 | } 594 | 595 | /** 596 | * Massive actions to be added 597 | * 598 | * @param $input array of input datas 599 | * 600 | * @return array of results (nbok, nbko, nbnoright counts) 601 | **/ 602 | function massiveActions($type){ 603 | 604 | $prefix = $this->getType().MassiveAction::CLASS_ACTION_SEPARATOR; 605 | 606 | switch ($type) { 607 | case "ITILCategory": 608 | $output = []; 609 | if ($this->canCreate()) { 610 | $output = [ 611 | $prefix."add_seasonality" => __('Add seasonality', 'seasonality'), 612 | $prefix."delete_seasonality" => __('Delete seasonality', 'seasonality') 613 | ]; 614 | } 615 | return $output; 616 | } 617 | } 618 | 619 | /** 620 | * Massive actions display 621 | * 622 | * @param $input array of input datas 623 | * 624 | * @return array of results (nbok, nbko, nbnoright counts) 625 | * */ 626 | static function showMassiveActionsSubForm(MassiveAction $ma) { 627 | 628 | $itemtype = $ma->getItemtype(false); 629 | $seasonality = new PluginSeasonalitySeasonality(); 630 | 631 | switch ($itemtype) { 632 | case 'ITILCategory': 633 | switch ($ma->getAction()) { 634 | case "add_seasonality": 635 | if ($seasonality->canUpdate()){ 636 | Dropdown::show('PluginSeasonalitySeasonality', ['entity' => $_SESSION['glpiactiveentities']]); 637 | echo "

    "; 638 | } 639 | break; 640 | 641 | case "delete_seasonality": 642 | if ($seasonality->canUpdate()){ 643 | Dropdown::show('PluginSeasonalitySeasonality', ['entity' => $_SESSION['glpiactiveentities']]); 644 | echo "

    "; 645 | } 646 | break; 647 | } 648 | return parent::showMassiveActionsSubForm($ma); 649 | } 650 | } 651 | 652 | /** 653 | * @since version 0.85 654 | * 655 | * @see CommonDBTM::processMassiveActionsForOneItemtype() 656 | **/ 657 | static function processMassiveActionsForOneItemtype(MassiveAction $ma, CommonDBTM $item, 658 | array $ids) { 659 | $input = $ma->getInput(); 660 | $seasonalityItem = new self(); 661 | 662 | foreach ($ids as $key => $val) { 663 | if ($item->can($key, UPDATE) && $seasonalityItem->canUpdate()) { 664 | $result = false; 665 | switch ($ma->getAction()) { 666 | case "add_seasonality": 667 | if ($key) { 668 | $result = $seasonalityItem->add(['plugin_seasonality_seasonalities_id' => $input['plugin_seasonality_seasonalities_id'], 669 | 'itilcategories_id' => $val]); 670 | } 671 | break; 672 | 673 | case "delete_seasonality": 674 | if ($key) { 675 | $result = $seasonalityItem->deleteByCriteria(['plugin_seasonality_seasonalities_id' => $input['plugin_seasonality_seasonalities_id'], 676 | 'itilcategories_id' => $val]); 677 | } 678 | break; 679 | 680 | default : 681 | return parent::doSpecificMassiveActions($ma->POST); 682 | } 683 | 684 | if ($result) { 685 | $ma->itemDone($item->getType(), $key, MassiveAction::ACTION_OK); 686 | } else { 687 | $ma->itemDone($item->getType(), $key, MassiveAction::ACTION_KO); 688 | $ma->addMessage($item->getErrorMessage(ERROR_ON_ACTION)); 689 | } 690 | 691 | } else { 692 | $ma->itemDone($item->getType(), $key, MassiveAction::ACTION_NORIGHT); 693 | $ma->addMessage($item->getErrorMessage(ERROR_RIGHT)); 694 | } 695 | } 696 | } 697 | 698 | 699 | /** 700 | * Forbidden massive actions 701 | * 702 | * @return array 703 | */ 704 | function getForbiddenStandardMassiveAction() { 705 | 706 | $forbidden = parent::getForbiddenStandardMassiveAction(); 707 | $forbidden[] = 'update'; 708 | 709 | return $forbidden; 710 | } 711 | 712 | 713 | /** 714 | * checkMandatoryFields 715 | * 716 | * @param type $input 717 | * @return boolean 718 | */ 719 | function checkMandatoryFields($input){ 720 | $msg = []; 721 | $checkKo = false; 722 | 723 | $mandatory_fields = ['itilcategories_id' => __('Category'), 724 | 'plugin_seasonality_seasonalities_id' => _n('Seasonality', 'Seasonalities', 1, 'seasonality')]; 725 | 726 | foreach ($input as $key => $value) { 727 | if (array_key_exists($key, $mandatory_fields)) { 728 | if (empty($value)) { 729 | $msg[] = $mandatory_fields[$key]; 730 | $checkKo = true; 731 | } 732 | } 733 | } 734 | 735 | if ($checkKo) { 736 | Session::addMessageAfterRedirect(sprintf(__("Mandatory fields are not filled. Please correct: %s"), implode(', ', $msg)), true, ERROR); 737 | return false; 738 | } 739 | return true; 740 | } 741 | 742 | 743 | /** 744 | * Check if items have not same dates 745 | * 746 | * @param type $input 747 | * @return type 748 | */ 749 | function checkItemDate($input) { 750 | $datas = $this->getItemsForCategory($input['itilcategories_id']); 751 | 752 | $seasonality = new PluginSeasonalitySeasonality(); 753 | $seasonality->getFromDB($input['plugin_seasonality_seasonalities_id']); 754 | 755 | foreach ($datas as $data) { 756 | if($seasonality->fields['begin_date'] >= $data['begin_date'] && $seasonality->fields['end_date'] <= $data['end_date']){ 757 | Session::addMessageAfterRedirect(__("Cannot add two seasonalities with same date interval", "seasonality"), true, ERROR); 758 | return false; 759 | } 760 | } 761 | 762 | return true; 763 | } 764 | 765 | } -------------------------------------------------------------------------------- /lib/daterangepicker/moment.min.js: -------------------------------------------------------------------------------- 1 | //! moment.js 2 | //! version : 2.10.3 3 | //! authors : Tim Wood, Iskren Chernev, Moment.js contributors 4 | //! license : MIT 5 | //! momentjs.com 6 | !function(a,b){"object"==typeof exports&&"undefined"!=typeof module?module.exports=b():"function"==typeof define&&define.amd?define(b):a.moment=b()}(this,function(){"use strict";function a(){return Dc.apply(null,arguments)}function b(a){Dc=a}function c(a){return"[object Array]"===Object.prototype.toString.call(a)}function d(a){return a instanceof Date||"[object Date]"===Object.prototype.toString.call(a)}function e(a,b){var c,d=[];for(c=0;c0)for(c in Fc)d=Fc[c],e=b[d],"undefined"!=typeof e&&(a[d]=e);return a}function n(b){m(this,b),this._d=new Date(+b._d),Gc===!1&&(Gc=!0,a.updateOffset(this),Gc=!1)}function o(a){return a instanceof n||null!=a&&null!=a._isAMomentObject}function p(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=b>=0?Math.floor(b):Math.ceil(b)),c}function q(a,b,c){var d,e=Math.min(a.length,b.length),f=Math.abs(a.length-b.length),g=0;for(d=0;e>d;d++)(c&&a[d]!==b[d]||!c&&p(a[d])!==p(b[d]))&&g++;return g+f}function r(){}function s(a){return a?a.toLowerCase().replace("_","-"):a}function t(a){for(var b,c,d,e,f=0;f0;){if(d=u(e.slice(0,b).join("-")))return d;if(c&&c.length>=b&&q(e,c,!0)>=b-1)break;b--}f++}return null}function u(a){var b=null;if(!Hc[a]&&"undefined"!=typeof module&&module&&module.exports)try{b=Ec._abbr,require("./locale/"+a),v(b)}catch(c){}return Hc[a]}function v(a,b){var c;return a&&(c="undefined"==typeof b?x(a):w(a,b),c&&(Ec=c)),Ec._abbr}function w(a,b){return null!==b?(b.abbr=a,Hc[a]||(Hc[a]=new r),Hc[a].set(b),v(a),Hc[a]):(delete Hc[a],null)}function x(a){var b;if(a&&a._locale&&a._locale._abbr&&(a=a._locale._abbr),!a)return Ec;if(!c(a)){if(b=u(a))return b;a=[a]}return t(a)}function y(a,b){var c=a.toLowerCase();Ic[c]=Ic[c+"s"]=Ic[b]=a}function z(a){return"string"==typeof a?Ic[a]||Ic[a.toLowerCase()]:void 0}function A(a){var b,c,d={};for(c in a)f(a,c)&&(b=z(c),b&&(d[b]=a[c]));return d}function B(b,c){return function(d){return null!=d?(D(this,b,d),a.updateOffset(this,c),this):C(this,b)}}function C(a,b){return a._d["get"+(a._isUTC?"UTC":"")+b]()}function D(a,b,c){return a._d["set"+(a._isUTC?"UTC":"")+b](c)}function E(a,b){var c;if("object"==typeof a)for(c in a)this.set(c,a[c]);else if(a=z(a),"function"==typeof this[a])return this[a](b);return this}function F(a,b,c){for(var d=""+Math.abs(a),e=a>=0;d.lengthb;b++)Mc[d[b]]?d[b]=Mc[d[b]]:d[b]=H(d[b]);return function(e){var f="";for(b=0;c>b;b++)f+=d[b]instanceof Function?d[b].call(e,a):d[b];return f}}function J(a,b){return a.isValid()?(b=K(b,a.localeData()),Lc[b]||(Lc[b]=I(b)),Lc[b](a)):a.localeData().invalidDate()}function K(a,b){function c(a){return b.longDateFormat(a)||a}var d=5;for(Kc.lastIndex=0;d>=0&&Kc.test(a);)a=a.replace(Kc,c),Kc.lastIndex=0,d-=1;return a}function L(a,b,c){_c[a]="function"==typeof b?b:function(a){return a&&c?c:b}}function M(a,b){return f(_c,a)?_c[a](b._strict,b._locale):new RegExp(N(a))}function N(a){return a.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e}).replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function O(a,b){var c,d=b;for("string"==typeof a&&(a=[a]),"number"==typeof b&&(d=function(a,c){c[b]=p(a)}),c=0;cd;d++){if(e=h([2e3,d]),c&&!this._longMonthsParse[d]&&(this._longMonthsParse[d]=new RegExp("^"+this.months(e,"").replace(".","")+"$","i"),this._shortMonthsParse[d]=new RegExp("^"+this.monthsShort(e,"").replace(".","")+"$","i")),c||this._monthsParse[d]||(f="^"+this.months(e,"")+"|^"+this.monthsShort(e,""),this._monthsParse[d]=new RegExp(f.replace(".",""),"i")),c&&"MMMM"===b&&this._longMonthsParse[d].test(a))return d;if(c&&"MMM"===b&&this._shortMonthsParse[d].test(a))return d;if(!c&&this._monthsParse[d].test(a))return d}}function V(a,b){var c;return"string"==typeof b&&(b=a.localeData().monthsParse(b),"number"!=typeof b)?a:(c=Math.min(a.date(),R(a.year(),b)),a._d["set"+(a._isUTC?"UTC":"")+"Month"](b,c),a)}function W(b){return null!=b?(V(this,b),a.updateOffset(this,!0),this):C(this,"Month")}function X(){return R(this.year(),this.month())}function Y(a){var b,c=a._a;return c&&-2===j(a).overflow&&(b=c[cd]<0||c[cd]>11?cd:c[dd]<1||c[dd]>R(c[bd],c[cd])?dd:c[ed]<0||c[ed]>24||24===c[ed]&&(0!==c[fd]||0!==c[gd]||0!==c[hd])?ed:c[fd]<0||c[fd]>59?fd:c[gd]<0||c[gd]>59?gd:c[hd]<0||c[hd]>999?hd:-1,j(a)._overflowDayOfYear&&(bd>b||b>dd)&&(b=dd),j(a).overflow=b),a}function Z(b){a.suppressDeprecationWarnings===!1&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+b)}function $(a,b){var c=!0,d=a+"\n"+(new Error).stack;return g(function(){return c&&(Z(d),c=!1),b.apply(this,arguments)},b)}function _(a,b){kd[a]||(Z(b),kd[a]=!0)}function aa(a){var b,c,d=a._i,e=ld.exec(d);if(e){for(j(a).iso=!0,b=0,c=md.length;c>b;b++)if(md[b][1].exec(d)){a._f=md[b][0]+(e[6]||" ");break}for(b=0,c=nd.length;c>b;b++)if(nd[b][1].exec(d)){a._f+=nd[b][0];break}d.match(Yc)&&(a._f+="Z"),ta(a)}else a._isValid=!1}function ba(b){var c=od.exec(b._i);return null!==c?void(b._d=new Date(+c[1])):(aa(b),void(b._isValid===!1&&(delete b._isValid,a.createFromInputFallback(b))))}function ca(a,b,c,d,e,f,g){var h=new Date(a,b,c,d,e,f,g);return 1970>a&&h.setFullYear(a),h}function da(a){var b=new Date(Date.UTC.apply(null,arguments));return 1970>a&&b.setUTCFullYear(a),b}function ea(a){return fa(a)?366:365}function fa(a){return a%4===0&&a%100!==0||a%400===0}function ga(){return fa(this.year())}function ha(a,b,c){var d,e=c-b,f=c-a.day();return f>e&&(f-=7),e-7>f&&(f+=7),d=Aa(a).add(f,"d"),{week:Math.ceil(d.dayOfYear()/7),year:d.year()}}function ia(a){return ha(a,this._week.dow,this._week.doy).week}function ja(){return this._week.dow}function ka(){return this._week.doy}function la(a){var b=this.localeData().week(this);return null==a?b:this.add(7*(a-b),"d")}function ma(a){var b=ha(this,1,4).week;return null==a?b:this.add(7*(a-b),"d")}function na(a,b,c,d,e){var f,g,h=da(a,0,1).getUTCDay();return h=0===h?7:h,c=null!=c?c:e,f=e-h+(h>d?7:0)-(e>h?7:0),g=7*(b-1)+(c-e)+f+1,{year:g>0?a:a-1,dayOfYear:g>0?g:ea(a-1)+g}}function oa(a){var b=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==a?b:this.add(a-b,"d")}function pa(a,b,c){return null!=a?a:null!=b?b:c}function qa(a){var b=new Date;return a._useUTC?[b.getUTCFullYear(),b.getUTCMonth(),b.getUTCDate()]:[b.getFullYear(),b.getMonth(),b.getDate()]}function ra(a){var b,c,d,e,f=[];if(!a._d){for(d=qa(a),a._w&&null==a._a[dd]&&null==a._a[cd]&&sa(a),a._dayOfYear&&(e=pa(a._a[bd],d[bd]),a._dayOfYear>ea(e)&&(j(a)._overflowDayOfYear=!0),c=da(e,0,a._dayOfYear),a._a[cd]=c.getUTCMonth(),a._a[dd]=c.getUTCDate()),b=0;3>b&&null==a._a[b];++b)a._a[b]=f[b]=d[b];for(;7>b;b++)a._a[b]=f[b]=null==a._a[b]?2===b?1:0:a._a[b];24===a._a[ed]&&0===a._a[fd]&&0===a._a[gd]&&0===a._a[hd]&&(a._nextDay=!0,a._a[ed]=0),a._d=(a._useUTC?da:ca).apply(null,f),null!=a._tzm&&a._d.setUTCMinutes(a._d.getUTCMinutes()-a._tzm),a._nextDay&&(a._a[ed]=24)}}function sa(a){var b,c,d,e,f,g,h;b=a._w,null!=b.GG||null!=b.W||null!=b.E?(f=1,g=4,c=pa(b.GG,a._a[bd],ha(Aa(),1,4).year),d=pa(b.W,1),e=pa(b.E,1)):(f=a._locale._week.dow,g=a._locale._week.doy,c=pa(b.gg,a._a[bd],ha(Aa(),f,g).year),d=pa(b.w,1),null!=b.d?(e=b.d,f>e&&++d):e=null!=b.e?b.e+f:f),h=na(c,d,e,g,f),a._a[bd]=h.year,a._dayOfYear=h.dayOfYear}function ta(b){if(b._f===a.ISO_8601)return void aa(b);b._a=[],j(b).empty=!0;var c,d,e,f,g,h=""+b._i,i=h.length,k=0;for(e=K(b._f,b._locale).match(Jc)||[],c=0;c0&&j(b).unusedInput.push(g),h=h.slice(h.indexOf(d)+d.length),k+=d.length),Mc[f]?(d?j(b).empty=!1:j(b).unusedTokens.push(f),Q(f,d,b)):b._strict&&!d&&j(b).unusedTokens.push(f);j(b).charsLeftOver=i-k,h.length>0&&j(b).unusedInput.push(h),j(b).bigHour===!0&&b._a[ed]<=12&&b._a[ed]>0&&(j(b).bigHour=void 0),b._a[ed]=ua(b._locale,b._a[ed],b._meridiem),ra(b),Y(b)}function ua(a,b,c){var d;return null==c?b:null!=a.meridiemHour?a.meridiemHour(b,c):null!=a.isPM?(d=a.isPM(c),d&&12>b&&(b+=12),d||12!==b||(b=0),b):b}function va(a){var b,c,d,e,f;if(0===a._f.length)return j(a).invalidFormat=!0,void(a._d=new Date(0/0));for(e=0;ef)&&(d=f,c=b));g(a,c||b)}function wa(a){if(!a._d){var b=A(a._i);a._a=[b.year,b.month,b.day||b.date,b.hour,b.minute,b.second,b.millisecond],ra(a)}}function xa(a){var b,e=a._i,f=a._f;return a._locale=a._locale||x(a._l),null===e||void 0===f&&""===e?l({nullInput:!0}):("string"==typeof e&&(a._i=e=a._locale.preparse(e)),o(e)?new n(Y(e)):(c(f)?va(a):f?ta(a):d(e)?a._d=e:ya(a),b=new n(Y(a)),b._nextDay&&(b.add(1,"d"),b._nextDay=void 0),b))}function ya(b){var f=b._i;void 0===f?b._d=new Date:d(f)?b._d=new Date(+f):"string"==typeof f?ba(b):c(f)?(b._a=e(f.slice(0),function(a){return parseInt(a,10)}),ra(b)):"object"==typeof f?wa(b):"number"==typeof f?b._d=new Date(f):a.createFromInputFallback(b)}function za(a,b,c,d,e){var f={};return"boolean"==typeof c&&(d=c,c=void 0),f._isAMomentObject=!0,f._useUTC=f._isUTC=e,f._l=c,f._i=a,f._f=b,f._strict=d,xa(f)}function Aa(a,b,c,d){return za(a,b,c,d,!1)}function Ba(a,b){var d,e;if(1===b.length&&c(b[0])&&(b=b[0]),!b.length)return Aa();for(d=b[0],e=1;ea&&(a=-a,c="-"),c+F(~~(a/60),2)+b+F(~~a%60,2)})}function Ha(a){var b=(a||"").match(Yc)||[],c=b[b.length-1]||[],d=(c+"").match(td)||["-",0,0],e=+(60*d[1])+p(d[2]);return"+"===d[0]?e:-e}function Ia(b,c){var e,f;return c._isUTC?(e=c.clone(),f=(o(b)||d(b)?+b:+Aa(b))-+e,e._d.setTime(+e._d+f),a.updateOffset(e,!1),e):Aa(b).local();return c._isUTC?Aa(b).zone(c._offset||0):Aa(b).local()}function Ja(a){return 15*-Math.round(a._d.getTimezoneOffset()/15)}function Ka(b,c){var d,e=this._offset||0;return null!=b?("string"==typeof b&&(b=Ha(b)),Math.abs(b)<16&&(b=60*b),!this._isUTC&&c&&(d=Ja(this)),this._offset=b,this._isUTC=!0,null!=d&&this.add(d,"m"),e!==b&&(!c||this._changeInProgress?$a(this,Va(b-e,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,a.updateOffset(this,!0),this._changeInProgress=null)),this):this._isUTC?e:Ja(this)}function La(a,b){return null!=a?("string"!=typeof a&&(a=-a),this.utcOffset(a,b),this):-this.utcOffset()}function Ma(a){return this.utcOffset(0,a)}function Na(a){return this._isUTC&&(this.utcOffset(0,a),this._isUTC=!1,a&&this.subtract(Ja(this),"m")),this}function Oa(){return this._tzm?this.utcOffset(this._tzm):"string"==typeof this._i&&this.utcOffset(Ha(this._i)),this}function Pa(a){return a=a?Aa(a).utcOffset():0,(this.utcOffset()-a)%60===0}function Qa(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Ra(){if(this._a){var a=this._isUTC?h(this._a):Aa(this._a);return this.isValid()&&q(this._a,a.toArray())>0}return!1}function Sa(){return!this._isUTC}function Ta(){return this._isUTC}function Ua(){return this._isUTC&&0===this._offset}function Va(a,b){var c,d,e,g=a,h=null;return Fa(a)?g={ms:a._milliseconds,d:a._days,M:a._months}:"number"==typeof a?(g={},b?g[b]=a:g.milliseconds=a):(h=ud.exec(a))?(c="-"===h[1]?-1:1,g={y:0,d:p(h[dd])*c,h:p(h[ed])*c,m:p(h[fd])*c,s:p(h[gd])*c,ms:p(h[hd])*c}):(h=vd.exec(a))?(c="-"===h[1]?-1:1,g={y:Wa(h[2],c),M:Wa(h[3],c),d:Wa(h[4],c),h:Wa(h[5],c),m:Wa(h[6],c),s:Wa(h[7],c),w:Wa(h[8],c)}):null==g?g={}:"object"==typeof g&&("from"in g||"to"in g)&&(e=Ya(Aa(g.from),Aa(g.to)),g={},g.ms=e.milliseconds,g.M=e.months),d=new Ea(g),Fa(a)&&f(a,"_locale")&&(d._locale=a._locale),d}function Wa(a,b){var c=a&&parseFloat(a.replace(",","."));return(isNaN(c)?0:c)*b}function Xa(a,b){var c={milliseconds:0,months:0};return c.months=b.month()-a.month()+12*(b.year()-a.year()),a.clone().add(c.months,"M").isAfter(b)&&--c.months,c.milliseconds=+b-+a.clone().add(c.months,"M"),c}function Ya(a,b){var c;return b=Ia(b,a),a.isBefore(b)?c=Xa(a,b):(c=Xa(b,a),c.milliseconds=-c.milliseconds,c.months=-c.months),c}function Za(a,b){return function(c,d){var e,f;return null===d||isNaN(+d)||(_(b,"moment()."+b+"(period, number) is deprecated. Please use moment()."+b+"(number, period)."),f=c,c=d,d=f),c="string"==typeof c?+c:c,e=Va(c,d),$a(this,e,a),this}}function $a(b,c,d,e){var f=c._milliseconds,g=c._days,h=c._months;e=null==e?!0:e,f&&b._d.setTime(+b._d+f*d),g&&D(b,"Date",C(b,"Date")+g*d),h&&V(b,C(b,"Month")+h*d),e&&a.updateOffset(b,g||h)}function _a(a){var b=a||Aa(),c=Ia(b,this).startOf("day"),d=this.diff(c,"days",!0),e=-6>d?"sameElse":-1>d?"lastWeek":0>d?"lastDay":1>d?"sameDay":2>d?"nextDay":7>d?"nextWeek":"sameElse";return this.format(this.localeData().calendar(e,this,Aa(b)))}function ab(){return new n(this)}function bb(a,b){var c;return b=z("undefined"!=typeof b?b:"millisecond"),"millisecond"===b?(a=o(a)?a:Aa(a),+this>+a):(c=o(a)?+a:+Aa(a),c<+this.clone().startOf(b))}function cb(a,b){var c;return b=z("undefined"!=typeof b?b:"millisecond"),"millisecond"===b?(a=o(a)?a:Aa(a),+a>+this):(c=o(a)?+a:+Aa(a),+this.clone().endOf(b)a?Math.ceil(a):Math.floor(a)}function gb(a,b,c){var d,e,f=Ia(a,this),g=6e4*(f.utcOffset()-this.utcOffset());return b=z(b),"year"===b||"month"===b||"quarter"===b?(e=hb(this,f),"quarter"===b?e/=3:"year"===b&&(e/=12)):(d=this-f,e="second"===b?d/1e3:"minute"===b?d/6e4:"hour"===b?d/36e5:"day"===b?(d-g)/864e5:"week"===b?(d-g)/6048e5:d),c?e:fb(e)}function hb(a,b){var c,d,e=12*(b.year()-a.year())+(b.month()-a.month()),f=a.clone().add(e,"months");return 0>b-f?(c=a.clone().add(e-1,"months"),d=(b-f)/(f-c)):(c=a.clone().add(e+1,"months"),d=(b-f)/(c-f)),-(e+d)}function ib(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")}function jb(){var a=this.clone().utc();return 0b;b++)if(this._weekdaysParse[b]||(c=Aa([2e3,1]).day(b),d="^"+this.weekdays(c,"")+"|^"+this.weekdaysShort(c,"")+"|^"+this.weekdaysMin(c,""),this._weekdaysParse[b]=new RegExp(d.replace(".",""),"i")),this._weekdaysParse[b].test(a))return b}function Mb(a){var b=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=a?(a=Hb(a,this.localeData()),this.add(a-b,"d")):b}function Nb(a){var b=(this.day()+7-this.localeData()._week.dow)%7;return null==a?b:this.add(a-b,"d")}function Ob(a){return null==a?this.day()||7:this.day(this.day()%7?a:a-7)}function Pb(a,b){G(a,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),b)})}function Qb(a,b){return b._meridiemParse}function Rb(a){return"p"===(a+"").toLowerCase().charAt(0)}function Sb(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"}function Tb(a){G(0,[a,3],0,"millisecond")}function Ub(){return this._isUTC?"UTC":""}function Vb(){return this._isUTC?"Coordinated Universal Time":""}function Wb(a){return Aa(1e3*a)}function Xb(){return Aa.apply(null,arguments).parseZone()}function Yb(a,b,c){var d=this._calendar[a];return"function"==typeof d?d.call(b,c):d}function Zb(a){var b=this._longDateFormat[a];return!b&&this._longDateFormat[a.toUpperCase()]&&(b=this._longDateFormat[a.toUpperCase()].replace(/MMMM|MM|DD|dddd/g,function(a){return a.slice(1)}),this._longDateFormat[a]=b),b}function $b(){return this._invalidDate}function _b(a){return this._ordinal.replace("%d",a)}function ac(a){return a}function bc(a,b,c,d){var e=this._relativeTime[c];return"function"==typeof e?e(a,b,c,d):e.replace(/%d/i,a)}function cc(a,b){var c=this._relativeTime[a>0?"future":"past"];return"function"==typeof c?c(b):c.replace(/%s/i,b)}function dc(a){var b,c;for(c in a)b=a[c],"function"==typeof b?this[c]=b:this["_"+c]=b;this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)}function ec(a,b,c,d){var e=x(),f=h().set(d,b);return e[c](f,a)}function fc(a,b,c,d,e){if("number"==typeof a&&(b=a,a=void 0),a=a||"",null!=b)return ec(a,b,c,e);var f,g=[];for(f=0;d>f;f++)g[f]=ec(a,f,c,e);return g}function gc(a,b){return fc(a,b,"months",12,"month")}function hc(a,b){return fc(a,b,"monthsShort",12,"month")}function ic(a,b){return fc(a,b,"weekdays",7,"day")}function jc(a,b){return fc(a,b,"weekdaysShort",7,"day")}function kc(a,b){return fc(a,b,"weekdaysMin",7,"day")}function lc(){var a=this._data;return this._milliseconds=Rd(this._milliseconds),this._days=Rd(this._days),this._months=Rd(this._months),a.milliseconds=Rd(a.milliseconds),a.seconds=Rd(a.seconds),a.minutes=Rd(a.minutes),a.hours=Rd(a.hours),a.months=Rd(a.months),a.years=Rd(a.years),this}function mc(a,b,c,d){var e=Va(b,c);return a._milliseconds+=d*e._milliseconds,a._days+=d*e._days,a._months+=d*e._months,a._bubble()}function nc(a,b){return mc(this,a,b,1)}function oc(a,b){return mc(this,a,b,-1)}function pc(){var a,b,c,d=this._milliseconds,e=this._days,f=this._months,g=this._data,h=0;return g.milliseconds=d%1e3,a=fb(d/1e3),g.seconds=a%60,b=fb(a/60),g.minutes=b%60,c=fb(b/60),g.hours=c%24,e+=fb(c/24),h=fb(qc(e)),e-=fb(rc(h)),f+=fb(e/30),e%=30,h+=fb(f/12),f%=12,g.days=e,g.months=f,g.years=h,this}function qc(a){return 400*a/146097}function rc(a){return 146097*a/400}function sc(a){var b,c,d=this._milliseconds;if(a=z(a),"month"===a||"year"===a)return b=this._days+d/864e5,c=this._months+12*qc(b),"month"===a?c:c/12;switch(b=this._days+Math.round(rc(this._months/12)),a){case"week":return b/7+d/6048e5;case"day":return b+d/864e5;case"hour":return 24*b+d/36e5;case"minute":return 1440*b+d/6e4;case"second":return 86400*b+d/1e3;case"millisecond":return Math.floor(864e5*b)+d;default:throw new Error("Unknown unit "+a)}}function tc(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*p(this._months/12)}function uc(a){return function(){return this.as(a)}}function vc(a){return a=z(a),this[a+"s"]()}function wc(a){return function(){return this._data[a]}}function xc(){return fb(this.days()/7)}function yc(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function zc(a,b,c){var d=Va(a).abs(),e=fe(d.as("s")),f=fe(d.as("m")),g=fe(d.as("h")),h=fe(d.as("d")),i=fe(d.as("M")),j=fe(d.as("y")),k=e0,k[4]=c,yc.apply(null,k)}function Ac(a,b){return void 0===ge[a]?!1:void 0===b?ge[a]:(ge[a]=b,!0)}function Bc(a){var b=this.localeData(),c=zc(this,!a,b);return a&&(c=b.pastFuture(+this,c)),b.postformat(c)}function Cc(){var a=he(this.years()),b=he(this.months()),c=he(this.days()),d=he(this.hours()),e=he(this.minutes()),f=he(this.seconds()+this.milliseconds()/1e3),g=this.asSeconds();return g?(0>g?"-":"")+"P"+(a?a+"Y":"")+(b?b+"M":"")+(c?c+"D":"")+(d||e||f?"T":"")+(d?d+"H":"")+(e?e+"M":"")+(f?f+"S":""):"P0D"}var Dc,Ec,Fc=a.momentProperties=[],Gc=!1,Hc={},Ic={},Jc=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|x|X|zz?|ZZ?|.)/g,Kc=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,Lc={},Mc={},Nc=/\d/,Oc=/\d\d/,Pc=/\d{3}/,Qc=/\d{4}/,Rc=/[+-]?\d{6}/,Sc=/\d\d?/,Tc=/\d{1,3}/,Uc=/\d{1,4}/,Vc=/[+-]?\d{1,6}/,Wc=/\d+/,Xc=/[+-]?\d+/,Yc=/Z|[+-]\d\d:?\d\d/gi,Zc=/[+-]?\d+(\.\d{1,3})?/,$c=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,_c={},ad={},bd=0,cd=1,dd=2,ed=3,fd=4,gd=5,hd=6;G("M",["MM",2],"Mo",function(){return this.month()+1}),G("MMM",0,0,function(a){return this.localeData().monthsShort(this,a)}),G("MMMM",0,0,function(a){return this.localeData().months(this,a)}),y("month","M"),L("M",Sc),L("MM",Sc,Oc),L("MMM",$c),L("MMMM",$c),O(["M","MM"],function(a,b){b[cd]=p(a)-1}),O(["MMM","MMMM"],function(a,b,c,d){var e=c._locale.monthsParse(a,d,c._strict);null!=e?b[cd]=e:j(c).invalidMonth=a});var id="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),jd="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),kd={};a.suppressDeprecationWarnings=!1;var ld=/^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,md=[["YYYYYY-MM-DD",/[+-]\d{6}-\d{2}-\d{2}/],["YYYY-MM-DD",/\d{4}-\d{2}-\d{2}/],["GGGG-[W]WW-E",/\d{4}-W\d{2}-\d/],["GGGG-[W]WW",/\d{4}-W\d{2}/],["YYYY-DDD",/\d{4}-\d{3}/]],nd=[["HH:mm:ss.SSSS",/(T| )\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],od=/^\/?Date\((\-?\d+)/i;a.createFromInputFallback=$("moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.",function(a){a._d=new Date(a._i+(a._useUTC?" UTC":""))}),G(0,["YY",2],0,function(){return this.year()%100}),G(0,["YYYY",4],0,"year"),G(0,["YYYYY",5],0,"year"),G(0,["YYYYYY",6,!0],0,"year"),y("year","y"),L("Y",Xc),L("YY",Sc,Oc),L("YYYY",Uc,Qc),L("YYYYY",Vc,Rc),L("YYYYYY",Vc,Rc),O(["YYYY","YYYYY","YYYYYY"],bd),O("YY",function(b,c){c[bd]=a.parseTwoDigitYear(b)}),a.parseTwoDigitYear=function(a){return p(a)+(p(a)>68?1900:2e3)};var pd=B("FullYear",!1);G("w",["ww",2],"wo","week"),G("W",["WW",2],"Wo","isoWeek"),y("week","w"),y("isoWeek","W"),L("w",Sc),L("ww",Sc,Oc),L("W",Sc),L("WW",Sc,Oc),P(["w","ww","W","WW"],function(a,b,c,d){b[d.substr(0,1)]=p(a)});var qd={dow:0,doy:6};G("DDD",["DDDD",3],"DDDo","dayOfYear"),y("dayOfYear","DDD"),L("DDD",Tc),L("DDDD",Pc),O(["DDD","DDDD"],function(a,b,c){c._dayOfYear=p(a)}),a.ISO_8601=function(){};var rd=$("moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",function(){var a=Aa.apply(null,arguments);return this>a?this:a}),sd=$("moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(){var a=Aa.apply(null,arguments);return a>this?this:a});Ga("Z",":"),Ga("ZZ",""),L("Z",Yc),L("ZZ",Yc),O(["Z","ZZ"],function(a,b,c){c._useUTC=!0,c._tzm=Ha(a)});var td=/([\+\-]|\d\d)/gi;a.updateOffset=function(){};var ud=/(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,vd=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/;Va.fn=Ea.prototype;var wd=Za(1,"add"),xd=Za(-1,"subtract");a.defaultFormat="YYYY-MM-DDTHH:mm:ssZ";var yd=$("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(a){return void 0===a?this.localeData():this.locale(a)});G(0,["gg",2],0,function(){return this.weekYear()%100}),G(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Ab("gggg","weekYear"),Ab("ggggg","weekYear"),Ab("GGGG","isoWeekYear"),Ab("GGGGG","isoWeekYear"),y("weekYear","gg"),y("isoWeekYear","GG"),L("G",Xc),L("g",Xc),L("GG",Sc,Oc),L("gg",Sc,Oc),L("GGGG",Uc,Qc),L("gggg",Uc,Qc),L("GGGGG",Vc,Rc),L("ggggg",Vc,Rc),P(["gggg","ggggg","GGGG","GGGGG"],function(a,b,c,d){b[d.substr(0,2)]=p(a)}),P(["gg","GG"],function(b,c,d,e){c[e]=a.parseTwoDigitYear(b)}),G("Q",0,0,"quarter"),y("quarter","Q"),L("Q",Nc),O("Q",function(a,b){b[cd]=3*(p(a)-1)}),G("D",["DD",2],"Do","date"),y("date","D"),L("D",Sc),L("DD",Sc,Oc),L("Do",function(a,b){return a?b._ordinalParse:b._ordinalParseLenient}),O(["D","DD"],dd),O("Do",function(a,b){b[dd]=p(a.match(Sc)[0],10)});var zd=B("Date",!0);G("d",0,"do","day"),G("dd",0,0,function(a){return this.localeData().weekdaysMin(this,a)}),G("ddd",0,0,function(a){return this.localeData().weekdaysShort(this,a)}),G("dddd",0,0,function(a){return this.localeData().weekdays(this,a)}),G("e",0,0,"weekday"),G("E",0,0,"isoWeekday"),y("day","d"),y("weekday","e"),y("isoWeekday","E"),L("d",Sc),L("e",Sc),L("E",Sc),L("dd",$c),L("ddd",$c),L("dddd",$c),P(["dd","ddd","dddd"],function(a,b,c){var d=c._locale.weekdaysParse(a);null!=d?b.d=d:j(c).invalidWeekday=a}),P(["d","e","E"],function(a,b,c,d){b[d]=p(a)});var Ad="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),Bd="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),Cd="Su_Mo_Tu_We_Th_Fr_Sa".split("_");G("H",["HH",2],0,"hour"),G("h",["hh",2],0,function(){return this.hours()%12||12}),Pb("a",!0),Pb("A",!1),y("hour","h"),L("a",Qb),L("A",Qb),L("H",Sc),L("h",Sc),L("HH",Sc,Oc),L("hh",Sc,Oc),O(["H","HH"],ed),O(["a","A"],function(a,b,c){c._isPm=c._locale.isPM(a),c._meridiem=a}),O(["h","hh"],function(a,b,c){b[ed]=p(a),j(c).bigHour=!0});var Dd=/[ap]\.?m?\.?/i,Ed=B("Hours",!0);G("m",["mm",2],0,"minute"),y("minute","m"),L("m",Sc),L("mm",Sc,Oc),O(["m","mm"],fd);var Fd=B("Minutes",!1);G("s",["ss",2],0,"second"),y("second","s"),L("s",Sc),L("ss",Sc,Oc),O(["s","ss"],gd);var Gd=B("Seconds",!1);G("S",0,0,function(){return~~(this.millisecond()/100)}),G(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),Tb("SSS"),Tb("SSSS"),y("millisecond","ms"),L("S",Tc,Nc),L("SS",Tc,Oc),L("SSS",Tc,Pc),L("SSSS",Wc),O(["S","SS","SSS","SSSS"],function(a,b){b[hd]=p(1e3*("0."+a))});var Hd=B("Milliseconds",!1);G("z",0,0,"zoneAbbr"),G("zz",0,0,"zoneName");var Id=n.prototype;Id.add=wd,Id.calendar=_a,Id.clone=ab,Id.diff=gb,Id.endOf=sb,Id.format=kb,Id.from=lb,Id.fromNow=mb,Id.to=nb,Id.toNow=ob,Id.get=E,Id.invalidAt=zb,Id.isAfter=bb,Id.isBefore=cb,Id.isBetween=db,Id.isSame=eb,Id.isValid=xb,Id.lang=yd,Id.locale=pb,Id.localeData=qb,Id.max=sd,Id.min=rd,Id.parsingFlags=yb,Id.set=E,Id.startOf=rb,Id.subtract=xd,Id.toArray=wb,Id.toDate=vb,Id.toISOString=jb,Id.toJSON=jb,Id.toString=ib,Id.unix=ub,Id.valueOf=tb,Id.year=pd,Id.isLeapYear=ga,Id.weekYear=Cb,Id.isoWeekYear=Db,Id.quarter=Id.quarters=Gb,Id.month=W,Id.daysInMonth=X,Id.week=Id.weeks=la,Id.isoWeek=Id.isoWeeks=ma,Id.weeksInYear=Fb,Id.isoWeeksInYear=Eb,Id.date=zd,Id.day=Id.days=Mb,Id.weekday=Nb,Id.isoWeekday=Ob,Id.dayOfYear=oa,Id.hour=Id.hours=Ed,Id.minute=Id.minutes=Fd,Id.second=Id.seconds=Gd,Id.millisecond=Id.milliseconds=Hd,Id.utcOffset=Ka,Id.utc=Ma,Id.local=Na,Id.parseZone=Oa,Id.hasAlignedHourOffset=Pa,Id.isDST=Qa,Id.isDSTShifted=Ra,Id.isLocal=Sa,Id.isUtcOffset=Ta,Id.isUtc=Ua,Id.isUTC=Ua,Id.zoneAbbr=Ub,Id.zoneName=Vb,Id.dates=$("dates accessor is deprecated. Use date instead.",zd),Id.months=$("months accessor is deprecated. Use month instead",W),Id.years=$("years accessor is deprecated. Use year instead",pd),Id.zone=$("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779",La);var Jd=Id,Kd={sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},Ld={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY LT",LLLL:"dddd, MMMM D, YYYY LT"},Md="Invalid date",Nd="%d",Od=/\d{1,2}/,Pd={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour", 7 | hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},Qd=r.prototype;Qd._calendar=Kd,Qd.calendar=Yb,Qd._longDateFormat=Ld,Qd.longDateFormat=Zb,Qd._invalidDate=Md,Qd.invalidDate=$b,Qd._ordinal=Nd,Qd.ordinal=_b,Qd._ordinalParse=Od,Qd.preparse=ac,Qd.postformat=ac,Qd._relativeTime=Pd,Qd.relativeTime=bc,Qd.pastFuture=cc,Qd.set=dc,Qd.months=S,Qd._months=id,Qd.monthsShort=T,Qd._monthsShort=jd,Qd.monthsParse=U,Qd.week=ia,Qd._week=qd,Qd.firstDayOfYear=ka,Qd.firstDayOfWeek=ja,Qd.weekdays=Ib,Qd._weekdays=Ad,Qd.weekdaysMin=Kb,Qd._weekdaysMin=Cd,Qd.weekdaysShort=Jb,Qd._weekdaysShort=Bd,Qd.weekdaysParse=Lb,Qd.isPM=Rb,Qd._meridiemParse=Dd,Qd.meridiem=Sb,v("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(a){var b=a%10,c=1===p(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th";return a+c}}),a.lang=$("moment.lang is deprecated. Use moment.locale instead.",v),a.langData=$("moment.langData is deprecated. Use moment.localeData instead.",x);var Rd=Math.abs,Sd=uc("ms"),Td=uc("s"),Ud=uc("m"),Vd=uc("h"),Wd=uc("d"),Xd=uc("w"),Yd=uc("M"),Zd=uc("y"),$d=wc("milliseconds"),_d=wc("seconds"),ae=wc("minutes"),be=wc("hours"),ce=wc("days"),de=wc("months"),ee=wc("years"),fe=Math.round,ge={s:45,m:45,h:22,d:26,M:11},he=Math.abs,ie=Ea.prototype;ie.abs=lc,ie.add=nc,ie.subtract=oc,ie.as=sc,ie.asMilliseconds=Sd,ie.asSeconds=Td,ie.asMinutes=Ud,ie.asHours=Vd,ie.asDays=Wd,ie.asWeeks=Xd,ie.asMonths=Yd,ie.asYears=Zd,ie.valueOf=tc,ie._bubble=pc,ie.get=vc,ie.milliseconds=$d,ie.seconds=_d,ie.minutes=ae,ie.hours=be,ie.days=ce,ie.weeks=xc,ie.months=de,ie.years=ee,ie.humanize=Bc,ie.toISOString=Cc,ie.toString=Cc,ie.toJSON=Cc,ie.locale=pb,ie.localeData=qb,ie.toIsoString=$("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",Cc),ie.lang=yd,G("X",0,0,"unix"),G("x",0,0,"valueOf"),L("x",Xc),L("X",Zc),O("X",function(a,b,c){c._d=new Date(1e3*parseFloat(a,10))}),O("x",function(a,b,c){c._d=new Date(p(a))}),a.version="2.10.3",b(Aa),a.fn=Jd,a.min=Ca,a.max=Da,a.utc=h,a.unix=Wb,a.months=gc,a.isDate=d,a.locale=v,a.invalid=l,a.duration=Va,a.isMoment=o,a.weekdays=ic,a.parseZone=Xb,a.localeData=x,a.isDuration=Fa,a.monthsShort=hc,a.weekdaysMin=kc,a.defineLocale=w,a.weekdaysShort=jc,a.normalizeUnits=z,a.relativeTimeThreshold=Ac;var je=a;return je}); --------------------------------------------------------------------------------