xref: /webtrees/app/Module/StoriesModule.php (revision bba27599b02e0b6f90f62348bb4fa32bd74056d0)
1<?php
2/**
3 * webtrees: online genealogy
4 * Copyright (C) 2018 webtrees development team
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16namespace Fisharebest\Webtrees\Module;
17
18use Fisharebest\Webtrees\Auth;
19use Fisharebest\Webtrees\Database;
20use Fisharebest\Webtrees\I18N;
21use Fisharebest\Webtrees\Individual;
22use Fisharebest\Webtrees\Menu;
23use Fisharebest\Webtrees\Tree;
24use stdClass;
25use Symfony\Component\HttpFoundation\RedirectResponse;
26use Symfony\Component\HttpFoundation\Request;
27use Symfony\Component\HttpFoundation\Response;
28
29/**
30 * Class StoriesModule
31 */
32class StoriesModule extends AbstractModule implements ModuleTabInterface, ModuleConfigInterface, ModuleMenuInterface {
33	/** {@inheritdoc} */
34	public function getTitle() {
35		return /* I18N: Name of a module */
36			I18N::translate('Stories');
37	}
38
39	/** {@inheritdoc} */
40	public function getDescription() {
41		return /* I18N: Description of the “Stories” module */
42			I18N::translate('Add narrative stories to individuals in the family tree.');
43	}
44
45	/** {@inheritdoc} */
46	public function getConfigLink() {
47		return route('module', ['module' => $this->getName(), 'action' => 'Admin']);
48	}
49
50	/** {@inheritdoc} */
51	public function defaultTabOrder() {
52		return 55;
53	}
54
55	/** {@inheritdoc} */
56	public function getTabContent(Individual $individual) {
57		return view('modules/stories/tab', [
58			'is_admin'   => Auth::isAdmin(),
59			'individual' => $individual,
60			'stories'    => $this->getStoriesForIndividual($individual),
61		]);
62	}
63
64	/** {@inheritdoc} */
65	public function hasTabContent(Individual $individual) {
66		return Auth::isManager($individual->getTree()) || !empty($this->getStoriesForIndividual($individual));
67	}
68
69	/** {@inheritdoc} */
70	public function isGrayedOut(Individual $individual) {
71		return !empty($this->getStoriesForIndividual($individual));
72	}
73
74	/** {@inheritdoc} */
75	public function canLoadAjax() {
76		return false;
77	}
78
79	/**
80	 * @param Individual $individual
81	 *
82	 * @return stdClass[]
83	 */
84	private function getStoriesForIndividual(Individual $individual): array {
85		$block_ids =
86			Database::prepare(
87				"SELECT SQL_CACHE block_id" .
88				" FROM `##block`" .
89				" WHERE module_name = :module_name" .
90				" AND xref          = :xref" .
91				" AND gedcom_id     = :tree_id"
92			)->execute([
93				'module_name' => $this->getName(),
94				'xref'        => $individual->getXref(),
95				'tree_id'     => $individual->getTree()->getTreeId(),
96			])->fetchOneColumn();
97
98		$stories = [];
99		foreach ($block_ids as $block_id) {
100			// Only show this block for certain languages
101			$languages = $this->getBlockSetting($block_id, 'languages', '');
102			if ($languages === '' || in_array(WT_LOCALE, explode(',', $languages))) {
103				$stories[] = (object) [
104					'block_id'   => $block_id,
105					'title'      => $this->getBlockSetting($block_id, 'title'),
106					'story_body' => $this->getBlockSetting($block_id, 'story_body'),
107				];
108			}
109		}
110
111		return $stories;
112	}
113
114	/**
115	 * The user can re-order menus. Until they do, they are shown in this order.
116	 *
117	 * @return int
118	 */
119	public function defaultMenuOrder() {
120		return 30;
121	}
122
123	/**
124	 * What is the default access level for this module?
125	 *
126	 * Some modules are aimed at admins or managers, and are not generally shown to users.
127	 *
128	 * @return int
129	 */
130	public function defaultAccessLevel() {
131		return Auth::PRIV_HIDE;
132	}
133
134	/**
135	 * A menu, to be added to the main application menu.
136	 *
137	 * @return Menu|null
138	 */
139	public function getMenu() {
140		$menu = new Menu($this->getTitle(), e(route('module', ['module' => $this->getName(), 'action' => 'ShowList'])), 'menu-story');
141
142		return $menu;
143	}
144
145	/**
146	 * @param Request $request
147	 *
148	 * @return Response
149	 */
150	public function getAdminAction(Request $request): Response {
151		/** @var Tree $tree */
152		$tree = $request->attributes->get('tree');
153
154		$this->layout = 'layouts/administration';
155
156		$stories = Database::prepare(
157			"SELECT block_id, xref, gedcom_id" .
158			" FROM `##block` b" .
159			" WHERE module_name = :module_name" .
160			" AND gedcom_id = :tree_id" .
161			" ORDER BY gedcom_id, xref"
162		)->execute([
163			'tree_id'     => $tree->getTreeId(),
164			'module_name' => $this->getName(),
165		])->fetchAll();
166
167		foreach ($stories as $story) {
168			$story->individual = Individual::getInstance($story->xref, $tree);
169			$story->title      = $this->getBlockSetting($story->block_id, 'title');
170			$story->languages  = $this->getBlockSetting($story->block_id, 'languages');
171		}
172
173		return $this->viewResponse('modules/stories/config', [
174			'stories'    => $stories,
175			'title'      => $this->getTitle() . ' — ' . $tree->getTitle(),
176			'tree'       => $tree,
177			'tree_names' => Tree::getNameList(),
178		]);
179	}
180
181	/**
182	 * @param Request $request
183	 *
184	 * @return Response
185	 */
186	public function getAdminEditAction(Request $request): Response {
187		/** @var Tree $tree */
188		$tree = $request->attributes->get('tree');
189
190		$this->layout = 'layouts/administration';
191
192		$block_id = (int) $request->get('block_id');
193
194		if ($block_id === 0) {
195			// Creating a new story
196			$individual  = Individual::getInstance($request->get('xref', ''), $tree);
197			$story_title = '';
198			$story_body  = '';
199			$languages   = [];
200
201			$title = I18N::translate('Add a story') . ' — ' . e($tree->getTitle());
202		} else {
203			// Editing an existing story
204			$xref = Database::prepare(
205				"SELECT xref FROM `##block` WHERE block_id = :block_id"
206			)->execute([
207				'block_id' => $block_id,
208			])->fetchOne();
209
210			$individual  = Individual::getInstance($xref, $tree);
211			$story_title = $this->getBlockSetting($block_id, 'title', '');
212			$story_body  = $this->getBlockSetting($block_id, 'story_body', '');
213			$languages   = explode(',', $this->getBlockSetting($block_id, 'languages'));
214
215			$title = I18N::translate('Edit the story') . ' — ' . e($tree->getTitle());
216		}
217
218		return $this->viewResponse('modules/stories/edit', [
219			'block_id'    => $block_id,
220			'languages'   => $languages,
221			'story_body'  => $story_body,
222			'story_title' => $story_title,
223			'title'       => $title,
224			'tree'        => $tree,
225			'individual'  => $individual,
226		]);
227	}
228
229	/**
230	 * @param Request $request
231	 *
232	 * @return RedirectResponse
233	 */
234	public function postAdminEditAction(Request $request): RedirectResponse {
235		/** @var Tree $tree */
236		$tree = $request->attributes->get('tree');
237
238		$block_id    = (int) $request->get('block_id');
239		$xref        = $request->get('xref', '');
240		$story_body  = $request->get('story_body', '');
241		$story_title = $request->get('story_title', '');
242		$languages   = $request->get('languages', []);
243
244		if ($block_id !== 0) {
245			Database::prepare(
246				"UPDATE `##block` SET gedcom_id = :tree_id, xref = :xref WHERE block_id = :block_id"
247			)->execute([
248				'tree_id'  => $tree->getTreeId(),
249				'xref'     => $xref,
250				'block_id' => $block_id,
251			]);
252		} else {
253			Database::prepare(
254				"INSERT INTO `##block` (gedcom_id, xref, module_name, block_order) VALUES (:tree_id, :xref, 'stories', 0)"
255			)->execute([
256				'tree_id' => $tree->getTreeId(),
257				'xref'    => $xref,
258			]);
259
260			$block_id = Database::getInstance()->lastInsertId();
261		}
262
263		$this->setBlockSetting($block_id, 'story_body', $story_body);
264		$this->setBlockSetting($block_id, 'title', $story_title);
265		$this->setBlockSetting($block_id, 'languages', implode(',', $languages));
266
267		$url = route('module', ['module' => 'stories', 'action' => 'Admin', 'ged' => $tree->getName()]);
268
269		return new RedirectResponse($url);
270	}
271
272	/**
273	 * @param Request $request
274	 *
275	 * @return Response
276	 */
277	public function postAdminDeleteAction(Request $request): Response {
278		/** @var Tree $tree */
279		$tree = $request->attributes->get('tree');
280
281		$block_id = (int) $request->get('block_id');
282
283		Database::prepare(
284			"DELETE FROM `##block_setting` WHERE block_id = :block_id"
285		)->execute([
286			'block_id' => $block_id,
287		]);
288
289		Database::prepare(
290			"DELETE FROM `##block` WHERE block_id = :block_id"
291		)->execute([
292			'block_id' => $block_id,
293		]);
294
295		$url = route('module', ['module' => 'stories', 'action' => 'Admin', 'ged' => $tree->getName()]);
296
297		return new RedirectResponse($url);
298	}
299
300	/**
301	 * @param Request $request
302	 *
303	 * @return Response
304	 */
305	public function getShowListAction(Request $request): Response {
306		/** @var Tree $tree
307		 */
308		$tree = $request->attributes->get('tree');
309
310		$stories = Database::prepare(
311			"SELECT block_id, xref" .
312			" FROM `##block` b" .
313			" WHERE module_name = :module_name" .
314			" AND gedcom_id = :tree_id" .
315			" ORDER BY xref"
316		)->execute([
317			'module_name' => $this->getName(),
318			'tree_id'     => $tree->getTreeId(),
319		])->fetchAll();
320
321		foreach ($stories as $story) {
322			$story->individual = Individual::getInstance($story->xref, $tree);
323			$story->title      = $this->getBlockSetting($story->block_id, 'title');
324			$story->languages  = $this->getBlockSetting($story->block_id, 'languages');
325		}
326
327		// Filter non-existant and private individuals.
328		$stories = array_filter($stories, function (stdClass $story) {
329			return $story->individual !== null && $story->individual->canShow();
330		});
331
332		// Filter foreign languages.
333		$stories = array_filter($stories, function (stdClass $story) {
334			return $story->language === '' || in_array(WT_LOCALE, explode(',', $story->language));
335		});
336
337		return $this->viewResponse('modules/stories/list', [
338			'stories' => $stories,
339			'title'   => $this->getTitle(),
340		]);
341	}
342}
343