├── .htaccess ├── admin ├── layout.tpl ├── scripts.js └── styles.css ├── common ├── Show.php ├── Template.php ├── Util.php ├── common.php ├── config.php ├── content │ ├── ContentParser.php │ ├── Editor.php │ └── categories │ │ ├── CategoriesManager.php │ │ ├── Category.php │ │ └── template │ │ ├── checkboxCategories.php │ │ ├── listCategories.php │ │ ├── selectCategory.php │ │ └── selectOneCategory.php ├── core │ ├── Core.php │ ├── Lang.php │ ├── Logger.php │ ├── Plugin.php │ ├── PluginsManager.php │ ├── Theme.php │ ├── controllers │ │ ├── AdminController.php │ │ ├── Controller.php │ │ ├── CoreController.php │ │ └── PublicController.php │ ├── environment │ │ └── Env.php │ ├── http │ │ ├── Curl.php │ │ └── Request.php │ ├── responses │ │ ├── AdminResponse.php │ │ ├── ApiResponse.php │ │ ├── PublicResponse.php │ │ ├── Response.php │ │ └── StringResponse.php │ ├── router │ │ ├── AltoRouter.php │ │ └── Router.php │ └── storage │ │ ├── Cache.php │ │ ├── JsonActiveRecord.php │ │ └── Zip.php ├── langs │ ├── en.ini │ ├── fr.ini │ └── ru.ini └── template │ └── 404.tpl ├── index.php ├── install.php ├── plugin ├── antispam │ ├── antispam.php │ ├── controllers │ │ └── AntispamAdminController.php │ ├── langs │ │ ├── en.ini │ │ ├── fr.ini │ │ └── ru.ini │ ├── lib │ │ ├── AntispamAbstractCaptcha.php │ │ ├── AntispamIconCaptcha.php │ │ ├── AntispamReCaptcha.php │ │ └── AntispamTextCaptcha.php │ ├── param │ │ ├── config.json │ │ ├── hooks.json │ │ ├── infos.json │ │ └── routes.php │ └── template │ │ ├── captcha-icon.tpl │ │ ├── config.tpl │ │ ├── help.tpl │ │ └── public.css ├── blog │ ├── blog.php │ ├── controllers │ │ ├── BlogAdminCategoriesController.php │ │ ├── BlogAdminCommentsController.php │ │ ├── BlogAdminConfigController.php │ │ ├── BlogAdminPostsController.php │ │ ├── BlogListController.php │ │ └── BlogReadController.php │ ├── entities │ │ ├── BlogCategoriesManager.php │ │ ├── BlogCategory.php │ │ ├── news.php │ │ ├── newsComment.php │ │ └── newsManager.php │ ├── langs │ │ ├── en.ini │ │ ├── fr.ini │ │ └── ru.ini │ ├── param │ │ ├── config.json │ │ ├── hooks.json │ │ ├── infos.json │ │ └── routes.php │ └── template │ │ ├── admin-edit-category.tpl │ │ ├── admin-edit.tpl │ │ ├── admin-list-comments.tpl │ │ ├── admin-list.tpl │ │ ├── admin.css │ │ ├── comment.tpl │ │ ├── list.tpl │ │ ├── param.tpl │ │ ├── public.css │ │ ├── public.js │ │ └── read.tpl ├── configmanager │ ├── configmanager.php │ ├── controllers │ │ ├── ConfigManagerAdminController.php │ │ ├── ConfigManagerBackupAdminController.php │ │ └── ConfigManagerUpdateController.php │ ├── entities │ │ ├── ConfigManagerBackup.php │ │ └── ConfigManagerBackupsManager.php │ ├── langs │ │ ├── en.ini │ │ ├── fr.ini │ │ └── ru.ini │ ├── lib │ │ └── UpdaterManager.php │ ├── param │ │ ├── config.json │ │ ├── hooks.json │ │ ├── infos.json │ │ └── routes.php │ └── template │ │ ├── admin.css │ │ ├── backup.tpl │ │ └── config.tpl ├── contact │ ├── contact.php │ ├── controllers │ │ ├── ContactAdminController.php │ │ └── ContactController.php │ ├── langs │ │ ├── en.ini │ │ ├── fr.ini │ │ └── ru.ini │ ├── param │ │ ├── config.json │ │ ├── hooks.json │ │ ├── infos.json │ │ └── routes.php │ └── template │ │ ├── admin-contact.tpl │ │ ├── contact.tpl │ │ └── param.tpl ├── filemanager │ ├── controllers │ │ └── FileManagerAPIController.php │ ├── filemanager.php │ ├── langs │ │ ├── en.ini │ │ ├── fr.ini │ │ └── ru.ini │ ├── lib │ │ ├── File.php │ │ ├── FileManager.php │ │ └── Folder.php │ ├── param │ │ ├── config.json │ │ ├── infos.json │ │ └── routes.php │ └── template │ │ ├── admin.css │ │ └── listview.tpl ├── galerie │ ├── controllers │ │ ├── GalerieAdminController.php │ │ └── GalerieController.php │ ├── galerie.php │ ├── langs │ │ ├── en.ini │ │ ├── fr.ini │ │ └── ru.ini │ ├── param │ │ ├── config.json │ │ ├── hooks.json │ │ ├── infos.json │ │ └── routes.php │ └── template │ │ ├── admin.css │ │ ├── admin.tpl │ │ ├── galerie.tpl │ │ ├── help.tpl │ │ ├── param.tpl │ │ ├── public.css │ │ └── public.js ├── page │ ├── controllers │ │ ├── PageAdminController.php │ │ └── PageController.php │ ├── langs │ │ ├── en.ini │ │ ├── fr.ini │ │ └── ru.ini │ ├── page.php │ ├── param │ │ ├── config.json │ │ ├── hooks.json │ │ ├── infos.json │ │ └── routes.php │ └── template │ │ ├── admin.js │ │ ├── edit-link.tpl │ │ ├── edit-parent.tpl │ │ ├── edit.tpl │ │ ├── help.tpl │ │ ├── list.tpl │ │ └── read.tpl ├── pluginsmanager │ ├── controllers │ │ └── PluginsManagerController.php │ ├── langs │ │ ├── en.ini │ │ ├── fr.ini │ │ └── ru.ini │ ├── param │ │ ├── config.json │ │ ├── infos.json │ │ └── routes.php │ ├── pluginsmanager.php │ └── template │ │ ├── help.tpl │ │ └── list.tpl ├── seo │ ├── controllers │ │ └── SEOAdminController.php │ ├── langs │ │ ├── en.ini │ │ ├── fr.ini │ │ └── ru.ini │ ├── param │ │ ├── config.json │ │ ├── hooks.json │ │ ├── infos.json │ │ └── routes.php │ ├── seo.php │ └── template │ │ ├── admin.tpl │ │ ├── help.tpl │ │ └── public.css ├── tinymce │ ├── langs │ │ ├── en.ini │ │ ├── fr.ini │ │ └── ru.ini │ ├── lib │ │ └── tinymce │ │ │ ├── icons │ │ │ └── default │ │ │ │ └── icons.min.js │ │ │ ├── langs │ │ │ ├── fr.js │ │ │ └── ru.js │ │ │ ├── license.md │ │ │ ├── models │ │ │ └── dom │ │ │ │ └── model.min.js │ │ │ ├── plugins │ │ │ ├── accordion │ │ │ │ └── plugin.min.js │ │ │ ├── advlist │ │ │ │ └── plugin.min.js │ │ │ ├── anchor │ │ │ │ └── plugin.min.js │ │ │ ├── autolink │ │ │ │ └── plugin.min.js │ │ │ ├── autoresize │ │ │ │ └── plugin.min.js │ │ │ ├── autosave │ │ │ │ └── plugin.min.js │ │ │ ├── charmap │ │ │ │ └── plugin.min.js │ │ │ ├── code │ │ │ │ └── plugin.min.js │ │ │ ├── codesample │ │ │ │ └── plugin.min.js │ │ │ ├── directionality │ │ │ │ └── plugin.min.js │ │ │ ├── emoticons │ │ │ │ ├── js │ │ │ │ │ ├── emojiimages.js │ │ │ │ │ ├── emojiimages.min.js │ │ │ │ │ ├── emojis.js │ │ │ │ │ └── emojis.min.js │ │ │ │ └── plugin.min.js │ │ │ ├── fullscreen │ │ │ │ └── plugin.min.js │ │ │ ├── help │ │ │ │ ├── js │ │ │ │ │ └── i18n │ │ │ │ │ │ └── keynav │ │ │ │ │ │ ├── ar.js │ │ │ │ │ │ ├── bg_BG.js │ │ │ │ │ │ ├── ca.js │ │ │ │ │ │ ├── cs.js │ │ │ │ │ │ ├── da.js │ │ │ │ │ │ ├── de.js │ │ │ │ │ │ ├── el.js │ │ │ │ │ │ ├── en.js │ │ │ │ │ │ ├── es.js │ │ │ │ │ │ ├── eu.js │ │ │ │ │ │ ├── fa.js │ │ │ │ │ │ ├── fi.js │ │ │ │ │ │ ├── fr.js │ │ │ │ │ │ ├── he_IL.js │ │ │ │ │ │ ├── hi.js │ │ │ │ │ │ ├── hr.js │ │ │ │ │ │ ├── hu_HU.js │ │ │ │ │ │ ├── id.js │ │ │ │ │ │ ├── it.js │ │ │ │ │ │ ├── ja.js │ │ │ │ │ │ ├── kk.js │ │ │ │ │ │ ├── ko_KR.js │ │ │ │ │ │ ├── ms.js │ │ │ │ │ │ ├── nb_NO.js │ │ │ │ │ │ ├── nl.js │ │ │ │ │ │ ├── pl.js │ │ │ │ │ │ ├── pt_BR.js │ │ │ │ │ │ ├── pt_PT.js │ │ │ │ │ │ ├── ro.js │ │ │ │ │ │ ├── ru.js │ │ │ │ │ │ ├── sk.js │ │ │ │ │ │ ├── sl_SI.js │ │ │ │ │ │ ├── sv_SE.js │ │ │ │ │ │ ├── th_TH.js │ │ │ │ │ │ ├── tr.js │ │ │ │ │ │ ├── uk.js │ │ │ │ │ │ ├── vi.js │ │ │ │ │ │ ├── zh_CN.js │ │ │ │ │ │ └── zh_TW.js │ │ │ │ └── plugin.min.js │ │ │ ├── image │ │ │ │ └── plugin.min.js │ │ │ ├── importcss │ │ │ │ └── plugin.min.js │ │ │ ├── insertdatetime │ │ │ │ └── plugin.min.js │ │ │ ├── link │ │ │ │ └── plugin.min.js │ │ │ ├── lists │ │ │ │ └── plugin.min.js │ │ │ ├── media │ │ │ │ └── plugin.min.js │ │ │ ├── nonbreaking │ │ │ │ └── plugin.min.js │ │ │ ├── pagebreak │ │ │ │ └── plugin.min.js │ │ │ ├── preview │ │ │ │ └── plugin.min.js │ │ │ ├── quickbars │ │ │ │ └── plugin.min.js │ │ │ ├── save │ │ │ │ └── plugin.min.js │ │ │ ├── searchreplace │ │ │ │ └── plugin.min.js │ │ │ ├── table │ │ │ │ └── plugin.min.js │ │ │ ├── visualblocks │ │ │ │ └── plugin.min.js │ │ │ ├── visualchars │ │ │ │ └── plugin.min.js │ │ │ └── wordcount │ │ │ │ └── plugin.min.js │ │ │ ├── skins │ │ │ ├── content │ │ │ │ ├── dark │ │ │ │ │ ├── content.js │ │ │ │ │ └── content.min.css │ │ │ │ ├── default │ │ │ │ │ ├── content.js │ │ │ │ │ └── content.min.css │ │ │ │ ├── document │ │ │ │ │ ├── content.js │ │ │ │ │ └── content.min.css │ │ │ │ ├── tinymce-5-dark │ │ │ │ │ ├── content.js │ │ │ │ │ └── content.min.css │ │ │ │ ├── tinymce-5 │ │ │ │ │ ├── content.js │ │ │ │ │ └── content.min.css │ │ │ │ └── writer │ │ │ │ │ ├── content.js │ │ │ │ │ └── content.min.css │ │ │ └── ui │ │ │ │ ├── oxide-dark │ │ │ │ ├── content.inline.js │ │ │ │ ├── content.inline.min.css │ │ │ │ ├── content.js │ │ │ │ ├── content.min.css │ │ │ │ ├── skin.js │ │ │ │ ├── skin.min.css │ │ │ │ ├── skin.shadowdom.js │ │ │ │ └── skin.shadowdom.min.css │ │ │ │ ├── oxide │ │ │ │ ├── content.inline.js │ │ │ │ ├── content.inline.min.css │ │ │ │ ├── content.js │ │ │ │ ├── content.min.css │ │ │ │ ├── skin.js │ │ │ │ ├── skin.min.css │ │ │ │ ├── skin.shadowdom.js │ │ │ │ └── skin.shadowdom.min.css │ │ │ │ ├── tinymce-5-dark │ │ │ │ ├── content.inline.js │ │ │ │ ├── content.inline.min.css │ │ │ │ ├── content.js │ │ │ │ ├── content.min.css │ │ │ │ ├── skin.js │ │ │ │ ├── skin.min.css │ │ │ │ ├── skin.shadowdom.js │ │ │ │ └── skin.shadowdom.min.css │ │ │ │ └── tinymce-5 │ │ │ │ ├── content.inline.js │ │ │ │ ├── content.inline.min.css │ │ │ │ ├── content.js │ │ │ │ ├── content.min.css │ │ │ │ ├── skin.js │ │ │ │ ├── skin.min.css │ │ │ │ ├── skin.shadowdom.js │ │ │ │ └── skin.shadowdom.min.css │ │ │ ├── themes │ │ │ └── silver │ │ │ │ └── theme.min.js │ │ │ ├── tinymce.d.ts │ │ │ └── tinymce.min.js │ ├── param │ │ ├── config.json │ │ ├── hooks.json │ │ └── infos.json │ ├── template │ │ ├── admin.css │ │ └── editor.css │ └── tinymce.php └── users │ ├── controllers │ ├── UsersAdminController.php │ ├── UsersAdminManagementController.php │ └── UsersLoginController.php │ ├── entities │ ├── PasswordRecovery.php │ ├── User.php │ └── UsersManager.php │ ├── langs │ ├── en.ini │ ├── fr.ini │ └── ru.ini │ ├── param │ ├── config.json │ ├── hooks.json │ ├── infos.json │ └── routes.php │ ├── template │ ├── login.tpl │ ├── lostpwd-step2.tpl │ ├── lostpwd.tpl │ ├── public.css │ ├── usersadd.tpl │ ├── usersedit.tpl │ └── userslist.tpl │ └── users.php └── theme └── default ├── functions.php ├── header.jpg ├── icon.png ├── infos.json ├── layout.tpl ├── scripts.js └── styles.css /.htaccess: -------------------------------------------------------------------------------- 1 | 2 | RewriteEngine on 3 | 4 | 5 | 6 | # Apache 2.4 7 | Require all denied 8 | 9 | 10 | # Apache 2.2 11 | Order deny,allow 12 | Deny from all 13 | 14 | 15 | 16 | Options -Indexes 17 | 18 | RewriteCond %{REQUEST_FILENAME} !-f 19 | RewriteRule . index.php [L] 20 | -------------------------------------------------------------------------------- /common/config.php: -------------------------------------------------------------------------------- 1 | 7 | * @author Maxence Cauderlier 8 | * @author Frédéric Kaplon 9 | * @author Florent Fortat 10 | * 11 | * @package 299Ko https://github.com/299Ko/299ko 12 | */ 13 | 14 | const VERSION = '2.0.0'; 15 | const COMMON = ROOT . 'common' . DS; 16 | const DATA = ROOT . 'data' . DS; 17 | const UPLOAD = ROOT . 'data' . DS . 'upload' . DS; 18 | const DATA_PLUGIN = ROOT . 'data' . DS . 'plugin' . DS; 19 | const CACHE = ROOT . 'data' . DS . 'cache' . DS; 20 | const THEMES = ROOT . 'theme' . DS; 21 | const PLUGINS = ROOT . 'plugin' . DS; 22 | const ADMIN_PATH = ROOT . 'admin' . DS; 23 | const FONTICON = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css'; 24 | const FANCYCSS = 'https://cdnjs.cloudflare.com/ajax/libs/fancyapps-ui/4.0.31/fancybox.min.css'; 25 | const FANCYJS = 'https://cdnjs.cloudflare.com/ajax/libs/fancyapps-ui/4.0.31/fancybox.umd.min.js'; 26 | 27 | $filename = DATA . 'key.php'; 28 | if (file_exists($filename)) { 29 | include $filename; 30 | } -------------------------------------------------------------------------------- /common/content/categories/template/checkboxCategories.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * @package 299Ko https://github.com/299Ko/299ko 8 | */ 9 | defined('ROOT') OR exit('Access denied!'); 10 | 11 | switch ($catDisplay) { 12 | case 'root': 13 | // Categories Container 14 | ?> 15 |
16 | imbricatedCategories)) { 18 | echo lang::get($this->getPrefix() . 'none') . ' ' . lang::get($this->getPrefix() . 'addOne') . ''; 19 | } else { ?> 20 |
    21 | imbricatedCategories as $cat) { 23 | $cat->outputAsCheckbox($itemId); 24 | } 25 | ?> 26 |
27 | 28 |
29 | hasChildren) { 34 | echo "
  • "; 35 | } else { 36 | echo "
  • "; 37 | } 38 | echo "
    items)) { 41 | echo ' checked'; 42 | } 43 | echo "/>
    "; 44 | if ($this->hasChildren) { 45 | echo ""; 46 | echo "
      "; 47 | foreach ($this->children as $child) { 48 | $child->outputAsCheckbox($itemId); 49 | } 50 | echo "
    "; 51 | } 52 | echo "
  • "; 53 | break; 54 | } -------------------------------------------------------------------------------- /common/content/categories/template/selectCategory.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * @package 299Ko https://github.com/299Ko/299ko 8 | */ 9 | defined('ROOT') OR exit('Access denied!'); 10 | 11 | switch ($catDisplay) { 12 | case 'root': 13 | // Categories Container 14 | ?> 15 | 28 | 33 | 36 | hasChildren) { 37 | foreach ($this->children as $child) { 38 | if ($child->id == $categorieId) { 39 | // We dont display the current categorie xor his children 40 | continue; 41 | } 42 | $child->outputAsSelect($parentId, $categoryId); 43 | } 44 | } 45 | break; 46 | } -------------------------------------------------------------------------------- /common/content/categories/template/selectOneCategory.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * @package 299Ko https://github.com/299Ko/299ko 8 | */ 9 | defined('ROOT') OR exit('Access denied!'); 10 | 11 | switch ($catDisplay) { 12 | case 'root': 13 | // Categories Container 14 | if (empty($this->imbricatedCategories)) { 15 | $noCategoriesText = lang::get('blog.categories.none'); 16 | $addCategoryLink = '' . lang::get('blog.categories.addOne') . ''; 17 | echo $noCategoriesText . ' ' . $addCategoryLink; 18 | return; 19 | } 20 | ?> 21 | 30 | 35 | 38 | hasChildren) { 39 | foreach ($this->children as $child) { 40 | $child->outputAsSelectOne($itemId); 41 | } 42 | } 43 | break; 44 | } -------------------------------------------------------------------------------- /common/core/controllers/AdminController.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') OR exit('Access denied!'); 11 | 12 | class AdminController extends Controller { 13 | 14 | /** 15 | * Current plugin instance 16 | * @var plugin 17 | */ 18 | protected plugin $runPlugin; 19 | 20 | /** 21 | * Current User 22 | * @var User 23 | */ 24 | protected User $user; 25 | 26 | public function __construct() { 27 | parent::__construct(); 28 | if (IS_ADMIN === false) { 29 | $this->core->error404(); 30 | } 31 | $pluginName = $this->core->getPluginToCall(); 32 | if (pluginsManager::isActivePlugin($pluginName)) { 33 | $this->runPlugin = $this->pluginsManager->getPlugin($pluginName); 34 | } else { 35 | $this->core->error404(); 36 | } 37 | 38 | if (!defined('ADMIN_MODE')) { 39 | define('ADMIN_MODE', true); 40 | } 41 | $this->user = UsersManager::getCurrentUser(); 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /common/core/controllers/Controller.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') OR exit('Access denied!'); 11 | 12 | abstract class Controller { 13 | 14 | /** 15 | * Core instance 16 | * @var core 17 | */ 18 | protected core $core; 19 | 20 | /** 21 | * Router instance 22 | * @var router 23 | */ 24 | protected router $router; 25 | 26 | /** 27 | * pluginsManager instance 28 | * @var pluginsManager 29 | */ 30 | protected pluginsManager $pluginsManager; 31 | 32 | /** 33 | * Request instance 34 | * @var Request 35 | */ 36 | protected Request $request; 37 | 38 | /** 39 | * SLogger instance 40 | * @var Logger 41 | */ 42 | protected Logger $logger; 43 | 44 | /** 45 | * JSON data sent by fetch, used for API 46 | * @var array 47 | */ 48 | protected array $jsonData = []; 49 | 50 | public function __construct() { 51 | $this->core = core::getInstance(); 52 | $this->router = router::getInstance(); 53 | $this->pluginsManager = pluginsManager::getInstance(); 54 | $this->request = new Request(); 55 | $contentType = isset($_SERVER["CONTENT_TYPE"]) ? trim($_SERVER["CONTENT_TYPE"]) : ''; 56 | if ($contentType === "application/json") { 57 | $content = trim(file_get_contents("php://input")); 58 | $this->jsonData = json_decode($content, true); 59 | } 60 | $this->logger = $this->core->getLogger(); 61 | } 62 | } -------------------------------------------------------------------------------- /common/core/controllers/CoreController.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') or exit('Access denied!'); 11 | 12 | class CoreController extends Controller 13 | { 14 | 15 | protected plugin $defaultPlugin; 16 | 17 | public function __construct() { 18 | parent::__construct(); 19 | $pluginName = $this->core->getPluginToCall(); 20 | 21 | if (pluginsManager::isActivePlugin($pluginName)) { 22 | $this->defaultPlugin = $this->pluginsManager->getPlugin($pluginName); 23 | } else { 24 | $this->core->error404(); 25 | } 26 | } 27 | 28 | public function renderHome() 29 | { 30 | if ($this->defaultPlugin->getIsCallableOnPublic()) { 31 | $callback = $this->defaultPlugin->getCallablePublic(); 32 | if (method_exists($callback[0], $callback[1])) { 33 | $obj = new $callback[0](); 34 | $response = call_user_func([$obj, $callback[1]]); 35 | return $response; 36 | } 37 | } 38 | core::getInstance()->error404(); 39 | } 40 | 41 | public function renderAdminHome() 42 | { 43 | if ($this->defaultPlugin->getIsCallableOnAdmin()) { 44 | $callback = $this->defaultPlugin->getCallableAdmin(); 45 | if (method_exists($callback[0], $callback[1])) { 46 | $obj = new $callback[0](); 47 | $response = call_user_func([$obj, $callback[1]]); 48 | return $response; 49 | } 50 | } 51 | core::getInstance()->error404(); 52 | } 53 | } -------------------------------------------------------------------------------- /common/core/controllers/PublicController.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') OR exit('Access denied!'); 11 | 12 | class PublicController extends Controller { 13 | 14 | /** 15 | * Current plugin instance 16 | * @var plugin 17 | */ 18 | protected plugin $runPlugin; 19 | 20 | /** 21 | * Current User 22 | * @var User 23 | */ 24 | protected ?User $user; 25 | 26 | public function __construct() { 27 | parent::__construct(); 28 | $pluginName = $this->core->getPluginToCall(); 29 | if (pluginsManager::isActivePlugin($pluginName)) { 30 | $this->runPlugin = $this->pluginsManager->getPlugin($pluginName); 31 | } else { 32 | $this->core->error404(); 33 | } 34 | if (!defined('ADMIN_MODE')) { 35 | define('ADMIN_MODE', false); 36 | } 37 | $this->user = UsersManager::getCurrentUser() ? UsersManager::getCurrentUser() : null; 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /common/core/http/Request.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') or exit('Access denied!'); 11 | 12 | /** 13 | * The Request class provides methods to access and manipulate HTTP request data. 14 | */ 15 | class Request 16 | { 17 | 18 | /** 19 | * Gets a GET parameter by name. 20 | * 21 | * @param string $key The parameter name. 22 | * @param mixed $default The default value if the parameter does not exist. 23 | * 24 | * @return mixed The value of the requested GET parameter or the default value. 25 | */ 26 | public function get(string $key, $default = null) 27 | { 28 | return $_GET[$key] ?? $default; 29 | } 30 | 31 | /** 32 | * Retrieves a POST parameter by name. 33 | * 34 | * @param string $key The parameter name. 35 | * @param mixed $default The default value if the parameter does not exist. 36 | * 37 | * @return mixed The value of the requested POST parameter or the default value. 38 | */ 39 | public function post(string $key, $default = null) 40 | { 41 | return $_POST[$key] ?? $default; 42 | } 43 | 44 | /** 45 | * Retrieves a file from the $_FILES array by name. 46 | * 47 | * @param string $key The name of the file. 48 | * 49 | * @return array|null The file data or null if the file does not exist. 50 | */ 51 | public function file($key) { 52 | return $_FILES[$key] ?? null; 53 | } 54 | 55 | /** 56 | * Checks if the current request is an AJAX request. 57 | * 58 | * @return bool True if the request is an AJAX request, false otherwise. 59 | */ 60 | public function isAjax(): bool { 61 | return !empty($_SERVER['HTTP_X_REQUESTED_WITH']) && 62 | strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'; 63 | } 64 | 65 | /** 66 | * Retrieves the HTTP request method used for the current request. 67 | * 68 | * @return string The HTTP request method (e.g., 'GET', 'POST'). 69 | */ 70 | public function getMethod(): string { 71 | return $_SERVER['REQUEST_METHOD']; 72 | } 73 | } -------------------------------------------------------------------------------- /common/core/responses/AdminResponse.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') OR exit('Access denied!'); 11 | 12 | class AdminResponse extends Response { 13 | 14 | /** 15 | * Layout 16 | * @var Template 17 | */ 18 | protected Template $layout; 19 | 20 | protected ?string $title = null; 21 | 22 | public function __construct() { 23 | parent::__construct(); 24 | $this->layout = new Template(ADMIN_PATH .'layout.tpl'); 25 | } 26 | 27 | /** 28 | * Create a new Template, from plugin 29 | * @param string $pluginName 30 | * @param string $templateName 31 | * @return Template 32 | */ 33 | public function createPluginTemplate(string $pluginName, string $templateName):Template { 34 | $file = PLUGINS . $pluginName . '/template/' . $templateName . '.tpl'; 35 | $tpl = new Template($file); 36 | return $tpl; 37 | } 38 | 39 | /** 40 | * Return the response 41 | * @return string Content of all template 42 | */ 43 | public function output():string 44 | { 45 | $content = ''; 46 | foreach ($this->templates as $tpl) { 47 | $content .= $tpl->output(); 48 | } 49 | $this->layout->set('CONTENT', $content); 50 | $this->layout->set('PAGE_TITLE' , $this->title ?? false); 51 | return $this->layout->output(); 52 | } 53 | 54 | /** 55 | * Set the title of the admin page 56 | * @param string $title 57 | */ 58 | public function setTitle(string $title) { 59 | $this->title = $title; 60 | } 61 | } -------------------------------------------------------------------------------- /common/core/responses/ApiResponse.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') or exit('Access denied!'); 11 | 12 | class ApiResponse extends Response 13 | { 14 | 15 | const STATUS_SUCCESS = 'HTTP/1.1 200 OK'; 16 | const STATUS_CREATED = 'HTTP/1.1 201 Created'; 17 | const STATUS_ACCEPTED = 'HTTP/1.1 202 Accepted'; 18 | const STATUS_NO_CONTENT = 'HTTP/1.1 204 No Content'; 19 | const STATUS_BAD_REQUEST = 'HTTP/1.1 400 Bad Request'; 20 | const STATUS_NOT_AUTHORIZED = 'HTTP/1.1 401 Not Authorized'; 21 | const STATUS_FORBIDDEN = 'HTTP/1.1 403 Forbidden'; 22 | const STATUS_NOT_FOUND = 'HTTP/1.1 404 Not Found'; 23 | 24 | public string $status = self::STATUS_NOT_FOUND; 25 | public $body = null; 26 | 27 | protected $headers = [ 28 | "Access-Control-Allow-Origin: *", 29 | "Content-Type: application/json; charset=UTF-8", 30 | "Cache-Control: no-cache", 31 | "Pragma: no-cache" 32 | ]; 33 | 34 | public function output():string { 35 | foreach($this->headers as $header){ 36 | header($header,true); 37 | }; 38 | header($this->status, true); 39 | if (empty($this->body)) { 40 | die(); 41 | } 42 | return json_encode($this->body); 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /common/core/responses/Response.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') OR exit('Access denied!'); 11 | 12 | abstract class Response { 13 | 14 | /** 15 | * Templates array 16 | * @var array Template 17 | */ 18 | protected array $templates = []; 19 | 20 | abstract public function output() : string; 21 | 22 | /** 23 | * Construct 24 | */ 25 | public function __construct() { 26 | 27 | } 28 | 29 | /** 30 | * Add a Template in content 31 | * @param Template $template 32 | * @return void 33 | */ 34 | public function addTemplate(Template $template) { 35 | $this->templates[] = $template; 36 | } 37 | 38 | 39 | } -------------------------------------------------------------------------------- /common/core/responses/StringResponse.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') OR exit('Access denied!'); 11 | 12 | class StringResponse extends Response { 13 | 14 | /** 15 | * Current theme name 16 | * @var Theme 17 | */ 18 | protected Theme $theme; 19 | 20 | public function __construct() { 21 | parent::__construct(); 22 | $this->theme = new Theme(core::getInstance()->getConfigVal('theme')); 23 | } 24 | 25 | /** 26 | * Return the response 27 | * @return string Content of all template 28 | */ 29 | public function output():string 30 | { 31 | $content = ''; 32 | foreach ($this->templates as $tpl) { 33 | $content .= $tpl->output(); 34 | } 35 | return $content; 36 | } 37 | 38 | /** 39 | * Create a new Template, from plugin 40 | * Eg : if plugin is 'blog' & asked template is 'read', look for 'THEMES/theme/template/blog.read.tpl' 41 | * else create tpl with PLUGINS/blog/template/read.tpl 42 | * @param string $pluginName 43 | * @param string $templateName 44 | * @return Template 45 | */ 46 | public function createPluginTemplate(string $pluginName, string $templateName):Template { 47 | $themeFile = $this->theme->getPluginTemplatePath($pluginName, $templateName) ?? ''; 48 | if (file_exists($themeFile)) { 49 | $tpl = new Template($themeFile); 50 | } else { 51 | $tpl = new Template(PLUGINS . $pluginName .'/template/' . $templateName . '.tpl'); 52 | } 53 | return $tpl; 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /common/core/router/Router.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') OR exit('Access denied!'); 11 | 12 | class router extends AltoRouter { 13 | 14 | /** 15 | * 16 | * @var \router 17 | */ 18 | private static $instance; 19 | 20 | protected static $url; 21 | 22 | private function __construct() { 23 | if (!empty($_SERVER['REQUEST_URL'])) { 24 | $url = $this->stripFbclid($_SERVER['REQUEST_URL']); 25 | } else { 26 | $url = $this->stripFbclid($_SERVER['REQUEST_URI']); 27 | } 28 | $url = str_replace('index.php', '', $url); 29 | $url = str_replace('//', '/', $url); 30 | self::$url = $url; 31 | parent::__construct(); 32 | $this->setBasePath(str_replace('\\', '/', BASE_PATH)); 33 | $this->map('GET', '/', 'CoreController#renderHome', 'home'); 34 | $this->map('GET', '/index.php[/?]', 'CoreController#renderHome'); 35 | $this->map('GET', '/admin/', 'CoreController#renderAdminHome', 'admin'); 36 | } 37 | 38 | public function getCleanURI() { 39 | $requestUrl = self::$url; 40 | return substr($requestUrl, strlen($this->basePath)); 41 | } 42 | 43 | protected function stripFbclid($url) { 44 | $patterns = array( 45 | '/(\?|&)fbclid=[^&]*$/' => '', 46 | '/\?fbclid=[^&]*&/' => '?', 47 | '/&fbclid=[^&]*&/' => '&' 48 | ); 49 | $search = array_keys($patterns); 50 | $replace = array_values($patterns); 51 | return preg_replace($search, $replace, $url); 52 | } 53 | 54 | /** 55 | * Return Core Instance 56 | * 57 | * @return \self 58 | */ 59 | public static function getInstance() { 60 | if (is_null(self::$instance)) 61 | self::$instance = new router(); 62 | return self::$instance; 63 | } 64 | 65 | public function generate($routeName, array $params = []):string { 66 | return parent::generate($routeName, $params); 67 | } 68 | 69 | public function match ($requestUrl = null, $requestMethod = null) { 70 | return parent::match(self::$url); 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /common/core/storage/Zip.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') or exit('Access denied!'); 11 | 12 | class Zip 13 | { 14 | 15 | public string $filename; 16 | 17 | public string $filesize; 18 | 19 | public function __construct($filename) 20 | { 21 | $this->filename = $filename; 22 | if (!file_exists($this->filename)) { 23 | throw new Exception("File not found: $filename"); 24 | } 25 | $this->filesize = $this->humanFilesize(filesize($filename)); 26 | } 27 | 28 | protected function humanFilesize($bytes, $decimals = 2) 29 | { 30 | $sz = 'BKMGTP'; 31 | $factor = floor((strlen($bytes) - 1) / 3); 32 | return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$sz[$factor]; 33 | } 34 | 35 | public static function createZipFromFolder(string $folder, string $filename, array $ignoreRegex = []): bool 36 | { 37 | $zip = new ZipArchive(); 38 | $zip->open($filename, ZipArchive::CREATE); 39 | $itemsToSave = util::scanDirRecursive($folder); 40 | foreach ($itemsToSave['dir'] as $dir) { 41 | $dir = trim($dir,'.'); 42 | $dir = str_replace('\\', '/', $dir); 43 | $dir = trim($dir, '/'); 44 | foreach ($ignoreRegex as $regex) { 45 | if (preg_match($regex, $dir)) { 46 | continue 2; 47 | } 48 | } 49 | $zip->addEmptyDir($dir); 50 | } 51 | foreach ($itemsToSave['file'] as $file) { 52 | $file = trim($file,'.'); 53 | $file = str_replace('\\', '/', $file); 54 | $file = trim($file, '/'); 55 | foreach ($ignoreRegex as $regex) { 56 | if (preg_match($regex, $file)) { 57 | continue 2; 58 | } 59 | } 60 | $zip->addFile($file, $file); 61 | } 62 | $zip->close(); 63 | return true; 64 | } 65 | } -------------------------------------------------------------------------------- /common/template/404.tpl: -------------------------------------------------------------------------------- 1 |
    2 |

    {{ Lang.core-404-desc }}

    3 |

    {{ Lang.core-404-goto-home }}

    4 |
    -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 7 | * @author Maxence Cauderlier 8 | * @author Frédéric Kaplon 9 | * @author Florent Fortat 10 | * 11 | * @package 299Ko https://github.com/299Ko/299ko 12 | */ 13 | ob_start(); 14 | const ROOT = '.' . DIRECTORY_SEPARATOR; 15 | const DS = DIRECTORY_SEPARATOR; 16 | 17 | include_once(ROOT . 'common' . DS . 'common.php'); 18 | 19 | if (!$core->isInstalled()) { 20 | header('location:' . ROOT . 'install.php'); 21 | die(); 22 | } 23 | 24 | define('IS_LOGGED', UsersManager::isLogged()); 25 | // For futures versions 26 | define('IS_ADMIN', IS_LOGGED); 27 | 28 | Template::addGlobal('IS_LOGGED', IS_LOGGED); 29 | Template::addGlobal('IS_ADMIN', IS_ADMIN); 30 | 31 | $match = $router->match(); 32 | if (is_array($match)) { 33 | if ($runPlugin) { 34 | $runPlugin->loadControllers(); 35 | } 36 | list($controller, $action) = explode('#', $match['target']); 37 | if (method_exists($controller, $action)) { 38 | $obj = new $controller(); 39 | $core->callHook('beforeRunPlugin'); 40 | $response = call_user_func_array([$obj,$action], $match['params']); 41 | echo $response->output(); 42 | ob_end_flush(); 43 | die(); 44 | } else { 45 | // unreachable target 46 | $core->error404(); 47 | } 48 | } 49 | 50 | $core->error404(); 51 | ob_end_flush(); -------------------------------------------------------------------------------- /plugin/antispam/antispam.php: -------------------------------------------------------------------------------- 1 | 7 | * @author Maxence Cauderlier 8 | * @author Frédéric Kaplon 9 | * @author Florent Fortat 10 | * 11 | * @package 299Ko https://github.com/299Ko/299ko 12 | */ 13 | defined('ROOT') OR exit('Access denied!'); 14 | 15 | ## Fonction d'installation 16 | 17 | function antispamInstall() { 18 | 19 | } 20 | 21 | ## Hooks 22 | ## Code relatif au plugin 23 | 24 | class antispam { 25 | 26 | protected $captcha; 27 | 28 | public function __construct() { 29 | $pluginManager = pluginsManager::getInstance(); 30 | $typeCaptcha = $pluginManager->getPluginConfVal('antispam', 'type'); 31 | if ($typeCaptcha === 'useText') { 32 | // UseText lib 33 | require_once PLUGINS . 'antispam' . DS . 'lib' . DS . 'AntispamAbstractCaptcha.php'; 34 | require_once PLUGINS . 'antispam' . DS . 'lib' . DS . 'AntispamTextCaptcha.php'; 35 | $this->captcha = new AntispamTextCaptcha(); 36 | } elseif ($typeCaptcha === 'useRecaptcha') { 37 | // ReCaptcha lib 38 | require_once PLUGINS . 'antispam' . DS . 'lib' . DS . 'AntispamReCaptcha.php'; 39 | $public = $pluginManager->getPluginConfVal('antispam', 'recaptchaPublicKey'); 40 | $secret = $pluginManager->getPluginConfVal('antispam', 'recaptchaSecretKey'); 41 | $this->captcha = new AntispamReCaptcha($public, $secret); 42 | } elseif ($typeCaptcha === 'useIcon') { 43 | require_once PLUGINS . 'antispam' . DS . 'lib' . DS . 'AntispamAbstractCaptcha.php'; 44 | require_once PLUGINS . 'antispam' . DS . 'lib' . DS . 'AntispamIconCaptcha.php'; 45 | $this->captcha = new AntispamIconCaptcha(); 46 | } 47 | } 48 | 49 | public function show() { 50 | return $this->captcha->getText(); 51 | } 52 | 53 | public function isValid() { 54 | return $this->captcha->isValid(); 55 | } 56 | 57 | } 58 | 59 | -------------------------------------------------------------------------------- /plugin/antispam/controllers/AntispamAdminController.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') or exit('Access denied!'); 11 | 12 | class AntispamAdminController extends AdminController 13 | { 14 | public function home() { 15 | $response = new AdminResponse(); 16 | $tpl = $response->createPluginTemplate('antispam', 'config'); 17 | $tpl->set('useText', ($this->runPlugin->getConfigVal('type') === 'useText') ? 'checked' : '' ); 18 | $tpl->set('useIcon', ($this->runPlugin->getConfigVal('type') === 'useIcon') ? 'checked' : '' ); 19 | $tpl->set('useRecaptcha', ($this->runPlugin->getConfigVal('type') === 'useRecaptcha') ? 'checked' : '' ); 20 | $response->addTemplate($tpl); 21 | return $response; 22 | } 23 | 24 | public function save() { 25 | if (!$this->user->isAuthorized()) { 26 | $this->core->error404(); 27 | } 28 | if ($_POST['captcha'] === 'useRecaptcha') { 29 | // Use ReCaptcha 30 | if (!isset($_POST['recaptchaPublicKey']) || !isset($_POST['recaptchaSecretKey']) || 31 | trim($_POST['recaptchaPublicKey']) == '' || trim($_POST['recaptchaSecretKey']) == '') { 32 | // Empty keys 33 | show::msg(Lang::get('antispam.google-captcha-empty'), 'error'); 34 | $this->core->redirect($this->router->generate('antispam-admin')); 35 | } 36 | // Save ReCaptcha 37 | $this->runPlugin->setConfigVal('recaptchaPublicKey', trim($_POST['recaptchaPublicKey'])); 38 | $this->runPlugin->setConfigVal('recaptchaSecretKey', trim($_POST['recaptchaSecretKey'])); 39 | } 40 | // Save Type 41 | $this->runPlugin->setConfigVal('type', trim($_POST['captcha'])); 42 | $this->pluginsManager->savePluginConfig($this->runPlugin); 43 | 44 | show::msg(Lang::get('core-changes-saved'), 'success'); 45 | $this->core->redirect($this->router->generate('antispam-admin')); 46 | } 47 | } -------------------------------------------------------------------------------- /plugin/antispam/langs/en.ini: -------------------------------------------------------------------------------- 1 | antispam.name = Antispam 2 | antispam.description = Protects your forms from spam bots 3 | 4 | antispam.conf-head = "Antispam Configuration" 5 | 6 | antispam.0 = zero 7 | antispam.1 = one 8 | antispam.2 = two 9 | antispam.3 = three 10 | antispam.4 = four 11 | antispam.5 = five 12 | antispam.6 = six 13 | antispam.7 = seven 14 | antispam.8 = eight 15 | antispam.9 = nine 16 | antispam.10 = ten 17 | antispam.11 = eleven 18 | antispam.12 = twelve 19 | 20 | antispam.how-counts = "How much is " 21 | antispam.minus = " minus " 22 | antispam.minus-alt = " subtracted from " 23 | antispam.plus = " plus " 24 | antispam.plus-alt = " added to " 25 | antispam.in-numbers = " (in digits)" 26 | 27 | antispam.text-captcha = Text captcha 28 | antispam.use-text-captcha = "Use text captcha" 29 | antispam.use-icon-captcha = "Use icon captcha" 30 | antispam.use-text-desc = "This is a simple captcha where the user must perform a basic math operation." 31 | antispam.use-icon-desc = "This is an icon-based captcha. The user must select the least or most represented image." 32 | 33 | antispam.icon-less-present = "Please select the least represented image among the images below" 34 | antispam.icon-more-present = "Please select the most represented image among the images below" 35 | 36 | antispam.google-captcha = "Google ReCaptcha" 37 | antispam.google-captcha-config = "ReCaptcha Configuration" 38 | antispam.google-captcha-desc = "A more powerful captcha, but requires registration to retrieve the two API keys." 39 | antispam.use-google-captcha = "Use Google ReCaptcha" 40 | antispam.use-google-captcha-register = "registration" 41 | antispam.google-captcha-public-key = "Site key (public key)" 42 | antispam.google-captcha-secret-key = "Secret key" 43 | antispam.google-captcha-empty = "ReCaptcha keys cannot be empty" 44 | 45 | antispam.invalid-captcha = "Invalid antispam, please try again" 46 | -------------------------------------------------------------------------------- /plugin/antispam/langs/fr.ini: -------------------------------------------------------------------------------- 1 | antispam.name = Antispam 2 | antispam.description = Protège vos formulaires des robots spammeurs 3 | 4 | antispam.conf-head = "Configuration de l'antispam" 5 | 6 | antispam.0 = zéro 7 | antispam.1 = un 8 | antispam.2 = deux 9 | antispam.3 = trois 10 | antispam.4 = quatre 11 | antispam.5 = cinq 12 | antispam.6 = six 13 | antispam.7 = sept 14 | antispam.8 = huit 15 | antispam.9 = neuf 16 | antispam.10 = dix 17 | antispam.11 = onze 18 | antispam.12 = douze 19 | 20 | antispam.how-counts = "Combien font " 21 | antispam.minus = " moins " 22 | antispam.minus-alt = " retranché(s) de " 23 | antispam.plus = " plus " 24 | antispam.plus-alt = " ajouté(s) à " 25 | antispam.in-numbers = " (en chiffres)" 26 | 27 | antispam.text-captcha = Captcha texte 28 | antispam.use-text-captcha = "Utiliser un captcha texte" 29 | antispam.use-icon-captcha = "Utiliser un captcha icône" 30 | antispam.use-text-desc = "Il s'agit d'un simple Captcha où l'utilisateur doit effectuer une simple opération mathématique." 31 | antispam.use-icon-desc = "Il s'agit d'un Captcha basé sur des icônes. L'utilisateur doit choisir la moins ou la plus représentée." 32 | 33 | antispam.icon-less-present = "Veuillez sélectionner l'image la moins présente parmis les images ci-dessous" 34 | antispam.icon-more-present = "Veuillez sélectionner l'image la plus présente parmis les images ci-dessous" 35 | 36 | antispam.google-captcha = "Google ReCaptcha" 37 | antispam.google-captcha-config = "Configuration de ReCaptcha" 38 | antispam.google-captcha-desc = "Captcha plus performant, mais nécessite une inscription pour pouvoir récupérer les 2 clés d'API." 39 | antispam.use-google-captcha = "Utiliser ReCaptcha de Google" 40 | antispam.use-google-captcha-register = "inscription" 41 | antispam.google-captcha-public-key = "Clé du site (clé publique)" 42 | antispam.google-captcha-secret-key = "Clé secrète" 43 | antispam.google-captcha-empty = "Les clés de ReCaptcha ne peuvent pas être vides" 44 | 45 | antispam.invalid-captcha = "Antispam invalide, veuillez réessayer" -------------------------------------------------------------------------------- /plugin/antispam/langs/ru.ini: -------------------------------------------------------------------------------- 1 | antispam.name = Антиспам 2 | antispam.description = Защищает ваши формы от спам-ботов 3 | 4 | antispam.conf-head = "Настройка антиспама" 5 | 6 | antispam.0 = ноль 7 | antispam.1 = один 8 | antispam.2 = два 9 | antispam.3 = три 10 | antispam.4 = четыре 11 | antispam.5 = пять 12 | antispam.6 = шесть 13 | antispam.7 = семь 14 | antispam.8 = восемь 15 | antispam.9 = девять 16 | antispam.10 = десять 17 | antispam.11 = одиннадцать 18 | antispam.12 = двенадцать 19 | 20 | antispam.how-counts = "Сколько будет " 21 | antispam.minus = " минус " 22 | antispam.minus-alt = " вычесть из " 23 | antispam.plus = " плюс " 24 | antispam.plus-alt = " прибавить к " 25 | antispam.in-numbers = " (цифрами)" 26 | 27 | antispam.text-captcha = Текстовая капча 28 | antispam.use-text-captcha = "Использовать текстовую капчу" 29 | antispam.use-icon-captcha = "Использовать иконковую капчу" 30 | antispam.use-text-desc = "Это простая капча, в которой пользователь должен выполнить элементарную математическую операцию." 31 | antispam.use-icon-desc = "Это капча на основе иконок. Пользователь должен выбрать наименее или наиболее часто встречающуюся картинку." 32 | 33 | antispam.icon-less-present = "Пожалуйста, выберите наименее представленное изображение из приведённых ниже" 34 | antispam.icon-more-present = "Пожалуйста, выберите наиболее представленное изображение из приведённых ниже" 35 | 36 | antispam.google-captcha = "Google ReCaptcha" 37 | antispam.google-captcha-config = "Настройка ReCaptcha" 38 | antispam.google-captcha-desc = "Более мощная капча, но требует регистрации для получения двух API-ключей." 39 | antispam.use-google-captcha = "Использовать Google ReCaptcha" 40 | antispam.use-google-captcha-register = "регистрация" 41 | antispam.google-captcha-public-key = "Ключ сайта (публичный ключ)" 42 | antispam.google-captcha-secret-key = "Секретный ключ" 43 | antispam.google-captcha-empty = "Ключи ReCaptcha не могут быть пустыми" 44 | 45 | antispam.invalid-captcha = "Неверный антиспам, пожалуйста, попробуйте ещё раз" 46 | -------------------------------------------------------------------------------- /plugin/antispam/lib/AntispamAbstractCaptcha.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') OR exit('Access denied!'); 11 | 12 | abstract class AntispamAbstractCaptcha { 13 | 14 | public function isGenericValid():bool { 15 | if (isset($_POST['ant_name_a']) && !empty($_POST['ant_name_a'])) { 16 | // If bot has filled the form, antispam is not valid 17 | return false; 18 | } 19 | return true; 20 | } 21 | 22 | public function getGenericHtml():string { 23 | return ''; 24 | } 25 | } -------------------------------------------------------------------------------- /plugin/antispam/lib/AntispamReCaptcha.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') OR exit('Access denied!'); 11 | 12 | class AntispamReCaptcha { 13 | 14 | protected $publicKey; 15 | protected $secretKey; 16 | 17 | public function __construct($publicKey, $secretKey) { 18 | $this->publicKey = $publicKey; 19 | $this->secretKey = $secretKey; 20 | } 21 | 22 | public function getText() { 23 | $input = ''; 24 | $script = ''; 25 | $script .= '"; 29 | $infos = '

    Protection par ReCaptcha. Confidentialité' 30 | . ' - Conditions

    '; 31 | return $input . $infos . $script; 32 | } 33 | 34 | public function isValid() { 35 | if (!isset($_POST['recaptcha-response']) || empty($_POST['recaptcha-response'])) { 36 | // Captcha not set or empty 37 | return false; 38 | } 39 | $url = "https://www.google.com/recaptcha/api/siteverify?secret=" 40 | . $this->secretKey . "&response=" . $_POST['recaptcha-response']; 41 | // Verify that CURL is available 42 | if (function_exists('curl_version')) { 43 | $curl = curl_init($url); 44 | curl_setopt($curl, CURLOPT_HEADER, false); 45 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); 46 | curl_setopt($curl, CURLOPT_TIMEOUT, 1); 47 | curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); 48 | $response = curl_exec($curl); 49 | } else { 50 | // Use file_get_contents 51 | $response = file_get_contents($url); 52 | } 53 | if (empty($response) || is_null($response)) { 54 | // Bad config or no response by API 55 | return false; 56 | } 57 | $data = json_decode($response); 58 | if ($data->success) { 59 | // Captcha is OK 60 | return true; 61 | } 62 | return false; 63 | } 64 | } -------------------------------------------------------------------------------- /plugin/antispam/lib/AntispamTextCaptcha.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') OR exit('Access denied!'); 11 | 12 | class AntispamTextCaptcha extends AntispamAbstractCaptcha { 13 | 14 | protected $operation; 15 | protected $result; 16 | 17 | public function getText() { 18 | if (!isset($this->operation)) { 19 | $this->generate(); 20 | } 21 | return '


    ' . $this->getGenericHtml(); 22 | } 23 | 24 | public function isValid() { 25 | return (isset($_SESSION['antispam_result']) && isset($_POST['antispam']) && $_SESSION['antispam_result'] === sha1($_POST['antispam']) && $this->isGenericValid()); 26 | } 27 | 28 | protected function generate() { 29 | $numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; 30 | $letters = []; 31 | foreach ($numbers as $number) { 32 | $letters[] = lang::get('antispam.' . $number); 33 | } 34 | $first = rand(0, count($numbers) - 1); 35 | $second = rand(0, count($numbers) - 1); 36 | $sign = rand(0, 1); 37 | $o = lang::get('antispam.how-counts') . $letters[$first]; 38 | if ($second <= $first && $sign == 1) { 39 | $r = $numbers[$first] - $numbers[$second]; 40 | $o .= lang::get('antispam.minus-alt'); 41 | } elseif ($second <= $first && $sign == 0) { 42 | $r = $numbers[$first] - $numbers[$second]; 43 | $o .= lang::get('antispam.minus'); 44 | } elseif ($second > $first && $sign == 1) { 45 | $r = $numbers[$first] + $numbers[$second]; 46 | $o .= lang::get('antispam.plus-alt'); 47 | } else { 48 | $r = $numbers[$first] + $numbers[$second]; 49 | $o .= lang::get('antispam.plus'); 50 | } 51 | $this->operation = $o . $letters[$second] . " ?"; 52 | $this->result = $r; 53 | $_SESSION['antispam_result'] = sha1($this->result); 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /plugin/antispam/param/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "priority" : "2", 3 | "label" : "Antispam", 4 | "type" : "useText", 5 | "recaptchaPublicKey" : "", 6 | "recaptchaSecretKey" : "" 7 | } -------------------------------------------------------------------------------- /plugin/antispam/param/hooks.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/299Ko/299ko/10ab59722e3cf472ea57acafd371c74d4dbc92f3/plugin/antispam/param/hooks.json -------------------------------------------------------------------------------- /plugin/antispam/param/infos.json: -------------------------------------------------------------------------------- 1 | { 2 | "slug" : "antispam", 3 | "name" : "Antispam", 4 | "icon" : "fa-solid fa-shield-virus", 5 | "description" : "Protège vos formulaires des robots spammeurs", 6 | "authorEmail" : "mx.koder@gmail.com", 7 | "authorWebsite" : "https://kodercloud.ovh", 8 | "version" : "2.0", 9 | "homeAdminMethod" : "AntispamAdminController#home" 10 | } -------------------------------------------------------------------------------- /plugin/antispam/param/routes.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') OR exit('Access denied!'); 11 | 12 | $router = router::getInstance(); 13 | 14 | $router->map('GET', '/admin/antispam[/?]', 'AntispamAdminController#home', 'antispam-admin'); 15 | $router->map('POST', '/admin/antispam/saveconf[/?]', 'AntispamAdminController#save', 'antispam-saveconf'); -------------------------------------------------------------------------------- /plugin/antispam/template/captcha-icon.tpl: -------------------------------------------------------------------------------- 1 |
    2 |

    3 | {% if lessOrMore %} 4 | {{ Lang.antispam.icon-more-present }} 5 | {% else %} 6 | {{ Lang.antispam.icon-less-present }} 7 | {% endif %} 8 |

    9 |
    10 | {% for k , icon in IconsToDisplay %} 11 |
    12 | 13 | 16 |
    17 | {% endfor %} 18 |
    19 |
    20 | 21 | -------------------------------------------------------------------------------- /plugin/antispam/template/config.tpl: -------------------------------------------------------------------------------- 1 | 25 |
    26 |
    {{Lang.antispam.conf-head}}
    27 |
    28 | {{SHOW.tokenField()}} 29 |

    30 | 31 |

    32 |

    33 | 34 |

    35 |

    36 | 37 |

    38 |
    39 |
    {{Lang.antispam.google-captcha-config}}
    40 |

    41 |
    42 | 43 |

    44 |

    45 |
    46 | 47 |

    48 |
    49 |

    50 | 51 |

    52 |
    53 |
    54 | -------------------------------------------------------------------------------- /plugin/antispam/template/help.tpl: -------------------------------------------------------------------------------- 1 |

    {{ Lang.antispam.text-captcha }}

    2 |

    {{Lang.antispam.use-text-desc}}

    3 |

    {{Lang.antispam.google-captcha}}

    4 |

    {{Lang.antispam.google-captcha-desc}}

    -------------------------------------------------------------------------------- /plugin/antispam/template/public.css: -------------------------------------------------------------------------------- 1 | 2 | .grecaptcha-badge { 3 | visibility: hidden; 4 | } 5 | 6 | .antispam-icon-container { 7 | display: flex; 8 | flex-direction: row; 9 | flex-wrap: wrap; 10 | align-items: center; 11 | justify-content: center; 12 | } 13 | 14 | .antispam-icon label { 15 | font-size: 2em; 16 | padding: 0.1em 0.25em ; 17 | cursor: pointer; 18 | transition: all 0.4s ease-in-out; 19 | } 20 | 21 | .antispam-icon > input[type="radio"]:checked + label { 22 | color: var(--primary-color); 23 | } 24 | .antispam-icon > input[type="radio"] { 25 | display: none; 26 | } 27 | 28 | #first_name_a { 29 | display: none; 30 | } -------------------------------------------------------------------------------- /plugin/blog/blog.php: -------------------------------------------------------------------------------- 1 | 7 | * @author Maxence Cauderlier 8 | * @author Frédéric Kaplon 9 | * @author Florent Fortat 10 | * 11 | * @package 299Ko https://github.com/299Ko/299ko 12 | */ 13 | defined('ROOT') OR exit('Access denied!'); 14 | 15 | require_once PLUGINS . 'blog/entities/news.php'; 16 | require_once PLUGINS . 'blog/entities/newsComment.php'; 17 | require_once PLUGINS . 'blog/entities/newsManager.php'; 18 | require_once PLUGINS . 'blog/entities/BlogCategoriesManager.php'; 19 | require_once PLUGINS . 'blog/entities/BlogCategory.php'; 20 | 21 | 22 | 23 | ## Fonction d'installation 24 | 25 | function blogInstall() { 26 | 27 | } 28 | 29 | ## Hooks 30 | 31 | function blogEndFrontHead() { 32 | $core = core::getInstance(); 33 | echo '' . "\n"; 34 | } 35 | 36 | function blogLinkShortcode(array $attributes):string { 37 | $newsManager = new newsManager(); 38 | $news = $newsManager->create((int) $attributes['id']); 39 | if (!$news) { 40 | return ''; 41 | } 42 | if (!isset($attributes['name'])) { 43 | $attributes['name'] = $news->getName(); 44 | } 45 | return '' . $attributes['name'] . ''; 46 | } 47 | 48 | function blogBeforeRunPlugin() { 49 | ContentParser::addShortcode('blogLink', 'BlogLinkShortcode'); 50 | } 51 | 52 | function BlogAdminCategoriesTemplates() { 53 | global $runPlugin; 54 | if ($runPlugin->getName() !== 'blog') { 55 | return; 56 | } 57 | 58 | $catsManager = new BlogCategoriesManager(); 59 | 60 | echo ''; 61 | } 62 | 63 | 64 | ## Code relatif au plugin 65 | 66 | -------------------------------------------------------------------------------- /plugin/blog/controllers/BlogAdminConfigController.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') OR exit('Access denied!'); 11 | 12 | class BlogAdminConfigController extends AdminController { 13 | 14 | public function saveConfig() { 15 | if ($this->user->isAuthorized()) { 16 | $this->runPlugin->setConfigVal('label', trim($_REQUEST['label'])); 17 | $this->runPlugin->setConfigVal('itemsByPage', trim(intval($_REQUEST['itemsByPage']))); 18 | $this->runPlugin->setConfigVal('hideContent', (isset($_POST['hideContent']) ? 1 : 0)); 19 | $this->runPlugin->setConfigVal('comments', (isset($_POST['comments']) ? 1 : 0)); 20 | $this->runPlugin->setConfigVal('displayTOC', filter_input(INPUT_POST, 'displayTOC', FILTER_SANITIZE_FULL_SPECIAL_CHARS)); 21 | $this->runPlugin->setConfigVal('displayAuthor', (isset($_POST['displayAuthor']) ? 1 : 0)); 22 | $this->runPlugin->setConfigVal('authorName', trim($_POST['authorName'])); 23 | $this->runPlugin->setConfigVal('authorAvatar', trim($_POST['authorAvatar'])); 24 | $this->runPlugin->setConfigVal('authorBio', $this->core->callHook('beforeSaveEditor',htmlspecialchars($_POST['authorBio']))); 25 | if ($this->pluginsManager->savePluginConfig($this->runPlugin)) { 26 | show::msg(lang::get('core-changes-saved'), 'success'); 27 | } else { 28 | show::msg(lang::get('core-changes-not-saved'), 'error'); 29 | } 30 | } 31 | // Open the posts list 32 | $controller = new BlogAdminPostsController(); 33 | return $controller->list(); 34 | } 35 | } -------------------------------------------------------------------------------- /plugin/blog/entities/BlogCategoriesManager.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') OR exit('Access denied!'); 11 | 12 | class BlogCategoriesManager extends CategoriesManager { 13 | 14 | protected string $pluginId = 'blog'; 15 | protected string $name = 'categories'; 16 | protected string $className = 'BlogCategory'; 17 | protected bool $nested = true; 18 | protected bool $chooseMany = true; 19 | 20 | public function getAddCategoryUrl():string { 21 | return router::getInstance()->generate('admin-blog-add-category'); 22 | } 23 | 24 | public function getDeleteUrl() :string { 25 | return router::getInstance()->generate('admin-blog-delete-category'); 26 | } 27 | 28 | public function getAjaxDisplayListUrl():string { 29 | return router::getInstance()->generate('admin-blog-list-ajax-categories'); 30 | } 31 | 32 | public function getEditUrl():string { 33 | return router::getInstance()->generate('admin-blog-edit-category'); 34 | } 35 | 36 | public function outputAsList() { 37 | echo '
    '; 38 | echo '
    '. lang::get('blog-categories-management-title').'
    '; 39 | echo parent::outputAsList(); 40 | echo '
    '; 41 | } 42 | } -------------------------------------------------------------------------------- /plugin/blog/entities/BlogCategory.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') OR exit('Access denied!'); 11 | 12 | class BlogCategory extends Category { 13 | 14 | protected string $pluginId = 'blog'; 15 | protected string $name = 'categories'; 16 | protected bool $nested = true; 17 | protected bool $chooseMany = true; 18 | 19 | 20 | } -------------------------------------------------------------------------------- /plugin/blog/param/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "priority" : "2", 3 | "label" : "Blog", 4 | "itemsByPage" : "5", 5 | "displayTOC" : "no", 6 | "hideContent" : "0", 7 | "comments" : "1", 8 | "authorName" : "", 9 | "authorAvatar" : "", 10 | "authorBio" : "", 11 | "displayAuthor" : false 12 | } -------------------------------------------------------------------------------- /plugin/blog/param/hooks.json: -------------------------------------------------------------------------------- 1 | { 2 | "endFrontHead" : "blogEndFrontHead", 3 | "adminToolsTemplates" : "BlogAdminCategoriesTemplates", 4 | "beforeRunPlugin" : "blogBeforeRunPlugin" 5 | } -------------------------------------------------------------------------------- /plugin/blog/param/infos.json: -------------------------------------------------------------------------------- 1 | { 2 | "slug" : "blog", 3 | "name" : "Blog", 4 | "icon" : "fa-regular fa-newspaper", 5 | "description" : "Blog management", 6 | "authorEmail" : "mx.koder@gmail.com", 7 | "authorWebsite" : "https://299ko.ovh", 8 | "version" : "2.0", 9 | "homePublicMethod" : "BlogListController#home", 10 | "homeAdminMethod" : "BlogAdminPostsController#list" 11 | } -------------------------------------------------------------------------------- /plugin/blog/param/routes.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') OR exit('Access denied!'); 11 | 12 | $router = router::getInstance(); 13 | 14 | // Public 15 | $router->map('GET', '/blog[/?]', 'BlogListController#home', 'blog-home'); 16 | $router->map('GET', '/blog/cat-[*:name]-[i:id]/[i:page][/?]', 'BlogListController#categoryPage', 'blog-category-page'); 17 | $router->map('GET', '/blog/cat-[*:name]-[i:id].html', 'BlogListController#category', 'blog-category'); 18 | $router->map('GET', '/blog/[*:name]-[i:id].html', 'BlogReadController#read', 'blog-read'); 19 | $router->map('POST', '/blog/send.html', 'BlogReadController#send', 'blog-send'); 20 | $router->map('GET', '/blog/rss.html', 'BlogReadController#rss', 'blog-rss'); 21 | $router->map('GET', '/blog/[i:page][/?]', 'BlogListController#page', 'blog-page'); 22 | 23 | // Categories 24 | $router->map('POST', '/admin/blog/addCategory', 'BlogAdminCategoriesController#addCategory', 'admin-blog-add-category'); 25 | $router->map('POST', '/admin/blog/deleteCategory', 'BlogAdminCategoriesController#deleteCategory', 'admin-blog-delete-category'); 26 | $router->map('POST', '/admin/blog/editCategory', 'BlogAdminCategoriesController#editCategory', 'admin-blog-edit-category'); 27 | $router->map('POST', '/admin/blog/saveCategory/[i:id]', 'BlogAdminCategoriesController#saveCategory', 'admin-blog-save-category'); 28 | $router->map('GET', '/admin/blog/listAjaxCategories', 'BlogAdminCategoriesController#listAjaxCategories', 'admin-blog-list-ajax-categories'); 29 | 30 | // Configuration 31 | $router->map('POST', '/admin/blog/saveConfig', 'BlogAdminConfigController#saveConfig', 'admin-blog-save-config'); 32 | 33 | // Posts 34 | $router->map('GET', '/admin/blog[/?]', 'BlogAdminPostsController#list', 'admin-blog-list'); 35 | $router->map('POST', '/admin/blog/deletePost', 'BlogAdminPostsController#deletePost', 'admin-blog-delete-post'); 36 | $router->map('GET', '/admin/blog/editPost/[i:id]?', 'BlogAdminPostsController#editPost', 'admin-blog-edit-post'); 37 | $router->map('POST', '/admin/blog/savePost', 'BlogAdminPostsController#savePost', 'admin-blog-save-post'); 38 | 39 | // Comments 40 | $router->map('POST', '/admin/blog/deleteComment', 'BlogAdminCommentsController#deleteComment', 'admin-blog-delete-comment'); 41 | $router->map('POST', '/admin/blog/saveComment', 'BlogAdminCommentsController#saveComment', 'admin-blog-save-comment'); 42 | $router->map('GET', '/admin/blog/listComments/[i:id]', 'BlogAdminCommentsController#listComments', 'admin-blog-list-comments'); -------------------------------------------------------------------------------- /plugin/blog/template/admin-edit-category.tpl: -------------------------------------------------------------------------------- 1 |
    2 |
    {{ Lang.blog.categories.editCategory}}
    3 | 4 | 5 | 6 | {{categoriesManager.outputAsSelect(category.parentId, category.id, "category-list-edit-parentId")}} 7 | 8 |
    9 | -------------------------------------------------------------------------------- /plugin/blog/template/admin.css: -------------------------------------------------------------------------------- 1 | #seoDesc { 2 | margin-bottom: 0; 3 | } 4 | 5 | #seoDescProgress { 6 | height:8px; 7 | max-width: 100%; 8 | transition: background-color 0.5s ease; 9 | } 10 | 11 | #seoDescProgress.warning { 12 | background-color: #e53935; 13 | } 14 | 15 | #seoDescProgress.care { 16 | background-color: #ffb300; 17 | } 18 | 19 | #seoDescProgress.good { 20 | background-color: #7cb342; 21 | } 22 | 23 | #seoDescCounter { 24 | font-size: 13px; 25 | } 26 | 27 | span.blog-category { 28 | margin-right: 10px; 29 | position: relative; 30 | } 31 | span.blog-category:after { 32 | position: absolute; 33 | left:100%; 34 | content:';'; 35 | padding-left:5px; 36 | } 37 | 38 | #news-image-container { 39 | display: flex; 40 | flex-direction: row; 41 | flex-wrap: wrap; 42 | } 43 | 44 | #news-image-container section { 45 | width: 48%; 46 | } 47 | 48 | @media only screen and (max-width: 960px) { 49 | #news-image-container section { 50 | width: 100%; 51 | } 52 | } -------------------------------------------------------------------------------- /plugin/blog/template/comment.tpl: -------------------------------------------------------------------------------- 1 |
  • 2 | 28 | {% if comment.hasReplies %} 29 |
      30 | {% for reply in comment.replies %} 31 | {{ reply.show}} 32 | {% endfor %} 33 |
    34 | {% endif %} 35 | 36 |
  • 37 | -------------------------------------------------------------------------------- /plugin/blog/template/list.tpl: -------------------------------------------------------------------------------- 1 | {% if mode === "list_empty" %} 2 |

    {{Lang.galerie.no-item-found}}

    3 | {% else %} 4 | {% for k, v in news %} 5 |
    6 | {% if runPlugin.getConfigVal("hideContent") == false %} 7 |
    8 | {% if v.img %} 9 | {{v.img}} 10 | {% endif %} 11 |
    12 |

    13 | {{v.name}} 14 |

    15 |

    {{v.date}} 16 | {% if runPlugin.getConfigVal("comments") && v.commentsOff == false %} 17 | | 18 | {{ newsManager.countComments(v.id) }} 19 | commentaire{% if newsManager.countComments(v.id) > 1 %}s{% endif %} 20 | {% endif %} 21 | | 22 | {% if count(v.cats) == 0 %} 23 | Non classé 24 | {% else %} 25 | {% for cat in v.cats %} 26 | {{ cat.label }} 27 | {% endfor %} 28 | {% endif %} 29 |

    30 | 31 |
    32 |
    33 | {% if v.intro %} 34 | {{htmlspecialchars_decode(v.intro)}} 35 | {% else %} 36 | {{htmlspecialchars_decode(v.content)}} 37 | {% endif %} 38 | {% else %} 39 |

    40 | {{v.name}} 41 |

    42 |

    {{v.date}} 43 | {% if runPlugin.getConfigVal("comments") && v.commentsOff == false %} 44 | | 45 | {{ newsManager.countComments(v.id) }} 46 | commentaire{% if newsManager.countComments(v.id) > 1 %}s{% endif %} 47 | {% endif %} 48 |

    49 | {% endif %} 50 |
    51 | {% endfor %} 52 | {% if pagination %} 53 |
      54 | {% for k, v in pagination %} 55 |
    • 56 | {{v.num}} 57 |
    • 58 | {% endfor %} 59 |
    60 | {% endif %} 61 | {% endif %} 62 | -------------------------------------------------------------------------------- /plugin/blog/template/public.css: -------------------------------------------------------------------------------- 1 | .blog-author { 2 | display: flex; 3 | } 4 | 5 | .blog-avatar { 6 | width:auto; 7 | } 8 | 9 | .blog-avatar img { 10 | max-width: 80px; 11 | border-radius: 50%; 12 | border:2px solid #ccc; 13 | box-shadow: 0 0 5px rgba(0,0,0,0.15); 14 | } 15 | 16 | .blog-infos { 17 | margin-left: 20px; 18 | } 19 | 20 | .blog-infos-name span { 21 | font-size: 14px; 22 | font-weight: 600; 23 | } 24 | 25 | .blog-infos-bio { 26 | font-size: 12px; 27 | } 28 | 29 | .blog-label-category { 30 | position: relative; 31 | } 32 | 33 | .blog-label-category:after { 34 | content: ','; 35 | margin-left: 5px; 36 | margin-right: 5px; 37 | } 38 | 39 | .blog-label-category:last-of-type:after { 40 | content: ''; 41 | margin-left: 0px; 42 | margin-right: 0px; 43 | } 44 | 45 | /* Comments */ 46 | 47 | #comments-add-respond { 48 | position: relative; 49 | } 50 | 51 | #comments-cancel-respond { 52 | display: none; 53 | position: absolute; 54 | top:0; 55 | right:0; 56 | } 57 | 58 | .comment-content { 59 | clear:both; 60 | } -------------------------------------------------------------------------------- /plugin/blog/template/public.js: -------------------------------------------------------------------------------- 1 | document.addEventListener("DOMContentLoaded", function () { 2 | document.querySelectorAll('.btn-add-respond').forEach(function (item) { 3 | item.addEventListener("click", function (e) { 4 | e.preventDefault(); 5 | var $form = document.querySelector('#comments-add-respond'); 6 | var parent_id = item.getAttribute('data-id'); 7 | var $comment = document.querySelector('#comment' + parent_id); 8 | document.querySelector("#comments-title").textContent = document.querySelector('#comment' + parent_id + "Infos").getAttribute('data-author'); 9 | document.querySelector('#commentParentId').value = parent_id; 10 | $comment.after($form); 11 | var $aRem = document.querySelector("#comments-cancel-respond"); 12 | $aRem.style.display = "block"; 13 | }); 14 | }); 15 | 16 | if (document.querySelector('#comments-cancel-respond')) { 17 | document.querySelector('#comments-cancel-respond').addEventListener("click", function (e) { 18 | e.preventDefault(); 19 | var $aRem = document.querySelector("#comments-cancel-respond"); 20 | $aRem.style.display = "none"; 21 | var $form = document.querySelector('#comments-add-respond'); 22 | var $container = document.querySelector('#comments-add-container'); 23 | document.querySelector("#comments-title").textContent = document.querySelector("#comments-title").getAttribute('data-title'); 24 | document.querySelector('#commentParentId').value = 0; 25 | $container.after($form); 26 | }); 27 | } 28 | 29 | }); -------------------------------------------------------------------------------- /plugin/configmanager/controllers/ConfigManagerUpdateController.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') or exit('Access denied!'); 11 | 12 | class ConfigManagerUpdateController extends AdminController { 13 | 14 | public function process($token) { 15 | $updaterManager = new UpdaterManager(); 16 | if ($updaterManager->isReady) { 17 | $nextVersion = $updaterManager->getNextVersion(); 18 | } else { 19 | $nextVersion = false; 20 | } 21 | if ($nextVersion && $this->user->isAuthorized()) { 22 | $updaterManager->update(); 23 | show::msg(Lang::get('configmanager-updated', $nextVersion), 'success'); 24 | $updaterManager->clearCache(); 25 | $this->core->redirect($this->router->generate('configmanager-admin')); 26 | } 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /plugin/configmanager/entities/ConfigManagerBackup.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') or exit('Access denied!'); 11 | 12 | class ConfigManagerBackup extends Zip { 13 | 14 | public string $filename; 15 | 16 | public string $date; 17 | 18 | public int $timestamp = 0; 19 | 20 | public string $url; 21 | 22 | public function __construct($filename) { 23 | parent::__construct(DATA_PLUGIN . 'configmanager' . DS . $filename); 24 | if (preg_match('/backup-(\d{4})-(\d{2})-(\d{2})-(\d{2})-(\d{2})-(\d{2})/', $filename, $matches)) { 25 | $this->date = "{$matches[1]}-{$matches[2]}-{$matches[3]} {$matches[4]}:{$matches[5]}:{$matches[6]}"; 26 | $this->timestamp = util::getTimestampFromDate($this->date); 27 | } else { 28 | throw new Exception("Invalid backup filename: $filename"); 29 | } 30 | $this->url = router::getInstance()->generate('configmanager-dl-backup', [ 31 | 'token' => UsersManager::getCurrentUser()->token, 32 | 'timestamp' => $this->timestamp 33 | ]); 34 | } 35 | 36 | public function delete() { 37 | return unlink( $this->filename); 38 | } 39 | } -------------------------------------------------------------------------------- /plugin/configmanager/entities/ConfigManagerBackupsManager.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') or exit('Access denied!'); 11 | 12 | class ConfigManagerBackupsManager { 13 | 14 | public static function getAll():array { 15 | $backups = []; 16 | $backupFiles = []; 17 | $files = scandir(DATA_PLUGIN . 'configmanager' . DS); 18 | foreach ($files as $file) { 19 | if (preg_match('/backup-.*\.zip/i', $file)) { 20 | $backupFiles[] = $file; 21 | } 22 | } 23 | foreach ($backupFiles as $file) { 24 | $obj = new ConfigManagerBackup($file); 25 | $timestamp = util::getTimestampFromDate($obj->date); 26 | $backups[$timestamp] = $obj; 27 | } 28 | krsort($backups, SORT_NUMERIC); 29 | return $backups; 30 | } 31 | } -------------------------------------------------------------------------------- /plugin/configmanager/langs/en.ini: -------------------------------------------------------------------------------- 1 | configmanager.name = Configuration 2 | configmanager.description = Configuration Manager 3 | 4 | configmanager-settings = "Site Settings" 5 | configmanager-advanced-settings = "Advanced settings" 6 | configmanager-hide-titles = "Hide Page Titles" 7 | configmanager-public-default-plugin = "Default Plugin (Public)" 8 | configmanager-admin-default-plugin = "Default Plugin (Admin)" 9 | configmanager-sitename = "Site Name" 10 | configmanager-sitedesc = "Site Description" 11 | configmanager-sitelang = "Site Language" 12 | configmanager-theme = "Site Theme" 13 | configmanager-delete-cache-desc = "Deleting the site cache can be useful if you are having trouble updating 299Ko" 14 | configmanager-delete-cache = "Delete Cache" 15 | configmanager-debug = "Debug Mode" 16 | configmanager-siteurl = "Site URL (without trailing slash)" 17 | configmanager-htaccess = ".htaccess File Content" 18 | 19 | configmanager-updated = "Version %s has been installed. See the logs.txt file for more information." 20 | configmanager-update-msg = "A new version is available.
    21 | Click below to update your site to version %s.
    22 | Remember to back up your site before performing this update.
    23 | You can view the changelog of 299Ko versions here." 25 | configmanager-update = "Update Site" 26 | 27 | configmanager-deleted-install = "The installation file has been successfully deleted." 28 | configmanager-delete-install-error = "Unable to delete the 'install.php' file." 29 | configmanager-delete-install-msg = "The install.php file is still present. For safety reasons, it is recommended to delete it.
    30 | If the installation of 299ko went smoothly, click the button below to delete it." 31 | configmanager-delete-install = "Delete the install.php file" 32 | 33 | configmanager-cache-cleared = "The cache has been cleared." 34 | 35 | configmanager-backup = "Backup and Restore" 36 | configmanager-backup-desc = "Site backup and restore" 37 | configmanager-backup-list = "Backup list" 38 | configmanager-backup-create = "Create a backup" 39 | configmanager-backup-done-success = "Backup completed successfully" 40 | configmanager-backup-done-error = "An error occurred during the backup" 41 | configmanager-backup-date = "Backup date" 42 | configmanager-backup-size = "Backup size" 43 | configmanager-backup-download = "Download backup" 44 | configmanager-backup-delete = "Delete backup" 45 | configmanager-backup-delete-success = "Backup deleted successfully" 46 | configmanager-backup-delete-error = "An error occurred while deleting the backup" 47 | configmanager-backup-no-backup = "No backups available" -------------------------------------------------------------------------------- /plugin/configmanager/param/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "priority" : "2", 3 | "protected" : "1" 4 | } -------------------------------------------------------------------------------- /plugin/configmanager/param/hooks.json: -------------------------------------------------------------------------------- 1 | { 2 | "afterAdminTitle" : "configManagerDisplayInstallFile", 3 | "adminHead" : "configManagerCheckNewVersion", 4 | "adminToolsTemplates" : "configmanagerBackupTemplates" 5 | } -------------------------------------------------------------------------------- /plugin/configmanager/param/infos.json: -------------------------------------------------------------------------------- 1 | { 2 | "slug" : "configmanager", 3 | "name" : "Configuration", 4 | "icon" : "fa-solid fa-gears", 5 | "description" : "Gestion de la configuration", 6 | "authorEmail" : "mx.koder@gmail.com", 7 | "authorWebsite" : "https://299ko.ovh", 8 | "version" : "2.0", 9 | "homeAdminMethod" : "ConfigManagerAdminController#home" 10 | } -------------------------------------------------------------------------------- /plugin/configmanager/param/routes.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') OR exit('Access denied!'); 11 | 12 | $router = router::getInstance(); 13 | 14 | $router->map('GET', '/admin/configmanager[/?]', 'ConfigManagerAdminController#home', 'configmanager-admin'); 15 | $router->map('POST', '/admin/configmanager/save', 'ConfigManagerAdminController#save', 'configmanager-admin-save'); 16 | $router->map('GET', '/admin/configmanager/cacheclear/[a:token]', 'ConfigManagerAdminController#clearCache', 'configmanager-admin-cache-clear'); 17 | $router->map('GET', '/admin/configmanager/update/[a:token]', 'ConfigManagerUpdateController#process', 'configmanager-update'); 18 | $router->map('GET', '/admin/configmanager/delete-install/[a:token]', 'ConfigManagerAdminController#deleteInstall', 'configmanager-delete-install'); 19 | 20 | // Backups 21 | $router->map('GET', '/admin/configmanager/backup', 'ConfigManagerBackupAdminController#home', 'configmanager-backup'); 22 | $router->map('GET', '/admin/configmanager/create-backup/[a:token]', 'ConfigManagerBackupAdminController#create', 'configmanager-create-backup'); 23 | $router->map('GET', '/admin/configmanager/dl-backup/[a:token]/[i:timestamp]', 'ConfigManagerBackupAdminController#download', 'configmanager-dl-backup'); 24 | $router->map('POST', '/admin/configmanager/delete-backup', 'ConfigManagerBackupAdminController#delete', 'configmanager-delete-backup'); -------------------------------------------------------------------------------- /plugin/configmanager/template/admin.css: -------------------------------------------------------------------------------- 1 | #configmanager-backup { 2 | float: right; 3 | opacity: 0.65; 4 | transition: opacity 0.4s ease-in-out; 5 | font-size: 22px; 6 | margin-left: 10px; 7 | background-color: var(--primary-color); 8 | color: #fff; 9 | border-radius: 50%; 10 | padding: 10px; 11 | text-align: center; 12 | width: 47px; 13 | height: 47px; 14 | } -------------------------------------------------------------------------------- /plugin/configmanager/template/backup.tpl: -------------------------------------------------------------------------------- 1 |
    2 |
    {{ Lang.configmanager-backup-list }}
    3 | token]) }}">{{ Lang.configmanager-backup-create }} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {% for backup in backups %} 12 | 13 | 14 | 15 | 18 | 23 | 24 | {% endfor %} 25 | {% if emptyBackups %} 26 | 27 | 28 | 29 | {% endif %} 30 |
    {{ Lang.configmanager-backup-date }}{{ Lang.configmanager-backup-size }}{{ Lang.configmanager-backup-download }}{{ Lang.configmanager-backup-delete }}
    {{ util.getDateHour(backup.date) }}{{ backup.filesize }} 16 | 17 | 19 | 20 | 21 | 22 |
    {{ Lang.configmanager-backup-no-backup }}
    31 |
    32 | 33 | -------------------------------------------------------------------------------- /plugin/contact/contact.php: -------------------------------------------------------------------------------- 1 | 7 | * @author Maxence Cauderlier 8 | * @author Frédéric Kaplon 9 | * @author Florent Fortat 10 | * 11 | * @package 299Ko https://github.com/299Ko/299ko 12 | */ 13 | defined('ROOT') OR exit('No pagesFileect script access allowed'); 14 | 15 | ## Fonction d'installation 16 | 17 | function contactInstall() { 18 | util::writeJsonFile(DATA_PLUGIN . 'contact/emails.json', []); 19 | $data = util::readJsonFile(DATA_PLUGIN . 'contact/config.json'); 20 | lang::loadLanguageFile(PLUGINS . 'contact/langs/'); 21 | $data['acceptation'] = lang::get('contact.default-acceptation'); 22 | util::writeJsonFile(DATA_PLUGIN . 'contact/config.json', $data); 23 | } 24 | 25 | ## Hooks 26 | ## Code relatif au plugin 27 | 28 | function contactSave($email) { 29 | $data = util::readJsonFile(DATA_PLUGIN . 'contact/emails.json'); 30 | $data[] = $email; 31 | util::writeJsonFile(DATA_PLUGIN . 'contact/emails.json', array_unique($data)); 32 | } 33 | 34 | function contactSend() { 35 | global $runPlugin; 36 | $core = core::getInstance(); 37 | $from = '299ko@' . $_SERVER['SERVER_NAME']; 38 | $reply = strip_tags(trim($_POST['email'])); 39 | $name = strip_tags(trim($_POST['name'])); 40 | $firstName = strip_tags(trim($_POST['firstname'])); 41 | $msg = strip_tags(trim($_POST['message'])); 42 | if (!util::isEmail($reply) || $name == '' || $firstName == '' || $msg == '') 43 | return false; 44 | contactSave($reply); 45 | $to = User::findPK($runPlugin->getConfigVal('userMailId'))->email; 46 | $subject = 'Contact ' . $core->getConfigVal('siteName'); 47 | $msg = $msg . "\n\n----------\n\n" . $name . " " . $firstName . " (" . $reply . ")"; 48 | if (util::isEmail($runPlugin->getConfigVal('copy'))) 49 | util::sendEmail($from, $reply, $runPlugin->getConfigVal('copy'), $subject, $msg); 50 | return util::sendEmail($from, $reply, $to, $subject, $msg); 51 | } -------------------------------------------------------------------------------- /plugin/contact/controllers/ContactAdminController.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') or exit('Access denied!'); 11 | 12 | class ContactAdminController extends AdminController { 13 | 14 | public function home() { 15 | $response = new AdminResponse(); 16 | $tpl = $response->createPluginTemplate('contact', 'admin-contact'); 17 | 18 | $selectedUserId = $this->runPlugin->getConfigVal('userMailId'); 19 | 20 | Template::addGlobal('contactUsers', User::all()); 21 | Template::addGlobal('contactSelected', $selectedUserId); 22 | $tpl->set('token', $this->user->token); 23 | $tpl->set('emails', implode("\n", util::readJsonFile(DATA_PLUGIN . 'contact/emails.json'))); 24 | $response->addTemplate($tpl); 25 | return $response; 26 | } 27 | 28 | public function saveParams() { 29 | if (!$this->user->isAuthorized()) { 30 | return $this->home(); 31 | } 32 | $this->runPlugin->setConfigVal('label', $_POST['label']); 33 | $this->runPlugin->setConfigVal('copy', $_POST['copy']); 34 | $this->runPlugin->setConfigVal('acceptation', $_POST['acceptation']); 35 | $this->runPlugin->setConfigVal('userMailId', (int) $_POST['selectedUser']); 36 | 37 | return $this->savePluginConf(); 38 | } 39 | 40 | public function saveConfig() { 41 | if (!$this->user->isAuthorized()) { 42 | return $this->home(); 43 | } 44 | $this->runPlugin->setConfigVal('content1', $this->core->callHook('beforeSaveEditor', $_POST['content1'])); 45 | $this->runPlugin->setConfigVal('content2', $this->core->callHook('beforeSaveEditor', $_POST['content2'])); 46 | 47 | return $this->savePluginConf(); 48 | } 49 | 50 | public function emptyMails($token) { 51 | if (!$this->user->isAuthorized()) { 52 | return $this->home(); 53 | } 54 | util::writeJsonFile(DATA_PLUGIN . 'contact/emails.json', []); 55 | show::msg(lang::get('contact.base_deleted'), 'info'); 56 | logg(lang::get('contact.base_deleted'), 'INFO'); 57 | return $this->home(); 58 | } 59 | 60 | protected function savePluginConf() { 61 | if ($this->pluginsManager->savePluginConfig($this->runPlugin)) { 62 | show::msg(lang::get('core-changes-saved'), 'success'); 63 | } else { 64 | show::msg(lang::get('core-changes-not-saved'), 'error'); 65 | } 66 | return $this->home(); 67 | } 68 | 69 | 70 | } -------------------------------------------------------------------------------- /plugin/contact/controllers/ContactController.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') or exit('Access denied!'); 11 | 12 | class ContactController extends PublicController 13 | { 14 | 15 | public function home() 16 | { 17 | $antispam = ($this->pluginsManager->isActivePlugin('antispam')) ? new antispam() : false; 18 | 19 | $this->runPlugin->setMainTitle($this->runPlugin->getConfigVal('label')); 20 | $this->runPlugin->setTitleTag($this->runPlugin->getConfigVal('label')); 21 | $antispamField = ($antispam) ? $antispam->show() : ''; 22 | 23 | $response = new PublicResponse(); 24 | $tpl = $response->createPluginTemplate('contact', 'contact'); 25 | 26 | $tpl->set('name', $_POST['name'] ?? ''); 27 | $tpl->set('firstname', $_POST['firstname'] ?? ''); 28 | $tpl->set('email', $_POST['email'] ?? ''); 29 | $tpl->set('message', $_POST['message'] ?? ''); 30 | $tpl->set('acceptation', (trim($this->runPlugin->getConfigVal('acceptation')) != '') ? true : false); 31 | $tpl->set('antispam', $antispam); 32 | $tpl->set('antispamField', $antispamField); 33 | $tpl->set('sendUrl', $this->router->generate('contact-send')); 34 | $response->addTemplate($tpl); 35 | return $response; 36 | } 37 | 38 | public function send() 39 | { 40 | $sendError = false; 41 | $antispam = ($this->pluginsManager->isActivePlugin('antispam')) ? new antispam() : false; 42 | // quelques contrôles et temps mort volontaire avant le send... 43 | sleep(2); 44 | if ($antispam) { 45 | if (!$antispam->isValid()) { 46 | show::msg(lang::get('antispam.invalid-captcha'), 'error'); 47 | $sendError = true; 48 | } 49 | } 50 | if (!$sendError) { 51 | if ($_POST['_name'] == '' && strchr($_SERVER['HTTP_REFERER'], 'contact') !== false) { 52 | if (contactSend()) { 53 | show::msg(lang::get('contact.msg-sent'), 'success'); 54 | } else { 55 | show::msg(lang::get('something-wrong'), 'error'); 56 | $sendError = true; 57 | } 58 | } else { 59 | show::msg(lang::get('contact.fiels-error'), 'error'); 60 | $sendError = true; 61 | } 62 | } 63 | return $this->home(); 64 | } 65 | } -------------------------------------------------------------------------------- /plugin/contact/langs/en.ini: -------------------------------------------------------------------------------- 1 | ; General 2 | 3 | contact.name = Contact 4 | contact.description = "Contact Form" 5 | 6 | ; Public 7 | 8 | contact.form_name = "Second Name" 9 | contact.form_firstname = "First name" 10 | contact.form_email = "Email" 11 | contact.form_message = "Message" 12 | contact.form_send = "Send" 13 | contact.msg-sent = "Message sent" 14 | contact.fiels-error = "Incomplete field(s) or invalid email" 15 | contact.default-acceptation = "By checking this box and submitting this form, I agree that my personal data will be used to contact me in the context of my request indicated in this form. No further processing will be carried out with my information." 16 | 17 | ; Admin 18 | 19 | contact.copy_recipient = "Recipient in copy" 20 | contact.page_title = "Page Title" 21 | contact.form_acceptance_text = "Acceptance text before sending the form" 22 | contact.content = "Content" 23 | contact.before_form = "Before the form" 24 | contact.after_form = "After the form" 25 | contact.collected_email_addresses = "Collected email addresses" 26 | contact.emails_collected = "Email Addresses collected by the form" 27 | contact.delete_base = "Delete base" 28 | contact.confirm_empty_mail_base = "Are you sure you want to empty the collected email addresses base?" 29 | contact.base_deleted = "The email base has been emptied" 30 | contact.select-user = "Select the user who will receive contact emails" -------------------------------------------------------------------------------- /plugin/contact/langs/fr.ini: -------------------------------------------------------------------------------- 1 | ; General 2 | 3 | contact.name = Contact 4 | contact.description = "Formulaire de contact" 5 | 6 | ; Public 7 | 8 | contact.form_name = "Nom" 9 | contact.form_firstname = "Prénom" 10 | contact.form_email = "Email" 11 | contact.form_message = "Message" 12 | contact.form_send = "Envoyer" 13 | contact.msg-sent = "Message envoyé" 14 | contact.fiels-error = "Champ(s) incomplet(s) ou email invalide" 15 | contact.default-acceptation = "En cochant cette case et en soumettant ce formulaire, j'accepte que mes données personnelles soient utilisées pour me recontacter dans le cadre de ma demande indiquée dans ce formulaire. Aucun autre traitement ne sera effectué avec mes informations." 16 | 17 | ; Admin 18 | 19 | contact.copy_recipient = "Destinataire en copie" 20 | contact.page_title = "Titre de page" 21 | contact.form_acceptance_text = "Texte d'acceptation avant envoi du formulaire" 22 | contact.content = "Contenu" 23 | contact.before_form = "Avant le formulaire" 24 | contact.after_form = "Après le formulaire" 25 | contact.collected_email_addresses = "Adresses email récoltées" 26 | contact.emails_collected = "Adresses Email récoltées par le formulaire" 27 | contact.delete_base = "Supprimer la base" 28 | contact.confirm_empty_mail_base = "Êtes-vous sûr de vouloir vider la base des adresses mail collectées ?" 29 | contact.base_deleted = "La base des emails a été vidée" 30 | contact.select-user = "Sélectionnez l'utilisateur qui recevra les mails de contact" -------------------------------------------------------------------------------- /plugin/contact/langs/ru.ini: -------------------------------------------------------------------------------- 1 | ; Основное 2 | 3 | contact.name = Связаться 4 | contact.description = "Контактная форма" 5 | 6 | ; Публичное 7 | 8 | contact.form_name = "Фамилия" 9 | contact.form_firstname = "Имя" 10 | contact.form_email = "Электронная почта" 11 | contact.form_message = "Сообщение" 12 | contact.form_send = "Отправить" 13 | contact.msg-sent = "Сообщение отправлено" 14 | contact.fiels-error = "Незаполненное поле (поля) или неверный адрес электронной почты" 15 | contact.default-acceptation = "Отмечая это поле и отправляя эту форму, я соглашаюсь с тем, что мои личные данные будут использованы для связи со мной в контексте моего запроса, указанного в этой форме. Никакой дальнейшей обработки моей информации производиться не будет." 16 | 17 | ; Администратор 18 | 19 | contact.copy_recipient = "Получатель в копии" 20 | contact.page_title = "Заголовок страницы" 21 | contact.form_acceptance_text = "Текст подтверждения перед отправкой формы" 22 | contact.content = "Содержание" 23 | contact.before_form = "Перед формой" 24 | contact.after_form = "После формы" 25 | contact.collected_email_addresses = "Собранные адреса электронной почты" 26 | contact.emails_collected = "Адреса электронной почты, собранные с помощью формы" 27 | contact.delete_base = "Удалить базу" 28 | contact.confirm_empty_mail_base = "Вы уверены, что хотите очистить базу собранных адресов электронной почты?" 29 | contact.base_deleted = "База электронных адресов была очищена" 30 | contact.select-user = "Выберите пользователя, который будет получать контактные электронные письма" -------------------------------------------------------------------------------- /plugin/contact/param/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "priority" : "2", 3 | "content1" : "", 4 | "content2" : "", 5 | "label" : "Contact", 6 | "copy" : "", 7 | "acceptation" : "", 8 | "userMailId": 1 9 | } -------------------------------------------------------------------------------- /plugin/contact/param/hooks.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/299Ko/299ko/10ab59722e3cf472ea57acafd371c74d4dbc92f3/plugin/contact/param/hooks.json -------------------------------------------------------------------------------- /plugin/contact/param/infos.json: -------------------------------------------------------------------------------- 1 | { 2 | "slug" : "contact", 3 | "name" : "Contact", 4 | "icon" : "fa-solid fa-address-card", 5 | "description" : "Formulaire de contact", 6 | "authorEmail" : "mx.koder@gmail.com", 7 | "authorWebsite" : "https://299ko.ovh", 8 | "version" : "2.0", 9 | "homePublicMethod" : "ContactController#home", 10 | "homeAdminMethod" : "ContactAdminController#home" 11 | } -------------------------------------------------------------------------------- /plugin/contact/param/routes.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') OR exit('Access denied!'); 11 | 12 | $router = router::getInstance(); 13 | 14 | $router->map('GET', '/contact[/?]', 'ContactController#home', 'contact-home'); 15 | $router->map('POST', '/contact/send.html', 'ContactController#send', 'contact-send'); 16 | 17 | $router->map('GET', '/admin/contact[/?]', 'ContactAdminController#home', 'contact-admin-home'); 18 | $router->map('POST', '/admin/contact/saveParams', 'ContactAdminController#saveParams', 'contact-saveParams'); 19 | $router->map('POST', '/admin/contact/saveConfig', 'ContactAdminController#saveConfig', 'contact-saveConfig'); 20 | $router->map('GET', '/admin/contact/emptyMails/[a:token]', 'ContactAdminController#emptyMails', 'contact-empty-mails'); -------------------------------------------------------------------------------- /plugin/contact/template/admin-contact.tpl: -------------------------------------------------------------------------------- 1 |
    2 |
    {{ Lang.contact.content }}
    3 |
    4 | {{ show.tokenField() }} 5 |

    6 | 7 |
    8 | {{ filemanagerDisplayManagerButton() }} 9 |

    10 |

    11 |
    12 |
    13 | {{ filemanagerDisplayManagerButton() }} 14 |

    15 | 16 |
    17 |
    18 |
    19 |
    {{ Lang.contact.collected_email_addresses }}
    20 |

    21 | 22 | 23 |

    24 | token]) }}" class="button alert" 25 | onclick="return(confirm('{{ Lang.contact.confirm_empty_mail_base }}'));">{{ Lang.contact.delete_base }} 26 |
    -------------------------------------------------------------------------------- /plugin/contact/template/contact.tpl: -------------------------------------------------------------------------------- 1 |
    2 | {{ runPlugin.getConfigVal("content1") }} 3 | 4 |
    5 |

    6 |
    7 | 8 | 9 |

    10 |

    11 |
    12 | 13 |

    14 |

    15 |
    16 | 17 |

    18 |

    19 |
    20 | 21 |

    22 | {% if acceptation %} 23 |

    24 | 25 |

    26 | {% endif %} 27 | {% if antispam %} 28 | {{ antispamField}} 29 | {% endif %} 30 |

    31 | 32 |

    33 |
    34 | {{ runPlugin.getConfigVal("content2") }} 35 |
    -------------------------------------------------------------------------------- /plugin/contact/template/param.tpl: -------------------------------------------------------------------------------- 1 |
    2 | {{ show.tokenField() }} 3 |

    4 |
    5 | 6 |

    7 |

    8 |
    9 | 10 |

    11 |

    12 |
    13 | 20 |

    21 |

    22 |
    23 | 24 |

    25 |

    26 |
    -------------------------------------------------------------------------------- /plugin/filemanager/filemanager.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') OR exit('Access denied!'); 11 | 12 | require_once(PLUGINS . 'filemanager/lib/FileManager.php'); 13 | 14 | /** 15 | * Install function 16 | */ 17 | function filemanagerInstall() { 18 | if (!file_exists(DATA_PLUGIN . 'filemanager/files.json')) { 19 | @mkdir(UPLOAD . 'files/'); 20 | @chmod(UPLOAD . 'files', 0755); 21 | util::writeJsonFile(DATA_PLUGIN . 'filemanager/files.json', []); 22 | } 23 | } 24 | 25 | /** 26 | * Function to display the button to manage files by Ajax 27 | */ 28 | function filemanagerDisplayManagerButton($textareaId = false, $buttonLabel = false):string { 29 | if (!$buttonLabel) { 30 | $buttonLabel = lang::get('filemanager.button-label'); 31 | } 32 | return ' '. $buttonLabel.''; 36 | 37 | } 38 | -------------------------------------------------------------------------------- /plugin/filemanager/langs/en.ini: -------------------------------------------------------------------------------- 1 | filemanager.name = File Manager 2 | filemanager.description = "Allows transferring and managing files on the server" 3 | 4 | filemanager.add-file = Add File 5 | filemanager.add-folder = Create Folder 6 | filemanager.send-file = Send File 7 | filemanager.go-up = Go Up 8 | filemanager.parent-folder = Parent Folder 9 | filemanager.folder-name = Folder Name 10 | filemanager.folder-name-empty = "Folder name is empty" 11 | filemanager.folder-creation-failed = "Folder creation failed" 12 | filemanager.folder-created = "Folder created" 13 | filemanager.clipboard-unavailable = "Clipboard unavailable" 14 | filemanager.copied-to-clipboard = "Copied to clipboard" 15 | filemanager.copy-failed = "Copy failed" 16 | filemanager.upload-failed = "Upload failed" 17 | filemanager.no-file-selected = "No file selected" 18 | filemanager.button-label = "Files" -------------------------------------------------------------------------------- /plugin/filemanager/langs/fr.ini: -------------------------------------------------------------------------------- 1 | filemanager.name = Gestionnaire de fichiers 2 | filemanager.description = Permet de transférer et gérer des fichiers sur le serveur 3 | 4 | filemanager.add-file = Envoyer un fichier 5 | filemanager.add-folder = Créer un dossier 6 | filemanager.send-file = Envoyer le fichier 7 | filemanager.go-up = Remonter 8 | filemanager.parent-folder = Dossier parent 9 | filemanager.folder-name = Nom du dossier 10 | filemanager.folder-name-empty = "Le nom du dossier est vide" 11 | filemanager.folder-creation-failed = "La création du dossier a échoué" 12 | filemanager.folder-created = "Le dossier a été créé" 13 | filemanager.clipboard-unavailable = "Le presse-papier est indisponible" 14 | filemanager.copied-to-clipboard = "Copié dans le presse-papier" 15 | filemanager.copy-failed = "La copie a échoué" 16 | filemanager.upload-failed = "L'envoi a échoué" 17 | filemanager.no-file-selected = "Aucun fichier sélectionné" 18 | filemanager.button-label = "Fichiers" 19 | -------------------------------------------------------------------------------- /plugin/filemanager/langs/ru.ini: -------------------------------------------------------------------------------- 1 | filemanager.name = Файловый менеджер 2 | filemanager.description = "Отвечает зп передачу и управление файлами на сервере" 3 | 4 | filemanager.add-file = Добавить файл 5 | filemanager.add-folder = Создать папку 6 | filemanager.send-file = Отправить файл 7 | filemanager.go-up = Наверх 8 | filemanager.parent-folder = Родительская папка 9 | filemanager.folder-name = Имя папки 10 | filemanager.folder-name-empty = "Не указано имя папки" 11 | filemanager.folder-creation-failed = "Не удалось создать папку" 12 | filemanager.folder-created = "Папка создана" 13 | filemanager.clipboard-unavailable = "Буфер обмена недоступен" 14 | filemanager.copied-to-clipboard = "Скопировано в буфер обмена" 15 | filemanager.copy-failed = "Ошибка при копировании" 16 | filemanager.upload-failed = "Ошибка при загрузке" 17 | filemanager.no-file-selected = "Файл не выбран" 18 | filemanager.button-label = "Файлы" -------------------------------------------------------------------------------- /plugin/filemanager/lib/File.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') OR exit('Access denied!'); 11 | 12 | class File { 13 | 14 | public string $name = ''; 15 | 16 | protected string $directory = ''; 17 | 18 | public function __construct($name, $directory) { 19 | $this->name = $name; 20 | $this->directory = trim($directory, '/') . '/'; 21 | } 22 | 23 | public function getUrl() { 24 | return util::urlBuild($this->directory . $this->name); 25 | } 26 | 27 | public function getRelUrl() { 28 | $parts = explode('/', $this->directory); 29 | $dir = ''; 30 | foreach ($parts as $part) { 31 | if ($part === '.' || $part === '..' || $part === '') { 32 | continue; 33 | } 34 | $dir .= $part . '/'; 35 | } 36 | return $dir . ltrim($this->name,'/'); 37 | } 38 | 39 | public function isPicture() { 40 | if (in_array(util::getFileExtension($this->name), ['gif', 'jpg', 'jpeg','png','bmp'])) { 41 | return true; 42 | } 43 | } 44 | 45 | public function getFileMTime() { 46 | return filemtime($this->directory . $this->name); 47 | } 48 | 49 | public function delete() { 50 | return unlink($this->directory . $this->name); 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /plugin/filemanager/lib/Folder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') OR exit('Access denied!'); 11 | 12 | class Folder { 13 | 14 | public string $name = ''; 15 | 16 | protected string $directory = ''; 17 | 18 | public function __construct($name, $directory) { 19 | $this->name = $name; 20 | $this->directory = trim($directory, '/') . '/'; 21 | } 22 | 23 | public function delete() { 24 | if (!is_dir($this->directory . $this->name)) { 25 | return false; 26 | } 27 | $manager = new FileManager($this->directory . $this->name); 28 | 29 | $error = $manager->deleteAllFiles(); 30 | if ($error) { 31 | return false; 32 | } 33 | 34 | $error = $manager->deleteAllFolders(); 35 | if ($error) { 36 | return false; 37 | } 38 | 39 | return rmdir($this->directory . $this->name); 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /plugin/filemanager/param/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "priority" : "2", 3 | "label" : "FileManager", 4 | "protected" : "1" 5 | } -------------------------------------------------------------------------------- /plugin/filemanager/param/infos.json: -------------------------------------------------------------------------------- 1 | { 2 | "slug" : "filemanager", 3 | "name" : "Gestionnaire de fichiers", 4 | "icon" : "fa-regular fa-file", 5 | "description" :"Un gestionnaire de fichiers pour télécharger et organiser vos médias", 6 | "authorEmail" : "mx.koder@gmail.com", 7 | "authorWebsite" : "https://kodercloud.ovh", 8 | "version" : "2.0", 9 | "homeAdminMethod" : "FileManagerAPIController#home" 10 | } -------------------------------------------------------------------------------- /plugin/filemanager/param/routes.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') OR exit('Access denied!'); 11 | 12 | $router = router::getInstance(); 13 | 14 | $router->map('GET', '/admin/filemanager[/?]', 'FileManagerAPIController#home', 'filemanager-home'); 15 | $router->map('POST', '/admin/filemanager/view-ajax/upload/[a:token]', 'FileManagerAPIController#upload', 'filemanager-upload'); 16 | $router->map('POST', '/admin/filemanager/view-ajax/delete/[a:token]', 'FileManagerAPIController#delete', 'filemanager-delete'); 17 | $router->map('POST', '/admin/filemanager/view-ajax/create/[a:token]', 'FileManagerAPIController#create', 'filemanager-create'); 18 | 19 | $router->map('POST', '/admin/filemanager/view', 'FileManagerAPIController#view', 'filemanager-view'); 20 | $router->map('POST', '/admin/filemanager/view-ajax', 'FileManagerAPIController#viewAjax', 'filemanager-view-ajax'); 21 | $router->map('GET', '/admin/filemanager/view-ajax/[a:token]/[*:editor]?', 'FileManagerAPIController#viewAjaxHome', 'filemanager-view-ajax-home'); 22 | 23 | $router->map('POST', '/admin/filemanager/api/upload/[a:token]', 'FileManagerAPIController#uploadAPI', 'filemanager-upload-api'); 24 | -------------------------------------------------------------------------------- /plugin/galerie/controllers/GalerieController.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') or exit('Access denied!'); 11 | 12 | class GalerieController extends PublicController 13 | { 14 | 15 | public function home() 16 | { 17 | $galerie = new galerie(); 18 | $this->runPlugin->setTitleTag($this->runPlugin->getConfigVal('label')); 19 | if($this->runPlugin->getIsDefaultPlugin()){ 20 | $this->runPlugin->setTitleTag($this->core->getConfigVal('siteName')); 21 | $this->runPlugin->setMetaDescriptionTag($this->core->getConfigVal('siteDescription')); 22 | } 23 | $this->runPlugin->setMainTitle($this->runPlugin->getConfigVal('label')); 24 | 25 | $response = new PublicResponse(); 26 | $tpl = $response->createPluginTemplate('galerie', 'galerie'); 27 | 28 | $tpl->set('galerie', $galerie); 29 | 30 | $response->addTemplate($tpl); 31 | return $response; 32 | } 33 | } -------------------------------------------------------------------------------- /plugin/galerie/langs/en.ini: -------------------------------------------------------------------------------- 1 | galerie.name = Gallery 2 | galerie.description = "A simple and clean photo gallery" 3 | 4 | galerie.images-list = "List of images" 5 | galerie.toggle-hidden = "Toggle to show hidden items" 6 | galerie.hide-hidden = "Hide hidden items" 7 | galerie.preview = "Preview" 8 | galerie.title = Title 9 | galerie.category = Category 10 | galerie.url = Address 11 | galerie.actions = Actions 12 | galerie.make-invisible = "Make invisible" 13 | galerie.make-invisible-description = "This option allows hiding an item, while still being able to use the image in other content (pages, news, etc...) via its URL." 14 | galerie.existing-categories = "Existing categories" 15 | galerie.select-category = "Select category %s" 16 | galerie.image-category = "Image category" 17 | galerie.content = Content 18 | galerie.date = Date 19 | galerie.image = Image 20 | galerie.file = "File" 21 | 22 | galerie.show-image-titles = "Show image titles" 23 | galerie.page-title = "Page title" 24 | galerie.image-order = "Image order" 25 | galerie.image-size = "Image size" 26 | galerie.introduction = "Introduction" 27 | galerie.natural-order = "Natural" 28 | galerie.order-by-name = "By name" 29 | galerie.order-by-date = "By date" 30 | galerie.small = "Small" 31 | galerie.large = "Large" 32 | galerie.extra-large = "Extra large" 33 | 34 | galerie.display-all-categories = Show all 35 | galerie.no-item-found = "No items found." 36 | galerie.featured-image = "Featured image" 37 | galerie.delete-featured-image = "Delete featured image" 38 | -------------------------------------------------------------------------------- /plugin/galerie/langs/fr.ini: -------------------------------------------------------------------------------- 1 | galerie.name = Galerie 2 | galerie.description = "Une galerie photos simple et épurée" 3 | 4 | galerie.images-list = "Liste des images" 5 | galerie.toggle-hidden = "Basculer sur l'affichage des éléments cachés" 6 | galerie.hide-hidden = "Cacher les éléments cachés" 7 | galerie.preview = "Aperçu" 8 | galerie.title = Titre 9 | galerie.category = Catégorie 10 | galerie.url = Adresse 11 | galerie.actions = Actions 12 | galerie.make-invisible = "Rendre invisible" 13 | galerie.make-invisible-description = "Cette option permet de masquer un élément, tout en ayant la possibilité d'utiliser l'image dans d'autres contenus (pages, news, etc...) via son URL." 14 | galerie.existing-categories = "Catégories existantes" 15 | galerie.select-category = "Sélectionner la catégorie %s" 16 | galerie.image-category = "Catégorie de l'image" 17 | galerie.content = Contenu 18 | galerie.date = Date 19 | galerie.image = Image 20 | galerie.file = "Fichier" 21 | 22 | galerie.show-image-titles = "Afficher le titre des images" 23 | galerie.page-title = "Titre de page" 24 | galerie.image-order = "Ordre des images" 25 | galerie.image-size = "Taille des images" 26 | galerie.introduction = "Introduction" 27 | galerie.natural-order = "Naturel" 28 | galerie.order-by-name = "Par nom" 29 | galerie.order-by-date = "Par date" 30 | galerie.small = "Petite" 31 | galerie.large = "Grande" 32 | galerie.extra-large = "Très grande" 33 | 34 | galerie.display-all-categories = Afficher tout 35 | galerie.no-item-found = "Aucun élément n'a été trouvé." 36 | galerie.featured-image = "Image à la une" 37 | galerie.delete-featured-image = "Supprimer l'image à la une." -------------------------------------------------------------------------------- /plugin/galerie/langs/ru.ini: -------------------------------------------------------------------------------- 1 | galerie.name = Галерея 2 | galerie.description = "Простая и понятная фотогалерея" 3 | 4 | galerie.images-list = "Список изображений" 5 | galerie.toggle-hidden = "Переключение на отображение скрытого элемента" 6 | galerie.hide-hidden = "Скрывать скрытые элементы" 7 | galerie.preview = "Превью" 8 | galerie.title = Заголовок 9 | galerie.category = Категория 10 | galerie.url = Адрес 11 | galerie.actions = Действия 12 | galerie.make-invisible = "Сделать невидимым" 13 | galerie.make-invisible-description = "Эта опция позволяет скрыть элемент, но при этом использовать изображение в другом контенте (страницы, новости и т. д.) по его URL." 14 | galerie.existing-categories = "Существующие категории" 15 | galerie.select-category = "Выбрать категорию %s" 16 | galerie.image-category = "Категория изображения" 17 | galerie.content = Содержание 18 | galerie.date = Дата 19 | galerie.image = Изображение 20 | galerie.file = "Файл" 21 | 22 | galerie.show-image-titles = "Показать заголовки изображений" 23 | galerie.page-title = "Заголовок страницы" 24 | galerie.image-order = "Порядок изображений" 25 | galerie.image-size = "Размер изображения" 26 | galerie.introduction = "Введение" 27 | galerie.natural-order = "Естественный" 28 | galerie.order-by-name = "По имени" 29 | galerie.order-by-date = "По дате" 30 | galerie.small = "Маленький" 31 | galerie.large = "Большой" 32 | galerie.extra-large = "Очень большой" 33 | 34 | galerie.display-all-categories = Показать все 35 | galerie.no-item-found = "Не найдено ни одного объекта." 36 | galerie.featured-image = "Изображение на обложке" 37 | galerie.delete-featured-image = "Удалить изображение на обложке" 38 | -------------------------------------------------------------------------------- /plugin/galerie/param/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "priority" : "2", 3 | "label" : "Galerie", 4 | "order" : "byDate", 5 | "onlyImg" : "0", 6 | "introduction" : "", 7 | "showTitles" : "1", 8 | "size" :"1024" 9 | } -------------------------------------------------------------------------------- /plugin/galerie/param/hooks.json: -------------------------------------------------------------------------------- 1 | { 2 | "endFrontHead" : "galerieEndFrontHead" 3 | } -------------------------------------------------------------------------------- /plugin/galerie/param/infos.json: -------------------------------------------------------------------------------- 1 | { 2 | "slug" : "galerie", 3 | "name" : "Galerie", 4 | "icon" : "fa-regular fa-images", 5 | "description" : "Une galerie photos simple et épurée", 6 | "authorEmail" : "mx.koder@gmail.com", 7 | "authorWebsite" : "https://299ko.ovh", 8 | "version" : "2.0", 9 | "homePublicMethod" : "GalerieController#home", 10 | "homeAdminMethod" : "GalerieAdminController#home" 11 | } -------------------------------------------------------------------------------- /plugin/galerie/param/routes.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') OR exit('Access denied!'); 11 | 12 | $router = router::getInstance(); 13 | 14 | $router->map('GET', '/galerie[/?]', 'GalerieController#home', 'galerie-home'); 15 | 16 | $router->map('GET', '/admin/galerie[/?]', 'GalerieAdminController#home', 'admin-galerie-home'); 17 | $router->map('GET', '/admin/galerie/edit', 'GalerieAdminController#edit', 'admin-galerie-edit'); 18 | $router->map('GET', '/admin/galerie/edit/[a:id]', 'GalerieAdminController#editId', 'admin-galerie-edit-id'); 19 | $router->map('GET', '/admin/galerie/delete/[a:id]/[a:token]', 'GalerieAdminController#delete', 'admin-galerie-delete'); 20 | $router->map('POST', '/admin/galerie/save', 'GalerieAdminController#save', 'admin-galerie-save'); 21 | $router->map('POST', '/admin/galerie/saveConf', 'GalerieAdminController#saveConf', 'admin-galerie-save-config'); 22 | -------------------------------------------------------------------------------- /plugin/galerie/template/admin.css: -------------------------------------------------------------------------------- 1 | #content.galerie-admin a.category { 2 | opacity: 0.75; 3 | transition: opacity 0.4s ease; 4 | background: #555; 5 | color:#fff; 6 | border-radius: 3px; 7 | padding: 4px; 8 | margin: 5px; 9 | font-size: 12px; 10 | } 11 | 12 | #content.galerie-admin a.category:hover { 13 | opacity: 1; 14 | } 15 | 16 | #content.galerie-admin a.category > i { 17 | margin-right: 4px; 18 | } -------------------------------------------------------------------------------- /plugin/galerie/template/galerie.tpl: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | {{ htmlspecialchars_decode(runPlugin.getConfigVal("introduction")) }} 5 | 6 | {% if galerie.useCategories() %} 7 |
      8 | {% if count(galerie.listCategories(false)) > 0 %} 9 |
    • 10 | {% endif %} 11 | {% for k, v in galerie.listCategories(false) %} 12 |
    • 13 | {% endfor %} 14 |
    15 | {% endif %} 16 |
    17 |
    18 | {% if galerie.countItems() == false %} 19 |

    {{Lang.galerie.no-item-found}}

    20 | {% else %} 21 |
    -------------------------------------------------------------------------------- /plugin/galerie/template/help.tpl: -------------------------------------------------------------------------------- 1 |

    {{ Lang.galerie.make-invisible }}

    2 |

    {{ Lang.galerie.make-invisible-description }}

    -------------------------------------------------------------------------------- /plugin/galerie/template/param.tpl: -------------------------------------------------------------------------------- 1 |
    2 | {{ SHOW.tokenField()}} 3 | 4 |

    5 | 6 | 7 |

    8 | 9 |

    10 |
    11 | 12 |

    13 |

    14 |
    15 | 20 |

    21 |

    22 |
    23 | 28 |

    29 |

    30 | {{ galerieGenerateEditor() }} 31 |

    32 | 33 |

    34 |
    -------------------------------------------------------------------------------- /plugin/galerie/template/public.css: -------------------------------------------------------------------------------- 1 | /* Galerie */ 2 | 3 | #content.galerie #list{ 4 | margin-left: 0; 5 | padding-left: 0; 6 | list-style: none inside; 7 | overflow: hidden; 8 | width: 100%; 9 | } 10 | 11 | #content.galerie #list li{ 12 | float: left; 13 | width: 25%; 14 | height: 150px; 15 | background-repeat: no-repeat; 16 | background-position: center; 17 | background-size: cover; 18 | border: 5px solid #fff; 19 | position: relative; 20 | } 21 | 22 | #content.galerie #list li:hover{ 23 | opacity: 0.75; 24 | } 25 | 26 | #content.galerie #list li a{ 27 | display: block; 28 | width: 100%; 29 | height: 100%; 30 | } 31 | 32 | #content.galerie #list li span{ 33 | display: none; 34 | } 35 | 36 | #content.galerie #list li:hover span{ 37 | display: block; 38 | width: 100%; 39 | position: absolute; 40 | background: rgba(0, 0, 0, 0.5); 41 | bottom: 0; 42 | text-align: center; 43 | color: #fff; 44 | padding: 5px; 45 | font-size: 12px; 46 | } 47 | 48 | #content.galerie .categories{ 49 | font-size: 14px; 50 | margin-left: 0; 51 | padding-left: 0; 52 | list-style: none inside; 53 | overflow: hidden; 54 | } 55 | 56 | #content.galerie .categories li{ 57 | display: inline-block; 58 | margin-right: 5px; 59 | margin-bottom: 15px; 60 | } 61 | 62 | #content.galerie .categories button > i{ 63 | margin-right: 6px; 64 | } 65 | 66 | #content.galerie .categories a:hover{ 67 | color: #fff; 68 | } -------------------------------------------------------------------------------- /plugin/galerie/template/public.js: -------------------------------------------------------------------------------- 1 | 2 | document.addEventListener("DOMContentLoaded", function() { 3 | 4 | if (document.querySelector('.galerie .categories button')) { 5 | 6 | document.querySelectorAll('.galerie .categories button').forEach(function(item, index) { 7 | item.addEventListener('click', function(){ 8 | var rel = this.getAttribute('rel'); 9 | 10 | document.querySelectorAll('.galerie #list li').forEach(function(item, index) { 11 | item.style.display = 'none'; 12 | }); 13 | 14 | document.querySelectorAll('.galerie #list li.'+rel).forEach(function(item, index) { 15 | fadeIn(item, 'block'); 16 | }); 17 | 18 | }); 19 | }); 20 | 21 | } 22 | 23 | }); -------------------------------------------------------------------------------- /plugin/page/param/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "priority" : 2, 3 | "protected" : 1 4 | } -------------------------------------------------------------------------------- /plugin/page/param/hooks.json: -------------------------------------------------------------------------------- 1 | { 2 | "endFrontHead" : "pageEndFrontHead" 3 | } -------------------------------------------------------------------------------- /plugin/page/param/infos.json: -------------------------------------------------------------------------------- 1 | { 2 | "slug" : "page", 3 | "name" : "Pages & Menu", 4 | "icon" : "fa-regular fa-file-lines", 5 | "description" : "Gestion des pages et du menu", 6 | "authorEmail" : "mx.koder@gmail.com", 7 | "authorWebsite" : "https://299ko.ovh", 8 | "version" : "2.0", 9 | "homePublicMethod" : "PageController#home", 10 | "homeAdminMethod" : "PageAdminController#list" 11 | } -------------------------------------------------------------------------------- /plugin/page/param/routes.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') OR exit('Access denied!'); 11 | 12 | $router = router::getInstance(); 13 | 14 | $router->map('GET|POST', '/page[/?]', 'PageController#home', 'page-home'); 15 | $router->map('GET|POST', '/page/[*:name]-[i:id][/?]', 'PageController#read', 'page-read'); 16 | 17 | $router->map('GET', '/admin/page[/?]', 'PageAdminController#list', 'page-admin-home'); 18 | $router->map('GET', '/admin/page/new', 'PageAdminController#new', 'page-admin-new'); 19 | $router->map('POST', '/admin/page/save', 'PageAdminController#save', 'page-admin-save'); 20 | $router->map('GET', '/admin/page/edit/[a:id]', 'PageAdminController#edit', 'page-admin-edit'); 21 | $router->map('GET', '/admin/page/new-parent', 'PageAdminController#newParent', 'page-admin-new-parent'); 22 | $router->map('GET', '/admin/page/new-link', 'PageAdminController#newLink', 'page-admin-new-link'); 23 | $router->map('GET', '/admin/page/maintenance/[a:id]/[a:token]', 'PageAdminController#maintenance', 'page-admin-maintenance'); 24 | $router->map('GET', '/admin/page/delete/[a:id]/[a:token]', 'PageAdminController#delete', 'page-admin-delete'); 25 | $router->map('GET', '/admin/page/page-up/[a:id]/[a:token]', 'PageAdminController#pageUp', 'page-admin-page-up'); 26 | $router->map('GET', '/admin/page/page-down/[a:id]/[a:token]', 'PageAdminController#pageDown', 'page-admin-page-down'); -------------------------------------------------------------------------------- /plugin/page/template/admin.js: -------------------------------------------------------------------------------- 1 | document.addEventListener("DOMContentLoaded", function() { 2 | if (document.querySelector('.page-admin table')) { 3 | document.querySelector('.page-admin tr:first-child .up').style.display = 'none'; 4 | document.querySelector('.page-admin tr:last-child .down').style.display = 'none'; 5 | } 6 | }); -------------------------------------------------------------------------------- /plugin/page/template/edit-parent.tpl: -------------------------------------------------------------------------------- 1 |
    2 |
    {{ Lang.page.edit-parent }}
    3 |
    4 | {{ show::tokenField() }} 5 | 6 | 7 | 8 |

    9 | 10 | 11 |

    12 |

    13 |
    14 | 15 |

    16 |

    17 | 18 | 19 |

    20 |

    21 | 22 | 23 |

    24 |

    25 | 26 |

    27 |
    28 |
    29 | -------------------------------------------------------------------------------- /plugin/page/template/help.tpl: -------------------------------------------------------------------------------- 1 |

    {{ Lang.page.hide-from-menu }}

    2 |

    {{ Lang.page.hide-from-menu-desc }}

    3 |

    {{ Lang.page.parent-item }}

    4 |

    {{ Lang.page.parent-item-desc }}

    5 |

    {{ Lang.page.css-class }}

    6 |

    {{ Lang.page.css-class-desc }}

    7 |

    {{ Lang.page.position }}

    8 |

    {{ Lang.page.position-desc }}

    9 |

    {{ Lang.page.restrict-access-password }}

    10 |

    {{ Lang.page.restrict-access-password-desc }}

    11 |

    {{ Lang.page.page-title }}

    12 |

    {{ Lang.page.page-title-desc }}

    13 |

    {{ Lang.page.include-file }}

    14 |

    {{ Lang.page.include-file-desc }}

    15 |

    {{ Lang.page.featured-image }}

    16 |

    {{ Lang.page.featured-image-desc }}

    -------------------------------------------------------------------------------- /plugin/page/template/read.tpl: -------------------------------------------------------------------------------- 1 |
    2 | {% if page.isUnlocked(pageItem) %} 3 | {% if pluginsManager.isActivePlugin("galerie") && galerie.searchByFileName(pageItem.getImg) %} 4 |
    5 | {{ pageItem.getName }} 6 |
    7 | {% endif %} 8 | {{ htmlspecialchars_decode(pageItem.getContent) }} 9 | {% else %} 10 |
    11 |
    12 |

    Cette page est protégée par un mot de passe.

    13 |
    14 |
    15 |
    16 | 17 |

    18 |
    19 | 20 | 21 |

    22 |

    23 | 24 |

    25 |
    26 | {% endif %} 27 |
    28 | -------------------------------------------------------------------------------- /plugin/pluginsmanager/langs/en.ini: -------------------------------------------------------------------------------- 1 | pluginsmanager.name = Plugins 2 | pluginsmanager.description = "Plugins management" 3 | 4 | pluginsmanager.plugins-list = "Plugins list" 5 | pluginsmanager.plugin-name = "Plugin name" 6 | pluginsmanager.plugin-version = "Version" 7 | pluginsmanager.priority = "Priority" 8 | pluginsmanager.activate = "Activate" 9 | pluginsmanager.maintenance-required = "Maintenance required" 10 | 11 | pluginsmanager.plugin-priority = "Plugin priority" 12 | pluginsmanager.plugin-priority-description = "This parameter allows modifying the loading order of a plugin. A low value results in priority loading over other plugins." 13 | -------------------------------------------------------------------------------- /plugin/pluginsmanager/langs/fr.ini: -------------------------------------------------------------------------------- 1 | pluginsmanager.name = Plugins 2 | pluginsmanager.description = "Gestion des plugins" 3 | 4 | pluginsmanager.plugins-list = "Liste des plugins" 5 | pluginsmanager.plugin-name = "Nom du plugin" 6 | pluginsmanager.plugin-version = "Version" 7 | pluginsmanager.priority = "Priorité" 8 | pluginsmanager.activate = "Activer" 9 | pluginsmanager.maintenance-required = "Maintenance requise" 10 | 11 | pluginsmanager.plugin-priority = "Priorité d'un plugin" 12 | pluginsmanager.plugin-priority-description = "Ce paramètre permet de modifier l'ordre de chargement d'un plugin. Une valeur basse se traduit par un chargement prioritaire sur les autres plugins." -------------------------------------------------------------------------------- /plugin/pluginsmanager/langs/ru.ini: -------------------------------------------------------------------------------- 1 | pluginsmanager.name = Плагины 2 | pluginsmanager.description = "Управление плагинами" 3 | 4 | pluginsmanager.plugins-list = "Список плагинов" 5 | pluginsmanager.plugin-name = "Наименование плагина" 6 | pluginsmanager.plugin-version = "Версия" 7 | pluginsmanager.priority = "Приоритет" 8 | pluginsmanager.activate = "Активация" 9 | pluginsmanager.maintenance-required = "Требуется техническое обслуживание" 10 | 11 | pluginsmanager.plugin-priority = "Приоритет плагина" 12 | pluginsmanager.plugin-priority-description = "Этот параметр позволяет изменить порядок загрузки плагина. Низкое значение приводит к приоритетной загрузке по отношению к другим плагинам." 13 | -------------------------------------------------------------------------------- /plugin/pluginsmanager/param/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "priority" : 2, 3 | "protected" : 1 4 | } -------------------------------------------------------------------------------- /plugin/pluginsmanager/param/infos.json: -------------------------------------------------------------------------------- 1 | { 2 | "slug" : "pluginsmanager", 3 | "name" : "Plugins", 4 | "icon" : "fa-solid fa-puzzle-piece", 5 | "description" : "Gestion des plugins", 6 | "authorEmail" : "mx.koder@gmail.com", 7 | "authorWebsite" : "https://299ko.ovh", 8 | "version" : "2.0", 9 | "homeAdminMethod" : "PluginsManagerController#list" 10 | } -------------------------------------------------------------------------------- /plugin/pluginsmanager/param/routes.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') OR exit('Access denied!'); 11 | 12 | $router = router::getInstance(); 13 | 14 | $router->map('GET', '/admin/pluginsmanager[/?]', 'PluginsManagerController#list', 'pluginsmanager-list'); 15 | $router->map('POST', '/admin/pluginsmanager/save', 'PluginsManagerController#save', 'pluginsmanager-save'); 16 | $router->map('GET', '/admin/pluginsmanager/[a:plugin]/[a:token]', 'PluginsManagerController#maintenance', 'pluginsmanager-maintenance'); -------------------------------------------------------------------------------- /plugin/pluginsmanager/pluginsmanager.php: -------------------------------------------------------------------------------- 1 | 7 | * @author Maxence Cauderlier 8 | * @author Frédéric Kaplon 9 | * @author Florent Fortat 10 | * 11 | * @package 299Ko https://github.com/299Ko/299ko 12 | */ 13 | defined('ROOT') OR exit('Access denied!'); 14 | 15 | ## Fonction d'installation 16 | 17 | function pluginsmanagerInstall() { 18 | 19 | } 20 | 21 | ## Hooks 22 | ## Code relatif au plugin -------------------------------------------------------------------------------- /plugin/pluginsmanager/template/help.tpl: -------------------------------------------------------------------------------- 1 |

    {{ Lang.pluginsmanager.plugin-priority }}

    2 |

    {{ Lang.pluginsmanager.plugin-priority-description }}

    3 | -------------------------------------------------------------------------------- /plugin/pluginsmanager/template/list.tpl: -------------------------------------------------------------------------------- 1 |
    2 |
    {{ Lang.pluginsmanager.plugins-list }}
    3 |
    4 | {{ show::tokenField }} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | {% for plugin in plugins %} 16 | 17 | 26 | 27 | 34 | 37 | 38 | {% endfor %} 39 | 40 |
    {{ Lang.pluginsmanager.plugin-name }}{{ Lang.pluginsmanager.plugin-version }}{{ Lang.pluginsmanager.priority }}{{ Lang.pluginsmanager.activate }}
    18 | {{ plugin.getTranslatedName() }} 19 | : {{ plugin.getTranslatedDesc() }} 20 | {% if plugin.getConfigVal("activate") && plugin.isInstalled() == false %} 21 |

    22 | plugin.getName(), "token" => token]) }}">{{ Lang.pluginsmanager.maintenance-required }} 23 |

    24 | {% endif %} 25 |
    {{ plugin.getInfoVal("version") }} 28 | 33 | 35 | 36 |
    41 |
    42 |
    43 | -------------------------------------------------------------------------------- /plugin/seo/controllers/SEOAdminController.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') OR exit('Access denied!'); 11 | 12 | class SEOAdminController extends AdminController { 13 | 14 | public function home() { 15 | $response = new AdminResponse(); 16 | $tpl = $response->createPluginTemplate('seo', 'admin'); 17 | 18 | $tpl->set('position', $this->runPlugin->getConfigVal('position')); 19 | 20 | $response->addTemplate($tpl); 21 | return $response; 22 | } 23 | 24 | public function save() { 25 | if (!$this->user->isAuthorized()) { 26 | return $this->home(); 27 | } 28 | $pos = $_POST['position']; 29 | $this->seoSavePositionMenu($pos); 30 | 31 | $this->runPlugin->setConfigVal('position', $pos); 32 | $this->runPlugin->setConfigVal('trackingId', trim($_POST['trackingId'])); 33 | $this->runPlugin->setConfigVal('wt', trim($_POST['wt'])); 34 | 35 | // Save Social adress 36 | $vars = seoGetSocialVars(); 37 | foreach ($vars as $v) { 38 | $this->runPlugin->setConfigVal($v, trim($_POST[$v])); 39 | } 40 | 41 | if ($this->pluginsManager->savePluginConfig($this->runPlugin)) { 42 | show::msg(lang::get('core-changes-saved'), 'success'); 43 | } else { 44 | show::msg(lang::get('core-changes-not-saved'), 'error'); 45 | } 46 | $this->core->redirect($this->router->generate('seo-admin-home')); 47 | } 48 | 49 | protected function seoSavePositionMenu($position) { 50 | $arr = ['endFrontHead' => 'seoEndFrontHead']; 51 | switch ($position) { 52 | case 'menu': 53 | $tmp = ['endMainNavigation' => 'seoMainNavigation']; 54 | break; 55 | case 'footer': 56 | $tmp = ['footer' => 'seoFooter']; 57 | break; 58 | case 'endfooter': 59 | $tmp = ['endFooter' => 'seoFooter']; 60 | break; 61 | case 'float': 62 | $tmp = ['endFrontBody' => 'seoEndFrontBody']; 63 | break; 64 | default: 65 | $tmp = []; 66 | } 67 | $data = array_merge($arr, $tmp); 68 | util::writeJsonFile(PLUGINS . 'seo/param/hooks.json', $data); 69 | } 70 | } -------------------------------------------------------------------------------- /plugin/seo/langs/en.ini: -------------------------------------------------------------------------------- 1 | seo.name = SEO 2 | seo.description = "Adds options for social networks" 3 | 4 | seo.analytics.id = "Analytics Tracking ID" 5 | seo.analytics.id.desc = "This ID (example: UA-XXXXXXXX-X) should be obtained from your Analytics account." 6 | seo.analytics.meta = "Google Site Verification Meta Tag" 7 | seo.analytics.meta.desc = "The content of this tag (example: rfeR0JazK3Hgj2jkj1WG8Tl_V2RLPtdqfBu1hhJLWLm) should be obtained from your Analytics account." 8 | 9 | seo.display = "Display" 10 | seo.menu-position = "SEO Menu Position" 11 | seo.nav-menu = "Navigation menu" 12 | seo.top-footer-page = "Top footer page" 13 | seo.bottom-footer-page = "Bottom footer page" 14 | seo.float = "Floating" 15 | seo.google = "Google" 16 | seo.socials-links = "Social media links" 17 | seo.follow-on = "Follow us on %s" 18 | -------------------------------------------------------------------------------- /plugin/seo/langs/fr.ini: -------------------------------------------------------------------------------- 1 | seo.name = SEO 2 | seo.description = "Ajoute des options pour les réseaux sociaux" 3 | 4 | seo.analytics.id = "Identifiant de suivi Analytics" 5 | seo.analytics.id.desc = "Cet identifiant (exemple : UA-XXXXXXXX-X) doit être récupéré dans votre compte Analytics." 6 | seo.analytics.meta = "Meta google site verification" 7 | seo.analytics.meta.desc = "Le contenu de cette balise (exemple : rfeR0JazK3Hgj2jkj1WG8Tl_V2RLPtdqfBu1hhJLWLm) doit être récupéré dans votre compte Analytics." 8 | 9 | seo.display = "Affichage" 10 | seo.menu-position = "Position du menu SEO" 11 | seo.nav-menu = "Menu de navigation" 12 | seo.top-footer-page = "Haut de pied de page" 13 | seo.bottom-footer-page = "Bas de pied de page" 14 | seo.float = "Flottant" 15 | seo.google = "Google" 16 | seo.socials-links = "Liens sur les réseaux sociaux" 17 | seo.follow-on = "Suivez nous sur %s" -------------------------------------------------------------------------------- /plugin/seo/langs/ru.ini: -------------------------------------------------------------------------------- 1 | seo.name = SEO 2 | seo.description = "Добавляет опции для социальных сетей" 3 | 4 | seo.analytics.id = "ID отслеживания Analytics" 5 | seo.analytics.id.desc = "Этот идентификатор (например, UA-XXXXXXXX-X) можно получить из учетной записи Analytics." 6 | seo.analytics.meta = "Мета-тег Google Site Verification" 7 | seo.analytics.meta.desc = "Содержимое этого тега (пример: rfeR0JazK3Hgj2jkj1WG8Tl_V2RLPtdqfBu1hhJLWLm) должно быть получено из вашего аккаунта Analytics." 8 | 9 | seo.display = "Отображение" 10 | seo.menu-position = "Позиция меню SEO" 11 | seo.nav-menu = "В навигационном меню" 12 | seo.top-footer-page = "Вверху страницы 'подвала'" 13 | seo.bottom-footer-page = "Внизу страницы 'подвала'" 14 | seo.float = "Плавающий" 15 | seo.google = "Google" 16 | seo.socials-links = "Ссылки на социальные сети" 17 | seo.follow-on = "Следите за нами в %s" 18 | -------------------------------------------------------------------------------- /plugin/seo/param/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "position" : "float", 3 | "priority" : "2", 4 | "trackingId" : "", 5 | "wt" : "", 6 | "facebook" : "https://www.facebook.com/299kocms", 7 | "twitter" : "https://twitter.com/299kocms", 8 | "youtube" : "", 9 | "instagram" : "", 10 | "tiktok": "", 11 | "pinterest" : "", 12 | "linkedin" : "", 13 | "viadeo" : "", 14 | "github" : "https://github.com/299ko/", 15 | "gitlab" : "", 16 | "mastodon" : "https://piaille.fr/@MaxKoder", 17 | "twitch" : "", 18 | "discord": "", 19 | "codepen": "", 20 | "tumblr": "" 21 | } -------------------------------------------------------------------------------- /plugin/seo/param/hooks.json: -------------------------------------------------------------------------------- 1 | {"endFrontHead":"seoEndFrontHead","footer":"seoFooter"} -------------------------------------------------------------------------------- /plugin/seo/param/infos.json: -------------------------------------------------------------------------------- 1 | { 2 | "slug" : "seo", 3 | "name" : "SEO", 4 | "icon" : "fa-solid fa-magnifying-glass-chart", 5 | "description" : "Ajoute des options liées au référencement et aux réseaux sociaux", 6 | "authorEmail" : "mx.koder@gmail.com", 7 | "authorWebsite" : "https://299ko.ovh", 8 | "version" : "2.0", 9 | "homeAdminMethod" : "SEOAdminController#home" 10 | } 11 | -------------------------------------------------------------------------------- /plugin/seo/param/routes.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @package 299Ko https://github.com/299Ko/299ko 9 | */ 10 | defined('ROOT') OR exit('Access denied!'); 11 | 12 | $router = router::getInstance(); 13 | 14 | $router->map('GET', '/admin/seo[/?]', 'SEOAdminController#home', 'seo-admin-home'); 15 | $router->map('POST', '/admin/seo/save', 'SEOAdminController#save', 'seo-admin-save'); -------------------------------------------------------------------------------- /plugin/seo/template/admin.tpl: -------------------------------------------------------------------------------- 1 |
    2 | {{ show.tokenField() }} 3 |
    4 |
    {{ Lang.seo.display }}
    5 |

    6 |
    7 | 13 |

    14 |
    15 |
    16 |
    {{ Lang.seo.google }}
    17 |

    18 |
    19 | 20 |

    21 |

    22 |
    23 | 24 |

    25 |
    26 |
    27 |
    {{ Lang.seo.socials-links }}
    28 | {% set social = seoGetSocialVars() %} 29 | {% for k, v in social %} 30 |

    31 |
    32 | 33 |

    34 | {% endfor %} 35 |

    36 | 37 |

    38 |
    39 |
    40 | -------------------------------------------------------------------------------- /plugin/seo/template/help.tpl: -------------------------------------------------------------------------------- 1 |

    {{ Lang.seo.analytics.id }}

    2 |

    {{ Lang.seo.analytics.id.desc }}

    3 |

    {{ Lang.seo.analytics.meta }}

    4 |

    {{ Lang.seo.analytics.meta.desc }}

    -------------------------------------------------------------------------------- /plugin/seo/template/public.css: -------------------------------------------------------------------------------- 1 | #navigation li.seo_element { 2 | display: inline-block; 3 | width:auto; 4 | margin-right:0; 5 | font-size: 19px; 6 | } 7 | 8 | #navigation li.seo_element { 9 | margin-left:10vw; 10 | } 11 | 12 | #navigation li.seo_element + li.seo_element { 13 | margin-left:0; 14 | } 15 | 16 | @media only screen and (max-width: 960px){ 17 | #navigation li.seo_element { 18 | margin-left:0; 19 | margin-right: 20px; 20 | } 21 | } 22 | 23 | #seo_social_float{ 24 | position: fixed; 25 | z-index: 999; 26 | right: 0; 27 | bottom: 0; 28 | } 29 | 30 | #seo_social_float ul{ 31 | list-style: none; 32 | } 33 | 34 | #seo_social_float a{ 35 | display: block; 36 | color: var(--primary-inverse-color); 37 | text-transform: uppercase; 38 | padding: 15px; 39 | font-size: 19px; 40 | float: left; 41 | margin-left: 1px; 42 | background: var(--primary-color); 43 | } 44 | 45 | #seo_social_float a i { 46 | width:16px; 47 | } 48 | 49 | #seo_social ul { 50 | list-style: none; 51 | } 52 | 53 | #seo_social li { 54 | display: inline-block; 55 | } 56 | 57 | #seo_social a { 58 | padding: 8px; 59 | font-size: 16px; 60 | } -------------------------------------------------------------------------------- /plugin/tinymce/langs/en.ini: -------------------------------------------------------------------------------- 1 | warning.success = "Success block" 2 | warning.error = "Error block" 3 | warning.info = "Information block" 4 | warning.warning = "Warning block" -------------------------------------------------------------------------------- /plugin/tinymce/langs/fr.ini: -------------------------------------------------------------------------------- 1 | warning.success = "Bloc de succès" 2 | warning.error = "Bloc d'erreur" 3 | warning.info = "Bloc d'information" 4 | warning.warning = "Bloc d'attention" 5 | 6 | tiny.choose-icon-title = "Insérer une icône Font-Awesome" 7 | tiny.enter-icon-code = "Code de l'icône" -------------------------------------------------------------------------------- /plugin/tinymce/langs/ru.ini: -------------------------------------------------------------------------------- 1 | warning.success = "Блок успеха" 2 | warning.error = "Блок ошибки" 3 | warning.info = "Информационный блок" 4 | warning.warning = "Предупредительный блок" -------------------------------------------------------------------------------- /plugin/tinymce/lib/tinymce/license.md: -------------------------------------------------------------------------------- 1 | # Software License Agreement 2 | 3 | **TinyMCE** – [](https://github.com/tinymce/tinymce) 4 | Copyright (c) 2024, Ephox Corporation DBA Tiny Technologies, Inc. 5 | 6 | Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html). 7 | -------------------------------------------------------------------------------- /plugin/tinymce/lib/tinymce/plugins/anchor/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TinyMCE version 7.1.0 (2024-05-08) 3 | */ 4 | !function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),t=tinymce.util.Tools.resolve("tinymce.dom.RangeUtils"),o=tinymce.util.Tools.resolve("tinymce.util.Tools");const n=("allow_html_in_named_anchor",e=>e.options.get("allow_html_in_named_anchor"));const a="a:not([href])",r=e=>!e,i=e=>e.getAttribute("id")||e.getAttribute("name")||"",l=e=>(e=>"a"===e.nodeName.toLowerCase())(e)&&!e.getAttribute("href")&&""!==i(e),s=e=>e.dom.getParent(e.selection.getStart(),a),d=(e,a)=>{const r=s(e);r?((e,t,o)=>{o.removeAttribute("name"),o.id=t,e.addVisual(),e.undoManager.add()})(e,a,r):((e,a)=>{e.undoManager.transact((()=>{n(e)||e.selection.collapse(!0),e.selection.isCollapsed()?e.insertContent(e.dom.createHTML("a",{id:a})):((e=>{const n=e.dom;t(n).walk(e.selection.getRng(),(e=>{o.each(e,(e=>{var t;l(t=e)&&!t.firstChild&&n.remove(e,!1)}))}))})(e),e.formatter.remove("namedAnchor",void 0,void 0,!0),e.formatter.apply("namedAnchor",{value:a}),e.addVisual())}))})(e,a),e.focus()},c=e=>(e=>r(e.attr("href"))&&!r(e.attr("id")||e.attr("name")))(e)&&!e.firstChild,m=e=>t=>{for(let o=0;ot=>{const o=()=>{t.setEnabled(e.selection.isEditable())};return e.on("NodeChange",o),o(),()=>{e.off("NodeChange",o)}};e.add("anchor",(e=>{(e=>{(0,e.options.register)("allow_html_in_named_anchor",{processor:"boolean",default:!1})})(e),(e=>{e.on("PreInit",(()=>{e.parser.addNodeFilter("a",m("false")),e.serializer.addNodeFilter("a",m(null))}))})(e),(e=>{e.addCommand("mceAnchor",(()=>{(e=>{const t=(e=>{const t=s(e);return t?i(t):""})(e);e.windowManager.open({title:"Anchor",size:"normal",body:{type:"panel",items:[{name:"id",type:"input",label:"ID",placeholder:"example"}]},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:{id:t},onSubmit:t=>{((e,t)=>/^[A-Za-z][A-Za-z0-9\-:._]*$/.test(t)?(d(e,t),!0):(e.windowManager.alert("ID should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores."),!1))(e,t.getData().id)&&t.close()}})})(e)}))})(e),(e=>{const t=()=>e.execCommand("mceAnchor");e.ui.registry.addToggleButton("anchor",{icon:"bookmark",tooltip:"Anchor",onAction:t,onSetup:t=>{const o=e.selection.selectorChangedWithUnbind("a:not([href])",t.setActive).unbind,n=u(e)(t);return()=>{o(),n()}}}),e.ui.registry.addMenuItem("anchor",{icon:"bookmark",text:"Anchor...",onAction:t,onSetup:u(e)})})(e),e.on("PreInit",(()=>{(e=>{e.formatter.register("namedAnchor",{inline:"a",selector:a,remove:"all",split:!0,deep:!0,attributes:{id:"%value"},onmatch:(e,t,o)=>l(e)})})(e)}))}))}(); -------------------------------------------------------------------------------- /plugin/tinymce/lib/tinymce/plugins/autoresize/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TinyMCE version 7.1.0 (2024-05-08) 3 | */ 4 | !function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),t=tinymce.util.Tools.resolve("tinymce.Env");const o=e=>t=>t.options.get(e),s=o("min_height"),i=o("max_height"),n=o("autoresize_overflow_padding"),r=o("autoresize_bottom_margin"),l=(e,t)=>{const o=e.getBody();o&&(o.style.overflowY=t?"":"hidden",t||(o.scrollTop=0))},g=(e,t,o,s)=>{var i;const n=parseInt(null!==(i=e.getStyle(t,o,s))&&void 0!==i?i:"",10);return isNaN(n)?0:n},a=(e,o,r,c)=>{var d;const f=e.dom,u=e.getDoc();if(!u)return;if((e=>e.plugins.fullscreen&&e.plugins.fullscreen.isFullscreen())(e))return void l(e,!0);const m=u.documentElement,h=c?c():n(e),p=null!==(d=s(e))&&void 0!==d?d:e.getElement().offsetHeight;let y=p;const S=g(f,m,"margin-top",!0),v=g(f,m,"margin-bottom",!0);let C=m.offsetHeight+S+v+h;C<0&&(C=0);const b=e.getContainer().offsetHeight-e.getContentAreaContainer().offsetHeight;C+b>p&&(y=C+b);const w=i(e);if(w&&y>w?(y=w,l(e,!0)):l(e,!1),y!==o.get()){const s=y-o.get();if(f.setStyle(e.getContainer(),"height",y+"px"),o.set(y),(e=>{e.dispatch("ResizeEditor")})(e),t.browser.isSafari()&&(t.os.isMacOS()||t.os.isiOS())){const t=e.getWin();t.scrollTo(t.pageXOffset,t.pageYOffset)}e.hasFocus()&&(e=>{if("setcontent"===(null==e?void 0:e.type.toLowerCase())){const t=e;return!0===t.selection||!0===t.paste}return!1})(r)&&e.selection.scrollIntoView(),(t.browser.isSafari()||t.browser.isChromium())&&s<0&&a(e,o,r,c)}};e.add("autoresize",(e=>{if((e=>{const t=e.options.register;t("autoresize_overflow_padding",{processor:"number",default:1}),t("autoresize_bottom_margin",{processor:"number",default:50})})(e),e.options.isSet("resize")||e.options.set("resize",!1),!e.inline){const o=(e=>{let t=0;return{get:()=>t,set:e=>{t=e}}})();((e,t)=>{e.addCommand("mceAutoResize",(()=>{a(e,t)}))})(e,o),((e,o)=>{let s,i,l=()=>r(e);e.on("init",(i=>{s=0;const r=n(e),g=e.dom;g.setStyles(e.getDoc().documentElement,{height:"auto"}),t.browser.isEdge()||t.browser.isIE()?g.setStyles(e.getBody(),{paddingLeft:r,paddingRight:r,"min-height":0}):g.setStyles(e.getBody(),{paddingLeft:r,paddingRight:r}),a(e,o,i,l),s+=1})),e.on("NodeChange SetContent keyup FullscreenStateChanged ResizeContent",(t=>{if(1===s)i=e.getContainer().offsetHeight,a(e,o,t,l),s+=1;else if(2===s){const t=i0):l,s+=1}else a(e,o,t,l)}))})(e,o)}}))}(); -------------------------------------------------------------------------------- /plugin/tinymce/lib/tinymce/plugins/code/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TinyMCE version 7.1.0 (2024-05-08) 3 | */ 4 | !function(){"use strict";tinymce.util.Tools.resolve("tinymce.PluginManager").add("code",(e=>((e=>{e.addCommand("mceCodeEditor",(()=>{(e=>{const o=(e=>e.getContent({source_view:!0}))(e);e.windowManager.open({title:"Source Code",size:"large",body:{type:"panel",items:[{type:"textarea",name:"code"}]},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:{code:o},onSubmit:o=>{((e,o)=>{e.focus(),e.undoManager.transact((()=>{e.setContent(o)})),e.selection.setCursorLocation(),e.nodeChanged()})(e,o.getData().code),o.close()}})})(e)}))})(e),(e=>{const o=()=>e.execCommand("mceCodeEditor");e.ui.registry.addButton("code",{icon:"sourcecode",tooltip:"Source code",onAction:o}),e.ui.registry.addMenuItem("code",{icon:"sourcecode",text:"Source code",onAction:o})})(e),{})))}(); -------------------------------------------------------------------------------- /plugin/tinymce/lib/tinymce/plugins/nonbreaking/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TinyMCE version 7.1.0 (2024-05-08) 3 | */ 4 | !function(){"use strict";var n=tinymce.util.Tools.resolve("tinymce.PluginManager");const e=n=>e=>typeof e===n,o=e("boolean"),a=e("number"),t=n=>e=>e.options.get(n),i=t("nonbreaking_force_tab"),s=t("nonbreaking_wrap"),r=(n,e)=>{let o="";for(let a=0;a{const o=s(n)||n.plugins.visualchars?`${r(" ",e)}`:r(" ",e);n.undoManager.transact((()=>n.insertContent(o)))};var l=tinymce.util.Tools.resolve("tinymce.util.VK");const u=n=>e=>{const o=()=>{e.setEnabled(n.selection.isEditable())};return n.on("NodeChange",o),o(),()=>{n.off("NodeChange",o)}};n.add("nonbreaking",(n=>{(n=>{const e=n.options.register;e("nonbreaking_force_tab",{processor:n=>o(n)?{value:n?3:0,valid:!0}:a(n)?{value:n,valid:!0}:{valid:!1,message:"Must be a boolean or number."},default:!1}),e("nonbreaking_wrap",{processor:"boolean",default:!0})})(n),(n=>{n.addCommand("mceNonBreaking",(()=>{c(n,1)}))})(n),(n=>{const e=()=>n.execCommand("mceNonBreaking");n.ui.registry.addButton("nonbreaking",{icon:"non-breaking",tooltip:"Nonbreaking space",onAction:e,onSetup:u(n)}),n.ui.registry.addMenuItem("nonbreaking",{icon:"non-breaking",text:"Nonbreaking space",onAction:e,onSetup:u(n)})})(n),(n=>{const e=i(n);e>0&&n.on("keydown",(o=>{if(o.keyCode===l.TAB&&!o.isDefaultPrevented()){if(o.shiftKey)return;o.preventDefault(),o.stopImmediatePropagation(),c(n,e)}}))})(n)}))}(); -------------------------------------------------------------------------------- /plugin/tinymce/lib/tinymce/plugins/pagebreak/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TinyMCE version 7.1.0 (2024-05-08) 3 | */ 4 | !function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),a=tinymce.util.Tools.resolve("tinymce.Env");const t=e=>a=>a.options.get(e),n=t("pagebreak_separator"),o=t("pagebreak_split_block"),r="mce-pagebreak",s=e=>{const t=``;return e?`

    ${t}

    `:t},c=e=>a=>{const t=()=>{a.setEnabled(e.selection.isEditable())};return e.on("NodeChange",t),t(),()=>{e.off("NodeChange",t)}};e.add("pagebreak",(e=>{(e=>{const a=e.options.register;a("pagebreak_separator",{processor:"string",default:"\x3c!-- pagebreak --\x3e"}),a("pagebreak_split_block",{processor:"boolean",default:!1})})(e),(e=>{e.addCommand("mcePageBreak",(()=>{e.insertContent(s(o(e)))}))})(e),(e=>{const a=()=>e.execCommand("mcePageBreak");e.ui.registry.addButton("pagebreak",{icon:"page-break",tooltip:"Page break",onAction:a,onSetup:c(e)}),e.ui.registry.addMenuItem("pagebreak",{text:"Page break",icon:"page-break",onAction:a,onSetup:c(e)})})(e),(e=>{const a=n(e),t=()=>o(e),c=new RegExp(a.replace(/[\?\.\*\[\]\(\)\{\}\+\^\$\:]/g,(e=>"\\"+e)),"gi");e.on("BeforeSetContent",(e=>{e.content=e.content.replace(c,s(t()))})),e.on("PreInit",(()=>{e.serializer.addNodeFilter("img",(n=>{let o,s,c=n.length;for(;c--;)if(o=n[c],s=o.attr("class"),s&&-1!==s.indexOf(r)){const n=o.parent;if(n&&e.schema.getBlockElements()[n.name]&&t()){n.type=3,n.value=a,n.raw=!0,o.remove();continue}o.type=3,o.value=a,o.raw=!0}}))}))})(e),(e=>{e.on("ResolveName",(a=>{"IMG"===a.target.nodeName&&e.dom.hasClass(a.target,r)&&(a.name="pagebreak")}))})(e)}))}(); -------------------------------------------------------------------------------- /plugin/tinymce/lib/tinymce/plugins/preview/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TinyMCE version 7.1.0 (2024-05-08) 3 | */ 4 | !function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),t=tinymce.util.Tools.resolve("tinymce.Env"),o=tinymce.util.Tools.resolve("tinymce.util.Tools");const n=e=>t=>t.options.get(e),i=n("content_style"),s=n("content_css_cors"),c=n("body_class"),r=n("body_id");e.add("preview",(e=>{(e=>{e.addCommand("mcePreview",(()=>{(e=>{const n=(e=>{var n;let l="";const a=e.dom.encode,d=null!==(n=i(e))&&void 0!==n?n:"";l+='';const m=s(e)?' crossorigin="anonymous"':"";o.each(e.contentCSS,(t=>{l+='"})),d&&(l+='");const y=r(e),u=c(e),v='