1<?php
2
3use chrisbliss18\phpico\PHPIco;
4use dokuwiki\Extension\AdminPlugin;
5use dokuwiki\Form\Form;
6use dokuwiki\Utf8\PhpString;
7use splitbrain\RingIcon\RingIcon;
8
9/**
10 * DokuWiki Plugin farmer (Admin Component)
11 *
12 * Create new animals
13 *
14 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
15 * @author  Michael Große <grosse@cosmocode.de>
16 */
17class admin_plugin_farmer_new extends AdminPlugin
18{
19    /** @var helper_plugin_farmer $helper */
20    protected $helper;
21
22    /**
23     * admin_plugin_farmer_new constructor.
24     */
25    public function __construct()
26    {
27        $this->helper = plugin_load('helper', 'farmer');
28    }
29
30    /** @inheritdoc */
31    public function showInMenu()
32    {
33        return false;
34    }
35
36    /**
37     * Should carry out any processing required by the plugin.
38     */
39    public function handle()
40    {
41        global $INPUT;
42        global $ID;
43        if (!$INPUT->has('farmer__submit')) return;
44
45        $data = $this->validateAnimalData();
46        if (!$data) return;
47        if (
48            $this->createNewAnimal(
49                $data['name'],
50                $data['admin'],
51                $data['pass'],
52                $data['template'],
53                $data['aclpolicy'],
54                $data['allowreg']
55            )
56        ) {
57            $url = $this->helper->getAnimalURL($data['name']);
58            $link = '<a href="' . $url . '">' . hsc($data['name']) . '</a>';
59
60            msg(sprintf($this->getLang('animal creation success'), $link), 1);
61            $link = wl($ID, ['do' => 'admin', 'page' => 'farmer', 'sub' => 'new'], true, '&');
62            send_redirect($link);
63        }
64    }
65
66    /**
67     * Render HTML output, e.g. helpful text and a form
68     */
69    public function html()
70    {
71        global $lang;
72        $farmconfig = $this->helper->getConfig();
73
74        $form = new Form();
75        $form->addClass('plugin_farmer')->id('farmer__create_animal_form');
76
77        $form->addFieldsetOpen($this->getLang('animal configuration'));
78        $form->addTextInput('animalname', $this->getLang('animal'));
79        $form->addFieldsetClose();
80
81        $animals = $this->helper->getAllAnimals();
82        array_unshift($animals, '');
83        $form->addFieldsetOpen($this->getLang('animal template'));
84        $form->addDropdown('animaltemplate', $animals)
85            ->addClass('farmer_chosen_animals');
86        $form->addFieldsetClose();
87
88        $form->addFieldsetOpen($lang['i_policy'])->attr('id', 'aclPolicyFieldset');
89        $policyOptions = ['open' => $lang['i_pol0'], 'public' => $lang['i_pol1'], 'closed' => $lang['i_pol2']];
90        $form->addDropdown('aclpolicy', $policyOptions)
91            ->addClass('acl_chosen');
92        if ($farmconfig['inherit']['main']) {
93            $form->addRadioButton('allowreg', $this->getLang('inherit user registration'))
94                ->val('inherit')
95                ->attr('checked', 'checked');
96            $form->addRadioButton('allowreg', $this->getLang('enable user registration'))
97                ->val('allow');
98            $form->addRadioButton('allowreg', $this->getLang('disable user registration'))
99                ->val('disable');
100        } else {
101            $form->addCheckbox('allowreg', $lang['i_allowreg'])
102                ->attr('checked', 'checked');
103        }
104
105        $form->addFieldsetClose();
106
107        $form->addFieldsetOpen($this->getLang('animal administrator'));
108
109        $btn = $form->addRadioButton('adminsetup', $this->getLang('noUsers'))
110            ->val('noUsers');
111        if ($farmconfig['inherit']['users']) {
112            $btn->attr('checked', 'checked');  // default when inherit available
113        } else {
114            // no user copying when inheriting
115            $form->addRadioButton('adminsetup', $this->getLang('importUsers'))
116                ->val('importUsers');
117            $form->addRadioButton('adminsetup', $this->getLang('currentAdmin'))
118                ->val('currentAdmin');
119        }
120        $btn = $form->addRadioButton('adminsetup', $this->getLang('newAdmin'))
121            ->val('newAdmin');
122        if (!$farmconfig['inherit']['users']) {
123            $btn->attr('checked', 'checked'); // default when inherit not available
124        }
125        $form->addPasswordInput('adminPassword', $this->getLang('admin password'));
126        $form->addFieldsetClose();
127
128        $form->addButton('farmer__submit', $this->getLang('submit'))
129            ->attr('type', 'submit')
130            ->val('newAnimal');
131        echo $form->toHTML();
132    }
133
134    /**
135     * Validate the data for a new animal
136     *
137     * @return array|bool false on errors, clean data otherwise
138     */
139    protected function validateAnimalData()
140    {
141        global $INPUT;
142
143        $animalname = $INPUT->filter('trim')->filter([PhpString::class, 'strtolower'])->str('animalname');
144        $adminsetup = $INPUT->str('adminsetup');
145        $adminpass = $INPUT->filter('trim')->str('adminPassword');
146        $template = $INPUT->filter('trim')->str('animaltemplate');
147        $aclpolicy = $INPUT->filter('trim')->str('aclpolicy');
148        $allowreg = $INPUT->str('allowreg');
149
150        $errors = [];
151
152        if ($animalname === '') {
153            $errors[] = $this->getLang('animalname_missing');
154        } elseif (!$this->helper->validateAnimalName($animalname)) {
155            $errors[] = $this->getLang('animalname_invalid');
156        }
157
158        if ($adminsetup === 'newAdmin' && $adminpass === '') {
159            $errors[] = $this->getLang('adminPassword_empty');
160        }
161
162        if ($animalname !== '' && file_exists(DOKU_FARMDIR . '/' . $animalname)) {
163            $errors[] = $this->getLang('animalname_preexisting');
164        }
165
166        if (!is_dir(DOKU_FARMDIR . $template) && !in_array($aclpolicy, ['open', 'public', 'closed'])) {
167            $errors[] = $this->getLang('aclpolicy missing/bad');
168        }
169
170        if ($errors) {
171            foreach ($errors as $error) {
172                msg($error, -1);
173            }
174            return false;
175        }
176
177        if (!is_dir(DOKU_FARMDIR . $template)) {
178            $template = '';
179        }
180        if ($template != '') {
181            $aclpolicy = '';
182        }
183
184        return [
185            'name' => $animalname,
186            'admin' => $adminsetup,
187            'pass' => $adminpass,
188            'template' => $template,
189            'aclpolicy' => $aclpolicy,
190            'allowreg' => $allowreg
191        ];
192    }
193
194    /**
195     * Create a new animal
196     *
197     * @param string $name name/title of the animal, will be the directory name for htaccess setup
198     * @param string $adminSetup newAdmin, currentAdmin or importUsers
199     * @param string $adminPassword required if $adminSetup is newAdmin
200     * @param string $template name of animal to copy
201     * @param $aclpolicy
202     * @param $userreg
203     * @return bool true if successful
204     * @throws Exception
205     */
206    protected function createNewAnimal($name, $adminSetup, $adminPassword, $template, $aclpolicy, $userreg)
207    {
208        $animaldir = DOKU_FARMDIR . $name;
209
210        // copy basic template
211        $ok = $this->helper->copyDir(__DIR__ . '/../_animal', $animaldir);
212        if (!$ok) {
213            msg($this->getLang('animal creation error'), -1);
214            return false;
215        }
216
217        // copy animal template
218        if ($template != '') {
219            foreach (['conf', 'data/pages', 'data/media', 'data/meta', 'data/media_meta', 'index'] as $dir) {
220                $templatedir = DOKU_FARMDIR . $template . '/' . $dir;
221                if (!is_dir($templatedir)) continue;
222                // do not copy changelogs in meta
223                if (str_ends_with($dir, 'meta')) {
224                    $exclude = '/\.changes$/';
225                } else {
226                    $exclude = '';
227                }
228                if (!$this->helper->copyDir($templatedir, $animaldir . '/' . $dir, $exclude)) {
229                    msg(sprintf($this->getLang('animal template copy error'), $dir), -1);
230                    // we go on anyway
231                }
232            }
233        }
234
235        // append title to local config
236        $ok &= io_saveFile($animaldir . '/conf/local.php', "\n" . '$conf[\'title\'] = \'' . $name . '\';' . "\n", true);
237
238        // create a random logo and favicon
239        if (!class_exists('\splitbrain\RingIcon\RingIcon', false)) {
240            require(__DIR__ . '/../3rdparty/RingIcon.php');
241        }
242        if (!class_exists('\chrisbliss18\phpico\PHPIco', false)) {
243            require(__DIR__ . '/../3rdparty/PHPIco.php');
244        }
245        try {
246            if (function_exists('imagecreatetruecolor')) {
247                $logo = $animaldir . '/data/media/wiki/logo.png';
248                if (!file_exists($logo)) {
249                    $ringicon = new RingIcon(64);
250                    $ringicon->createImage($animaldir, $logo);
251                }
252
253                $icon = $animaldir . '/data/media/wiki/favicon.ico';
254                if (!file_exists($icon)) {
255                    $icongen = new PHPIco($logo);
256                    $icongen->saveIco($icon);
257                }
258            }
259        } catch (\Exception $ignore) {
260            // something went wrong, but we don't care. this is a nice to have feature only
261        }
262
263        // create admin user
264        if ($adminSetup === 'newAdmin') {
265            $users = "# <?php exit()?>\n" . $this->makeAdminLine($adminPassword) . "\n";
266        } elseif ($adminSetup === 'currentAdmin') {
267            $users = "# <?php exit()?>\n" . $this->getAdminLine() . "\n";
268        } elseif ($adminSetup === 'noUsers') {
269            if (file_exists($animaldir . '/conf/users.auth.php')) {
270                // a user file exists already, probably from animal template - don't overwrite
271                $users = '';
272            } else {
273                // create empty user file
274                $users = "# <?php exit()?>\n";
275            }
276        } else {
277            $users = io_readFile(DOKU_CONF . 'users.auth.php');
278        }
279        if ($users) {
280            $ok &= io_saveFile($animaldir . '/conf/users.auth.php', $users);
281        }
282
283        if ($aclpolicy != '') {
284            $aclfile = file($animaldir . '/conf/acl.auth.php');
285            $aclfile = array_map('trim', $aclfile);
286            array_pop($aclfile);
287            switch ($aclpolicy) {
288                case 'open':
289                    $aclfile[] = "* @ALL 8";
290                    break;
291                case 'public':
292                    $aclfile[] = "* @ALL 1";
293                    $aclfile[] = "* @user 8";
294                    break;
295                case 'closed':
296                    $aclfile[] = "* @ALL 0";
297                    $aclfile[] = "* @user 8";
298                    break;
299                default:
300                    throw new Exception('Undefined aclpolicy given');
301            }
302            $ok &= io_saveFile($animaldir . '/conf/acl.auth.php', implode("\n", $aclfile) . "\n");
303
304            global $conf;
305            switch ($userreg) {
306                case 'allow':
307                    $disableactions = implode(',', array_diff(explode(',', $conf['disableactions']), ['register']));
308                    $ok &= io_saveFile(
309                        $animaldir . '/conf/local.php',
310                        "\n" . '$conf[\'disableactions\'] = \'' . $disableactions . '\';' . "\n",
311                        true
312                    );
313                    break;
314                case 'disable':
315                    $disableactions = implode(',', array_merge(explode(',', $conf['disableactions']), ['register']));
316                    $ok &= io_saveFile(
317                        $animaldir . '/conf/local.php',
318                        "\n" . '$conf[\'disableactions\'] = \'' . $disableactions . '\';' . "\n",
319                        true
320                    );
321                    break;
322                case 'inherit':
323                case true:
324                    // nothing needs to be done
325                    break;
326                default:
327                    $ok &= io_saveFile(
328                        $animaldir . '/conf/local.php',
329                        "\n" . '$conf[\'disableactions\'] = \'register\';' . "\n",
330                        true
331                    );
332            }
333        }
334
335        // deactivate plugins by default FIXME this should be nicer
336        $deactivatedPluginsList = explode(',', $this->getConf('deactivated plugins'));
337        $deactivatedPluginsList = array_map('trim', $deactivatedPluginsList);
338        $deactivatedPluginsList = array_unique($deactivatedPluginsList);
339        $deactivatedPluginsList = array_filter($deactivatedPluginsList);
340        foreach ($deactivatedPluginsList as $plugin) {
341            $this->helper->setPluginState(trim($plugin), $name, 0);
342        }
343
344        return $ok;
345    }
346
347    /**
348     * Creates a new user line
349     *
350     * @param $password
351     * @return string
352     */
353    protected function makeAdminLine($password)
354    {
355        $pass = auth_cryptPassword($password);
356        $line = implode(
357            ':',
358            ['admin', $pass, 'Administrator', 'admin@example.org', 'admin,user']
359        );
360        return $line;
361    }
362
363    /**
364     * Copies the current user as new admin line
365     *
366     * @return string
367     */
368    protected function getAdminLine()
369    {
370        $currentAdmin = $_SERVER['REMOTE_USER'];
371        $masterUsers = file_get_contents(DOKU_CONF . 'users.auth.php');
372        $masterUsers = ltrim(strstr($masterUsers, "\n" . $currentAdmin . ":"));
373
374        $newAdmin = substr($masterUsers, 0, strpos($masterUsers, "\n") + 1);
375        return $newAdmin;
376    }
377}
378