diff --git a/.composer-require-checker.config.json b/.composer-require-checker.config.json index 5f722f81e1..17f8e78ad1 100644 --- a/.composer-require-checker.config.json +++ b/.composer-require-checker.config.json @@ -19,8 +19,7 @@ "DB", "DBSlave", "// GLPI base constants (they are not detected as they are dynamically declared)", - "GLPI_AJAX_DASHBOARD", "GLPI_ALLOW_IFRAME_IN_RICH_TEXT", "GLPI_CALDAV_IMPORT_STATE", "GLPI_CACHE_DIR", "GLPI_CRON_DIR", "GLPI_CSRF_EXPIRES", "GLPI_CSRF_MAX_TOKENS", "GLPI_USE_IDOR_CHECK", "GLPI_IDOR_EXPIRES", "GLPI_DEMO_MODE", "GLPI_DOC_DIR", "GLPI_DUMP_DIR", "GLPI_FORCE_EMPTY_SQL_MODE", "GLPI_GRAPH_DIR", "GLPI_INSTALL_MODE", "GLPI_LOCAL_I18N_DIR", "GLPI_LOCK_DIR", "GLPI_LOG_DIR", "GLPI_MARKETPLACE_DIR", "GLPI_MARKETPLACE_PLUGINS_API_URI", "GLPI_MARKETPLACE_PRERELEASES", "GLPI_NETWORK_REGISTRATION_API_URL", "GLPI_PICTURE_DIR", "GLPI_PLUGIN_DOC_DIR", "GLPI_RSS_DIR", "GLPI_SESSION_DIR", "GLPI_TELEMETRY_URI", "GLPI_TMP_DIR", "GLPI_UPLOAD_DIR", "GLPI_USE_CSRF_CHECK", "GLPI_USER_AGENT_EXTRA_COMMENTS", "GLPI_VAR_DIR", - "GLPI_MARKETPLACE_ALLOW_OVERRIDE", "GLPI_MARKETPLACE_MANUAL_DOWNLOADS", + "GLPI_AJAX_DASHBOARD", "GLPI_ALLOW_IFRAME_IN_RICH_TEXT", "GLPI_CALDAV_IMPORT_STATE", "GLPI_CACHE_DIR", "GLPI_CRON_DIR", "GLPI_CSRF_EXPIRES", "GLPI_CSRF_MAX_TOKENS", "GLPI_USE_IDOR_CHECK", "GLPI_IDOR_EXPIRES", "GLPI_DEMO_MODE", "GLPI_DOC_DIR", "GLPI_DUMP_DIR", "GLPI_FORCE_EMPTY_SQL_MODE", "GLPI_GRAPH_DIR", "GLPI_INSTALL_MODE", "GLPI_LOCAL_I18N_DIR", "GLPI_LOCK_DIR", "GLPI_LOG_DIR", "GLPI_NETWORK_REGISTRATION_API_URL", "GLPI_PICTURE_DIR", "GLPI_PLUGIN_DOC_DIR", "GLPI_RSS_DIR", "GLPI_SESSION_DIR", "GLPI_TELEMETRY_URI", "GLPI_TMP_DIR", "GLPI_UPLOAD_DIR", "GLPI_USE_CSRF_CHECK", "GLPI_USER_AGENT_EXTRA_COMMENTS", "GLPI_VAR_DIR", "// GLPI optionnal constants", "GLPI_FORCE_MAIL", "GLPI_LOG_LVL", diff --git a/.eslintrc.json b/.eslintrc.json index 106c1e053f..3ab6f89327 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -4,7 +4,6 @@ "/config/*", "/files/*", "/lib/*", - "/marketplace/*", "/node_modules/*", "/plugins/*", "/public/lib/*", diff --git a/.github/actions/lint_php-lint.sh b/.github/actions/lint_php-lint.sh index c1d7be7a7d..27652c5a05 100755 --- a/.github/actions/lint_php-lint.sh +++ b/.github/actions/lint_php-lint.sh @@ -6,7 +6,6 @@ echo $ROOT_DIR echo "Check for syntax errors" vendor/bin/parallel-lint \ --exclude ./files/ \ - --exclude ./marketplace/ \ --exclude ./plugins/ \ --exclude ./tools/vendor/ \ --exclude ./vendor/ \ @@ -28,5 +27,5 @@ vendor/bin/phpcs \ -p \ --extensions=php \ --standard=vendor/glpi-project/coding-standard/GlpiStandard/ \ - --ignore="/.git/,^$ROOT_DIR/(config|files|lib|marketplace|node_modules|plugins|tests/config|vendor)/" \ + --ignore="/.git/,^$ROOT_DIR/(config|files|lib|node_modules|plugins|tests/config|vendor)/" \ . diff --git a/.gitignore b/.gitignore index ff281ff523..21b7d49dbe 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ /config/glpicrypt.key /config/local_define.php /tests/config_db* -/marketplace/ /plugins/ /files/ /.buildpath diff --git a/README.md b/README.md index 612a5d8f3f..217a19b437 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,6 @@ It is distributed under the GNU GENERAL PUBLIC LICENSE Version 2 - please consul - ldap (users authentication) - openssl (encrypted communication) - sodium (performances enhancement on sensitive data encryption/decryption) - - zip and bz2 (installation of zip and bz2 packages from marketplace) * Supported browsers: - Edge diff --git a/ajax/marketplace.php b/ajax/marketplace.php deleted file mode 100644 index 358c8f9d3e..0000000000 --- a/ajax/marketplace.php +++ /dev/null @@ -1,117 +0,0 @@ -. - * --------------------------------------------------------------------- - */ - -global $PLUGINS_EXCLUDED; - -// follow download progress of a plugin with a minimal loading of files -// So we get a ajax answer in 5ms instead 100ms -if (isset($_REQUEST["action"]) && $_REQUEST["action"] == "get_dl_progress") { - if (!defined('GLPI_ROOT')) { - define('GLPI_ROOT', dirname(__DIR__)); - } - - include_once GLPI_ROOT . '/inc/based_config.php'; - Session::setPath(); - Session::start(); - - echo $_SESSION['marketplace_dl_progress'][$_REQUEST['key']] ?? 0; - exit; -} - -if ($_REQUEST["action"] == "download_plugin" || $_REQUEST["action"] == "update_plugin") { - // Do not load plugin that will be updated, to be able to load its new informations - // by redefining its plugin_version_ function after files replacement. - $PLUGINS_EXCLUDED = [$_REQUEST['key']]; -} - - -// get common marketplace action, load GLPI framework -include ("../inc/includes.php"); - -Session::checkRight("config", UPDATE); - -use Glpi\Marketplace\Controller as MarketplaceController; -use Glpi\Marketplace\View as MarketplaceView; - -if (isset($_REQUEST['key'])) { - $marketplace_ctrl = new MarketplaceController($_REQUEST['key']); - if ($_REQUEST["action"] == "download_plugin" - || $_REQUEST["action"] == "update_plugin") { - $marketplace_ctrl->downloadPlugin(); - } - if ($_REQUEST["action"] == "clean_plugin") { - if ($marketplace_ctrl->cleanPlugin()) { - echo "cleaned"; - } - } - if ($_REQUEST["action"] == "install_plugin") { - $marketplace_ctrl->installPlugin(); - } - if ($_REQUEST["action"] == "uninstall_plugin") { - $marketplace_ctrl->uninstallPlugin(); - } - if ($_REQUEST["action"] == "enable_plugin") { - $marketplace_ctrl->enablePlugin(); - } - if ($_REQUEST["action"] == "disable_plugin") { - $marketplace_ctrl->disablePlugin(); - } - - echo MarketplaceView::getButtons($_REQUEST['key']); -} - -if ($_REQUEST["action"] == "refresh_plugin_list") { - switch ($_REQUEST['tab']) { - default: - case 'discover': - echo MarketplaceView::discover( - $_REQUEST['force'] ?? false, - true, - $_REQUEST['tag'] ?? "", - $_REQUEST['filter'] ?? "", - $_REQUEST['page'] ?? 1, - $_REQUEST['sort'] ?? "sort-alpha-asc" - ); - break; - case 'installed': - echo MarketplaceView::installed(true, true, $_REQUEST['filter'] ?? ""); - break; - } -} - -if ($_REQUEST["action"] == "getPagination") { - echo MarketplaceView::getPaginationHtml( - $_REQUEST['page'] ?? 1, - $_REQUEST['total'] ?? 1, - true - ); -} diff --git a/composer.json b/composer.json index 56fd0c3718..1cfd34eb90 100644 --- a/composer.json +++ b/composer.json @@ -89,9 +89,9 @@ "testweb": "php vendor/bin/atoum -p 'php -d memory_limit=512M' --debug --force-terminal --use-dot-report --bootstrap-file tests/bootstrap.php --no-code-coverage --max-children-number 1 -d tests/web", "testldap": "php vendor/bin/atoum -p 'php -d memory_limit=512M' --debug --force-terminal --use-dot-report --bootstrap-file tests/bootstrap.php --no-code-coverage --max-children-number 1 -d tests/LDAP", "testimap": "php vendor/bin/atoum -p 'php -d memory_limit=512M' --debug --force-terminal --use-dot-report --bootstrap-file tests/bootstrap.php --no-code-coverage --max-children-number 1 -d tests/imap", - "csp": "vendor/bin/phpcs --parallel=500 --cache -p --extensions=php --standard=vendor/glpi-project/coding-standard/GlpiStandard/ --ignore=\"/.git/,^$(pwd)/(config|files|lib|marketplace|node_modules|plugins|tests/config|vendor)/\" ./", - "cs": "vendor/bin/phpcs -d memory_limit=512M --cache -p --extensions=php --standard=vendor/glpi-project/coding-standard/GlpiStandard/ --ignore=\"/.git/,^$(pwd)/(config|files|lib|marketplace|node_modules|plugins|tests/config|vendor)/\" ./", - "lint": "vendor/bin/parallel-lint --exclude files --exclude marketplace --exclude plugins --exclude vendor --exclude tools/vendor .", + "csp": "vendor/bin/phpcs --parallel=500 --cache -p --extensions=php --standard=vendor/glpi-project/coding-standard/GlpiStandard/ --ignore=\"/.git/,^$(pwd)/(config|files|lib|node_modules|plugins|tests/config|vendor)/\" ./", + "cs": "vendor/bin/phpcs -d memory_limit=512M --cache -p --extensions=php --standard=vendor/glpi-project/coding-standard/GlpiStandard/ --ignore=\"/.git/,^$(pwd)/(config|files|lib|node_modules|plugins|tests/config|vendor)/\" ./", + "lint": "vendor/bin/parallel-lint --exclude files --exclude plugins --exclude vendor --exclude tools/vendor .", "post-install-cmd": [ "@php -r \"file_put_contents('.composer.hash', sha1_file('composer.lock'));\"", "patch -f -p1 -d vendor/laminas/laminas-mail/ < tools/laminas-mail-128.patch || echo 'Error applying patch, retrieving some mail attachement could fail'" diff --git a/css/marketplace.scss b/css/marketplace.scss deleted file mode 100644 index 3bd7716a4d..0000000000 --- a/css/marketplace.scss +++ /dev/null @@ -1,478 +0,0 @@ - -$break_phones: 900px; -$break_ss_screen: 1080px; -$break_s_screen: 1400px; - -.marketplace { - $left_width: 150px; - text-align: left; - display: flex; - background-color: #EEE; - padding: 10px; - margin: 0 -10px; - - @media screen and (max-width: $break_phones) { - display: block; - } - - .left-panel { - width: $left_width; - - @media screen and (max-width: $break_phones) { - width: initial; - } - - .plugins-tags { - vertical-align: top; - display: inline-block; - width: 100%; - font-size: 1.1em; - - @media screen and (max-width: $break_phones) { - display: flex; - flex-wrap: wrap; - margin: 0 0 10px 10px; - } - - .tag { - background: #FFF; - border-radius: 3px; - border: 1px solid #dddddd; - padding: 5px; - margin: 0 3px 3px 0; - text-align: center; - cursor: pointer; - - &:hover { - background: #d1d1d1; - } - - &.active { - background: #747474; - color: #FFF; - border-color: transparent; - } - } - } - } - - .right-panel { - padding-left: 5px; - width: 100%; - - .left-panel+& { - width: calc(100% - #{$left_width}); - } - - .top-panel { - display: flex; - - .filter-list { - flex: 1; - margin: 0 8px 5px 5px; - height: 25px; - } - - .controls { - width: 170px; - - .select2 { - .select2-selection.select2-selection--single { - height: 25px; - text-overflow: clip; - text-overflow: unset; - padding-left: 0; - } - .select2-selection.select2-selection--single:before { - content: ''; - } - } - - i { - vertical-align: middle; - cursor: pointer; - } - } - } - - .plugins { - display: flex; - flex-wrap: wrap; - position: relative; - - @media screen and (max-width: $break_phones) { - flex-direction: column; - } - - .loading-plugins { - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - -webkit-backdrop-filter: blur(2px); - backdrop-filter: blur(2px); - background-color: rgba(0, 0, 0, 0.5); - - i.fas { - position: absolute; - top: 50%; - left: calc(50% - 1.5rem); - color: white; - font-size: 3rem; - } - } - - .plugin { - $margin_width: 10px; - width: calc(25% - #{$margin_width}); //minus margin - margin: calc(#{$margin_width} / 2); - margin-bottom: 10px; - border-radius: 3px; - background-color: #FFF; - border: 1px solid #c2c2c2; - display: flex; - flex-direction: column; - align-items: stretch; - justify-content: space-between; - box-sizing: border-box; - - @media screen and (max-width: $break_s_screen) { - width: calc(33% - #{$margin_width}); - } - - @media screen and (max-width: $break_ss_screen) { - width: calc(50% - #{$margin_width}); - } - - @media screen and (max-width: $break_phones) { - width: calc(100% - #{$margin_width}); - } - - .main { - display: flex; - padding-bottom: 5px; - - .icon { - flex-shrink: 0; - padding: 5px; - width: 70px; - - img { - object-fit: contain; - max-width: 50px; - max-height: 50px; - margin: 2px 10px; - } - - .icon-text { - display: block; - margin-top: 2px; - text-align: center; - width: 50px; - height: 50px; - line-height: 50px; - font-size: 1.4em; - font-weight: bold; - text-transform: uppercase; - margin: 2px 10px; - } - } - - .details { - padding: 5px 10px; - - .title { - margin: 2px 0 5px; - font-size: 1.25em; - } - - .offers { - .badge { - margin: 1px; - padding: 2px 5px; - border-radius: 3px; - background-color: #d1d1d1; - border: 1px solid #5a5a5a; - color: #202020; - font-weight: bold; - - &.glpi-network { - background-color: #ffd45f; - color: #352804; - border-color: #c69100; - } - - i { - font-size: 10px; - margin: 1px 3px 0 0; - vertical-align: top; - color: rgba(0, 0, 0, .5); - } - } - } - - .description { - margin: 5px 0; - } - } - - .buttons { - width: 25px; - padding: 5px; - margin-left: auto; - - button { - background-color: #FAFAFA; - border: 1px solid #dddddd; - cursor: pointer; - width: 25px; - padding: 3px 5px; - margin: 1px; - - &:hover { - background-color: #dddddd; - } - - i { - color: #666; - } - - &[data-action="disable_plugin"] { - i { - color: #6ebb43; - } - } - - &[data-action="download_plugin"]:hover, - &[data-action="install_plugin"]:hover, - &[data-action="enable_plugin"]:hover { - background-color: rgb(226, 247, 226); - border-color: rgb(91, 150, 91); - i { - color: rgb(74, 121, 74); - } - } - - &[data-action="uninstall_plugin"]:hover, - &[data-action="disable_plugin"]:hover { - background-color: rgb(245, 199, 199); - border-color: rgb(190, 83, 83); - i { - color: rgb(105, 60, 60); - } - } - - &.download_manually, - &.need_offers { - background-color: rgb(253, 240, 215); - border-color: rgb(221, 188, 125); - i { - color: #8f5a0a; - } - - &:hover { - background-color: rgb(253, 227, 179); - border-color: rgb(97, 82, 54); - - i { - color: rgb(97, 82, 54); - } - } - } - } - - progress { - -webkit-appearance: none; - appearance: none; - border: none; - width: 25px; - height: 5px; - - &::-webkit-progress-bar { - background: transparent; - border: 1px solid #c2c2c2; - } - } - - .plugin-error { - color: red; - margin: 1px; - padding: 3px 5px; - } - } - } - - .footer { - background-color: #FAFAFA; - border-top: 1px solid rgb(221, 221, 221); - display: flex; - align-items: stretch; - justify-content: space-between; - padding: 5px; - color: #666; - - .misc-left { - .note { - width: 85px; - - i.fas, i.far { - color: #ffbb00; - } - } - - .links { - a { - i { - color: #999; - - &:hover { - color: #000; - } - } - } - } - } - - .misc-right { - width: calc(100% - 85px); - text-align: left; - - i { - margin-right: 5px; - } - - .authors { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - } - } - - .misc-right { - i { - margin-right: 5px; - } - - .authors { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - .license, .authors { - a { - font-size: 12px; - } - } - } - } - } - - .pagination { - width: 100%; - display: flex; - justify-content: center; - - li { - padding: 8px 16px; - transition: background-color .1s; - border: 1px solid #c2c2c2; - background-color: white; - - &.current { - background-color: #979797; - border-color: #979797; - color: white; - font-weight: bold; - - & + li { - border-left-width: 0; - } - } - - &.nav-disabled { - color: #c2c2c2; - } - - &.nb_plugin { - border: none; - background-color: transparent; - } - - &.dots { - padding: 8px 10px; - } - - &:not(.current):not(.nb_plugin):not(.nav-disabled):hover { - background-color: #ddd; - cursor: pointer; - } - - &:first-child { - border-top-left-radius: 3px; - border-bottom-left-radius: 3px; - } - - &:last-child { - border-top-right-radius: 3px; - border-bottom-right-radius: 3px; - } - - &:not(:first-child) { - border-left-width: 0; - } - } - } - - .network-mail { - display: inline-block; - margin: 20px 10px; - } - } - - &.installed { - .plugins { - .plugin { - min-height: 84px; - margin-bottom: 5px; - - .main { - height: 100%; - - .details { - flex: 1; - border-right: 1px solid #DDD; - padding: 5px 0 0 10px; - height: 100%; - display: flex; - flex-direction: column; - overflow: hidden; - text-overflow: ellipsis; - - .misc-right { - color: #8f8f8f; - } - } - - .buttons { - width: 26px; - padding: 4px 5px; - } - } - - .footer { - background: none; - border: none; - margin: -25px 36px 0 0; - - .misc-left { - margin-left: 15px; - } - } - } - } - } -} diff --git a/css/palettes/_darker.scss b/css/palettes/_darker.scss index 53aa94d76f..3167f2cbdf 100644 --- a/css/palettes/_darker.scss +++ b/css/palettes/_darker.scss @@ -848,61 +848,6 @@ div.progress { border: 1px solid #333; } -/** marketplace **/ -.marketplace { - background-color: inherit; - - .left-panel .plugins-tags .tag { - background: #292929; - border-color: #000; - } - - .right-panel { - .plugins { - .plugin { - background-color: transparent; - border-color: #000; - - .main .buttons button { - background-color: #000; - border-color: #000; - } - - .footer .misc-left .links a i { - color: rgb(102, 102, 102); - } - - .footer { - background-color: #151515; - border-top-color: #000; - } - } - } - - .pagination { - li { - background-color: #292929; - border-color: #000; - color: rgb(170, 170, 170); - - &.current { - color: rgb(128, 128, 128); - background: #000; - border-color: #000; - } - - &.nav-disabled { - color: #666; - } - - &:not(.current):not(.nb_plugin):not(.nav-disabled):hover { - background-color: #474747; - } - } - } - } -} - .ui-tabs-nav li.ui-tabs-tab:focus, .ui-tabs-nav li.ui-tabs-tab a:focus { outline: none; } diff --git a/front/marketplace.download.php b/front/marketplace.download.php deleted file mode 100644 index bc7e0c7777..0000000000 --- a/front/marketplace.download.php +++ /dev/null @@ -1,42 +0,0 @@ -. - * --------------------------------------------------------------------- - */ - -include ("../inc/includes.php"); - -Session::checkRight("config", UPDATE); - -use Glpi\Marketplace\Controller as MarketplaceController; - -if (isset($_REQUEST['key'])) { - $marketplace_ctrl = new MarketplaceController($_REQUEST['key']); - $marketplace_ctrl->proxifyPluginArchive(); -} diff --git a/front/marketplace.php b/front/marketplace.php deleted file mode 100644 index b443e05544..0000000000 --- a/front/marketplace.php +++ /dev/null @@ -1,47 +0,0 @@ -. - * --------------------------------------------------------------------- - */ - -include ('../inc/includes.php'); - -Session::checkRight("config", UPDATE); - -// This has to be called before search process is called, in order to add -// "new" plugins in DB to be able to display them. -$plugin = new Plugin(); -$plugin->checkStates(true); - -Html::header(__('Marketplace'), $_SERVER['PHP_SELF'], "config", "plugin", "marketplace"); - -$market_view = new \Glpi\Marketplace\View(); -$market_view->display(); - -Html::footer(); diff --git a/front/plugin.php b/front/plugin.php index 2ec51f9d72..d900458761 100644 --- a/front/plugin.php +++ b/front/plugin.php @@ -41,8 +41,6 @@ Html::header(__('Setup'), $_SERVER['PHP_SELF'], "config", "plugin"); -\Glpi\Marketplace\View::showFeatureSwitchDialog(); - $catalog_btn = '
' . '' . __('See the catalog of plugins') diff --git a/inc/based_config.php b/inc/based_config.php index 8a7dcdc492..5be91ade59 100644 --- a/inc/based_config.php +++ b/inc/based_config.php @@ -55,7 +55,6 @@ // Constants related to system paths 'GLPI_CONFIG_DIR' => GLPI_ROOT . '/config', // Path for configuration files (db, security key, ...) 'GLPI_VAR_DIR' => GLPI_ROOT . '/files', // Path for all files - 'GLPI_MARKETPLACE_DIR' => GLPI_ROOT . '/marketplace', // Path for marketplace plugins 'GLPI_DOC_DIR' => '{GLPI_VAR_DIR}', // Path for documents storage 'GLPI_CACHE_DIR' => '{GLPI_VAR_DIR}/_cache', // Path for cache 'GLPI_CRON_DIR' => '{GLPI_VAR_DIR}/_cron', // Path for cron storage @@ -82,11 +81,6 @@ // Constants related to GLPI Project external services 'GLPI_TELEMETRY_URI' => 'https://telemetry.glpi-project.org', // Telemetry project URL 'GLPI_INSTALL_MODE' => is_dir(GLPI_ROOT . '/.git') ? 'GIT' : 'TARBALL', // Install mode for telemetry - 'GLPI_MARKETPLACE_PLUGINS_API_URI' => '{GLPI_NETWORK_SERVICES}/api/glpi-plugins/', - // TODO set false before final release of 9.5.0 and remove this comment - 'GLPI_MARKETPLACE_PRERELEASES' => false, // display pre-releases of plugins in marketplace - 'GLPI_MARKETPLACE_ALLOW_OVERRIDE' => true, // allow marketplace to override a plugin found outside GLPI_MARKETPLACE_DIR - 'GLPI_MARKETPLACE_MANUAL_DOWNLOADS' => true, // propose manual download link of plugins which cannot be installed/updated by marketplace 'GLPI_USER_AGENT_EXTRA_COMMENTS' => '', // Extra comment to add to GLPI User-Agent // Other constants @@ -143,7 +137,6 @@ function ($matches) { // Order in this array is important (priority to first found). if (!defined('PLUGINS_DIRECTORIES')) { define('PLUGINS_DIRECTORIES', [ - GLPI_MARKETPLACE_DIR, GLPI_ROOT . '/plugins', ]); } else if (!is_array(PLUGINS_DIRECTORIES)) { diff --git a/inc/define.php b/inc/define.php index b229144cab..5c85c2f10d 100644 --- a/inc/define.php +++ b/inc/define.php @@ -337,7 +337,7 @@ 'ObjectLock', 'PlanningRecall', 'Problem', 'Project', 'ProjectTask', 'Reservation', 'SoftwareLicense', 'Ticket', 'User', - 'SavedSearch_Alert', 'Certificate', 'Glpi\\Marketplace\\Controller', + 'SavedSearch_Alert', 'Certificate', 'Domain']; $CFG_GLPI["contract_types"] = array_merge(['Computer', 'Monitor', 'NetworkEquipment', @@ -515,7 +515,6 @@ 'notificationtemplate' => ['tinymce'] ], 'plugin'=> [ - 'marketplace' => ['marketplace'] ] ], 'admin' => ['clipboard'], diff --git a/inc/html.class.php b/inc/html.class.php index 7453f73d68..39000f32ef 100644 --- a/inc/html.class.php +++ b/inc/html.class.php @@ -1294,11 +1294,6 @@ static function includeHeader($title = '', $sector = 'none', $item = 'none', $op Html::requireJs('dashboard'); } - if (in_array('marketplace', $jslibs)) { - echo Html::scss('css/marketplace'); - Html::requireJs('marketplace'); - } - if (in_array('rack', $jslibs)) { Html::requireJs('rack'); } @@ -6549,9 +6544,6 @@ static public function requireJs($name) { case 'dashboard': $_SESSION['glpi_js_toload'][$name][] = 'js/dashboard.js'; break; - case 'marketplace': - $_SESSION['glpi_js_toload'][$name][] = 'js/marketplace.js'; - break; case 'gridstack': $_SESSION['glpi_js_toload'][$name][] = 'public/lib/gridstack.js'; break; diff --git a/inc/includes.php b/inc/includes.php index ea98092141..da4782b5b2 100644 --- a/inc/includes.php +++ b/inc/includes.php @@ -148,7 +148,7 @@ && !isAPI() && isset($_POST) && is_array($_POST) && count($_POST)) { // No ajax pages - if (!preg_match(':'.$CFG_GLPI['root_doc'].'(/(plugins|marketplace)/[^/]*|)/ajax/:', $_SERVER['REQUEST_URI'])) { + if (!preg_match(':'.$CFG_GLPI['root_doc'].'(/plugins/[^/]*|)/ajax/:', $_SERVER['REQUEST_URI'])) { Session::checkCSRF($_POST); } } diff --git a/inc/marketplace/api/plugins.class.php b/inc/marketplace/api/plugins.class.php deleted file mode 100644 index 3eb64b6426..0000000000 --- a/inc/marketplace/api/plugins.class.php +++ /dev/null @@ -1,526 +0,0 @@ -. - * --------------------------------------------------------------------- - */ - -namespace Glpi\Marketplace\Api; - -if (!defined('GLPI_ROOT')) { - die("Sorry. You can't access directly to this file"); -} - -use GuzzleHttp\Exception\RequestException; -use GuzzleHttp\Psr7; -use GuzzleHttp\Psr7\Response; -use \GuzzleHttp\Client as Guzzle_Client; -use \GLPINetwork; -use \Toolbox; -use \Session; - -class Plugins { - protected $httpClient = null; - protected $last_error = ""; - - public const COL_PAGE = 200; - protected const TIMEOUT = 5; - - static $plugins = []; - - function __construct(bool $connect = false) { - global $CFG_GLPI; - - $options = [ - 'base_uri' => GLPI_MARKETPLACE_PLUGINS_API_URI, - 'connect_timeout' => self::TIMEOUT, - ]; - - // add proxy string if configured in glpi - if (!empty($CFG_GLPI["proxy_name"])) { - $proxy_creds = !empty($CFG_GLPI["proxy_user"]) - ? $CFG_GLPI["proxy_user"].":".Toolbox::sodiumDecrypt($CFG_GLPI["proxy_passwd"])."@" - : ""; - $proxy_string = "http://{$proxy_creds}".$CFG_GLPI['proxy_name'].":".$CFG_GLPI['proxy_port']; - $options['proxy'] = $proxy_string; - } - - // init guzzle client with base options - $this->httpClient = new Guzzle_Client($options); - } - - - /** - * Send a http request to services api - * using the base url set in constructor and the current endpoint - * - * @param string $endpoint which resource whe need to query - * @param array $options array of options for guzzle lib - * @param string $method GET/POST, etc - * - * @return Psr\Http\Message\ResponseInterface|false - */ - private function request( - string $endpoint = '', - array $options = [], - string $method = 'GET' - ) { - if (!GLPINetwork::isRegistered()) { - // Simulate empty response if registration key is not valid - return new Response(200, [], '[]'); - } - - $options['headers'] = array_merge_recursive( - [ - 'Accept' => 'application/json', - 'User-Agent' => GLPINetwork::getGlpiUserAgent(), - 'X-Registration-Key' => GLPINetwork::getRegistrationKey(), - 'X-Glpi-Network-Uid' => GLPINetwork::getGlpiNetworkUid(), - ], - $options['headers'] ?? [] - ); - - try { - $response = $this->httpClient->request($method, $endpoint, $options); - - } catch (RequestException $e) { - $this->last_error = [ - 'title' => "Plugins API error", - 'exception' => $e->getMessage(), - 'request' => Psr7\str($e->getRequest()), - ]; - if ($e->hasResponse()) { - $this->last_error['response'] = Psr7\str($e->getResponse()); - } - - if ($_SESSION['glpi_use_mode'] == Session::DEBUG_MODE) { - Toolbox::logDebug($this->last_error); - } - return false; - } - - return $response; - } - - - /** - * Send an http request on an endpoint accepting paginated queries - * - * @param string $endpoint which resource whe need to query - * @param array $options array of options for guzzle lib - * @param string $method GET/POST, etc - * - * @return array full collection - */ - private function getPaginatedCollection( - string $endpoint = '', - array $options = [], - string $method = 'GET' - ): array { - $collection = []; - $i = 0; - do { - $request_options = array_merge_recursive([ - 'headers' => [ - 'X-Range' => ($i * self::COL_PAGE)."-".(($i + 1) * self::COL_PAGE - 1), - ], - ], $options); - $response = $this->request($endpoint, $request_options, $method); - - if ($current = ($response !== false ? json_decode($response->getBody(), true) : false)) { - $collection = array_merge($collection, $current); - } - - $i++; - } while ($current !== false && count($current)); - - return $collection; - } - - - /** - * Return the full list of avaibles plugins on services API - * - * @param bool $force_refresh if false, we will return results stored in local cache - * @param string $tag_filter filter the plugin list by given tag - * @param string $string_filter filter the plugin list by given string - * @param string $sort sort-alpha-asc|sort-alpha-desc|sort-dl|sort-update|sort-added|sort-note - * - * @return array collection of plugins - */ - function getAllPlugins( - bool $force_refresh = false, - string $tag_filter = "", - string $string_filter = "", - string $sort = 'sort-alpha-asc' - ) { - global $GLPI_CACHE; - - $plugins_colct = []; - if (!$force_refresh && $GLPI_CACHE->has('marketplace_all_plugins')) { - $plugins_colct = $GLPI_CACHE->get('marketplace_all_plugins'); - } - - if (!count($plugins_colct)) { - $plugins = $this->getPaginatedCollection('plugins'); - - // replace keys indexes by system names - $plugins_keys = array_column($plugins, 'key'); - $plugins_colct = array_combine($plugins_keys, $plugins); - - foreach ($plugins_colct as &$plugin) { - usort( - $plugin['versions'], - function ($a, $b) { - return version_compare($a['num'], $b['num']); - } - ); - } - - $GLPI_CACHE->set('marketplace_all_plugins', $plugins_colct, HOUR_TIMESTAMP); - } - - // Filter versions. - // Done after caching process to be able to handle change of "GLPI_MARKETPLACE_PRERELEASES" - // without having to purge the cache manually. - foreach ($plugins_colct as &$plugin) { - if (!GLPI_MARKETPLACE_PRERELEASES) { - $plugin['versions'] = array_filter($plugin['versions'], function($version) { - return !isset($version['stability']) || $version['stability'] === "stable"; - }); - } - - if (count($plugin['versions']) === 0) { - continue; - } - - $higher_version = end($plugin['versions']); - if (is_array($higher_version)) { - $plugin['installation_url'] = $higher_version['download_url']; - $plugin['version'] = $higher_version['num']; - } - } - self::$plugins = $plugins_colct; - - // Remove plugins with no versions for current config (i.e. only unstable versions that are not proposed). - $plugins_colct = array_filter( - $plugins_colct, - function($plugin) { - return count($plugin['versions']) > 0; - } - ); - - if (strlen($tag_filter) > 0) { - $tagged_plugins = array_column($this->getPluginsForTag($tag_filter), 'key'); - $plugins_colct = array_intersect_key($plugins_colct, array_flip($tagged_plugins)); - } - - if (strlen($string_filter) > 0) { - $plugins_colct = array_filter($plugins_colct, function($plugin) use ($string_filter) { - return strpos(strtolower(json_encode($plugin)), strtolower($string_filter)) !== false; - }); - } - - // manage sorting of collection - uasort($plugins_colct, function($plugin1, $plugin2) use ($sort) { - switch ($sort) { - case "sort-alpha-asc": - return strnatcasecmp($plugin1['name'], $plugin2['name']); - case "sort-alpha-desc": - return strnatcasecmp($plugin2['name'], $plugin1['name']); - case "sort-dl": - return strnatcmp($plugin2['download_count'], $plugin1['download_count']); - case "sort-update": - return strnatcmp($plugin2['date_updated'], $plugin1['date_updated']); - case "sort-added": - return strnatcmp($plugin2['date_added'], $plugin1['date_added']); - case "sort-note": - return strnatcmp($plugin2['note'], $plugin1['note']); - } - }); - - return $plugins_colct; - } - - - /** - * Return plugins list for the given page - * - * @param bool $force_refresh if false, we will return results stored in local cache - * @param string $tag_filter filter the plugin list by given tag - * @param string $string_filter filter the plugin list by given string - * @param int $page which page to query - * @param int $nb_per_page how manyu per page we want - * @param string $sort sort-alpha-asc|sort-alpha-desc|sort-dl|sort-update|sort-added|sort-note - * - * @return array full collection - */ - function getPaginatedPlugins( - bool $force_refresh = false, - string $tag_filter = "", - string $string_filter = "", - int $page = 1, - int $nb_per_page = 15, - string $sort = 'sort-alpha-asc' - ) { - $plugins = $this->getAllPlugins($force_refresh, $tag_filter, $string_filter, $sort); - - $plugins_page = array_splice($plugins, max($page - 1, 0) * $nb_per_page, $nb_per_page); - return $plugins_page; - } - - - /** - * return the number of available plugins in distant API - * - * @param string $tag_filter filter the plugin list by given tag - * - * @return int number of plugins - */ - function getNbPlugins(string $tag_filter = "") { - $plugins = $this->getAllPlugins(false, $tag_filter); - - return count($plugins); - } - - - /** - * get top 10 plugins sorted by trending (most downloaded in the last month) criterion - * - * @return array collection of plugins - */ - function getTrendingPlugins() { - return $this->getTopPlugins("trending"); - } - - - /** - * get top 10 plugins sorted by popular (most downloaded all time) criterion - * - * @return array collection of plugins - */ - function getPopularPlugins() { - return $this->getTopPlugins("popular"); - } - - /** - * get top 10 plugins sorted by their submition date (DESC sort) criterion - * - * @return array collection of plugins - */ - function getNewPlugins() { - return $this->getTopPlugins("new"); - } - - /** - * get top 10 plugins sorted by their update date (DESC sort) criterion - * - * @return array collection of plugins - */ - function getUpdatedPlugins() { - return $this->getTopPlugins("updated"); - } - - /** - * get top 10 plugins sorted by given criterion (see other getTopXXX methods) - * - * @param string $endpoint criterion to filter plugsin - * - * @return array collection of plugins - */ - function getTopPlugins(string $endpoint = "") { - $response = $this->request("plugin/{$endpoint}"); - - if ($response === false) { - return []; - } - - $top = json_decode($response->getBody(), true); - $key_list = array_column($top, 'key', 'key'); - $plugins = $this->getAllPlugins(); - - $top_plugins = array_filter($plugins, function($plugin) use($key_list) { - return in_array($plugin['key'], $key_list); - }); - - return $top_plugins; - } - - - /** - * Get a single plugin array - * - * @param string $key plugin system name - * @param bool $force_refresh if false, we will return results stored in local cache - * - * @return array plugin data - */ - public function getPlugin(string $key = "", bool $force_refresh = false): array { - $plugins_list = []; - if ($force_refresh || !count(self::$plugins)) { - $plugins_list = $this->getAllPlugins($force_refresh); - } else { - $plugins_list = self::$plugins; - } - - return $plugins_list[$key] ?? []; - } - - - /** - * Inform plugins API that a plugin (by its key) has been downloaded - * and the download counter must be incremented - * - * @param string $key plugin system key - * - * @return void we don't wait for a response, this a fire and forget request - */ - public function incrementPluginDownload(string $key = "") { - $this->request( - "plugin/{$key}/download", - [ - 'allow_redirects' => false, // Prevent follow redirects to download page sent by Plugins API - ] - ); - } - - - /** - * Get top list of tags for current session language - * - * @return array top tags - */ - public function getTopTags(): array { - global $CFG_GLPI; - - $response = $this->request('tags/top', [ - 'headers' => [ - 'X-Lang' => $CFG_GLPI['languages'][$_SESSION['glpilanguage']][2] - ] - ]); - - if ($response === false) { - return []; - } - - $toptags = json_decode($response->getBody(), true); - - return $toptags; - } - - - /** - * get a plugins collection for the givent tag - * - * @param string $tag to filter plugins - * @param bool $force_refresh if false, we will return results stored in local cache - * - * @return array filtered plugin collection - */ - public function getPluginsForTag(string $tag = "", bool $force_refresh = false): array { - global $GLPI_CACHE; - - $plugins_colct = []; - if (!$force_refresh && $GLPI_CACHE->has("marketplace_tag_$tag")) { - $plugins_colct = $GLPI_CACHE->get("marketplace_tag_$tag"); - } - - if (!count($plugins_colct)) { - $plugins_colct = $this->getPaginatedCollection("tags/{$tag}/plugin"); - $GLPI_CACHE->set("marketplace_tag_$tag", $plugins_colct, HOUR_TIMESTAMP); - } - - return $plugins_colct; - } - - - /** - * Download plugin archive and follow progress with a session var `marketplace_dl_progress` - * - * @param string $url where is the plugin - * @param string $dest where we store it it - * @param string $plugin_key plugin system name - * - * @return bool - */ - public function downloadArchive(string $url, string $dest, string $plugin_key, bool $track_progress = true): bool { - if ($track_progress) { - if (!isset($_SESSION['marketplace_dl_progress'])) { - $_SESSION['marketplace_dl_progress'] = []; - } - $_SESSION['marketplace_dl_progress'][$plugin_key] = 0; - } - - // close session to permits polling of progress by frontend - session_write_close(); - - $options = [ - 'headers' => [ - 'Accept' => '*/*', - ], - 'sink' => $dest, - ]; - if ($track_progress) { - // track download progress - $options['progress'] = function($downloadTotal, $downloadedBytes) use ($plugin_key) { - // Prevent "net::ERR_RESPONSE_HEADERS_TOO_BIG" error - // Each time Session::start() is called, PHP add a 'Set-Cookie' header, - // so if a plugin takes more than a few seconds to be downloaded, PHP will set too many - // 'Set-Cookie' headers and response will not be accepted by browser. - // We can remove the 'Set-Cookie' here as it will be put back on next instruction (Session::start()). - header_remove('Set-Cookie'); - - // restart session to store percentage of download for this plugin - Session::start(); - - // calculate percent based on the size and store it in session - $percent = 0; - if ($downloadTotal > 0) { - $percent = round($downloadedBytes * 100 / $downloadTotal); - } - $_SESSION['marketplace_dl_progress'][$plugin_key] = $percent; - - // reclose session to avoid blocking ajax requests - session_write_close(); - }; - } - - $response = $this->request($url, $options); - - // restart session to permits write of vars - // (later, we also may have some addMessageAfterRedirect to provider errors to user) - Session::start(); - - if ($track_progress) { - // force finish of download (to avoid keeping js loop in case of errors) - $_SESSION['marketplace_dl_progress'][$plugin_key] = 100; - } - - return $response !== false && $response->getStatusCode() === 200; - } -} diff --git a/inc/marketplace/controller.class.php b/inc/marketplace/controller.class.php deleted file mode 100644 index cf110ed306..0000000000 --- a/inc/marketplace/controller.class.php +++ /dev/null @@ -1,497 +0,0 @@ -. - * --------------------------------------------------------------------- - */ - -namespace Glpi\Marketplace; - -if (!defined('GLPI_ROOT')) { - die("Sorry. You can't access directly to this file"); -} - - -use Glpi\Marketplace\Api\Plugins as PluginsApi; -use \wapmorgan\UnifiedArchive\UnifiedArchive; -use \wapmorgan\UnifiedArchive\Exceptions\ArchiveExtractionException; -use \Plugin; -use \Toolbox; -use \Session; -use \GLPINetwork; -use \CommonGLPI; -use \Config; -use \NotificationEvent; -use \CronTask; - -class Controller extends CommonGLPI { - protected $plugin_key = ""; - - static $rightname = 'config'; - static $api = null; - - const MP_REPLACE_ASK = 1; - const MP_REPLACE_YES = 2; - const MP_REPLACE_NEVER = 3; - - function __construct(string $plugin_key = "") { - $this->plugin_key = $plugin_key; - } - - - static function getTypeName($nb = 0) { - return __('Marketplace'); - } - - /** - * singleton return the current api instance - * - * @return PluginsApi - */ - static function getAPI(): PluginsApi { - return self::$api ?? (self::$api = new PluginsApi()); - } - - - /** - * Download and uncompress plugin archive - * - * @return int plugin status, @see properties of \Plugin class - */ - function downloadPlugin():int { - if (!self::hasWriteAccess()) { - return Plugin::UNKNOWN; - } - - $api = self::getAPI(); - $plugin = $api->getPlugin($this->plugin_key, true); - - $url = $plugin['installation_url'] ?? ""; - $filename = basename(parse_url($url, PHP_URL_PATH)); - $dest = GLPI_TMP_DIR . '/' . $filename; - - if (!$api->downloadArchive($url, $dest, $this->plugin_key)) { - Session::addMessageAfterRedirect( - __('Unable to download plugin archive.'), - false, - ERROR - ); - return Plugin::UNKNOWN; - } - - // extract the archive - if (!UnifiedArchive::canOpenArchive($dest)) { - $type = UnifiedArchive::detectArchiveType($dest); - Session::addMessageAfterRedirect( - sprintf(__('Plugin archive format is not supported by your system : %s.'), $type), - false, - ERROR - ); - return Plugin::UNKNOWN; - } - $archive = UnifiedArchive::open($dest); - $error = $archive === null; - if (!$error) { - // clean dir in case of update - Toolbox::deleteDir(GLPI_MARKETPLACE_DIR."/{$this->plugin_key}"); - - try { - // copy files - $archive->extractFiles(GLPI_MARKETPLACE_DIR) !== false; - } catch (ArchiveExtractionException $e) { - $error = true; - } - } - - if ($error) { - Session::addMessageAfterRedirect( - __('Unable to extract plugin archive.'), - false, - ERROR - ); - return Plugin::UNKNOWN; - } - - $plugin_inst = new Plugin(); - - if ($plugin_inst->getFromDBbyDir($this->plugin_key) - && !in_array($plugin_inst->fields['state'], [Plugin::ANEW, Plugin::NOTINSTALLED, Plugin::NOTUPDATED])) { - // Plugin was already existing, make it "not updated" before checking its state - // to prevent message like 'Plugin "xxx" version changed. It has been deactivated as its update process has to be launched.'. - $plugin_inst->update([ - 'id' => $plugin_inst->fields['id'], - 'state' => Plugin::NOTUPDATED - ]); - } - - $plugin_inst->checkPluginState($this->plugin_key); - $plugin_inst->getFromDBbyDir($this->plugin_key); - - // inform api the plugin has been downloaded - $api->incrementPluginDownload($this->plugin_key); - - // try to install (or update) directly the plugin - return $this->installPlugin(); - } - - - /** - * Get plugin archive from its download URL and serve it to the browser. - * - * @return void - */ - function proxifyPluginArchive(): void { - // close session to prevent blocking other requests - session_write_close(); - - $api = self::getAPI(); - $plugin = $api->getPlugin($this->plugin_key, true); - - if (!array_key_exists('installation_url', $plugin) || empty($plugin['installation_url'])) { - return; - } - - $url = $plugin['installation_url']; - $filename = basename(parse_url($url, PHP_URL_PATH)); - $dest = GLPI_TMP_DIR . '/' . mt_rand() . '.' . $filename; - - if (!$api->downloadArchive($url, $dest, $this->plugin_key, false)) { - http_response_code(500); - echo(__('Unable to download plugin archive.')); - return; - } - - Toolbox::sendFile($dest, $filename); - } - - /** - * Check if plugin can be overwritten. - * - * @return bool - */ - public function canBeOverwritten(): bool { - $found_in_marketplace_dir = file_exists(GLPI_MARKETPLACE_DIR . '/' . $this->plugin_key . '/setup.php'); - - // Compute marketplace dir priority - $marketplace_priority = null; - foreach (PLUGINS_DIRECTORIES as $position => $base_dir) { - if (realpath($base_dir) !== false && realpath($base_dir) === realpath(GLPI_MARKETPLACE_DIR)) { - $marketplace_priority = -$position; - break; - } - } - - $found_outside_marketplace = false; - $found_dir_priority = null; - foreach (PLUGINS_DIRECTORIES as $position => $base_dir) { - if (file_exists($base_dir . '/' . $this->plugin_key . '/setup.php')) { - $found_outside_marketplace = true; - $found_dir_priority = -$position; - break; // Do not search in other directories with lower priorities - } - } - - if ($found_outside_marketplace) { - if ($found_dir_priority > $marketplace_priority) { - // Plugin has been found outside marketplace and marketplace priority is lower than its parent directory - // -> disallow plugin update from marketplace as it cannot be loaded from there. - return false; - } else if ($found_in_marketplace_dir) { - // Plugin has been found on marketplace and marketplace priority is higher than other location - // -> allow plugin update from marketplace as it is already loaded from there. - return is_writable(GLPI_MARKETPLACE_DIR . '/' . $this->plugin_key); - } else { - // Plugin has been found outside marketplace and does not exist in marketplace - // -> allow plugin update unless GLPI_MARKETPLACE_ALLOW_OVERRIDE is false. - return GLPI_MARKETPLACE_ALLOW_OVERRIDE && self::hasWriteAccess(); - } - } - - return self::hasWriteAccess(); - } - - - /** - * Check if a given plugin has on update online - * - * @param Plugin $plugin_inst - * - * @return string|false new version number - */ - function checkUpdate(Plugin $plugin_inst = null) { - $api = self::getAPI(); - $api_plugin = $api->getPlugin($this->plugin_key); - $local_plugin = $plugin_inst->fields; - - $api_version = $api_plugin['version'] ?? ""; - $local_version = $local_plugin['version'] ?? ""; - - if (strlen($api_version) && $api_version !== $local_version) { - return $api_version; - } - - return false; - } - - - /** - * Check for plugins updates - * Parse all installed plugin and check against API if a news version is available - * - * @return array of [plugin_key => new_version_num] - */ - static function getAllUpdates() { - $plugin_inst = new Plugin; - $plugin_inst->init(true); - $installed = $plugin_inst->getList(); - - $updates = []; - - foreach ($installed as $plugin) { - $plugin_key = $plugin['directory']; - $plugin_inst->getFromDBbyDir($plugin_key); - - $mk_controller = new self($plugin_key); - if (false !== ($api_version = $mk_controller->checkUpdate($plugin_inst))) { - $updates[$plugin_key] = $api_version; - } - } - - return $updates; - } - - - static function cronInfo($name) { - return ['description' => __('Check all plugin updates')]; - } - - - /** - * Crontask : Check for plugins updates - * - * @param CronTask|null $task to log, if NULL display (default NULL) - * - * @return integer 0 : nothing to do 1 : done with success - */ - static function cronCheckAllUpdates(CronTask $task = null):int { - global $CFG_GLPI; - - $cron_status = 0; - - if (!GLPINetwork::isRegistered()) { - return $cron_status; - } - - $updates = self::getAllUpdates(); - if (count($updates)) { - $cron_status = 1; - $task->addVolume(count($updates)); - foreach ($updates as $plugin_key => $version) { - $task->log(sprintf(__("New version for plugin %s: %s"), $plugin_key, $version)); - } - - if (!$CFG_GLPI["use_notifications"]) { - return $cron_status; - } - - NotificationEvent::raiseEvent('checkpluginsupdate', new self(), [ - 'plugins' => $updates - ]); - } - - return $cron_status; - } - - - /** - * Do the current plugin requires some Glpi Network offers - * - * @return array [offer ref => offer title] - */ - function getRequiredOffers(): array { - $api = self::getAPI(); - $api_plugin = $api->getPlugin($this->plugin_key); - $offers = array_column(GLPINetwork::getOffers(), 'title', 'offer_reference'); - - $trans_offers = array_intersect_key($offers, array_flip($api_plugin['required_offers'] ?? [])); - - return $trans_offers; - } - - - /** - * Check a plugin can be download - * - * @return bool - */ - function canBeDownloaded() { - $api = self::getAPI(); - $api_plugin = $api->getPlugin($this->plugin_key); - - return strlen($api_plugin['installation_url'] ?? "") > 0; - } - - /** - * Check if plugin is eligible inside an higher offer. - * - * @return bool - */ - public function requiresHigherOffer(): bool { - $api_plugin = self::getAPI()->getPlugin($this->plugin_key); - - if (!isset($api_plugin['required_offers'])) { - return false; - } - - $registration_informations = GLPINetwork::getRegistrationInformations(); - if ($registration_informations['subscription'] !== null - && $registration_informations['subscription']['is_running']) { - if (in_array($registration_informations['subscription']['offer_reference'], $api_plugin['required_offers'])) { - return false; - } - } - - return true; - } - - - /** - * Install current plugin - * - * @param bool $disable_messages drop any messages after plugin installation - * - * @return bool - */ - function installPlugin(bool $disable_messages = false):bool { - $state = $this->setPluginState("install"); - - if ($disable_messages) { - $_SESSION['MESSAGE_AFTER_REDIRECT'] = []; - } - - return $state == Plugin::NOTACTIVATED; - } - - - /** - * Ununstall current plugin - * - * @return bool - */ - function uninstallPlugin():bool { - return $this->setPluginState("uninstall") == Plugin::NOTINSTALLED; - } - - - /** - * Enable current plugin - * - * @return bool - */ - function enablePlugin():bool { - return $this->setPluginState("activate") == Plugin::ACTIVATED; - } - - - /** - * Disable current plugin - * - * @return bool - */ - function disablePlugin():bool { - return $this->setPluginState("unactivate") == Plugin::NOTACTIVATED; - } - - - /** - * Clean (remove database data) current plugin - * - * @return bool - */ - function cleanPlugin():bool { - $plugin = new Plugin; - if ($plugin->getFromDBbyDir($this->plugin_key)) { - $plugin->clean($plugin->fields['id']); - } - - if (!$plugin->getFromDBbyDir($this->plugin_key)) { - return true; - } - - return false; - } - - /** - * Check if marketplace controller has write access to install/update plugins source code. - * - * @return bool - */ - public static function hasWriteAccess(): bool { - return is_dir(GLPI_MARKETPLACE_DIR) && is_writable(GLPI_MARKETPLACE_DIR); - } - - - /** - * Call an action method (install/enable/...) for the current plugin - * method called internally by installPlugin, uninstallPlugin, enablePlugin, disablePlugin - * - * @param string $method - * - * @return int plugin status, @see properties of \Plugin class - */ - private function setPluginState(string $method = ""): int { - ob_start(); - $plugin = new Plugin; - $plugin->checkPluginState($this->plugin_key); - if ($plugin->getFromDBbyDir($this->plugin_key)) { - call_user_func([$plugin, $method], $plugin->fields['id']); - } - - $plugin->checkPluginState($this->plugin_key); - $plugin->getFromDBbyDir($this->plugin_key); - - // reload plugins - $plugin->init(true); - - ob_end_clean(); - - return $plugin->fields['state'] ?? -1; - } - - - /** - * Return current config of for the replacement of former plugins list - * - * @return int config status (self::MP_REPLACE_ASK, self::MP_REPLACE_YES, self::MP_REPLACE_NEVER) - */ - static function getPluginPageConfig() { - $config = Config::getConfigurationValues('core', ['marketplace_replace_plugins']); - - return (int) ($config['marketplace_replace_plugins'] ?? self::MP_REPLACE_ASK); - } -} diff --git a/inc/marketplace/notificationtargetcontroller.class.php b/inc/marketplace/notificationtargetcontroller.class.php deleted file mode 100644 index 03277caa58..0000000000 --- a/inc/marketplace/notificationtargetcontroller.class.php +++ /dev/null @@ -1,133 +0,0 @@ -. - * --------------------------------------------------------------------- - */ - -namespace Glpi\Marketplace; - -if (!defined('GLPI_ROOT')) { - die("Sorry. You can't access directly to this file"); -} - -use \Notification; -use \NotificationTarget; -use \Plugin; -use \Session; - -// Class NotificationTarget -class NotificationTargetController extends NotificationTarget { - - /** - * Overwrite the function in NotificationTarget because there's only one target to be notified - * - * @see NotificationTarget::addNotificationTargets() - **/ - function addNotificationTargets($entity) { - - $this->addProfilesToTargets(); - $this->addGroupsToTargets($entity); - $this->addTarget(Notification::GLOBAL_ADMINISTRATOR, __('Administrator')); - } - - - function getEvents() { - return ['checkpluginsupdate' => __('Check all plugin updates')]; - } - - - function addDataForTemplate($event, $options = []) { - $updated_plugins = $options['plugins']; - $plugin = new Plugin; - foreach ($updated_plugins as $plugin_key => $version) { - $plugin_info = $plugin->getInformationsFromDirectory($plugin_key); - - $this->data['plugins'][] = [ - '##plugin.name##' => $plugin_info['name'], - '##plugin.key##' => $plugin_key, - '##plugin.version##' => $version, - '##plugin.old_version##' => $plugin_info['version'], - ]; - } - - $this->getTags(); - foreach ($this->tag_descriptions[NotificationTarget::TAG_LANGUAGE] as $tag => $values) { - if (!isset($this->data[$tag])) { - $this->data[$tag] = $values['label']; - } - } - } - - - function getTags() { - //Tags with just lang - $tags = [ - 'plugins_updates_available' => __('Some updates are available for your installed plugins!') - ]; - - foreach ($tags as $tag => $label) { - $this->addTagToList([ - 'tag' => $tag, - 'label' => $label, - 'value' => false, - 'lang' => true - ]); - } - - //Foreach global tags - $tags = [ - 'plugins' => _n('Plugin', 'Plugins', Session::getPluralNumber()), - ]; - - foreach ($tags as $tag => $label) { - $this->addTagToList([ - 'tag' => $tag, - 'label' => $label, - 'value' => false, - 'foreach' => true, - ]); - } - - // sub tags - $tags = [ - 'plugin.name' => __('Plugin name'), - 'plugin.key' => __('Plugin directory'), - 'plugin.version' => __('Plugin new version number'), - 'plugin.old_version' => __('Plugin old version number') - ]; - - foreach ($tags as $tag => $label) { - $this->addTagToList([ - 'tag' => $tag, - 'label' => $label, - 'value' => true, - ]); - } - } -} diff --git a/inc/marketplace/view.class.php b/inc/marketplace/view.class.php deleted file mode 100644 index 81d7c92341..0000000000 --- a/inc/marketplace/view.class.php +++ /dev/null @@ -1,993 +0,0 @@ -. - * --------------------------------------------------------------------- - */ - -namespace Glpi\Marketplace; - -if (!defined('GLPI_ROOT')) { - die("Sorry. You can't access directly to this file"); -} - -use Glpi\Marketplace\Api\Plugins as PluginsApi; -use Glpi\Marketplace\Controller as Controller; -use \Html; -use \Plugin; -use \Config; -use \CommonGLPI; -use \GLPINetwork; -use \Toolbox; - -class View extends CommonGLPI { - static $rightname = 'config'; - static $api = null; - - public $get_item_to_display_tab = true; - - - public const COL_PAGE = 12; - - /** - * singleton return the current api instance - * - * @return PluginsApi - */ - static function getAPI(): PluginsApi { - return self::$api ?? (self::$api = new PluginsApi()); - } - - - static function getTypeName($nb = 0) { - return __('Marketplace'); - } - - - static function canCreate() { - return self::canUpdate(); - } - - - static function getIcon() { - return "fas fa-store"; - } - - - static function getSearchURL($full = true) { - global $CFG_GLPI; - - $dir = ($full ? $CFG_GLPI['root_doc'] : ''); - return "$dir/front/marketplace.php"; - } - - - function defineTabs($options = []) { - $tabs = [ - 'no_all_tab' => true - ]; - $this->addStandardTab(__CLASS__, $tabs, $options); - - return $tabs; - } - - - function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) { - if ($item->getType() == __CLASS__) { - return [ - self::createTabEntry(__("Installed")), - self::createTabEntry(__("Discover")), - ]; - } - return ''; - } - - - static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0) { - if ($item->getType() == __CLASS__) { - switch ($tabnum) { - case 0: - self::installed(); - break; - case 1: - default: - self::discover(); - break; - } - } - - return true; - } - - - /** - * Check current reigstration status and display warning messages - * - * @return bool - */ - static function checkRegister() { - global $CFG_GLPI; - - $messages = []; - $registered = false; - - if (!GLPINetwork::isServicesAvailable()) { - array_push( - $messages, - sprintf(__('%1$s services website seems not available from your network or offline'), 'GLPI Network'), - "". - __("Maybe you could setup a proxy"). - " ". - __("or please check later") - ); - } else { - $registered = GLPINetwork::isRegistered(); - if (!$registered) { - $config_url = $CFG_GLPI['root_doc']."/front/config.form.php?forcetab=". - urlencode('GLPINetwork$1'); - - array_push( - $messages, - sprintf(__('Your %1$s registration is not valid.'), 'GLPI Network'), - __('A registration, at least a free one, is required to use marketplace!'), - "".sprintf(__('Register on %1$s'), 'GLPI Network')." ". - __('and'). " ". - "".__("fill your registration key in setup.")."" - ); - } - } - - if (count($messages)) { - echo "
"; - echo ""; - echo ""; - echo "
"; - echo "
"; - } - - return $registered; - } - - - /** - * Display installed tab (only currently installed plugins) - * - * @param bool $force_refresh do not rely on cache to get plugins list - * @param bool $only_lis display only the li tags in return html (used by ajax queries) - * @param string $tag_filter filter the plugin list by given tag - * @param string $string_filter filter the plugin by given string - * - * @return void display things - */ - static function installed( - bool $force_refresh = false, - bool $only_lis = false, - string $string_filter = "" - ) { - - $plugin_inst = new Plugin; - $plugin_inst->init(true); // reload plugins - $installed = $plugin_inst->getList(); - - $apiplugins = []; - if (self::checkRegister()) { - $api = self::getAPI(); - $apiplugins = $api->getAllPlugins($force_refresh); - } - - $plugins = []; - foreach ($installed as $plugin) { - $key = $plugin['directory']; - $apidata = $apiplugins[$key] ?? []; - - if (strlen($string_filter) - && strpos(strtolower(json_encode($plugin)), strtolower($string_filter)) === false) { - continue; - } - - $clean_plugin = [ - 'key' => $key, - 'name' => $plugin['name'], - 'logo_url' => $apidata['logo_url'] ?? "", - 'description' => $apidata['descriptions'][0]['short_description'] ?? "", - 'authors' => $apidata['authors'] ?? [['id' => 'all', 'name' => $plugin['author'] ?? ""]], - 'license' => $apidata['license'] ?? $plugin['license'] ?? "", - 'note' => $apidata['note'] ?? -1, - 'homepage_url' => $apidata['homepage_url'] ?? "", - 'issues_url' => $apidata['issues_url'] ?? "", - 'readme_url' => $apidata['readme_url'] ?? "", - 'version' => $plugin['version'] ?? "", - ]; - - $plugins[] = $clean_plugin; - } - - self::displayList($plugins, "installed", $only_lis); - } - - /** - * Display discover tab (all availble plugins) - * - * @param bool $force_refresh do not rely on cache to get plugins list - * @param bool $only_lis display only the li tags in return html (used by ajax queries) - * @param string $tag_filter filter the plugin list by given tag - * @param string $string_filter filter the plugin by given string - * @param int $page What's sub page of plugin we want to display - * @param string $sort sort-alpha-asc|sort-alpha-desc|sort-dl|sort-update|sort-added|sort-note - * - * @return void display things - */ - static function discover( - bool $force = false, - bool $only_lis = false, - string $tag_filter = "", - string $string_filter = "", - int $page = 1, - string $sort = 'sort-alpha-asc' - ) { - if (!self::checkRegister()) { - return; - } - - $api = self::getAPI(); - $plugins = $api->getPaginatedPlugins( - $force, - $tag_filter, - $string_filter, - $page, - self::COL_PAGE, - $sort - ); - - if (strlen($string_filter) > 0) { - $nb_plugins = count($plugins); - } else { - $nb_plugins = $api->getNbPlugins($tag_filter); - } - - header("X-GLPI-Marketplace-Total: $nb_plugins"); - self::displayList($plugins, "discover", $only_lis, $nb_plugins, $sort); - } - - - /** - * Return HTML part for tags list - * - * @return string tags list - */ - static function getTagsHtml() { - $api = self::getAPI(); - $tags = $api->getTopTags(); - - $tags_li = "
  • ".__("All")."
  • "; - foreach ($tags as $tag) { - $tags_li.= "
  • ".ucfirst($tag['tag'])."
  • "; - } - - return ""; - } - - - /** - * Display a list of plugins - * - * @param array $plugins list of plugins returned by - * - \Plugin::getList - * - \Glpi\Marketplace\Api\Plugins::getPaginatedPlugins - * @param string $tab current display tab (discover or installed) - * @param bool $only_lis display only the li tags in return html (used by ajax queries) - * @param int $nb_plugins total of plugins ($plugins contains only the current page) - * @param string $sort sort-alpha-asc|sort-alpha-desc|sort-dl|sort-update|sort-added|sort-note - * - * @return false|void displays things - */ - static function displayList( - array $plugins = [], - string $tab = "", - bool $only_lis = false, - int $nb_plugins = 0, - string $sort = 'sort-alpha-asc' - ) { - if (!self::canView()) { - return false; - } - - $plugins_li = ""; - foreach ($plugins as $plugin) { - $plugin['description'] = self::getLocalizedDescription($plugin); - $plugins_li.= self::getPluginCard($plugin, $tab); - } - - if (!$only_lis) { - // check writable state - if (!Controller::hasWriteAccess()) { - echo "
    - ". - sprintf(__("We can't write on the markeplace directory (%s)."), GLPI_MARKETPLACE_DIR)."
    ". - __("If you want to ease the plugins download, please check permissions and ownership of this directory.")."
    ". - __("Otherwise, you will need to download and unzip the plugins archives manually.")."
    ". - "
    "; - } - - $tags_list = $tab != "installed" - ? "
    ".self::getTagsHtml()."
    " - : ""; - $pagination = $tab != "installed" - ? self::getPaginationHtml(1, $nb_plugins) - : ""; - $sort_controls = ""; - if ($tab === "discover") { - $sort_controls = " - "; - } - - $yourplugin = __("Your plugin here ? Contact us."); - $networkmail = GLPI_NETWORK_MAIL; - $refresh_lbl = __("Refresh plugin list"); - $search_label = __("Filter plugin list"); - - $marketplace = << - {$tags_list} -
    -
    - -
    - $sort_controls - -
    -
    - - $pagination - - $yourplugin  - -
    -
    - -HTML; - echo $marketplace; - } else { - echo $plugins_li; - } - - $js = << "+option.text+""); - }; - - $('.sort-control').select2({ - templateResult: displaySortIcon, - templateSelection: displaySortIcon, - width: 135, - }); - }); -JS; - echo Html::scriptBlock($js); - } - - /** - * Return HTML part for plugin card - * - * @param array $plugin informations (title, description, etc) of the plugins - * @param string $tab current displayed tab (installed or discover) - * - * @return string the plugin card - */ - static function getPluginCard(array $plugin = [], string $tab = "discover"):string { - $plugin_key = $plugin['key']; - $plugin_inst = new Plugin; - $plugin_inst->getFromDBbyDir($plugin_key); - $plugin_state = Plugin::getStateKey($plugin_inst->fields['state'] ?? -1); - $buttons = self::getButtons($plugin_key); - - $name = Html::clean($plugin['name']); - $description = Html::clean($plugin['description']); - - $authors = Html::clean(implode(', ', array_column($plugin['authors'] ?? [], 'name', 'id')), false); - $authors_title = Html::clean($authors); - $authors = strlen($authors) - ? "{$authors}" - : ""; - - $licence = Html::clean($plugin['license'] ?? ''); - $licence = strlen($licence) - ? "{$licence}" - : ""; - - $version = Html::clean($plugin['version'] ?? ''); - $version = strlen($version) - ? "{$version}" - : ""; - - $stars = ($plugin['note'] ?? -1) > 0 - ? self::getStarsHtml($plugin['note']) - : ""; - - $home_url = Html::entities_deep($plugin['homepage_url'] ?? ""); - $home_url = strlen($home_url) - ? " - - " - : ""; - - $issues_url = Html::entities_deep($plugin['issues_url'] ?? ""); - $issues_url = strlen($issues_url) - ? " - - " - : ""; - - $readme_url = Html::entities_deep($plugin['readme_url'] ?? ""); - $readme_url = strlen($readme_url) - ? " - - " - : ""; - $icon = self::getPluginIcon($plugin); - $network = self::getNetworkInformations($plugin); - - if ($tab === "discover") { - $card = << -
    - {$icon} - -

    {$name}

    - $network -

    {$description}

    -
    - - {$buttons} - -
    - - -HTML; - } else { - $card = << -
    - {$icon} - -

    {$name}

    - -
    {$licence}
    -
    {$authors}
    -
    {$version}
    -
    -
    - - {$buttons} - -
    - - -HTML; - } - - return $card; - } - - /** - * Return HTML part for plugin stars - * - * @param float|int $value current stars note on 5 - * - * @return string plugins stars html - */ - static function getStarsHtml(float $value = 0):string { - $value = min(floor($value * 2) / 2, 5); - - $stars = ""; - for ($i = 1; $i < 6; $i++) { - if ($value >= $i) { - $stars.= ""; - } else if ($value + 0.5 == $i) { - $stars.= ""; - } else { - $stars.= ""; - } - } - - return $stars; - } - - - /** - * Return HTML part for plugin buttons - * - * @param string $plugin_key system name for the plugin - * - * @return string the buttons html - */ - static function getButtons(string $plugin_key = ""): string { - global $CFG_GLPI, $PLUGIN_HOOKS; - - $rand = mt_rand(); - $plugin_inst = new Plugin; - $exists = $plugin_inst->getFromDBbyDir($plugin_key); - $is_installed = $plugin_inst->isInstalled($plugin_key); - $is_actived = $plugin_inst->isActivated($plugin_key); - $mk_controller = new Controller($plugin_key); - $web_update_version = $mk_controller->checkUpdate($plugin_inst); - $has_web_update = $web_update_version !== false; - $has_loc_update = $plugin_inst->isUpdatable($plugin_key); - $can_be_overwritten = $mk_controller->canBeOverwritten(); - $can_be_downloaded = $mk_controller->canBeDownloaded(); - $required_offers = $mk_controller->getRequiredOffers(); - $can_be_updated = $has_web_update && $can_be_overwritten; - $can_be_cleaned = $exists && !$plugin_inst->isLoadable($plugin_key); - $config_page = $PLUGIN_HOOKS['config_page'][$plugin_key] ?? ""; - - $error = ""; - if ($exists) { - ob_start(); - $do_activate = $plugin_inst->checkVersions($plugin_key); - if (!$do_activate) { - $error.= "" . ob_get_contents() . ""; - } - ob_end_clean(); - - $function = 'plugin_' . $plugin_key . '_check_prerequisites'; - if ($do_activate && function_exists($function)) { - ob_start(); - if (!$function()) { - $error .= '' . ob_get_contents() . ''; - } - ob_end_clean(); - } - } - - $buttons = ""; - - if (strlen($error)) { - $buttons .=""; - Html::showToolTip($error, [ - 'applyto' => "plugin-error-$rand", - ]); - } - - if ($can_be_cleaned) { - $buttons .=""; - } else if ((!$exists && !$mk_controller->hasWriteAccess()) - || ($has_web_update && !$can_be_overwritten && GLPI_MARKETPLACE_MANUAL_DOWNLOADS)) { - $plugin_data = $mk_controller->getAPI()->getPlugin($plugin_key); - if (array_key_exists('installation_url', $plugin_data) && $can_be_downloaded) { - $warning = ""; - if ($has_web_update) { - $warning = __s("The plugin has an available update but its directory is not writable.")."
    "; - } - - $warning.= sprintf( - __s("Download archive manually, you must uncompress it in plugins directory (%s)"), - GLPI_ROOT . '/plugins' - ); - - // Use "marketplace.download.php" proxy if archive is downloadable from GLPI marketplace plugins API - // as this API will refuse to serve the archive if registration key is not set in headers. - $download_url = Toolbox::startsWith($plugin_data['installation_url'], GLPI_MARKETPLACE_PLUGINS_API_URI) - ? $CFG_GLPI['root_doc'] . '/front/marketplace.download.php?key=' . $plugin_key - : $plugin_data['installation_url']; - - $buttons .=" - - "; - } - } else if ($can_be_downloaded) { - if (!$exists) { - $buttons .=""; - } else if ($can_be_updated) { - $update_title = sprintf( - __s("A new version (%s) is available, update ?", 'marketplace'), - $web_update_version - ); - $buttons .=""; - } - } - - if ($mk_controller->requiresHigherOffer()) { - $warning = sprintf( - __s("You need a superior GLPI-Network offer to access to this plugin (%s)"), - implode(', ', $required_offers) - ); - - $buttons .=" - - "; - } - - if ($exists && !$can_be_cleaned && !$is_installed && !strlen($error)) { - $title = __s("Install"); - $icon = "fas fa-folder-plus"; - if ($has_loc_update) { - $title = __s("Update"); - $icon = "far fa-caret-square-up"; - } - $buttons .=""; - } - - if ($is_installed) { - if (!strlen($error)) { - if ($is_actived) { - $buttons .=""; - } else { - $buttons .=""; - } - } - - $buttons .=""; - - if (!strlen($error) && $is_actived && $config_page) { - $plugin_dir = Plugin::getWebDir($plugin_key, true); - $config_url = "$plugin_dir/$config_page"; - $buttons .=" - - "; - } - } - - return $buttons; - } - - /** - * Return HTML part for plugin logo/icon - * - * @param array $plugin data of the plugin. - * If it contains a key logo_url, the current will be inserted in a img tag - * else, it will use initials from plugin friendly name to construct - * a short and colored logo - * - * @return string the jtml for plugin logo - */ - static function getPluginIcon(array $plugin = []) { - $icon = ""; - - $logo_url = Html::entities_deep($plugin['logo_url'] ?? ""); - if (strlen($logo_url)) { - $icon = ""; - } else { - $words = explode(" ", Html::clean($plugin['name'])); - $initials = ""; - for ($i = 0; $i < 2; $i++) { - if (isset($words[$i])) { - $initials.= mb_substr($words[$i], 0, 1); - } - } - $bg_color = Toolbox::getColorForString($initials); - $fg_color = Toolbox::getFgColor($bg_color); - $icon = "$initials"; - } - - return $icon; - } - - - /** - * Return HTML part for Glpi Network informations for a given plugin - * @param array $plugin data of the plugin. - * if check agains plugin key if we need some subscription to use it - * @return string the subscription information html - */ - static function getNetworkInformations(array $plugin = []): string { - $mk_controller = new Controller($plugin['key']); - $require_offers = $mk_controller->getRequiredOffers(); - - $html = ""; - if (count($require_offers)) { - $fst_offer = array_splice($require_offers, 0, 1); - $offerkey = key($fst_offer); - $offerlabel = current($fst_offer); - - $html = "
    - - GLPI Network - - - $offerlabel - -
    "; - } - - return $html; - } - - - /** - * Retrieve localized description for a given plugin and matching the session lang - * - * @param array $plugin data of the plugin. - * in the `description` key, we must found an array of localized descirption - * indexed by lang key, return the good one - * @param string $version short_description or long_description - * - * @return string the localized description - */ - static function getLocalizedDescription(array $plugin = [], string $version = 'short_description'): string { - global $CFG_GLPI; - - $userlang = $CFG_GLPI['languages'][$_SESSION['glpilanguage']][3] ?? "en"; - - if (!isset($plugin['descriptions'])) { - return ""; - } - - $description = ""; - $fallback = ""; - foreach ($plugin['descriptions'] as $current) { - if ($current['lang'] == $userlang) { - $description = $current[$version]; - break; - } - - if ($current['lang'] == "en") { - $fallback = $current[$version]; - } - } - - if (strlen($description) === 0) { - $description = $fallback; - } - - return $description; - } - - - /** - * Return HTML part for plugins pagination - * - * @param int $current_page - * @param int $total - * @param bool $only_li display only the li tags in return html (used by ajax queries) - * - * @return string the pagination html - */ - static function getPaginationHtml(int $current_page = 1, int $total = 1, bool $only_li = false): string { - if ($total <= self::COL_PAGE) { - return ""; - } - - $nb_pages = ceil($total / self::COL_PAGE); - - $prev = max($current_page - 1, 1); - $next = min($current_page + 1, $nb_pages); - - $p_cls = $current_page === 1 - ? "class='nav-disabled'" - : ""; - $n_cls = $current_page == $nb_pages - ? "class='nav-disabled'" - : ""; - - $html = ""; - if (!$only_li) { - $html.= ""; - } - - return $html; - } - - - /** - * Display a dialog inviting the user to switch from former plugin list to marketplace new view. - * - * @return void display things - */ - static function showFeatureSwitchDialog() { - global $CFG_GLPI; - - if (isset($_POST['marketplace_replace'])) { - $mp_value = isset($_POST['marketplace_replace_plugins_yes']) - ? Controller::MP_REPLACE_YES - : (isset($_POST['marketplace_replace_plugins_never']) - ? Controller::MP_REPLACE_NEVER - : Controller::MP_REPLACE_ASK); - Config::setConfigurationValues('core', [ - 'marketplace_replace_plugins' => $mp_value - ]); - - // is user agree, redirect him to marketplace - if ($mp_value === Controller::MP_REPLACE_YES) { - Html::redirect($CFG_GLPI["root_doc"]."/front/marketplace.php"); - } - - // avoid annoying user for the current session - $_SESSION['skip_marketplace_invitation'] = true; - } - - // show modal for asking user preference - if (Controller::getPluginPageConfig() == Controller::MP_REPLACE_ASK - && !isset($_SESSION['skip_marketplace_invitation']) - && GLPI_INSTALL_MODE !== 'CLOUD') { - echo "
    "; - echo Html::image($CFG_GLPI['root_doc']."/pics/screenshots/marketplace.png", [ - 'style' => 'width: 600px', - ]); - echo "

    "; - echo __("GLPI provides a new marketplace to download and install plugins."); - echo "

    "; - echo "".__("Do you want to replace the plugins setup page by the new marketplace ?").""; - echo "

    "; - echo Html::submit(" ".__('Yes'), [ - 'name' => 'marketplace_replace_plugins_yes' - ]); - echo " "; - echo Html::submit(" ".__('No'), [ - 'name' => 'marketplace_replace_plugins_never', - 'class' => 'secondary' - ]); - echo " "; - echo Html::submit(" ".__('Later'), [ - 'name' => 'marketplace_replace_plugins_later', - 'class' => 'secondary' - ]); - echo Html::hidden('marketplace_replace'); - - Html::closeForm(); - - echo Html::scriptBlock("$(document).ready(function() { - $('#marketplace_dialog').dialog({ - 'modal': true, - 'width': 'auto', - 'title': \"".__s("Switch to marketplace")."\" - }); - });"); - } - } - -} diff --git a/inc/plugin.class.php b/inc/plugin.class.php index d5b3824522..664d126350 100644 --- a/inc/plugin.class.php +++ b/inc/plugin.class.php @@ -39,8 +39,6 @@ } use Psr\SimpleCache\CacheInterface; -use Glpi\Marketplace\View as MarketplaceView; -use Glpi\Marketplace\Controller as MarketplaceController; class Plugin extends CommonDBTM { @@ -124,12 +122,9 @@ static function getMenuContent() { $menu = parent::getMenuContent() ?: []; if (static::canView()) { - $redirect_mp = MarketplaceController::getPluginPageConfig(); $menu['title'] = self::getMenuName(); - $menu['page'] = $redirect_mp == MarketplaceController::MP_REPLACE_YES - ? '/front/marketplace.php' - : '/front/plugin.php'; + $menu['page'] = '/front/plugin.php'; $menu['icon'] = self::getIcon(); } if (count($menu)) { @@ -143,35 +138,17 @@ static function getAdditionalMenuLinks() { if (!static::canView()) { return false; } - $mp_icon = MarketplaceView::getIcon(); - $mp_title = MarketplaceView::getTypeName(); - $marketplace = "$mp_title"; - $cl_icon = Plugin::getIcon(); $cl_title = Plugin::getTypeName(); $classic = "$cl_title"; return [ - $marketplace => MarketplaceView::getSearchURL(false), $classic => Plugin::getSearchURL(false), ]; } - static function getAdditionalMenuOptions() { - if (static::canView()) { - return [ - 'marketplace' => [ - 'icon' => MarketplaceView::geticon(), - 'title' => MarketplaceView::getTypeName(), - 'page' => MarketplaceView::getSearchURL(false), - ] - ]; - } - } - - /** * Retrieve an item from the database using its directory * diff --git a/inc/system/requirement/directorywriteaccess.class.php b/inc/system/requirement/directorywriteaccess.class.php index f857603c66..c9a6a8371b 100644 --- a/inc/system/requirement/directorywriteaccess.class.php +++ b/inc/system/requirement/directorywriteaccess.class.php @@ -78,9 +78,6 @@ public function __construct(string $path, bool $optional = false) { case realpath(GLPI_LOCK_DIR): $this->title = __('Checking write permissions for lock files'); break; - case realpath(GLPI_MARKETPLACE_DIR): - $this->title = __('Checking write permissions for marketplace plugins directory'); - break; case realpath(GLPI_PLUGIN_DOC_DIR): $this->title = __('Checking write permissions for plugins document files'); break; diff --git a/inc/system/requirementsmanager.class.php b/inc/system/requirementsmanager.class.php index 8676d3ddd3..7a5fc86bf3 100644 --- a/inc/system/requirementsmanager.class.php +++ b/inc/system/requirementsmanager.class.php @@ -88,8 +88,6 @@ public function getCoreRequirementList(\DBmysql $db = null): RequirementsList { $requirements[] = new Extension('xmlrpc', true); // for XMLRPC API $requirements[] = new ExtensionClass('CAS', 'phpCAS', true); // for CAS lib $requirements[] = new Extension('exif', true); // for security reasons (images checks) - $requirements[] = new Extension('zip', true); // to handle zip packages on marketplace - $requirements[] = new Extension('bz2', true); // to handle bz2 packages on marketplace $requirements[] = new Extension('sodium', true); // to enhance performances on encrypt/decrypt (fallback to polyfill) if ($db instanceof \DBmysql) { @@ -107,8 +105,6 @@ public function getCoreRequirementList(\DBmysql $db = null): RequirementsList { $requirements[] = new DirectoryWriteAccess($directory); } - $requirements[] = new DirectoryWriteAccess(GLPI_MARKETPLACE_DIR, true); - $requirements[] = new ProtectedWebAccess(Variables::getDataDirectories()); $requirements[] = new SeLinux(); diff --git a/install/empty_data.php b/install/empty_data.php index 8f75b0c225..f605c79734 100644 --- a/install/empty_data.php +++ b/install/empty_data.php @@ -657,16 +657,6 @@ 'mode' => 2, 'lastrun' => null, 'logs_lifetime' => 30, - ], [ - 'id' => 37, - 'itemtype' => 'Glpi\\Marketplace\\Controller', - 'name' => 'checkAllUpdates', - 'frequency' => 86400, - 'param' => null, - 'state' => 1, - 'mode' => 2, - 'lastrun' => null, - 'logs_lifetime' => 30, ], [ 'id' => 38, 'itemtype' => 'Domain', @@ -2731,13 +2721,6 @@ 'event' => 'passwordexpires', 'is_recursive' => 1, 'is_active' => 1, - ], [ - 'id' => 71, - 'name' => 'Check plugin updates', - 'itemtype' => 'Glpi\\Marketplace\\Controller', - 'event' => 'checkpluginsupdate', - 'is_recursive' => 1, - 'is_active' => 1, ], ]; @@ -3898,10 +3881,6 @@ 'id' => '27', 'name' => 'Password expires alert', 'itemtype' => 'User', - ], [ - 'id' => '28', - 'name' => 'Plugin updates', - 'itemtype' => 'Glpi\\Marketplace\\Controller', ], ]; diff --git a/install/update_94_95.php b/install/update_94_95.php index ff417e3e12..96b6af61a3 100644 --- a/install/update_94_95.php +++ b/install/update_94_95.php @@ -1072,97 +1072,6 @@ function update94to95() { } /** /Dashboards */ - /** Marketplace */ - // crontask - CronTask::Register( - 'Glpi\\Marketplace\\Controller', - 'checkAllUpdates', - DAY_TIMESTAMP, - [ - 'mode' => CronTask::MODE_EXTERNAL, - 'state' => CronTask::STATE_WAITING, - ] - ); - - // notification - if (countElementsInTable('glpi_notifications', [ - 'itemtype' => 'Glpi\\\\Marketplace\\\\Controller' - ]) === 0) { - $DB->insertOrDie( - 'glpi_notificationtemplates', - [ - 'name' => 'Plugin updates', - 'itemtype' => 'Glpi\\\\Marketplace\\\\Controller', - 'date_mod' => new \QueryExpression('NOW()'), - ], - 'Add plugins updates notification template' - ); - $notificationtemplate_id = $DB->insertId(); - - $DB->insertOrDie( - 'glpi_notificationtemplatetranslations', - [ - 'notificationtemplates_id' => $notificationtemplate_id, - 'language' => '', - 'subject' => '##lang.plugins_updates_available##', - 'content_text' => << <<<HTML -&lt;p&gt;##lang.plugins_updates_available##&lt;/p&gt; -&lt;ul&gt;##FOREACHplugins## -&lt;li&gt;##plugin.name## :##plugin.old_version## -&gt; ##plugin.version##&lt;/li&gt; -##ENDFOREACHplugins##&lt;/ul&gt; -HTML - , - ], - 'Add plugins updates notification template translations' - ); - - $DB->insertOrDie( - 'glpi_notifications', - [ - 'name' => 'Check plugin updates', - 'entities_id' => 0, - 'itemtype' => 'Glpi\\\\Marketplace\\\\Controller', - 'event' => 'checkpluginsupdate', - 'comment' => null, - 'is_recursive' => 1, - 'is_active' => 1, - 'date_creation' => new \QueryExpression('NOW()'), - 'date_mod' => new \QueryExpression('NOW()'), - ], - 'Add plugins updates notification' - ); - $notification_id = $DB->insertId(); - - $DB->insertOrDie( - 'glpi_notifications_notificationtemplates', - [ - 'notifications_id' => $notification_id, - 'mode' => Notification_NotificationTemplate::MODE_MAIL, - 'notificationtemplates_id' => $notificationtemplate_id, - ], - 'Add plugins updates notification template instance' - ); - - $DB->insertOrDie( - 'glpi_notificationtargets', - [ - 'items_id' => Notification::GLOBAL_ADMINISTRATOR, - 'type' => 1, - 'notifications_id' => $notification_id, - ], - 'Add domains expiration notification targets' - ); - } - /** /Marketplace */ - /** Domains */ if (!$DB->tableExists('glpi_domaintypes')) { $query = "CREATE TABLE `glpi_domaintypes` ( diff --git a/js/marketplace.js b/js/marketplace.js deleted file mode 100644 index e719c97a43..0000000000 --- a/js/marketplace.js +++ /dev/null @@ -1,250 +0,0 @@ -/** - * --------------------------------------------------------------------- - * GLPI - Gestionnaire Libre de Parc Informatique - * Copyright (C) 2015-2021 Teclib' and contributors. - * - * http://glpi-project.org - * - * based on GLPI - Gestionnaire Libre de Parc Informatique - * Copyright (C) 2003-2014 by the INDEPNET Development Team. - * - * --------------------------------------------------------------------- - * - * LICENSE - * - * This file is part of GLPI. - * - * GLPI is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * GLPI is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with GLPI. If not, see <http://www.gnu.org/licenses/>. - * --------------------------------------------------------------------- - */ - -/* global displayAjaxMessageAfterRedirect, marketplace_total_plugin */ - -var current_page = 1; -var ajax_url; -var ajax_done = false; - -$(document).ready(function() { - ajax_url = CFG_GLPI.root_doc+"/ajax/marketplace.php"; - - // plugin actions (install, enable, etc) - $(document).on('click', '.marketplace .modify_plugin', function() { - var button = $(this); - var buttons = button.closest('.buttons'); - var li = button.closest('li.plugin'); - var icon = button.children('i'); - var installed = button.closest('.marketplace').hasClass('installed'); - var action = button.data('action'); - var plugin_key = li.data('key'); - - icon - .removeClass() - .addClass('fas fa-spinner fa-spin'); - - if (action === 'download_plugin' - || action === 'update_plugin') { - followDownloadProgress(button); - } - - ajax_done = false; - $.get(ajax_url, { - 'action': action, - 'key': plugin_key - }).done(function(html) { - ajax_done = true; - - if (html.indexOf("cleaned") !== -1 && installed) { - li.remove(); - } else { - html = html.replace('cleaned', ''); - buttons.html(html); - displayAjaxMessageAfterRedirect(); - addTooltips(); - } - }); - }); - - // sort control - $(document).on('select2:select', '.marketplace .sort-control', function() { - filterPluginList(); - }); - - // pagination - $(document).on('click', '.marketplace .pagination li', function() { - var li = $(this); - var page = li.data('page'); - - if (li.hasClass('nav-disabled') - || li.hasClass('current') - || isNaN(page)) { - return; - } - - refreshPlugins(page); - }); - - // filter by tag - $(document).on('click', '.marketplace .plugins-tags .tag', function() { - $(".marketplace:visible .plugins-tags .tag").removeClass('active'); - $(this).addClass('active'); - filterPluginList(); - }); - - // case insentive :contains selector -> ":icontains" - jQuery.expr.filters.icontains = function(elem, i, m) { - return (elem.innerText || elem.textContent || "").toLowerCase().indexOf(m[3].toLowerCase()) > -1; - }; - - // filter plugin list when something typed in search input - var chrono; - $(document).on('input', '.marketplace .filter-list', function() { - clearTimeout(chrono); - chrono = setTimeout(function() { - filterPluginList(); - }, 500); - }); - - // force refresh of plugin list - $(document).on('click', '.marketplace .refresh-plugin-list', function() { - refreshPlugins(current_page, true); - }); -}); - -// filter current plugin list based on tag selection or input filtering -var filterPluginList = function(page, force) { - page = page || 1; - force = force || false; - - var marketplace = $('.marketplace:visible'); - var pagination = marketplace.find('ul.pagination'); - var plugins_list = marketplace.find('ul.plugins'); - var dom_tag = marketplace.find('.plugins-tags .tag.active'); - var tag_key = dom_tag.length ? dom_tag.data('tag') : ""; - var filter_str = marketplace.find('.filter-list').val(); - var sort = 'sort-alpha-desc'; - - if (marketplace.find(".sort-control").length > 0) { - sort = marketplace.find(".sort-control").select2('data')[0].element.value; - } - - plugins_list - .append("<div class='loading-plugins'><i class='fas fa-spinner fa-pulse'></i></div>"); - pagination.find('li.current').removeClass('current'); - - var jqxhr = $.get(ajax_url, { - 'action': 'refresh_plugin_list', - 'tab': marketplace.data('tab'), - 'tag': tag_key, - 'filter': filter_str, - 'force': force ? 1 : 0, - 'page': page, - 'sort': sort, - }).done(function(html) { - plugins_list.html(html); - - var nb_plugins = jqxhr.getResponseHeader('X-GLPI-Marketplace-Total'); - $.get(ajax_url, { - 'action': 'getPagination', - 'page': page, - 'total': nb_plugins, - }).done(function(html) { - pagination.html(html); - }); - }); - - return jqxhr; -}; - -// refresh current list of plugins base on page -var refreshPlugins = function(page, force) { - force = force || false; - var icon = $('.marketplace:visible .refresh-plugin-list'); - - icon - .removeClass('fa-sync-alt') - .addClass('fa-spinner fa-spin'); - - $.when(filterPluginList(page, force)).then(function() { - icon - .removeClass('fa-spinner fa-spin') - .addClass('fa-sync-alt'); - current_page = page; - - addTooltips(); - }); -}; - -// apply qtip on all actions buttons (not already done) -var addTooltips = function() { - $(".qtip").remove(); - $(".marketplace:visible").find("[data-action][title], .add_tooltip").qtip({ - position: { - viewport: $(window), - my: "center left", - at: "center right", - adjust: { - x: 2, - method: "flip" - } - }, - style: { - classes: 'qtip-dark' - }, - show: { - solo: true, // hide all other tooltips - }, - hide: { - event: 'click mouseleave' - } - }); -}; - - -/** - * Perform a long polling download tracking, by asking server for progression - * and reflect it into the dom - * - * @param button dom element containing clicked button (should be download action) - */ -var followDownloadProgress = function(button) { - var buttons = button.closest('.buttons'); - var li = button.closest('li.plugin'); - var plugin_key = li.data('key'); - - var progress = $('<progress max="100" value="0"></progress>'); - buttons.html(progress); - - // we call a non-blocking loop function to send ajax request with a small delay - function loop () { - setTimeout(function() { - $.get(ajax_url, { - 'action': 'get_dl_progress', - 'key': plugin_key - }).done(function(progress_value) { - progress.attr('value', progress_value); - if (progress_value < 100) { - loop(); - } else if (!ajax_done) { - // set an animated icon when decompressing - buttons.html('<i class="fas fa-cog fa-spin"></i>'); - - // display messages from backend - displayAjaxMessageAfterRedirect(); - } - }); - }, 300); - } - - loop(); -}; diff --git a/marketplace/remove.txt b/marketplace/remove.txt deleted file mode 100644 index 977321426c..0000000000 --- a/marketplace/remove.txt +++ /dev/null @@ -1,3 +0,0 @@ -Vous pouvez effacer ce fichier sans dommages. - -You can safely remove this file. diff --git a/tools/modify_headers.pl b/tools/modify_headers.pl index 9598d48bd8..949c3fcf76 100755 --- a/tools/modify_headers.pl +++ b/tools/modify_headers.pl @@ -46,7 +46,7 @@ sub do_dir{ if ($_ ne '..' && $_ ne '.'){ if (-d "$dir/$_"){ # Excluded directories - if ($_ !~ m/^(.git|config|css_compiled|files|lib|marketplace|node_modules|plugins|vendor)$/i){ + if ($_ !~ m/^(.git|config|css_compiled|files|lib|node_modules|plugins|vendor)$/i){ do_dir("$dir/$_"); } } else {