summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/MetaBox.php81
-rw-r--r--src/Settings.php97
-rw-r--r--src/ThemeEditor.php73
-rw-r--r--src/TimberEditor.php148
-rw-r--r--src/codemirror-themes.php67
5 files changed, 466 insertions, 0 deletions
diff --git a/src/MetaBox.php b/src/MetaBox.php
new file mode 100644
index 0000000..d11336f
--- /dev/null
+++ b/src/MetaBox.php
@@ -0,0 +1,81 @@
+<?php
+
+namespace TimberEditor;
+
+class MetaBox
+{
+ /**
+ * Metabox constructor.
+ */
+ public function __construct()
+ {
+ add_action('add_meta_boxes', [$this, 'addMetaBoxes']);
+
+ foreach (Settings::getGeneralSupportedPostTypes() as $postType) {
+ add_action("save_post_{$postType}", [$this, 'savePost']);
+ }
+ }
+
+ /**
+ * add_meta_boxes action callback
+ *
+ * @param $postType
+ */
+ public function addMetaBoxes($postType)
+ {
+ if (! in_array($postType, Settings::getGeneralSupportedPostTypes())) {
+ return;
+ }
+
+ add_meta_box('timber-editor', 'Timber Editor', [$this, 'metaBoxTimberEditor'], '', 'advanced', 'high');
+ }
+
+ /**
+ * save_post action callback
+ * Writes the content to file
+ *
+ * @param $postId
+ */
+ public function savePost($postId)
+ {
+ if (
+ (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) ||
+ (! isset($_POST['post_ID']) || $_POST['post_ID'] != $postId) ||
+ ! check_admin_referer('metaBoxTimberEditor', 'metaBoxTimberEditor') ||
+ ! isset($_POST['timber-editor_content'])
+ ) {
+ return;
+ }
+
+ file_put_contents(TimberEditor::getTemplateFilePath($postId), $_POST['timber-editor_content']);
+ if (empty($_POST['timber-editor_content'])) {
+ wp_delete_file(TimberEditor::getTemplateFilePath($postId));
+ }
+ }
+
+ /**
+ * add_meta_box callback
+ */
+ public function metaBoxTimberEditor()
+ {
+ $file = TimberEditor::getTemplateFilePath();
+ if (file_exists($file)) {
+ $f = fopen($file, 'r');
+ $content = fread($f, filesize($file));
+ fclose($f);
+ }
+
+ wp_nonce_field('metaBoxTimberEditor', 'metaBoxTimberEditor');
+ ?>
+ <textarea name="timber-editor_content" id="timber-editor_content"><?= esc_textarea($content ?? '') ?></textarea>
+ <?php
+
+ $settings = wp_enqueue_code_editor([
+ 'file' => $file,
+ 'codemirror' => [
+ 'theme' => Settings::getCodeMirrorTheme(),
+ ],
+ ]);
+ wp_add_inline_script('code-editor', sprintf('jQuery( function() { wp.codeEditor.initialize( "timber-editor_content", %s ); } );', wp_json_encode($settings)));
+ }
+}
diff --git a/src/Settings.php b/src/Settings.php
new file mode 100644
index 0000000..d9947f8
--- /dev/null
+++ b/src/Settings.php
@@ -0,0 +1,97 @@
+<?php
+
+namespace TimberEditor;
+
+class Settings
+{
+ /**
+ * Settings constructor.
+ */
+ public function __construct()
+ {
+ add_action('admin_menu', [$this, 'adminMenu']);
+ add_action('admin_init', [$this, 'adminInit']);
+ }
+
+ /**
+ * admin_menu action callback
+ */
+ public function adminMenu()
+ {
+ add_submenu_page('options-general.php', 'Timber Editor', 'Timber Editor', 'manage_options', 'timber-editor', [$this, 'addSubmenuPage']);
+ }
+
+ /**
+ * add_submenu_page callback
+ */
+ public function addSubmenuPage()
+ {
+ ?>
+ <div class="wrap">
+ <h1><?= esc_html(get_admin_page_title()) ?></h1>
+ <form action="options.php" method="post">
+ <?php
+ settings_fields('timber-editor');
+ do_settings_sections('timber-editor');
+ submit_button();
+ ?>
+ </form>
+ </div>
+ <?php
+ }
+
+ /**
+ * admin_init action callback
+ */
+ public function adminInit()
+ {
+ register_setting('timber-editor', 'timber-editor_general_supported-post-types');
+ add_settings_section('timber-editor_general', __('General'), function () {}, 'timber-editor');
+ add_settings_field('timber-editor_general_supported-post-types', __('Supported Post Types'), function () {
+ $postTypes = get_post_types([], 'objects');
+ $supportedPostTypes = self::getGeneralSupportedPostTypes();
+ ?>
+ <select name="timber-editor_general_supported-post-types[]" id="timber-editor_general_supported-post-types" multiple>
+ <?php foreach ($postTypes as $pt): ?>
+ <option value="<?= $pt->name ?>" <?= in_array($pt->name, $supportedPostTypes) ? 'selected' : '' ?>><?= $pt->label ?></option>
+ <?php endforeach; ?>
+ </select>
+ <?php
+ }, 'timber-editor', 'timber-editor_general', ['label_for' => 'timber-editor_general_supported-post-types']);
+
+ register_setting('timber-editor', 'timber-editor_codemirror_theme');
+ add_settings_section('timber-editor_codemirror', 'CodeMirror', function () {}, 'timber-editor');
+ add_settings_field('timber-editor_codemirror_theme', 'Theme', function () {
+ $theme = self::getCodeMirrorTheme();
+ $themes = include_once 'codemirror-themes.php';
+ ?>
+ <select name="timber-editor_codemirror_theme" id="timber-editor_codemirror_theme">
+ <?php foreach ($themes as $t): ?>
+ <option value="<?= $t ?>" <?php selected($theme, $t) ?>><?= $t ?></option>
+ <?php endforeach; ?>
+ </select>
+ <p class="description">
+ <a href="https://codemirror.net/demo/theme.html#<?= $theme ?>" target="_blank">
+ <?= __('Preview') ?>
+ </a>
+ </p>
+ <?php
+ }, 'timber-editor', 'timber-editor_codemirror', ['label_for' => 'timber-editor_codemirror_theme']);
+ }
+
+ /**
+ * @return string[]
+ */
+ public static function getGeneralSupportedPostTypes()
+ {
+ return get_option('timber-editor_general_supported-post-types', ['page']) ?: [];
+ }
+
+ /**
+ * @return string
+ */
+ public static function getCodeMirrorTheme()
+ {
+ return get_option('timber-editor_codemirror_theme', 'default');
+ }
+}
diff --git a/src/ThemeEditor.php b/src/ThemeEditor.php
new file mode 100644
index 0000000..0b62264
--- /dev/null
+++ b/src/ThemeEditor.php
@@ -0,0 +1,73 @@
+<?php
+
+namespace TimberEditor;
+
+class ThemeEditor
+{
+ /**
+ * ThemeEditor constructor.
+ */
+ public function __construct()
+ {
+ add_filter('wp_theme_editor_filetypes', [$this, 'editableExtensions']);
+ add_filter('editable_extensions', [$this, 'editableExtensions']);
+ add_filter('wp_code_editor_settings', [$this, 'codeEditorSettings'], 10, 2);
+ add_action('wp_enqueue_code_editor', [$this, 'enqueueCodeEditor']);
+ }
+
+ /**
+ * wp_theme_editor_filetypes and editable_extensions filter callback
+ * Adds twig extension support
+ *
+ * @param $types
+ *
+ * @return array
+ */
+ public function editableExtensions($types)
+ {
+ $types[] = 'twig';
+
+ return $types;
+ }
+
+ /**
+ * wp_code_editor_settings filter callback
+ * Adds twig support and sets theme
+ *
+ * @param $settings
+ * @param $args
+ *
+ * @return array
+ */
+ public function codeEditorSettings($settings, $args) {
+ if (strpos($args['file'], '.twig') !== false) {
+ $settings['codemirror']['mode'] = ['name' => 'twig', 'base' => 'text/html'];
+ }
+
+ $settings['codemirror']['theme'] = Settings::getCodeMirrorTheme();
+
+ return $settings;
+ }
+
+ /**
+ * wp_enqueue_code_editor action callback
+ * Adds twig and custom mode support
+ * Adds selected theme css
+ *
+ * @param $settings
+ */
+ public function enqueueCodeEditor($settings) {
+ if (isset($settings['codemirror']['mode']['name']) && $settings['codemirror']['mode']['name'] == 'twig') {
+ wp_add_inline_script( # fix as described here: https://make.wordpress.org/core/2017/10/22/code-editing-improvements-in-wordpress-4-9/
+ 'wp-codemirror',
+ 'window.CodeMirror = wp.CodeMirror;'
+ );
+ wp_enqueue_script('mode-twig', 'https://unpkg.com/codemirror@5/mode/twig/twig.js', ['wp-codemirror']);
+ }
+
+ $theme = $settings['codemirror']['theme'];
+ if ($theme != 'default') {
+ wp_enqueue_style('codemirror-theme', "https://unpkg.com/codemirror@5/theme/$theme.css", ['wp-codemirror']);
+ }
+ }
+}
diff --git a/src/TimberEditor.php b/src/TimberEditor.php
new file mode 100644
index 0000000..2eb3b36
--- /dev/null
+++ b/src/TimberEditor.php
@@ -0,0 +1,148 @@
+<?php
+
+namespace TimberEditor;
+
+use Timber\Loader;
+use Timber\Post;
+use Timber\Timber;
+
+class TimberEditor
+{
+ /**
+ * TimberEditor constructor.
+ */
+ public function __construct()
+ {
+ if (! class_exists(Timber::class)) {
+ add_action('admin_notices', [$this, 'adminNoticeTimberLibraryMissing']);
+ return;
+ }
+ if (! class_exists(\Classic_Editor::class)) {
+ add_action('admin_notices', [$this, 'adminNoticeClassicEditor']);
+ }
+
+ $this->run();
+ }
+
+ /**
+ * admin_notices action callback for missing Timber Library
+ */
+ public function adminNoticeTimberLibraryMissing()
+ {
+ ?>
+ <div class="notice notice-error">
+ <p>
+ <a href="https://wordpress.org/plugins/timber-library/" target="_blank">Timber</a>
+ (<a href="http://word.press/wp-admin/plugin-install.php?s=timber&tab=search&type=term">install</a>)
+ needs to be installed and active.
+ </p>
+ </div>
+ <?php
+ }
+
+ /**
+ * admin_notices action callback for missing Classic Editor
+ */
+ public function adminNoticeClassicEditor()
+ {
+ ?>
+ <div class="notice notice-warning is-dismissible">
+ <p>
+ <a href="https://wordpress.org/plugins/classic-editor/" target="_blank">Classic Editor</a>
+ (<a href="http://word.press/wp-admin/plugin-install.php?s=classic+editor&tab=search&type=term">install</a>)
+ should be installed and active, because the <b>Gutenberg Editor</b> doesn't play well with <b>CodeMirror</b> <i>currently</i>.
+ </p>
+ </div>
+ <?php
+ }
+
+ /**
+ * Run the plugin
+ */
+ public function run()
+ {
+ if (is_null(Timber::$locations)) {
+ Timber::$locations = self::getTemplatesLocation();
+ }
+
+ new Settings();
+ new ThemeEditor();
+ new MetaBox();
+ }
+
+ /**
+ * Get the plugins' templates location
+ * try to use user provided location via Timber::$locations
+ * or fall back to uploads folder
+ *
+ * @return string
+ */
+ public static function getTemplatesLocation()
+ {
+ $location = Timber::$locations;
+ if (is_array($location)) {
+ $location = $location[array_key_first($location)];
+ } else if (is_null($location)) {
+ $location = wp_upload_dir()['basedir'] . '/timber-editor';
+ }
+
+ return apply_filters('TimberEditor/getTemplatesLocation', $location);
+ }
+
+ /**
+ * Get the current posts' template filename
+ *
+ * @param int|null $postId
+ *
+ * @return string
+ */
+ public static function getTemplateFilename($postId = null)
+ {
+ return apply_filters('TimberEditor/getTemplateFilename', ($postId ?? get_the_ID()) . '.twig', $postId);
+ }
+
+ /**
+ * Get the current posts' template filepath
+ *
+ * @param int|null $postId
+ *
+ * @return string
+ */
+ public static function getTemplateFilePath($postId = null)
+ {
+ return self::getTemplatesLocation() . '/' . self::getTemplateFilename($postId);
+ }
+
+ /**
+ * @param array|string $filenames
+ * @param array $context
+ * @param bool $expires
+ * @param string $cacheMode
+ *
+ * @return bool|string
+ */
+ public static function render($filenames = [], $context = [], $expires = false, $cacheMode = Loader::CACHE_USE_DEFAULT)
+ {
+ $filenames = (array)$filenames;
+ array_unshift($filenames, self::getTemplateFilename());
+
+ return Timber::render($filenames, $context, $expires, $cacheMode);
+ }
+
+ /**
+ * @param array|string $filenames
+ * @param array $context
+ * @param bool $expires
+ * @param string $cacheMode
+ *
+ * @return bool|string
+ */
+ public static function renderPost($filenames = [], $context = [], $expires = false, $cacheMode = Loader::CACHE_USE_DEFAULT)
+ {
+ if (! isset($context['post'])) {
+ $context['post'] = new Post();
+ }
+
+ return self::render($filenames, $context, $expires, $cacheMode);
+ }
+}
diff --git a/src/codemirror-themes.php b/src/codemirror-themes.php
new file mode 100644
index 0000000..a51779d
--- /dev/null
+++ b/src/codemirror-themes.php
@@ -0,0 +1,67 @@
+<?php
+
+return [
+ 'default',
+ '3024-day',
+ '3024-night',
+ 'abcdef',
+ 'ambiance',
+ 'ayu-dark',
+ 'ayu-mirage',
+ 'base16-dark',
+ 'base16-light',
+ 'bespin',
+ 'blackboard',
+ 'cobalt',
+ 'colorforth',
+ 'darcula',
+ 'dracula',
+ 'duotone-dark',
+ 'duotone-light',
+ 'eclipse',
+ 'elegant',
+ 'erlang-dark',
+ 'gruvbox-dark',
+ 'hopscotch',
+ 'icecoder',
+ 'idea',
+ 'isotope',
+ 'lesser-dark',
+ 'liquibyte',
+ 'lucario',
+ 'material',
+ 'material-darker',
+ 'material-palenight',
+ 'material-ocean',
+ 'mbo',
+ 'mdn-like',
+ 'midnight',
+ 'monokai',
+ 'moxer',
+ 'neat',
+ 'neo',
+ 'night',
+ 'nord',
+ 'oceanic-next',
+ 'panda-syntax',
+ 'paraiso-dark',
+ 'paraiso-light',
+ 'pastel-on-dark',
+ 'railscasts',
+ 'rubyblue',
+ 'seti',
+ 'shadowfox',
+ 'solarized dark',
+ 'solarized light',
+ 'the-matrix',
+ 'tomorrow-night-bright',
+ 'tomorrow-night-eighties',
+ 'ttcn',
+ 'twilight',
+ 'vibrant-ink',
+ 'xq-dark',
+ 'xq-light',
+ 'yeti',
+ 'yonce',
+ 'zenburn',
+];