xref: /plugin/dev/Skeletor.php (revision dd6f86a0365b464eb2ee2e42c2f2a6e64d71bdb8)
170316b84SAndreas Gohr<?php
270316b84SAndreas Gohr
370316b84SAndreas Gohrnamespace dokuwiki\plugin\dev;
470316b84SAndreas Gohr
570316b84SAndreas Gohruse RuntimeException;
670316b84SAndreas Gohr
770316b84SAndreas Gohr/**
870316b84SAndreas Gohr * This class holds basic information about a plugin or template and uses the skeleton files to
970316b84SAndreas Gohr * create new plugin or template specific versions of them.
1070316b84SAndreas Gohr *
1170316b84SAndreas Gohr * This class does not write any files, but only provides the data for the actual file creation.
1270316b84SAndreas Gohr */
1370316b84SAndreas Gohrclass Skeletor
1470316b84SAndreas Gohr{
1570316b84SAndreas Gohr    // FIXME this may change upstream we may want to update it via github action
1670316b84SAndreas Gohr    const PLUGIN_TYPES = ['auth', 'admin', 'syntax', 'action', 'renderer', 'helper', 'remote', 'cli'];
1770316b84SAndreas Gohr
1870316b84SAndreas Gohr    const TYPE_PLUGIN = 'plugin';
1970316b84SAndreas Gohr    const TYPE_TEMPLATE = 'template';
2070316b84SAndreas Gohr
2170316b84SAndreas Gohr    protected $type;
2270316b84SAndreas Gohr    protected $base;
2370316b84SAndreas Gohr    protected $author;
2470316b84SAndreas Gohr    protected $desc;
2570316b84SAndreas Gohr    protected $name;
2670316b84SAndreas Gohr    protected $email;
2770316b84SAndreas Gohr    protected $url;
2870316b84SAndreas Gohr    protected $dir;
2970316b84SAndreas Gohr
3070316b84SAndreas Gohr    /** @var array The files to be created in the form of [path => content] */
3170316b84SAndreas Gohr    protected $files = [];
3270316b84SAndreas Gohr
3370316b84SAndreas Gohr    /**
3470316b84SAndreas Gohr     * Initialize the skeletor from provided data
3570316b84SAndreas Gohr     *
3670316b84SAndreas Gohr     * @param string $type
3770316b84SAndreas Gohr     * @param string $base
3870316b84SAndreas Gohr     * @param string $desc
3970316b84SAndreas Gohr     * @param string $author
4070316b84SAndreas Gohr     * @param string $email
4170316b84SAndreas Gohr     * @param string $name
4270316b84SAndreas Gohr     * @param string $url
4370316b84SAndreas Gohr     */
4470316b84SAndreas Gohr    public function __construct($type, $base, $desc, $author, $email, $name = '', $url = '')
4570316b84SAndreas Gohr    {
4670316b84SAndreas Gohr        $this->type = $type;
4770316b84SAndreas Gohr        $this->base = $base;
4870316b84SAndreas Gohr        $this->desc = $desc;
4970316b84SAndreas Gohr        $this->author = $author;
5070316b84SAndreas Gohr        $this->email = $email;
5170316b84SAndreas Gohr        $this->name = $name ?: ucfirst($base . ' ' . $type);
5270316b84SAndreas Gohr
5370316b84SAndreas Gohr        if ($type == self::TYPE_PLUGIN) {
5470316b84SAndreas Gohr            $this->url = $url ?: 'https://www.dokuwiki.org/plugin:' . $base;
5570316b84SAndreas Gohr            $this->dir = 'lib/plugins/' . $base;
5670316b84SAndreas Gohr        } else {
5770316b84SAndreas Gohr            $this->url = $url ?: 'https://www.dokuwiki.org/template:' . $base;
5870316b84SAndreas Gohr            $this->dir = 'lib/tpl/' . $base;
5970316b84SAndreas Gohr        }
6070316b84SAndreas Gohr    }
6170316b84SAndreas Gohr
6270316b84SAndreas Gohr    /**
6370316b84SAndreas Gohr     * Create an instance using an existing plugin or template directory
6470316b84SAndreas Gohr     *
6570316b84SAndreas Gohr     * @param string $dir
6670316b84SAndreas Gohr     * @return Skeletor
6770316b84SAndreas Gohr     */
6870316b84SAndreas Gohr    static public function fromDir($dir)
6970316b84SAndreas Gohr    {
7070316b84SAndreas Gohr        if (file_exists($dir . '/plugin.info.txt')) {
7170316b84SAndreas Gohr            $type = self::TYPE_PLUGIN;
7270316b84SAndreas Gohr        } elseif (file_exists($dir . '/template.info.txt')) {
7370316b84SAndreas Gohr            $type = self::TYPE_TEMPLATE;
7470316b84SAndreas Gohr        } else {
7570316b84SAndreas Gohr            throw new RuntimeException('Not a plugin or template directory');
7670316b84SAndreas Gohr        }
7770316b84SAndreas Gohr
7870316b84SAndreas Gohr        $data = file($dir . '/' . $type . '.info.txt', FILE_IGNORE_NEW_LINES);
7970316b84SAndreas Gohr        $data = array_map(function ($item) {
80*dd6f86a0SAndreas Gohr            return array_map('trim', sexplode(' ', $item, 2, ''));
8170316b84SAndreas Gohr        }, $data);
8270316b84SAndreas Gohr        $data = array_combine(array_column($data, 0), array_column($data, 1));
8370316b84SAndreas Gohr
8470316b84SAndreas Gohr        return new self($type, $data['base'], $data['desc'], $data['author'], $data['email'], $data['url']);
8570316b84SAndreas Gohr    }
8670316b84SAndreas Gohr
8770316b84SAndreas Gohr    /**
8870316b84SAndreas Gohr     * Return the files to be created
8970316b84SAndreas Gohr     *
9070316b84SAndreas Gohr     * @return array [path => content]
9170316b84SAndreas Gohr     */
9270316b84SAndreas Gohr    public function getFiles()
9370316b84SAndreas Gohr    {
9470316b84SAndreas Gohr        return $this->files;
9570316b84SAndreas Gohr    }
9670316b84SAndreas Gohr
9770316b84SAndreas Gohr    // region content creators
9870316b84SAndreas Gohr
9970316b84SAndreas Gohr    /**
10070316b84SAndreas Gohr     * Add the basic files to the plugin
10170316b84SAndreas Gohr     */
10270316b84SAndreas Gohr    public function addBasics()
10370316b84SAndreas Gohr    {
10470316b84SAndreas Gohr        $this->loadSkeleton('info.txt', $this->type . '.info.txt');
10570316b84SAndreas Gohr        $this->loadSkeleton('README');
10670316b84SAndreas Gohr        $this->loadSkeleton('LICENSE');
10770316b84SAndreas Gohr        $this->loadSkeleton('.gitattributes');
10870316b84SAndreas Gohr    }
10970316b84SAndreas Gohr
11070316b84SAndreas Gohr    /**
11170316b84SAndreas Gohr     * Add another component to the plugin
11270316b84SAndreas Gohr     *
11370316b84SAndreas Gohr     * @param string $type
11470316b84SAndreas Gohr     * @param string $component
11570316b84SAndreas Gohr     */
11689e2f9d1SAndreas Gohr    public function addComponent($type, $component = '', $options = [])
11770316b84SAndreas Gohr    {
11870316b84SAndreas Gohr        if ($this->type !== self::TYPE_PLUGIN) {
11970316b84SAndreas Gohr            throw new RuntimeException('Components can only be added to plugins');
12070316b84SAndreas Gohr        }
12170316b84SAndreas Gohr
12270316b84SAndreas Gohr        if (!in_array($type, self::PLUGIN_TYPES)) {
12370316b84SAndreas Gohr            throw new RuntimeException('Invalid type ' . $type);
12470316b84SAndreas Gohr        }
12570316b84SAndreas Gohr
12670316b84SAndreas Gohr        $plugin = $this->base;
12770316b84SAndreas Gohr
12870316b84SAndreas Gohr        if ($component) {
12970316b84SAndreas Gohr            $path = $type . '/' . $component . '.php';
13070316b84SAndreas Gohr            $class = $type . '_plugin_' . $plugin . '_' . $component;
13170316b84SAndreas Gohr            $self = 'plugin_' . $plugin . '_' . $component;
13270316b84SAndreas Gohr        } else {
13370316b84SAndreas Gohr            $path = $type . '.php';
13470316b84SAndreas Gohr            $class = $type . '_plugin_' . $plugin;
13570316b84SAndreas Gohr            $self = 'plugin_' . $plugin;
13670316b84SAndreas Gohr        }
13770316b84SAndreas Gohr
13889e2f9d1SAndreas Gohr        if ($type === 'action') {
13989e2f9d1SAndreas Gohr            $replacements = $this->actionReplacements($options);
14089e2f9d1SAndreas Gohr        }
141cde324c2SAndreas Gohr        if ($type === 'renderer' && isset($options[0]) && $options[0] === 'Doku_Renderer_xhtml') {
142cde324c2SAndreas Gohr            $type = 'renderer_xhtml'; // different template then
143cde324c2SAndreas Gohr        }
14489e2f9d1SAndreas Gohr
14570316b84SAndreas Gohr        $replacements['@@PLUGIN_COMPONENT_NAME@@'] = $class;
14670316b84SAndreas Gohr        $replacements['@@SYNTAX_COMPONENT_NAME@@'] = $self;
14770316b84SAndreas Gohr        $this->loadSkeleton($type . '.php', $path, $replacements);
14870316b84SAndreas Gohr    }
14970316b84SAndreas Gohr
15070316b84SAndreas Gohr    /**
15170316b84SAndreas Gohr     * Add test framework optionally with a specific test
15270316b84SAndreas Gohr     *
15370316b84SAndreas Gohr     * @param string $test Name of the Test to add
15470316b84SAndreas Gohr     */
15570316b84SAndreas Gohr    public function addTest($test = '')
15670316b84SAndreas Gohr    {
15770316b84SAndreas Gohr        $test = ucfirst(strtolower($test));
15870316b84SAndreas Gohr        $this->loadSkeleton('.github/workflows/phpTestLinux.yml');
15970316b84SAndreas Gohr        if ($test) {
16070316b84SAndreas Gohr            $replacements = ['@@TEST@@' => $test];
16170316b84SAndreas Gohr            $this->loadSkeleton('_test/StandardTest.php', '_test/' . $test . 'Test.php', $replacements);
16270316b84SAndreas Gohr        } else {
16370316b84SAndreas Gohr            $this->loadSkeleton('_test/GeneralTest.php');
16470316b84SAndreas Gohr        }
16570316b84SAndreas Gohr    }
16670316b84SAndreas Gohr
16770316b84SAndreas Gohr    /**
16870316b84SAndreas Gohr     * Add configuration
16970316b84SAndreas Gohr     *
17070316b84SAndreas Gohr     * @param bool $translate if true the settings language file will be be added, too
17170316b84SAndreas Gohr     */
17270316b84SAndreas Gohr    public function addConf($translate = false)
17370316b84SAndreas Gohr    {
17470316b84SAndreas Gohr        $this->loadSkeleton('conf/default.php');
17570316b84SAndreas Gohr        $this->loadSkeleton('conf/metadata.php');
17670316b84SAndreas Gohr
17770316b84SAndreas Gohr        if ($translate) {
17870316b84SAndreas Gohr            $this->loadSkeleton('lang/settings.php', 'lang/en/settings.php');
17970316b84SAndreas Gohr        }
18070316b84SAndreas Gohr    }
18170316b84SAndreas Gohr
18270316b84SAndreas Gohr    /**
18370316b84SAndreas Gohr     * Add language
18470316b84SAndreas Gohr     *
18570316b84SAndreas Gohr     * Currently only english is added, theoretically this could also copy over the keys from an
18670316b84SAndreas Gohr     * existing english language file.
18770316b84SAndreas Gohr     *
18870316b84SAndreas Gohr     * @param bool $conf if true the settings language file will be be added, too
18970316b84SAndreas Gohr     */
19070316b84SAndreas Gohr    public function addLang($conf = false)
19170316b84SAndreas Gohr    {
19270316b84SAndreas Gohr        $this->loadSkeleton('lang/lang.php', 'lang/en/lang.php');
19370316b84SAndreas Gohr        if ($conf) {
19470316b84SAndreas Gohr            $this->loadSkeleton('lang/settings.php', 'lang/en/settings.php');
19570316b84SAndreas Gohr        }
19670316b84SAndreas Gohr    }
19770316b84SAndreas Gohr
19870316b84SAndreas Gohr    // endregion
19970316b84SAndreas Gohr
20070316b84SAndreas Gohr
20170316b84SAndreas Gohr    /**
20270316b84SAndreas Gohr     * Prepare the string replacements
20370316b84SAndreas Gohr     *
20470316b84SAndreas Gohr     * @param array $replacements override defaults
20570316b84SAndreas Gohr     * @return array
20670316b84SAndreas Gohr     */
20770316b84SAndreas Gohr    protected function prepareReplacements($replacements = [])
20870316b84SAndreas Gohr    {
20970316b84SAndreas Gohr        // defaults
21070316b84SAndreas Gohr        $data = [
21170316b84SAndreas Gohr            '@@AUTHOR_NAME@@' => $this->author,
21270316b84SAndreas Gohr            '@@AUTHOR_MAIL@@' => $this->email,
21370316b84SAndreas Gohr            '@@PLUGIN_NAME@@' => $this->base, // FIXME rename to @@PLUGIN_BASE@@
21470316b84SAndreas Gohr            '@@PLUGIN_DESC@@' => $this->desc,
21570316b84SAndreas Gohr            '@@PLUGIN_URL@@' => $this->url,
21670316b84SAndreas Gohr            '@@PLUGIN_TYPE@@' => $this->type,
21770316b84SAndreas Gohr            '@@INSTALL_DIR@@' => ($this->type == self::TYPE_PLUGIN) ? 'plugins' : 'tpl',
21870316b84SAndreas Gohr            '@@DATE@@' => date('Y-m-d'),
21970316b84SAndreas Gohr        ];
22070316b84SAndreas Gohr
22170316b84SAndreas Gohr        // merge given overrides
22270316b84SAndreas Gohr        return array_merge($data, $replacements);
22370316b84SAndreas Gohr    }
22470316b84SAndreas Gohr
22570316b84SAndreas Gohr    /**
22670316b84SAndreas Gohr     * Replacements needed for action components.
22770316b84SAndreas Gohr     *
22889e2f9d1SAndreas Gohr     * @param string[] $event Event names to handle
22970316b84SAndreas Gohr     * @return string[]
23070316b84SAndreas Gohr     */
23189e2f9d1SAndreas Gohr    protected function actionReplacements($events = [])
23270316b84SAndreas Gohr    {
23389e2f9d1SAndreas Gohr        if (!$events) $events = ['EXAMPLE_EVENT'];
23489e2f9d1SAndreas Gohr
23589e2f9d1SAndreas Gohr        $register = '';
23689e2f9d1SAndreas Gohr        $handler = '';
23789e2f9d1SAndreas Gohr
23889e2f9d1SAndreas Gohr        $template = file_get_contents(__DIR__ . '/skel/action_handler.php');
23989e2f9d1SAndreas Gohr
24089e2f9d1SAndreas Gohr        foreach ($events as $event) {
24170316b84SAndreas Gohr            $event = strtoupper($event);
24270316b84SAndreas Gohr            $fn = 'handle' . str_replace('_', '', ucwords(strtolower($event), '_'));
24389e2f9d1SAndreas Gohr
24489e2f9d1SAndreas Gohr            $register .= '        $controller->register_hook(\'' . $event .
24589e2f9d1SAndreas Gohr                '\', \'AFTER|BEFORE\', $this, \'' . $fn . '\');' . "\n";
24689e2f9d1SAndreas Gohr
24789e2f9d1SAndreas Gohr            $handler .= str_replace(['@@EVENT@@', '@@HANDLER@@'], [$event, $fn], $template);
24889e2f9d1SAndreas Gohr        }
24970316b84SAndreas Gohr
25070316b84SAndreas Gohr        return [
25189e2f9d1SAndreas Gohr            '@@REGISTER@@' => $register,
25270316b84SAndreas Gohr            '@@HANDLERS@@' => $handler,
25370316b84SAndreas Gohr        ];
25470316b84SAndreas Gohr    }
25570316b84SAndreas Gohr
25670316b84SAndreas Gohr    /**
25770316b84SAndreas Gohr     * Load a skeleton file, do the replacements and add it to the list of files
25870316b84SAndreas Gohr     *
25970316b84SAndreas Gohr     * @param string $skel Skeleton relative to the skel dir
26070316b84SAndreas Gohr     * @param string $target File name in the final plugin/template, empty for same as skeleton
26170316b84SAndreas Gohr     * @param array $replacements Non-default replacements to use
26270316b84SAndreas Gohr     */
26370316b84SAndreas Gohr    protected function loadSkeleton($skel, $target = '', $replacements = [])
26470316b84SAndreas Gohr    {
26570316b84SAndreas Gohr        $replacements = $this->prepareReplacements($replacements);
26670316b84SAndreas Gohr        if (!$target) $target = $skel;
26770316b84SAndreas Gohr
26870316b84SAndreas Gohr
26970316b84SAndreas Gohr        $file = __DIR__ . '/skel/' . $skel;
27070316b84SAndreas Gohr        if (!file_exists($file)) {
27170316b84SAndreas Gohr            throw new RuntimeException('Skeleton file not found: ' . $skel);
27270316b84SAndreas Gohr        }
27370316b84SAndreas Gohr        $content = file_get_contents($file);
27470316b84SAndreas Gohr        $this->files[$target] = str_replace(
27570316b84SAndreas Gohr            array_keys($replacements),
27670316b84SAndreas Gohr            array_values($replacements),
27770316b84SAndreas Gohr            $content
27870316b84SAndreas Gohr        );
27970316b84SAndreas Gohr    }
28070316b84SAndreas Gohr}
281