Browse Source

Miscellaneous page improvements, including additional data and sorting

Timothy J. Warren 4 weeks ago
parent
commit
16f62ceb8d

+ 0
- 21
.gitlab-ci.yml View File

@@ -1,21 +0,0 @@
1
-test:7.1:
2
-    stage: test
3
-    before_script:
4
-      - sh build/docker_install.sh > /dev/null
5
-      - apk add --no-cache php7-phpdbg
6
-      - curl -sS https://getcomposer.org/installer | php
7
-      - php composer.phar install --ignore-platform-reqs
8
-    image: php:7.1-alpine
9
-    script:
10
-      - phpdbg -qrr -- ./vendor/bin/phpunit --coverage-text --colors=never
11
-
12
-test:7.2:
13
-    stage: test
14
-    before_script:
15
-      - sh build/docker_install.sh > /dev/null
16
-      - apk add --no-cache php7-phpdbg
17
-      - curl -sS https://getcomposer.org/installer | php
18
-      - php composer.phar install --ignore-platform-reqs
19
-    image: php:7.2-alpine
20
-    script:
21
-      - phpdbg -qrr -- ./vendor/bin/phpunit --coverage-text --colors=never

+ 2
- 0
CHANGELOG.md View File

@@ -7,6 +7,8 @@
7 7
 * Updated console command to sync Kitsu and Anilist data (Kitsu can sync MAL, and MAL's API broke, so MAL sync was removed)
8 8
 * Added page to update settings without having to edit config files
9 9
 * Defaulted to secure (HTTPS) urls
10
+* Updated Character pages to show voice actors 
11
+* Added People pages, showing which works they contributed to, and in what role
10 12
 
11 13
 ## Version 4
12 14
 * Updated to use Kitsu API after discontinuation of Hummingbird

+ 8
- 0
app/appConf/routes.php View File

@@ -174,6 +174,14 @@ return [
174 174
 			'slug' => '[a-z0-9\-]+'
175 175
 		]
176 176
 	],
177
+	'person' => [
178
+		'path' => '/people/{id}',
179
+		'action' => 'index',
180
+		'params' => [],
181
+		'tokens' => [
182
+			'id' => '[a-z0-9\-]+'
183
+		]
184
+	],
177 185
 	'user_info' => [
178 186
 		'path' => '/me',
179 187
 		'action' => 'me',

+ 12
- 4
app/views/character.php View File

@@ -1,4 +1,7 @@
1
-<?php use Aviat\AnimeClient\API\Kitsu; ?>
1
+<?php
2
+use function Aviat\AnimeClient\getLocalImg;
3
+use Aviat\AnimeClient\API\Kitsu;
4
+?>
2 5
 <main class="details fixed">
3 6
 	<section class="flex flex-no-wrap">
4 7
 		<div>
@@ -89,14 +92,19 @@
89 92
 						<th>Cast Member</th>
90 93
 						<th>Series</th>
91 94
 					</tr>
92
-					<?php foreach($casting as $c):?>
95
+					<?php foreach($casting as $cid => $c):?>
93 96
 					<tr>
94 97
 						<td style="width:229px">
95 98
 							<article class="character">
96
-								<img src="<?= $c['person']['image'] ?>" alt="" />
99
+								<?php
100
+									$link = $url->generate('person', ['id' => $c['person']['id']]);
101
+								?>
102
+								<a href="<?= $link ?>">
103
+								<img src="<?= $urlGenerator->assetUrl(getLocalImg($c['person']['image'])) ?>" alt="" />
97 104
 								<div class="name">
98 105
 									<?= $c['person']['name'] ?>
99 106
 								</div>
107
+								</a>
100 108
 							</article>
101 109
 						</td>
102 110
 						<td>
@@ -108,7 +116,7 @@
108 116
 									$titles = Kitsu::filterTitles($series['attributes']);
109 117
 								?>
110 118
 								<a href="<?= $link ?>">
111
-									<img src="<?= $series['attributes']['posterImage']['small'] ?>" width="220" alt="" />
119
+									<img src="<?= $urlGenerator->assetUrl(getLocalImg($series['attributes']['posterImage']['small'])) ?>" width="220" alt="" />
112 120
 								</a>
113 121
 								<div class="name">
114 122
 									<a href="<?= $link ?>">

+ 74
- 0
app/views/person.php View File

@@ -0,0 +1,74 @@
1
+<?php
2
+use function Aviat\AnimeClient\getLocalImg;
3
+use Aviat\AnimeClient\API\Kitsu;
4
+?>
5
+<main class="details fixed">
6
+	<section class="flex flex-no-wrap">
7
+		<div>
8
+			<picture class="cover">
9
+				<source
10
+					srcset="<?= $urlGenerator->assetUrl("images/people/{$data['id']}-original.webp") ?>"
11
+					type="image/webp"
12
+				>
13
+				<source
14
+					srcset="<?= $urlGenerator->assetUrl("images/people/{$data['id']}-original.jpg") ?>"
15
+					type="image/jpeg"
16
+				>
17
+				<img src="<?= $urlGenerator->assetUrl("images/people/{$data['id']}-original.jpg") ?>" alt="" />
18
+			</picture>
19
+		</div>
20
+		<div>
21
+			<h2><?= $data['attributes']['name'] ?></h2>
22
+		</div>
23
+	</section>
24
+
25
+	<section>
26
+		<?php if ($castCount > 0): ?>
27
+			<h3>Castings</h3>
28
+			<?php foreach ($castings as $role => $entries): ?>
29
+				<h4><?= $role ?></h4>
30
+				<?php foreach ($entries as $type => $casting): ?>
31
+					<?php if ( ! empty($entries['manga'])): ?>
32
+					<h5><?= ucfirst($type) ?></h5>
33
+					<?php endif ?>
34
+					<section class="align_left media-wrap">
35
+					<?php foreach ($casting as $sid => $series): ?>
36
+						<article class="media">
37
+							<?php
38
+							$link = $url->generate('anime.details', ['id' => $series['attributes']['slug']]);
39
+							$titles = Kitsu::filterTitles($series['attributes']);
40
+							?>
41
+							<a href="<?= $link ?>">
42
+								<picture>
43
+									<source
44
+										srcset="<?= $urlGenerator->assetUrl("images/{$type}/{$sid}.webp") ?>"
45
+										type="image/webp"
46
+									/>
47
+									<source
48
+										srcset="<?= $urlGenerator->assetUrl("images/{$type}/{$sid}.jpg") ?>"
49
+										type="image/jpeg"
50
+									/>
51
+									<img
52
+										src="<?= $urlGenerator->assetUrl("images/{$type}/{$sid}.jpg") ?>"
53
+										width="220" alt=""
54
+									/>
55
+								</picture>
56
+							</a>
57
+							<div class="name">
58
+								<a href="<?= $link ?>">
59
+									<?= array_shift($titles) ?>
60
+									<?php foreach ($titles as $title): ?>
61
+										<br />
62
+										<small><?= $title ?></small>
63
+									<?php endforeach ?>
64
+								</a>
65
+							</div>
66
+						</article>
67
+					<?php endforeach; ?>
68
+					</section>
69
+					<br />
70
+				<?php endforeach ?>
71
+			<?php endforeach ?>
72
+		<?php endif ?>
73
+	</section>
74
+</main>

+ 5
- 0
src/API/JsonAPI.php View File

@@ -61,6 +61,11 @@ final class JsonAPI {
61 61
 		// Inline organized data
62 62
 		foreach($data['data'] as $i => &$item)
63 63
 		{
64
+			if ( ! is_array($item))
65
+			{
66
+				continue;
67
+			}
68
+
64 69
 			if (array_key_exists('relationships', $item))
65 70
 			{
66 71
 				foreach($item['relationships'] as $relType => $props)

+ 1
- 1
src/API/Kitsu/ListItem.php View File

@@ -103,7 +103,7 @@ final class ListItem implements ListItemInterface {
103 103
 
104 104
 		$request = $this->requestBuilder->newRequest('GET', "library-entries/{$id}")
105 105
 			->setQuery([
106
-				'include' => 'media,media.genres,media.mappings'
106
+				'include' => 'media,media.categories,media.mappings'
107 107
 			]);
108 108
 
109 109
 		if ($authHeader !== FALSE)

+ 18
- 3
src/API/Kitsu/Model.php View File

@@ -221,6 +221,21 @@ final class Model {
221 221
 		return $data;
222 222
 	}
223 223
 
224
+	/**
225
+	 * Get information about a person
226
+	 *
227
+	 * @param string $id
228
+	 * @return array
229
+	 */
230
+	public function getPerson(string $id): array
231
+	{
232
+		return $this->getRequest("people/{$id}", [
233
+			'query' => [
234
+				'include' => 'castings,castings.media,staff,staff.media,voices'
235
+			],
236
+		]);
237
+	}
238
+
224 239
 	/**
225 240
 	 * Get profile information for the configured user
226 241
 	 *
@@ -585,7 +600,7 @@ final class Model {
585 600
 		}
586 601
 
587 602
 		$transformed = $this->mangaTransformer->transform($baseData);
588
-		$transformed['included'] = $baseData['included'];
603
+		$transformed['included'] = JsonAPI::organizeIncluded($baseData['included']);
589 604
 		return $transformed;
590 605
 	}
591 606
 
@@ -936,8 +951,8 @@ final class Model {
936 951
 					'characters' => 'slug,name,image'
937 952
 				],
938 953
 				'include' => ($type === 'anime')
939
-					? 'categories,mappings,streamingLinks,animeCharacters.character'
940
-					: 'categories,mappings,mangaCharacters.character,castings.character',
954
+					? 'staff,staff.person,categories,mappings,streamingLinks,animeCharacters.character'
955
+					: 'staff,staff.person,categories,mappings,mangaCharacters.character,castings.character',
941 956
 			]
942 957
 		];
943 958
 

+ 66
- 24
src/AnimeClient.php View File

@@ -19,6 +19,10 @@ namespace Aviat\AnimeClient;
19 19
 use Aviat\Ion\ConfigInterface;
20 20
 use Yosymfony\Toml\{Toml, TomlBuilder};
21 21
 
22
+// ----------------------------------------------------------------------------
23
+//! TOML Functions
24
+// ----------------------------------------------------------------------------
25
+
22 26
 /**
23 27
  * Load configuration options from .toml files
24 28
  *
@@ -67,30 +71,6 @@ function loadTomlFile(string $filename): array
67 71
 	return Toml::parseFile($filename);
68 72
 }
69 73
 
70
-/**
71
- * Is the array sequential, not associative?
72
- *
73
- * @param mixed $array
74
- * @return bool
75
- */
76
-function isSequentialArray($array): bool
77
-{
78
-	if ( ! is_array($array))
79
-	{
80
-		return FALSE;
81
-	}
82
-
83
-	$i = 0;
84
-	foreach ($array as $k => $v)
85
-	{
86
-		if ($k !== $i++)
87
-		{
88
-			return FALSE;
89
-		}
90
-	}
91
-	return TRUE;
92
-}
93
-
94 74
 function _iterateToml(TomlBuilder $builder, $data, $parentKey = NULL): void
95 75
 {
96 76
 	foreach ($data as $key => $value)
@@ -147,6 +127,34 @@ function tomlToArray(string $toml): array
147 127
 	return Toml::parse($toml);
148 128
 }
149 129
 
130
+// ----------------------------------------------------------------------------
131
+//! Misc Functions
132
+// ----------------------------------------------------------------------------
133
+
134
+/**
135
+ * Is the array sequential, not associative?
136
+ *
137
+ * @param mixed $array
138
+ * @return bool
139
+ */
140
+function isSequentialArray($array): bool
141
+{
142
+	if ( ! is_array($array))
143
+	{
144
+		return FALSE;
145
+	}
146
+
147
+	$i = 0;
148
+	foreach ($array as $k => $v)
149
+	{
150
+		if ($k !== $i++)
151
+		{
152
+			return FALSE;
153
+		}
154
+	}
155
+	return TRUE;
156
+}
157
+
150 158
 /**
151 159
  * Check that folder permissions are correct for proper operation
152 160
  *
@@ -186,4 +194,38 @@ function checkFolderPermissions(ConfigInterface $config): array
186 194
 	}
187 195
 
188 196
 	return $errors;
197
+}
198
+
199
+/**
200
+ * Generate the path for the cached image from the original iamge
201
+ *
202
+ * @param string $kitsuUrl
203
+ * @return string
204
+ */
205
+function getLocalImg ($kitsuUrl): string
206
+{
207
+	if ( ! is_string($kitsuUrl))
208
+	{
209
+		return '/404';
210
+	}
211
+
212
+	$parts = parse_url($kitsuUrl);
213
+
214
+	if ($parts === FALSE)
215
+	{
216
+		return '/404';
217
+	}
218
+
219
+	$file = basename($parts['path']);
220
+	$fileParts = explode('.', $file);
221
+	$ext = array_pop($fileParts);
222
+	$segments = explode('/', trim($parts['path'], '/'));
223
+
224
+	// dump($segments);
225
+
226
+	$type = $segments[0] === 'users' ? $segments[1] : $segments[0];
227
+
228
+	$id = $segments[count($segments) - 2];
229
+
230
+	return implode('/', ['images', $type, "{$id}.{$ext}"]);
189 231
 }

+ 4
- 14
src/Command/UpdateThumbnails.php View File

@@ -16,20 +16,8 @@
16 16
 
17 17
 namespace Aviat\AnimeClient\Command;
18 18
 
19
-use Aviat\AnimeClient\API\{
20
-	FailedResponseException,
21
-	JsonAPI,
22
-	ParallelAPIRequest
23
-};
24
-use Aviat\AnimeClient\API\Anilist\Transformer\{
25
-	AnimeListTransformer as AALT,
26
-	MangaListTransformer as AMLT
27
-};
28
-use Aviat\AnimeClient\API\Mapping\{AnimeWatchingStatus, MangaReadingStatus};
19
+use Aviat\AnimeClient\API\JsonAPI;
29 20
 use Aviat\AnimeClient\Controller\Index;
30
-use Aviat\AnimeClient\Types\FormItem;
31
-use Aviat\Ion\Json;
32
-use DateTime;
33 21
 
34 22
 /**
35 23
  * Clears out image cache directories, then re-creates the image cache
@@ -69,9 +57,11 @@ final class UpdateThumbnails extends BaseCommand {
69 57
 			{
70 58
 				$this->controller->images($type, "{$id}.jpg", FALSE);
71 59
 			}
60
+
61
+			$this->echoBox("Finished regenerating {$type} thumbnails");
72 62
 		}
73 63
 
74
-		$this->echoBox('Finished regenerating thumbnails');
64
+		$this->echoBox('Finished regenerating all thumbnails');
75 65
 	}
76 66
 
77 67
 	public function clearThumbs()

+ 41
- 6
src/Controller/Anime.php View File

@@ -275,10 +275,11 @@ final class Anime extends BaseController {
275 275
 	 */
276 276
 	public function details(string $animeId): void
277 277
 	{
278
-		$show_data = $this->model->getAnime($animeId);
278
+		$data = $this->model->getAnime($animeId);
279 279
 		$characters = [];
280
+		$staff = [];
280 281
 
281
-		if ($show_data->title === '')
282
+		if ($data->title === '')
282 283
 		{
283 284
 			$this->notFound(
284 285
 				$this->config->get('whose_list') .
@@ -290,22 +291,56 @@ final class Anime extends BaseController {
290 291
 			return;
291 292
 		}
292 293
 
293
-		if (array_key_exists('characters', $show_data['included']))
294
+		if (array_key_exists('characters', $data['included']))
294 295
 		{
295
-			foreach($show_data['included']['characters'] as $id => $character)
296
+
297
+
298
+			foreach($data['included']['characters'] as $id => $character)
296 299
 			{
297 300
 				$characters[$id] = $character['attributes'];
298 301
 			}
299 302
 		}
300 303
 
304
+		if (array_key_exists('mediaStaff', $data['included']))
305
+		{
306
+			foreach ($data['included']['mediaStaff'] as $id => $person)
307
+			{
308
+				$personDetails = [];
309
+				foreach ($person['relationships']['person']['people'] as $p)
310
+				{
311
+					$personDetails = $p['attributes'];
312
+				}
313
+
314
+				$role = $person['attributes']['role'];
315
+
316
+				if ( ! array_key_exists($role, $staff))
317
+				{
318
+					$staff[$role] = [];
319
+				}
320
+
321
+				$staff[$role][$id] = [
322
+					'name' => $personDetails['name'] ?? '??',
323
+					'image' => $personDetails['image'],
324
+				];
325
+			}
326
+		}
327
+
328
+		uasort($characters, function ($a, $b) {
329
+			return $a['name'] <=> $b['name'];
330
+		});
331
+
332
+		// dump($characters);
333
+		// dump($staff);
334
+
301 335
 		$this->outputHTML('anime/details', [
302 336
 			'title' => $this->formatTitle(
303 337
 				$this->config->get('whose_list') . "'s Anime List",
304 338
 				'Anime',
305
-				$show_data->title
339
+				$data->title
306 340
 			),
307 341
 			'characters' => $characters,
308
-			'show_data' => $show_data,
342
+			'show_data' => $data,
343
+			'staff' => $staff,
309 344
 		]);
310 345
 	}
311 346
 

+ 49
- 11
src/Controller/Character.php View File

@@ -16,6 +16,8 @@
16 16
 
17 17
 namespace Aviat\AnimeClient\Controller;
18 18
 
19
+use function Aviat\AnimeClient\getLocalImg;
20
+
19 21
 use Aviat\AnimeClient\Controller as BaseController;
20 22
 use Aviat\AnimeClient\API\JsonAPI;
21 23
 use Aviat\Ion\ArrayWrapper;
@@ -23,7 +25,7 @@ use Aviat\Ion\ArrayWrapper;
23 25
 /**
24 26
  * Controller for character description pages
25 27
  */
26
-final class Character extends BaseController {
28
+class Character extends BaseController {
27 29
 
28 30
 	use ArrayWrapper;
29 31
 
@@ -57,6 +59,23 @@ final class Character extends BaseController {
57 59
 
58 60
 		$data = JsonAPI::organizeData($rawData);
59 61
 
62
+		if (array_key_exists('included', $data))
63
+		{
64
+			if (array_key_exists('anime', $data['included']))
65
+			{
66
+				uasort($data['included']['anime'], function ($a, $b) {
67
+					return $a['attributes']['canonicalTitle'] <=> $b['attributes']['canonicalTitle'];
68
+				});
69
+			}
70
+
71
+			if (array_key_exists('manga', $data['included']))
72
+			{
73
+				uasort($data['included']['manga'], function ($a, $b) {
74
+					return $a['attributes']['canonicalTitle'] <=> $b['attributes']['canonicalTitle'];
75
+				});
76
+			}
77
+		}
78
+
60 79
 		$viewData = [
61 80
 			'title' => $this->formatTitle(
62 81
 				'Characters',
@@ -67,10 +86,13 @@ final class Character extends BaseController {
67 86
 			'castings' => []
68 87
 		];
69 88
 
70
-		if (array_key_exists('included', $data) && array_key_exists('castings', $data['included']))
89
+		if (array_key_exists('included', $data))
71 90
 		{
72
-			$viewData['castings'] = $this->organizeCast($data['included']['castings']);
73
-			$viewData['castCount'] = $this->getCastCount($viewData['castings']);
91
+			if (array_key_exists('castings', $data['included']))
92
+			{
93
+				$viewData['castings'] = $this->organizeCast($data['included']['castings']);
94
+				$viewData['castCount'] = $this->getCastCount($viewData['castings']);
95
+			}
74 96
 		}
75 97
 
76 98
 		$this->outputHTML('character', $viewData);
@@ -121,25 +143,26 @@ final class Character extends BaseController {
121 143
 		return $output;
122 144
 	}
123 145
 
124
-	private function getCastCount(array $cast): int
146
+	protected function getCastCount(array $cast): int
125 147
 	{
126 148
 		$count = 0;
127 149
 
128 150
 		foreach($cast as $role)
129 151
 		{
130
-			if (
152
+			$count++;
153
+			/* if (
131 154
 				array_key_exists('attributes', $role) &&
132 155
 				array_key_exists('role', $role['attributes']) &&
133 156
 				$role['attributes']['role'] !== NULL
134 157
 			) {
135 158
 				$count++;
136
-			}
159
+			} */
137 160
 		}
138 161
 
139 162
 		return $count;
140 163
 	}
141 164
 
142
-	private function organizeCast(array $cast): array
165
+	protected function organizeCast(array $cast): array
143 166
 	{
144 167
 		$cast = $this->dedupeCast($cast);
145 168
 		$output = [];
@@ -157,8 +180,19 @@ final class Character extends BaseController {
157 180
 
158 181
 			if ($isVA)
159 182
 			{
160
-				$person = current($role['relationships']['person']['people'])['attributes'];
161
-				$name = $person['name'];
183
+				foreach($role['relationships']['person']['people'] as $pid => $peoples)
184
+				{
185
+					$p = $peoples;
186
+				}
187
+
188
+				$person = $p['attributes'];
189
+				$person['id'] = $pid;
190
+				$person['image'] = $person['image']['original'];
191
+
192
+				uasort($role['relationships']['media']['anime'], function ($a, $b) {
193
+					return $a['attributes']['canonicalTitle'] <=> $b['attributes']['canonicalTitle'];
194
+				});
195
+
162 196
 				$item = [
163 197
 					'person' => $person,
164 198
 					'series' => $role['relationships']['media']['anime']
@@ -168,7 +202,11 @@ final class Character extends BaseController {
168 202
 			}
169 203
 			else
170 204
 			{
171
-				$output[$roleName][] = $role['relationships']['person']['people'];
205
+				foreach($role['relationships']['person']['people'] as $pid => $person)
206
+				{
207
+					$person['id'] = $pid;
208
+					$output[$roleName][$pid] = $person;
209
+				}
172 210
 			}
173 211
 		}
174 212
 

+ 32
- 23
src/Controller/Index.php View File

@@ -268,36 +268,45 @@ final class Index extends BaseController {
268 268
 		$kitsuUrl = 'https://media.kitsu.io/';
269 269
 		$fileName = str_replace('-original', '', $file);
270 270
 		[$id, $ext] = explode('.', basename($fileName));
271
-		switch ($type)
271
+
272
+		$typeMap = [
273
+			'anime' => [
274
+				'kitsuUrl' => "anime/poster_images/{$id}/medium.{$ext}",
275
+				'width' => 220,
276
+			],
277
+			'avatars' => [
278
+				'kitsuUrl' => "users/avatars/{$id}/original.{$ext}",
279
+				'width' => null,
280
+			],
281
+			'characters' => [
282
+				'kitsuUrl' => "characters/images/{$id}/original.{$ext}",
283
+				'width' => 225,
284
+			],
285
+			'manga' => [
286
+				'kitsuUrl' => "manga/poster_images/{$id}/medium.{$ext}",
287
+				'width' => 220,
288
+			],
289
+			'people' => [
290
+				'kitsuUrl' => "people/images/{$id}/original.{$ext}",
291
+				'width' => null,
292
+			],
293
+		];
294
+
295
+		if ( ! array_key_exists($type, $typeMap))
272 296
 		{
273
-			case 'anime':
274
-				$kitsuUrl .= "anime/poster_images/{$id}/small.jpg";
275
-				$width = 220;
276
-			break;
277
-
278
-			case 'avatars':
279
-				$kitsuUrl .= "users/avatars/{$id}/original.jpg";
280
-			break;
281
-
282
-			case 'manga':
283
-				$kitsuUrl .= "manga/poster_images/{$id}/small.jpg";
284
-				$width = 220;
285
-			break;
286
-
287
-			case 'characters':
288
-				$kitsuUrl .= "characters/images/{$id}/original.jpg";
289
-				$width = 225;
290
-			break;
291
-
292
-			default:
293
-				$this->notFound();
294
-				return;
297
+			$this->notFound();
298
+			return;
295 299
 		}
296 300
 
301
+		$kitsuUrl .= $typeMap[$type]['kitsuUrl'];
302
+		$width = $typeMap[$type]['width'];
303
+
297 304
 		$promise = (new HummingbirdClient)->request($kitsuUrl);
298 305
 		$response = wait($promise);
299 306
 		$data = wait($response->getBody());
300 307
 
308
+		// echo "Fetching {$kitsuUrl}\n";
309
+
301 310
 		$baseSavePath = $this->config->get('img_cache_path');
302 311
 		$filePrefix = "{$baseSavePath}/{$type}/{$id}";
303 312
 

+ 35
- 3
src/Controller/Manga.php View File

@@ -280,6 +280,7 @@ final class Manga extends Controller {
280 280
 	public function details($manga_id): void
281 281
 	{
282 282
 		$data = $this->model->getManga($manga_id);
283
+		$staff = [];
283 284
 		$characters = [];
284 285
 
285 286
 		if (empty($data))
@@ -293,14 +294,44 @@ final class Manga extends Controller {
293 294
 			return;
294 295
 		}
295 296
 
296
-		foreach($data['included'] as $included)
297
+		if (array_key_exists('characters', $data['included']))
297 298
 		{
298
-			if ($included['type'] === 'characters')
299
+			foreach ($data['included']['characters'] as $id => $character)
299 300
 			{
300
-				$characters[$included['id']] = $included['attributes'];
301
+				$characters[$id] = $character['attributes'];
301 302
 			}
302 303
 		}
303 304
 
305
+		if (array_key_exists('mediaStaff', $data['included']))
306
+		{
307
+			foreach ($data['included']['mediaStaff'] as $id => $person)
308
+			{
309
+				$personDetails = [];
310
+				foreach ($person['relationships']['person']['people'] as $p)
311
+				{
312
+					$personDetails = $p['attributes'];
313
+				}
314
+
315
+				$role = $person['attributes']['role'];
316
+
317
+				if ( ! array_key_exists($role, $staff))
318
+				{
319
+					$staff[$role] = [];
320
+				}
321
+
322
+				$staff[$role][$id] = [
323
+					'name' => $personDetails['name'] ?? '??',
324
+					'image' => $personDetails['image'],
325
+				];
326
+			}
327
+		}
328
+
329
+		uasort($characters, function ($a, $b) {
330
+			return $a['name'] <=> $b['name'];
331
+		});
332
+
333
+		// dump($staff);
334
+
304 335
 		$this->outputHTML('manga/details', [
305 336
 			'title' => $this->formatTitle(
306 337
 				$this->config->get('whose_list') . "'s Manga List",
@@ -309,6 +340,7 @@ final class Manga extends Controller {
309 340
 			),
310 341
 			'characters' => $characters,
311 342
 			'data' => $data,
343
+			'staff' => $staff,
312 344
 		]);
313 345
 	}
314 346
 

+ 110
- 0
src/Controller/People.php View File

@@ -0,0 +1,110 @@
1
+<?php declare(strict_types=1);
2
+/**
3
+ * Hummingbird Anime List Client
4
+ *
5
+ * An API client for Kitsu to manage anime and manga watch lists
6
+ *
7
+ * PHP version 7.1
8
+ *
9
+ * @package     HummingbirdAnimeClient
10
+ * @author      Timothy J. Warren <tim@timshomepage.net>
11
+ * @copyright   2015 - 2018  Timothy J. Warren
12
+ * @license     http://www.opensource.org/licenses/mit-license.html  MIT License
13
+ * @version     4.1
14
+ * @link        https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
15
+ */
16
+
17
+namespace Aviat\AnimeClient\Controller;
18
+
19
+use Aviat\AnimeClient\Controller as BaseController;
20
+use Aviat\AnimeClient\API\JsonAPI;
21
+
22
+/**
23
+ * Controller for People pages
24
+ */
25
+final class People extends BaseController {
26
+	/**
27
+	 * Show information about a person
28
+	 *
29
+	 * @param string $id
30
+	 * @return void
31
+	 */
32
+	public function index(string $id): void
33
+	{
34
+		$model = $this->container->get('kitsu-model');
35
+
36
+		$rawData = $model->getPerson($id);
37
+
38
+		if (( ! array_key_exists('data', $rawData)) || empty($rawData['data']))
39
+		{
40
+			$this->notFound(
41
+				$this->formatTitle(
42
+					'People',
43
+					'Person not found'
44
+				),
45
+				'Person Not Found'
46
+			);
47
+
48
+			return;
49
+		}
50
+
51
+		$data = JsonAPI::organizeData($rawData);
52
+
53
+		$viewData = [
54
+			'title' => $this->formatTitle(
55
+				'People',
56
+				$data['attributes']['name']
57
+			),
58
+			'data' => $data,
59
+			'castCount' => 0,
60
+			'castings' => []
61
+		];
62
+
63
+		if (array_key_exists('included', $data) && array_key_exists('castings', $data['included']))
64
+		{
65
+			$viewData['castings'] = $this->organizeCast($data['included']['castings']);
66
+			$viewData['castCount'] = count($viewData['castings']);
67
+		}
68
+
69
+		$this->outputHTML('person', $viewData);
70
+	}
71
+
72
+	protected function organizeCast(array $cast): array
73
+	{
74
+		$output = [];
75
+
76
+		foreach ($cast as $id => $role)
77
+		{
78
+			if (empty($role['attributes']['role']))
79
+			{
80
+				continue;
81
+			}
82
+
83
+			$roleName = $role['attributes']['role'];
84
+			$media = $role['relationships']['media'];
85
+
86
+			if (array_key_exists('anime', $media))
87
+			{
88
+				foreach($media['anime'] as $sid => $series)
89
+				{
90
+					$output[$roleName]['anime'][$sid] = $series;
91
+				}
92
+				uasort($output[$roleName]['anime'], function ($a, $b) {
93
+					return $a['attributes']['canonicalTitle'] <=> $b['attributes']['canonicalTitle'];
94
+				});
95
+			}
96
+			else if (array_key_exists('manga', $media))
97
+			{
98
+				foreach ($media['manga'] as $sid => $series)
99
+				{
100
+					$output[$roleName]['manga'][$sid] = $series;
101
+				}
102
+				uasort($output[$roleName]['anime'], function ($a, $b) {
103
+					return $a['attributes']['canonicalTitle'] <=> $b['attributes']['canonicalTitle'];
104
+				});
105
+			}
106
+		}
107
+
108
+		return $output;
109
+	}
110
+}