xref: /webtrees/app/Module/StoriesModule.php (revision 86fcb7c44a85a153a1855e6d12fe22dbe08702f2)
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	/**
46	 * The URL to a page where the user can modify the configuration of this module.
47	 *
48	 * @return string
49	 */
50	public function getConfigLink() {
51		return route('module', ['module' => $this->getName(), 'action' => 'Admin']);
52	}
53
54	/** {@inheritdoc} */
55	public function defaultTabOrder() {
56		return 55;
57	}
58
59	/** {@inheritdoc} */
60	public function getTabContent(Individual $individual) {
61		return view('modules/stories/tab', [
62			'is_admin'   => Auth::isAdmin(),
63			'individual' => $individual,
64			'stories'    => $this->getStoriesForIndividual($individual),
65		]);
66	}
67
68	/** {@inheritdoc} */
69	public function hasTabContent(Individual $individual) {
70		return Auth::isManager($individual->getTree()) || !empty($this->getStoriesForIndividual($individual));
71	}
72
73	/** {@inheritdoc} */
74	public function isGrayedOut(Individual $individual) {
75		return !empty($this->getStoriesForIndividual($individual));
76	}
77
78	/** {@inheritdoc} */
79	public function canLoadAjax() {
80		return false;
81	}
82
83	/**
84	 * @param Individual $individual
85	 *
86	 * @return stdClass[]
87	 */
88	private function getStoriesForIndividual(Individual $individual): array {
89		$block_ids =
90			Database::prepare(
91				"SELECT SQL_CACHE block_id" .
92				" FROM `##block`" .
93				" WHERE module_name = :module_name" .
94				" AND xref          = :xref" .
95				" AND gedcom_id     = :tree_id"
96			)->execute([
97				'module_name' => $this->getName(),
98				'xref'        => $individual->getXref(),
99				'tree_id'     => $individual->getTree()->getTreeId(),
100			])->fetchOneColumn();
101
102		$stories = [];
103		foreach ($block_ids as $block_id) {
104			// Only show this block for certain languages
105			$languages = $this->getBlockSetting($block_id, 'languages', '');
106			if ($languages === '' || in_array(WT_LOCALE, explode(',', $languages))) {
107				$stories[] = (object) [
108					'block_id'   => $block_id,
109					'title'      => $this->getBlockSetting($block_id, 'title'),
110					'story_body' => $this->getBlockSetting($block_id, 'story_body'),
111				];
112			}
113		}
114
115		return $stories;
116	}
117
118	/**
119	 * The user can re-order menus. Until they do, they are shown in this order.
120	 *
121	 * @return int
122	 */
123	public function defaultMenuOrder() {
124		return 30;
125	}
126
127	/**
128	 * What is the default access level for this module?
129	 *
130	 * Some modules are aimed at admins or managers, and are not generally shown to users.
131	 *
132	 * @return int
133	 */
134	public function defaultAccessLevel() {
135		return Auth::PRIV_HIDE;
136	}
137
138	/**
139	 * A menu, to be added to the main application menu.
140	 *
141	 * @param Tree $tree
142	 *
143	 * @return Menu|null
144	 */
145	public function getMenu(Tree $tree) {
146		$menu = new Menu($this->getTitle(), e(route('module', ['module' => $this->getName(), 'action' => 'ShowList'])), 'menu-story');
147
148		return $menu;
149	}
150
151	/**
152	 * @param Request $request
153	 *
154	 * @return Response
155	 */
156	public function getAdminAction(Request $request): Response {
157		/** @var Tree $tree */
158		$tree = $request->attributes->get('tree');
159
160		$this->layout = 'layouts/administration';
161
162		$stories = Database::prepare(
163			"SELECT block_id, xref, gedcom_id" .
164			" FROM `##block` b" .
165			" WHERE module_name = :module_name" .
166			" AND gedcom_id = :tree_id" .
167			" ORDER BY gedcom_id, xref"
168		)->execute([
169			'tree_id'     => $tree->getTreeId(),
170			'module_name' => $this->getName(),
171		])->fetchAll();
172
173		foreach ($stories as $story) {
174			$story->individual = Individual::getInstance($story->xref, $tree);
175			$story->title      = $this->getBlockSetting($story->block_id, 'title');
176			$story->languages  = $this->getBlockSetting($story->block_id, 'languages');
177		}
178
179		return $this->viewResponse('modules/stories/config', [
180			'stories'    => $stories,
181			'title'      => $this->getTitle() . ' — ' . $tree->getTitle(),
182			'tree'       => $tree,
183			'tree_names' => Tree::getNameList(),
184		]);
185	}
186
187	/**
188	 * @param Request $request
189	 *
190	 * @return Response
191	 */
192	public function getAdminEditAction(Request $request): Response {
193		/** @var Tree $tree */
194		$tree = $request->attributes->get('tree');
195
196		$this->layout = 'layouts/administration';
197
198		$block_id = (int) $request->get('block_id');
199
200		if ($block_id === 0) {
201			// Creating a new story
202			$individual  = Individual::getInstance($request->get('xref', ''), $tree);
203			$story_title = '';
204			$story_body  = '';
205			$languages   = [];
206
207			$title = I18N::translate('Add a story') . ' — ' . e($tree->getTitle());
208		} else {
209			// Editing an existing story
210			$xref = Database::prepare(
211				"SELECT xref FROM `##block` WHERE block_id = :block_id"
212			)->execute([
213				'block_id' => $block_id,
214			])->fetchOne();
215
216			$individual  = Individual::getInstance($xref, $tree);
217			$story_title = $this->getBlockSetting($block_id, 'title', '');
218			$story_body  = $this->getBlockSetting($block_id, 'story_body', '');
219			$languages   = explode(',', $this->getBlockSetting($block_id, 'languages'));
220
221			$title = I18N::translate('Edit the story') . ' — ' . e($tree->getTitle());
222		}
223
224		return $this->viewResponse('modules/stories/edit', [
225			'block_id'    => $block_id,
226			'languages'   => $languages,
227			'story_body'  => $story_body,
228			'story_title' => $story_title,
229			'title'       => $title,
230			'tree'        => $tree,
231			'individual'  => $individual,
232		]);
233	}
234
235	/**
236	 * @param Request $request
237	 *
238	 * @return RedirectResponse
239	 */
240	public function postAdminEditAction(Request $request): RedirectResponse {
241		/** @var Tree $tree */
242		$tree = $request->attributes->get('tree');
243
244		$block_id    = (int) $request->get('block_id');
245		$xref        = $request->get('xref', '');
246		$story_body  = $request->get('story_body', '');
247		$story_title = $request->get('story_title', '');
248		$languages   = $request->get('languages', []);
249
250		if ($block_id !== 0) {
251			Database::prepare(
252				"UPDATE `##block` SET gedcom_id = :tree_id, xref = :xref WHERE block_id = :block_id"
253			)->execute([
254				'tree_id'  => $tree->getTreeId(),
255				'xref'     => $xref,
256				'block_id' => $block_id,
257			]);
258		} else {
259			Database::prepare(
260				"INSERT INTO `##block` (gedcom_id, xref, module_name, block_order) VALUES (:tree_id, :xref, 'stories', 0)"
261			)->execute([
262				'tree_id' => $tree->getTreeId(),
263				'xref'    => $xref,
264			]);
265
266			$block_id = Database::getInstance()->lastInsertId();
267		}
268
269		$this->setBlockSetting($block_id, 'story_body', $story_body);
270		$this->setBlockSetting($block_id, 'title', $story_title);
271		$this->setBlockSetting($block_id, 'languages', implode(',', $languages));
272
273		$url = route('module', ['module' => 'stories', 'action' => 'Admin', 'ged' => $tree->getName()]);
274
275		return new RedirectResponse($url);
276	}
277
278	/**
279	 * @param Request $request
280	 *
281	 * @return Response
282	 */
283	public function postAdminDeleteAction(Request $request): Response {
284		/** @var Tree $tree */
285		$tree = $request->attributes->get('tree');
286
287		$block_id = (int) $request->get('block_id');
288
289		Database::prepare(
290			"DELETE FROM `##block_setting` WHERE block_id = :block_id"
291		)->execute([
292			'block_id' => $block_id,
293		]);
294
295		Database::prepare(
296			"DELETE FROM `##block` WHERE block_id = :block_id"
297		)->execute([
298			'block_id' => $block_id,
299		]);
300
301		$url = route('module', ['module' => 'stories', 'action' => 'Admin', 'ged' => $tree->getName()]);
302
303		return new RedirectResponse($url);
304	}
305
306	/**
307	 * @param Request $request
308	 *
309	 * @return Response
310	 */
311	public function getShowListAction(Request $request): Response {
312		/** @var Tree $tree
313		 */
314		$tree = $request->attributes->get('tree');
315
316		$stories = Database::prepare(
317			"SELECT block_id, xref" .
318			" FROM `##block` b" .
319			" WHERE module_name = :module_name" .
320			" AND gedcom_id = :tree_id" .
321			" ORDER BY xref"
322		)->execute([
323			'module_name' => $this->getName(),
324			'tree_id'     => $tree->getTreeId(),
325		])->fetchAll();
326
327		foreach ($stories as $story) {
328			$story->individual = Individual::getInstance($story->xref, $tree);
329			$story->title      = $this->getBlockSetting($story->block_id, 'title');
330			$story->languages  = $this->getBlockSetting($story->block_id, 'languages');
331		}
332
333		// Filter non-existant and private individuals.
334		$stories = array_filter($stories, function (stdClass $story) {
335			return $story->individual !== null && $story->individual->canShow();
336		});
337
338		// Filter foreign languages.
339		$stories = array_filter($stories, function (stdClass $story) {
340			return $story->languages === '' || in_array(WT_LOCALE, explode(',', $story->languages));
341		});
342
343		return $this->viewResponse('modules/stories/list', [
344			'stories' => $stories,
345			'title'   => $this->getTitle(),
346		]);
347	}
348}
349