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