├── .gitignore ├── .htaccess ├── cache ├── .htaccess └── index.html ├── composer.json ├── config-default.php ├── config.md ├── git-repos ├── .htaccess └── index.php ├── index.php ├── json └── arty-animated.json ├── lib ├── Dirs.php ├── Query.php ├── Registry.php ├── Repositories.php └── Sync.php ├── package.json ├── phpunit.xml.dist ├── readme.md ├── robots.txt └── tests └── query_test.php /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | .idea 3 | .DS_Store 4 | config.json 5 | config.php 6 | cache/*.php 7 | vendor 8 | composer.phar 9 | composer.lock 10 | /git-repos/* 11 | !/git-repos/.htaccess 12 | !/git-repos/index.php 13 | -------------------------------------------------------------------------------- /.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine on 2 | RewriteCond %{REQUEST_FILENAME} !-d 3 | RewriteCond %{REQUEST_FILENAME} !-f 4 | RewriteRule . index.php [L] -------------------------------------------------------------------------------- /cache/.htaccess: -------------------------------------------------------------------------------- 1 | Order deny,allow 2 | Deny from all -------------------------------------------------------------------------------- /cache/index.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "PHP version of api.iconify.design", 3 | "version": "1.0.0", 4 | "type": "project", 5 | "license": "MIT", 6 | "homepage": "https://iconify.design/", 7 | "require": { 8 | "php": ">=5.6", 9 | "iconify/json": "*", 10 | "iconify/json-tools": "*" 11 | }, 12 | "require-dev": { 13 | "phpunit/phpunit": "*" 14 | }, 15 | "config": { 16 | "bin-dir": "bin" 17 | }, 18 | "autoload": { 19 | "psr-4": { 20 | "Iconify\\API\\": "lib" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /config-default.php: -------------------------------------------------------------------------------- 1 | '', 5 | 'env-region' => true, 6 | 'custom-icons-dir' => __DIR__ . '/json', 7 | 'serve-default-icons' => true, 8 | 'index-page' => 'https://iconify.design/', 9 | 'cache' => [ 10 | 'timeout' => 604800, 11 | 'min-refresh' => 604800, 12 | 'private' => false 13 | ], 14 | 'cors' => [ 15 | 'origins' => '*', 16 | 'timeout' => 86400, 17 | 'methods' => 'GET, OPTIONS', 18 | 'headers' => 'Origin, X-Requested-With, Content-Type, Accept, Accept-Encoding', 19 | ], 20 | 'sync' => [ 21 | 'versions' => __DIR__ . '/git-repos/versions.json', 22 | 'storage' => __DIR__ . '/git-repos', 23 | 'git' => 'git clone {repo} --depth 1 --no-tags {target}', 24 | 'secret' => '', 25 | 'iconify' => 'https://github.com/iconify/collections-json.git', 26 | 'custom' => '', 27 | 'custom-dir' => '' 28 | ], 29 | 'cache-dir' => __DIR__ . '/cache' 30 | ]; 31 | -------------------------------------------------------------------------------- /config.md: -------------------------------------------------------------------------------- 1 | # Configuration options 2 | 3 | Default options are in config-default.php 4 | 5 | Do not edit config-default.php unless you are making your own fork of project. All custom config options should be added to config.php. Create empty config.php: 6 | 7 | ``` 8 | locateCollections(); 106 | }); 107 | 108 | // Find collection 109 | $collection = $registry->getCollection($prefix); 110 | if ($collection === null) { 111 | sendError(404); 112 | exit(0); 113 | } 114 | 115 | // Parse query 116 | $result = \Iconify\API\Query::parse($collection, $query, $ext, $_GET); 117 | 118 | if (is_numeric($result)) { 119 | sendError($result); 120 | exit(0); 121 | } 122 | 123 | // Get collection cache time 124 | $time = $registry->getCollectionTime($prefix); 125 | if ($time) { 126 | header('Last-Modified: ' . gmdate('D, d M Y H:i:s T', $time)); 127 | } 128 | 129 | // Send response 130 | sendCacheHeaders($config); 131 | 132 | header('Content-Type: ' . $result['type']); 133 | header('ETag: ' . md5($result['body'])); 134 | 135 | // Check for download 136 | if (isset($result['filename']) && isset($_GET['download']) && ($_GET['download'] === '1' || $_GET['download'] === 'true')) { 137 | header('Content-Disposition: attachment; filename="' . $result['filename'] . '"'); 138 | } 139 | 140 | // Echo body and exit 141 | echo $result['body']; 142 | exit(0); 143 | } 144 | 145 | // Load config 146 | $config = getCustomConfig($config); 147 | 148 | // Check Origin 149 | if (isset($_SERVER['HTTP_ORIGIN']) && $config['cors']) { 150 | header('Access-Control-Allow-Origin: ' . $config['cors']['origins']); 151 | header('Access-Control-Allow-Credentials: true'); 152 | header('Access-Control-Max-Age: ' . $config['cors']['timeout']); 153 | } 154 | 155 | // Access-Control headers are received during OPTIONS requests 156 | if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'OPTIONS') { 157 | if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'])) { 158 | header('Access-Control-Allow-Methods: ' . $config['cors']['methods']); 159 | } 160 | 161 | if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) { 162 | header('Access-Control-Allow-Headers: ' . $config['cors']['headers']); 163 | } 164 | 165 | cacheHeaders($config); 166 | exit(0); 167 | } 168 | 169 | // Check for cache header 170 | if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { 171 | $time = @strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']); 172 | if ($config['cache'] && (!$time || $time > time() - $config['cache']['timeout'])) { 173 | http_response_code(304); 174 | exit(0); 175 | } 176 | } 177 | 178 | // Get URL 179 | $url = $_SERVER['REQUEST_URI']; 180 | $script = $_SERVER['SCRIPT_NAME']; 181 | $prefix = substr($script, 0, strlen($script) - strlen('index.php')); 182 | $url = substr($url, strlen($prefix)); 183 | $url = explode('?', $url); 184 | $url = $url[0]; 185 | 186 | if ($url === '') { 187 | // Index 188 | http_response_code(301); 189 | header('Location: ' . $config['index-page']); 190 | exit(0); 191 | } 192 | 193 | if ($url === 'version') { 194 | // Send version response 195 | $package = file_get_contents(__DIR__ . '/composer.json'); 196 | $data = json_decode($package, true); 197 | $version = $data['version']; 198 | echo 'Iconify API version ', $version, ' (PHP'; 199 | 200 | // Try to get region 201 | if ($config['env-region']) { 202 | $value = getenv('region'); 203 | if ($value !== false) { 204 | $config['region'] = $value; 205 | } 206 | } 207 | if ($config['region'] !== '') { 208 | echo ', ', $config['region']; 209 | } 210 | 211 | echo ')'; 212 | exit(0); 213 | } 214 | 215 | if ($url === 'sync') { 216 | // Synchronize repository 217 | if (!isset($_REQUEST['repo']) || !isset($_REQUEST['key']) || !is_string($_REQUEST['repo']) || !is_string($_REQUEST['key'])) { 218 | sendError(400); 219 | exit(0); 220 | } 221 | 222 | $start = time(); 223 | if (!isset($config['sync']) || empty($config['sync']['secret']) || $_REQUEST['key'] !== $config['sync']['secret']) { 224 | $result = false; 225 | } else { 226 | $sync = new \Iconify\API\Sync($config); 227 | $result = $sync->sync($_REQUEST['repo']); 228 | } 229 | 230 | // PHP cannot send response and then do stuff, so fake doing stuff few seconds 231 | $limit = 15; 232 | $end = time(); 233 | $diff = $end - $start; 234 | if ($diff < $limit) { 235 | sleep($limit - $diff); 236 | } 237 | 238 | exit(0); 239 | } 240 | 241 | // Split URL parts 242 | $url_parts = explode('.', $url); 243 | if (count($url_parts) !== 2) { 244 | sendError(404); 245 | exit(0); 246 | } 247 | $ext = $url_parts[1]; 248 | if (!preg_match('/^[a-z0-9:\/-]+$/', $url_parts[0])) { 249 | sendError(404); 250 | exit(0); 251 | } 252 | $url_parts = explode('/', $url_parts[0]); 253 | 254 | // Send to correct handler 255 | switch (count($url_parts)) { 256 | case 1: 257 | // 1 part request 258 | if ($ext === 'svg') { 259 | $parts = explode(':', $url_parts[0]); 260 | if (count($parts) === 2) { 261 | // prefix:icon.svg 262 | parseRequest($parts[0], $parts[1], $ext); 263 | } elseif (count($parts) === 1) { 264 | $parts = explode('-', $parts[0]); 265 | if (count($parts) > 1) { 266 | // prefix-icon.svg 267 | parseRequest(array_shift($parts), implode('-', $parts), $ext); 268 | } 269 | } 270 | } elseif ($ext === 'js' || $ext === 'json') { 271 | // prefix.json 272 | parseRequest($url_parts[0], 'icons', $ext); 273 | } 274 | break; 275 | 276 | case 2: 277 | // 2 part request 278 | if ($ext === 'js' || $ext === 'json' || $ext === 'svg') { 279 | // prefix/icon.svg 280 | // prefix/icons.json 281 | parseRequest($url_parts[0], $url_parts[1], $ext); 282 | } 283 | break; 284 | } 285 | 286 | // Invalid request 287 | sendError(404); 288 | exit(0); 289 | -------------------------------------------------------------------------------- /json/arty-animated.json: -------------------------------------------------------------------------------- 1 | { 2 | "prefix": "arty-animated", 3 | "icons": { 4 | "16-arc-180": { 5 | "body": "" 6 | }, 7 | "16-arc-270": { 8 | "body": "" 9 | }, 10 | "16-arc-90": { 11 | "body": "" 12 | }, 13 | "16-arrow-left": { 14 | "body": "" 15 | }, 16 | "16-arrows-from-2-corners": { 17 | "body": "" 18 | }, 19 | "16-arrows-from-corners": { 20 | "body": "" 21 | }, 22 | "16-arrows-horizontal": { 23 | "body": "" 24 | }, 25 | "16-arrows-to-2-corners": { 26 | "body": "" 27 | }, 28 | "16-arrows-to-corners": { 29 | "body": "" 30 | }, 31 | "16-caret-up-outline": { 32 | "body": "" 33 | }, 34 | "16-caret-up": { 35 | "body": "" 36 | }, 37 | "16-carets-vertical-outline": { 38 | "body": "" 39 | }, 40 | "16-carets-vertical": { 41 | "body": "" 42 | }, 43 | "16-chevron-left": { 44 | "body": "" 45 | }, 46 | "16-close": { 47 | "body": "" 48 | }, 49 | "16-confirm": { 50 | "body": "" 51 | }, 52 | "16-double-arrow-horizontal": { 53 | "body": "" 54 | }, 55 | "16-double-small-chevron-left": { 56 | "body": "" 57 | }, 58 | "16-drop-outline": { 59 | "body": "" 60 | }, 61 | "16-drop": { 62 | "body": "" 63 | }, 64 | "16-filters": { 65 | "body": "" 66 | }, 67 | "16-grid-3-outline": { 68 | "body": "" 69 | }, 70 | "16-grid-3": { 71 | "body": "" 72 | }, 73 | "16-home": { 74 | "body": "" 75 | }, 76 | "16-list-3-outline": { 77 | "body": "" 78 | }, 79 | "16-list-3": { 80 | "body": "" 81 | }, 82 | "16-panel-left": { 83 | "body": "" 84 | }, 85 | "16-search": { 86 | "body": "" 87 | }, 88 | "16-small-chevron-left": { 89 | "body": "" 90 | }, 91 | "20-arc-180": { 92 | "body": "", 93 | "width": 160, 94 | "height": 160 95 | }, 96 | "20-arc-270": { 97 | "body": "", 98 | "width": 160, 99 | "height": 160 100 | }, 101 | "20-arc-90": { 102 | "body": "", 103 | "width": 160, 104 | "height": 160 105 | }, 106 | "20-arrow-left": { 107 | "body": "", 108 | "width": 160, 109 | "height": 160 110 | }, 111 | "20-arrows-from-2-corners": { 112 | "body": "", 113 | "width": 160, 114 | "height": 160 115 | }, 116 | "20-arrows-from-corners": { 117 | "body": "", 118 | "width": 160, 119 | "height": 160 120 | }, 121 | "20-arrows-horizontal": { 122 | "body": "", 123 | "width": 160, 124 | "height": 160 125 | }, 126 | "20-arrows-to-2-corners": { 127 | "body": "", 128 | "width": 160, 129 | "height": 160 130 | }, 131 | "20-arrows-to-corners": { 132 | "body": "", 133 | "width": 160, 134 | "height": 160 135 | }, 136 | "20-caret-up-outline": { 137 | "body": "", 138 | "width": 160, 139 | "height": 160 140 | }, 141 | "20-caret-up": { 142 | "body": "", 143 | "width": 160, 144 | "height": 160 145 | }, 146 | "20-carets-vertical-outline": { 147 | "body": "", 148 | "width": 160, 149 | "height": 160 150 | }, 151 | "20-carets-vertical": { 152 | "body": "", 153 | "width": 160, 154 | "height": 160 155 | }, 156 | "20-chevron-left": { 157 | "body": "", 158 | "width": 160, 159 | "height": 160 160 | }, 161 | "20-close": { 162 | "body": "", 163 | "width": 160, 164 | "height": 160 165 | }, 166 | "20-confirm": { 167 | "body": "", 168 | "width": 160, 169 | "height": 160 170 | }, 171 | "20-double-arrow-horizontal": { 172 | "body": "", 173 | "width": 160, 174 | "height": 160 175 | }, 176 | "20-double-small-chevron-left": { 177 | "body": "", 178 | "width": 160, 179 | "height": 160 180 | }, 181 | "20-drop-outline": { 182 | "body": "", 183 | "width": 160, 184 | "height": 160 185 | }, 186 | "20-drop": { 187 | "body": "", 188 | "width": 160, 189 | "height": 160 190 | }, 191 | "20-filters": { 192 | "body": "", 193 | "width": 160, 194 | "height": 160 195 | }, 196 | "20-grid-3-outline": { 197 | "body": "", 198 | "width": 160, 199 | "height": 160 200 | }, 201 | "20-grid-3": { 202 | "body": "", 203 | "width": 160, 204 | "height": 160 205 | }, 206 | "20-home": { 207 | "body": "", 208 | "width": 160, 209 | "height": 160 210 | }, 211 | "20-list-3-outline": { 212 | "body": "", 213 | "width": 160, 214 | "height": 160 215 | }, 216 | "20-list-3": { 217 | "body": "", 218 | "width": 160, 219 | "height": 160 220 | }, 221 | "20-panel-left": { 222 | "body": "", 223 | "width": 160, 224 | "height": 160 225 | }, 226 | "20-search": { 227 | "body": "", 228 | "width": 160, 229 | "height": 160 230 | }, 231 | "20-small-chevron-left": { 232 | "body": "", 233 | "width": 160, 234 | "height": 160 235 | }, 236 | "24-arc-180": { 237 | "body": "", 238 | "width": 192, 239 | "height": 192 240 | }, 241 | "24-arc-270": { 242 | "body": "", 243 | "width": 192, 244 | "height": 192 245 | }, 246 | "24-arc-90": { 247 | "body": "", 248 | "width": 192, 249 | "height": 192 250 | }, 251 | "24-arrow-left": { 252 | "body": "", 253 | "width": 192, 254 | "height": 192 255 | }, 256 | "24-arrows-from-2-corners": { 257 | "body": "", 258 | "width": 192, 259 | "height": 192 260 | }, 261 | "24-arrows-from-corners": { 262 | "body": "", 263 | "width": 192, 264 | "height": 192 265 | }, 266 | "24-arrows-horizontal": { 267 | "body": "", 268 | "width": 192, 269 | "height": 192 270 | }, 271 | "24-arrows-to-2-corners": { 272 | "body": "", 273 | "width": 192, 274 | "height": 192 275 | }, 276 | "24-arrows-to-corners": { 277 | "body": "", 278 | "width": 192, 279 | "height": 192 280 | }, 281 | "24-caret-up-outline": { 282 | "body": "", 283 | "width": 192, 284 | "height": 192 285 | }, 286 | "24-caret-up": { 287 | "body": "", 288 | "width": 192, 289 | "height": 192 290 | }, 291 | "24-carets-vertical-outline": { 292 | "body": "", 293 | "width": 192, 294 | "height": 192 295 | }, 296 | "24-carets-vertical": { 297 | "body": "", 298 | "width": 192, 299 | "height": 192 300 | }, 301 | "24-chevron-left": { 302 | "body": "", 303 | "width": 192, 304 | "height": 192 305 | }, 306 | "24-close": { 307 | "body": "", 308 | "width": 192, 309 | "height": 192 310 | }, 311 | "24-confirm": { 312 | "body": "", 313 | "width": 192, 314 | "height": 192 315 | }, 316 | "24-double-arrow-horizontal": { 317 | "body": "", 318 | "width": 192, 319 | "height": 192 320 | }, 321 | "24-double-small-chevron-left": { 322 | "body": "", 323 | "width": 192, 324 | "height": 192 325 | }, 326 | "24-drop-outline": { 327 | "body": "", 328 | "width": 192, 329 | "height": 192 330 | }, 331 | "24-drop": { 332 | "body": "", 333 | "width": 192, 334 | "height": 192 335 | }, 336 | "24-filters": { 337 | "body": "", 338 | "width": 192, 339 | "height": 192 340 | }, 341 | "24-grid-3-outline": { 342 | "body": "", 343 | "width": 192, 344 | "height": 192 345 | }, 346 | "24-grid-3": { 347 | "body": "", 348 | "width": 192, 349 | "height": 192 350 | }, 351 | "24-home": { 352 | "body": "", 353 | "width": 192, 354 | "height": 192 355 | }, 356 | "24-list-3-outline": { 357 | "body": "", 358 | "width": 192, 359 | "height": 192 360 | }, 361 | "24-list-3": { 362 | "body": "", 363 | "width": 192, 364 | "height": 192 365 | }, 366 | "24-panel-left": { 367 | "body": "", 368 | "width": 192, 369 | "height": 192 370 | }, 371 | "24-search": { 372 | "body": "", 373 | "width": 192, 374 | "height": 192 375 | }, 376 | "24-small-chevron-left": { 377 | "body": "", 378 | "width": 192, 379 | "height": 192 380 | } 381 | }, 382 | "aliases": { 383 | "16-arrow-right": { 384 | "parent": "16-arrow-left", 385 | "hFlip": true 386 | }, 387 | "16-arrow-up": { 388 | "parent": "16-arrow-left", 389 | "rotate": 1, 390 | "vFlip": true 391 | }, 392 | "16-arrow-down": { 393 | "parent": "16-arrow-left", 394 | "rotate": 3 395 | }, 396 | "16-arrows-from-2-corners-rotated": { 397 | "parent": "16-arrows-from-2-corners", 398 | "hFlip": true 399 | }, 400 | "16-arrows-vertical": { 401 | "parent": "16-arrows-horizontal", 402 | "rotate": 1 403 | }, 404 | "16-arrows-to-2-corners-rotated": { 405 | "parent": "16-arrows-to-2-corners", 406 | "hFlip": true 407 | }, 408 | "16-caret-down-outline": { 409 | "parent": "16-caret-up-outline", 410 | "vFlip": true 411 | }, 412 | "16-caret-left-outline": { 413 | "parent": "16-caret-up-outline", 414 | "rotate": 1, 415 | "vFlip": true 416 | }, 417 | "16-caret-right-outline": { 418 | "parent": "16-caret-up-outline", 419 | "rotate": 1 420 | }, 421 | "16-caret-down": { 422 | "parent": "16-caret-up", 423 | "vFlip": true 424 | }, 425 | "16-caret-left": { 426 | "parent": "16-caret-up", 427 | "rotate": 1, 428 | "vFlip": true 429 | }, 430 | "16-caret-right": { 431 | "parent": "16-caret-up", 432 | "rotate": 1 433 | }, 434 | "16-carets-horizontal-outline": { 435 | "parent": "16-carets-vertical-outline", 436 | "rotate": 3 437 | }, 438 | "16-carets-horizontal": { 439 | "parent": "16-carets-vertical", 440 | "rotate": 3 441 | }, 442 | "16-chevron-right": { 443 | "parent": "16-chevron-left", 444 | "hFlip": true 445 | }, 446 | "16-chevron-up": { 447 | "parent": "16-chevron-left", 448 | "rotate": 1, 449 | "vFlip": true 450 | }, 451 | "16-chevron-down": { 452 | "parent": "16-chevron-left", 453 | "rotate": 3 454 | }, 455 | "16-double-arrow-vertical": { 456 | "parent": "16-double-arrow-horizontal", 457 | "rotate": 1 458 | }, 459 | "16-double-small-chevron-right": { 460 | "parent": "16-double-small-chevron-left", 461 | "hFlip": true 462 | }, 463 | "16-double-small-chevron-up": { 464 | "parent": "16-double-small-chevron-left", 465 | "rotate": 1, 466 | "vFlip": true 467 | }, 468 | "16-double-small-chevron-down": { 469 | "parent": "16-double-small-chevron-left", 470 | "rotate": 3 471 | }, 472 | "16-filters-horizontal": { 473 | "parent": "16-filters", 474 | "rotate": 3, 475 | "hFlip": true 476 | }, 477 | "16-list-3-outline-rtl": { 478 | "parent": "16-list-3-outline", 479 | "hFlip": true 480 | }, 481 | "16-list-3-rtl": { 482 | "parent": "16-list-3", 483 | "hFlip": true 484 | }, 485 | "16-panel-right": { 486 | "parent": "16-panel-left", 487 | "hFlip": true 488 | }, 489 | "16-panel-up": { 490 | "parent": "16-panel-left", 491 | "rotate": 1, 492 | "vFlip": true 493 | }, 494 | "16-panel-down": { 495 | "parent": "16-panel-left", 496 | "rotate": 3 497 | }, 498 | "16-search-rotated": { 499 | "parent": "16-search", 500 | "rotate": 3 501 | }, 502 | "16-small-chevron-right": { 503 | "parent": "16-small-chevron-left", 504 | "hFlip": true 505 | }, 506 | "16-small-chevron-up": { 507 | "parent": "16-small-chevron-left", 508 | "rotate": 1, 509 | "vFlip": true 510 | }, 511 | "16-small-chevron-down": { 512 | "parent": "16-small-chevron-left", 513 | "rotate": 3 514 | }, 515 | "20-arrow-right": { 516 | "parent": "20-arrow-left", 517 | "hFlip": true 518 | }, 519 | "20-arrow-up": { 520 | "parent": "20-arrow-left", 521 | "rotate": 1, 522 | "vFlip": true 523 | }, 524 | "20-arrow-down": { 525 | "parent": "20-arrow-left", 526 | "rotate": 3 527 | }, 528 | "20-arrows-from-2-corners-rotated": { 529 | "parent": "20-arrows-from-2-corners", 530 | "hFlip": true 531 | }, 532 | "20-arrows-vertical": { 533 | "parent": "20-arrows-horizontal", 534 | "rotate": 1 535 | }, 536 | "20-arrows-to-2-corners-rotated": { 537 | "parent": "20-arrows-to-2-corners", 538 | "hFlip": true 539 | }, 540 | "20-caret-down-outline": { 541 | "parent": "20-caret-up-outline", 542 | "vFlip": true 543 | }, 544 | "20-caret-left-outline": { 545 | "parent": "20-caret-up-outline", 546 | "rotate": 1, 547 | "vFlip": true 548 | }, 549 | "20-caret-right-outline": { 550 | "parent": "20-caret-up-outline", 551 | "rotate": 1 552 | }, 553 | "20-caret-down": { 554 | "parent": "20-caret-up", 555 | "vFlip": true 556 | }, 557 | "20-caret-left": { 558 | "parent": "20-caret-up", 559 | "rotate": 1, 560 | "vFlip": true 561 | }, 562 | "20-caret-right": { 563 | "parent": "20-caret-up", 564 | "rotate": 1 565 | }, 566 | "20-carets-horizontal-outline": { 567 | "parent": "20-carets-vertical-outline", 568 | "rotate": 3 569 | }, 570 | "20-carets-horizontal": { 571 | "parent": "20-carets-vertical", 572 | "rotate": 3 573 | }, 574 | "20-chevron-right": { 575 | "parent": "20-chevron-left", 576 | "hFlip": true 577 | }, 578 | "20-chevron-up": { 579 | "parent": "20-chevron-left", 580 | "rotate": 1, 581 | "vFlip": true 582 | }, 583 | "20-chevron-down": { 584 | "parent": "20-chevron-left", 585 | "rotate": 3 586 | }, 587 | "20-double-arrow-vertical": { 588 | "parent": "20-double-arrow-horizontal", 589 | "rotate": 1 590 | }, 591 | "20-double-small-chevron-right": { 592 | "parent": "20-double-small-chevron-left", 593 | "hFlip": true 594 | }, 595 | "20-double-small-chevron-up": { 596 | "parent": "20-double-small-chevron-left", 597 | "rotate": 1, 598 | "vFlip": true 599 | }, 600 | "20-double-small-chevron-down": { 601 | "parent": "20-double-small-chevron-left", 602 | "rotate": 3 603 | }, 604 | "20-filters-horizontal": { 605 | "parent": "20-filters", 606 | "rotate": 3, 607 | "hFlip": true 608 | }, 609 | "20-list-3-outline-rtl": { 610 | "parent": "20-list-3-outline", 611 | "hFlip": true 612 | }, 613 | "20-list-3-rtl": { 614 | "parent": "20-list-3", 615 | "hFlip": true 616 | }, 617 | "20-panel-right": { 618 | "parent": "20-panel-left", 619 | "hFlip": true 620 | }, 621 | "20-panel-up": { 622 | "parent": "20-panel-left", 623 | "rotate": 1, 624 | "vFlip": true 625 | }, 626 | "20-panel-down": { 627 | "parent": "20-panel-left", 628 | "rotate": 3 629 | }, 630 | "20-search-rotated": { 631 | "parent": "20-search", 632 | "rotate": 3 633 | }, 634 | "20-small-chevron-right": { 635 | "parent": "20-small-chevron-left", 636 | "hFlip": true 637 | }, 638 | "20-small-chevron-up": { 639 | "parent": "20-small-chevron-left", 640 | "rotate": 1, 641 | "vFlip": true 642 | }, 643 | "20-small-chevron-down": { 644 | "parent": "20-small-chevron-left", 645 | "rotate": 3 646 | }, 647 | "24-arrow-right": { 648 | "parent": "24-arrow-left", 649 | "hFlip": true 650 | }, 651 | "24-arrow-up": { 652 | "parent": "24-arrow-left", 653 | "rotate": 1, 654 | "vFlip": true 655 | }, 656 | "24-arrow-down": { 657 | "parent": "24-arrow-left", 658 | "rotate": 3 659 | }, 660 | "24-arrows-from-2-corners-rotated": { 661 | "parent": "24-arrows-from-2-corners", 662 | "hFlip": true 663 | }, 664 | "24-arrows-vertical": { 665 | "parent": "24-arrows-horizontal", 666 | "rotate": 1 667 | }, 668 | "24-arrows-to-2-corners-rotated": { 669 | "parent": "24-arrows-to-2-corners", 670 | "hFlip": true 671 | }, 672 | "24-caret-down-outline": { 673 | "parent": "24-caret-up-outline", 674 | "vFlip": true 675 | }, 676 | "24-caret-left-outline": { 677 | "parent": "24-caret-up-outline", 678 | "rotate": 1, 679 | "vFlip": true 680 | }, 681 | "24-caret-right-outline": { 682 | "parent": "24-caret-up-outline", 683 | "rotate": 1 684 | }, 685 | "24-caret-down": { 686 | "parent": "24-caret-up", 687 | "vFlip": true 688 | }, 689 | "24-caret-left": { 690 | "parent": "24-caret-up", 691 | "rotate": 1, 692 | "vFlip": true 693 | }, 694 | "24-caret-right": { 695 | "parent": "24-caret-up", 696 | "rotate": 1 697 | }, 698 | "24-carets-horizontal-outline": { 699 | "parent": "24-carets-vertical-outline", 700 | "rotate": 3 701 | }, 702 | "24-carets-horizontal": { 703 | "parent": "24-carets-vertical", 704 | "rotate": 3 705 | }, 706 | "24-chevron-right": { 707 | "parent": "24-chevron-left", 708 | "hFlip": true 709 | }, 710 | "24-chevron-up": { 711 | "parent": "24-chevron-left", 712 | "rotate": 1, 713 | "vFlip": true 714 | }, 715 | "24-chevron-down": { 716 | "parent": "24-chevron-left", 717 | "rotate": 3 718 | }, 719 | "24-double-arrow-vertical": { 720 | "parent": "24-double-arrow-horizontal", 721 | "rotate": 1 722 | }, 723 | "24-double-small-chevron-right": { 724 | "parent": "24-double-small-chevron-left", 725 | "hFlip": true 726 | }, 727 | "24-double-small-chevron-up": { 728 | "parent": "24-double-small-chevron-left", 729 | "rotate": 1, 730 | "vFlip": true 731 | }, 732 | "24-double-small-chevron-down": { 733 | "parent": "24-double-small-chevron-left", 734 | "rotate": 3 735 | }, 736 | "24-filters-horizontal": { 737 | "parent": "24-filters", 738 | "rotate": 3, 739 | "hFlip": true 740 | }, 741 | "24-list-3-outline-rtl": { 742 | "parent": "24-list-3-outline", 743 | "hFlip": true 744 | }, 745 | "24-list-3-rtl": { 746 | "parent": "24-list-3", 747 | "hFlip": true 748 | }, 749 | "24-panel-right": { 750 | "parent": "24-panel-left", 751 | "hFlip": true 752 | }, 753 | "24-panel-up": { 754 | "parent": "24-panel-left", 755 | "rotate": 1, 756 | "vFlip": true 757 | }, 758 | "24-panel-down": { 759 | "parent": "24-panel-left", 760 | "rotate": 3 761 | }, 762 | "24-search-rotated": { 763 | "parent": "24-search", 764 | "rotate": 3 765 | }, 766 | "24-small-chevron-right": { 767 | "parent": "24-small-chevron-left", 768 | "hFlip": true 769 | }, 770 | "24-small-chevron-up": { 771 | "parent": "24-small-chevron-left", 772 | "rotate": 1, 773 | "vFlip": true 774 | }, 775 | "24-small-chevron-down": { 776 | "parent": "24-small-chevron-left", 777 | "rotate": 3 778 | } 779 | }, 780 | "width": 128, 781 | "height": 128 782 | } -------------------------------------------------------------------------------- /lib/Dirs.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the license.txt 9 | * file that was distributed with this source code. 10 | * @license MIT 11 | */ 12 | 13 | namespace Iconify\API; 14 | 15 | use \Iconify\IconsJSON\Finder; 16 | 17 | class Dirs { 18 | protected static $_instance = null; 19 | 20 | protected $_config; 21 | 22 | protected $_repos; 23 | 24 | protected $_dirs = []; 25 | protected $_versions = null; 26 | protected $_reposBaseDir = null; 27 | 28 | /** 29 | * Get instance. This class can have only 1 instance 30 | * 31 | * @param $config 32 | * @return Dirs 33 | */ 34 | public static function instance($config) 35 | { 36 | if (self::$_instance === null) { 37 | self::$_instance = new Dirs($config); 38 | } 39 | return self::$_instance; 40 | } 41 | 42 | /** 43 | * Constructor 44 | * 45 | * @param array $config 46 | */ 47 | protected function __construct($config) 48 | { 49 | $this->_config = $config; 50 | $this->_repos = []; 51 | 52 | // Setup default directories 53 | if (!empty($config['serve-default-icons'])) { 54 | $this->_dirs['iconify'] = Finder::rootDir(); 55 | } 56 | 57 | if (!empty($config['custom-icons-dir'])) { 58 | $this->_dirs['custom'] = $config['custom-icons-dir']; 59 | } 60 | 61 | $this->_repos = array_keys($this->_dirs); 62 | $this->_checkSynchronizedRepositories(); 63 | } 64 | 65 | /** 66 | * Get repositories time 67 | * 68 | * @return array|null 69 | */ 70 | protected function _checkSynchronizedRepositories() 71 | { 72 | if ($this->_versions !== null) { 73 | return $this->_versions; 74 | } 75 | 76 | $this->_versions = []; 77 | if (empty($this->_config['sync']) || empty($this->_config['sync']['secret']) || empty($this->_config['sync']['versions']) || empty($this->_config['sync']['storage'])) { 78 | // Synchronization is inactive 79 | return $this->_versions; 80 | } 81 | 82 | // Check for possible repositories 83 | foreach($this->_repos as $repo) { 84 | if (!empty($this->_config['sync'][$repo])) { 85 | $this->_versions[$repo] = 0; 86 | } 87 | } 88 | if (!count($this->_versions)) { 89 | return $this->_versions; 90 | } 91 | 92 | // Check for versions.json 93 | $data = @file_get_contents($this->_config['sync']['versions']); 94 | $data = @json_decode($data, true); 95 | if (!is_array($data)) { 96 | return $this->_versions; 97 | } 98 | 99 | if ($this->_reposBaseDir === null) { 100 | $this->_getBaseReposDir(); 101 | } 102 | foreach ($data as $repo => $value) { 103 | if (!isset($this->_versions[$repo])) { 104 | continue; 105 | } 106 | $dir = $this->_reposBaseDir . '/' . $repo . '.' . $value; 107 | if (@is_dir($dir)) { 108 | $this->setRepositoryDir($repo, $value, $dir); 109 | } 110 | } 111 | 112 | return $this->_versions; 113 | } 114 | 115 | /** 116 | * Get root directory for repository 117 | * 118 | * @param string $repo 119 | * @return string 120 | */ 121 | public function rootDir($repo) 122 | { 123 | return isset($this->_dirs[$repo]) ? $this->_dirs[$repo] : ''; 124 | } 125 | 126 | /** 127 | * Get icons directory 128 | * 129 | * @param string $repo 130 | * @return string 131 | */ 132 | public function iconsDir($repo) 133 | { 134 | switch ($repo) { 135 | case 'iconify': 136 | $dir = $this->rootDir($repo); 137 | return $dir === '' ? '' : $dir . '/json'; 138 | 139 | default: 140 | return $this->rootDir($repo); 141 | } 142 | } 143 | 144 | /** 145 | * Get root directory for repository 146 | * 147 | * @param string $repo 148 | * @param string $time 149 | * @param string $dir 150 | */ 151 | public function setRepositoryDir($repo, $time, $dir = '') 152 | { 153 | $this->_versions[$repo] = $time; 154 | 155 | if ($dir === '') { 156 | if ($this->_reposBaseDir === null) { 157 | $this->_getBaseReposDir(); 158 | } 159 | $dir = $this->_reposBaseDir . '/' . $repo . '.' . $time; 160 | } 161 | 162 | $this->_setRootDir($repo, $dir); 163 | } 164 | 165 | /** 166 | * Set custom root directory for repository 167 | * 168 | * @param string $repo 169 | * @param string $dir 170 | */ 171 | protected function _setRootDir($repo, $dir) 172 | { 173 | $extraKey = $repo . '-dir'; 174 | if (isset($this->_config['sync']) && !empty($this->_config['sync'][$extraKey])) { 175 | $extra = $this->_config['sync'][$extraKey]; 176 | if (substr($extra, 0, 1) !== '/') { 177 | $extra = '/' . $extra; 178 | } 179 | if (substr($extra, -1) === '/') { 180 | $extra = substr($extra, 0, strlen($extra) - 1); 181 | } 182 | $dir .= $extra; 183 | } 184 | $this->_dirs[$repo] = $dir; 185 | } 186 | 187 | /** 188 | * Get all repositories 189 | * 190 | * @return array 191 | */ 192 | public function getRepos() 193 | { 194 | return $this->_repos; 195 | } 196 | 197 | /** 198 | * Get base repositories directory 199 | */ 200 | protected function _getBaseReposDir() 201 | { 202 | $this->_reposBaseDir = $this->_config['sync']['storage']; 203 | } 204 | 205 | /** 206 | * Save versions.json 207 | */ 208 | public function saveVersions() 209 | { 210 | if ($this->_versions === null) { 211 | return; 212 | } 213 | 214 | if (empty($this->_config['sync']) || empty($this->_config['sync']['secret']) || empty($this->_config['sync']['versions']) || empty($this->_config['sync']['storage'])) { 215 | // Synchronization is inactive 216 | return; 217 | } 218 | 219 | // Make storage directory 220 | @mkdir($this->_config['sync']['storage']); 221 | 222 | // Save versions.json 223 | $filename = $this->_config['sync']['versions']; 224 | @file_put_contents($filename, json_encode($this->_versions, JSON_PRETTY_PRINT)); 225 | } 226 | 227 | /** 228 | * Get last repository time for all synchronized repositories 229 | * 230 | * @return array|null 231 | */ 232 | public function getLatestRepos() 233 | { 234 | return $this->_versions; 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /lib/Query.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the license.txt 9 | * file that was distributed with this source code. 10 | * @license MIT 11 | */ 12 | 13 | namespace Iconify\API; 14 | 15 | use \Iconify\JSONTools\Collection; 16 | use \Iconify\JSONTools\SVG; 17 | 18 | class Query { 19 | /** 20 | * Generate data for query 21 | * 22 | * @param Collection $collection 23 | * @param string $query 24 | * @param string $ext 25 | * @param array $params 26 | * @return int|array 27 | */ 28 | public static function parse($collection, $query, $ext, $params) 29 | { 30 | switch ($ext) { 31 | case 'svg': 32 | // Generate SVG 33 | // query = icon name 34 | $icon = $collection->getIconData($query); 35 | if ($icon === null) { 36 | return 404; 37 | } 38 | $svg = new SVG($icon); 39 | $body = $svg->getSVG($params); 40 | return [ 41 | 'filename' => $query . '.svg', 42 | 'type' => 'image/svg+xml; charset=utf-8', 43 | 'body' => $body 44 | ]; 45 | 46 | case 'js': 47 | case 'json': 48 | if ($query !== 'icons' || !isset($params['icons']) || !is_string($params['icons'])) { 49 | return 404; 50 | } 51 | 52 | $result = $collection->getIcons(explode(',', $params['icons'])); 53 | 54 | if ($result === null || empty($result['icons'])) { 55 | return 404; 56 | } 57 | if (isset($result['aliases']) && empty($result['aliases'])) { 58 | unset($result['aliases']); 59 | } 60 | $result = json_encode($result); 61 | 62 | if ($ext === 'js') { 63 | if (isset($params['callback'])) { 64 | if (!preg_match('/^[a-z0-9_.]+$/i', $params['callback'])) { 65 | return 400; 66 | } 67 | $callback = $params['callback']; 68 | } else { 69 | $callback = 'SimpleSVG._loaderCallback'; 70 | } 71 | return [ 72 | 'type' => 'application/javascript; charset=utf-8', 73 | 'body' => $callback . '(' . $result . ')' 74 | ]; 75 | } 76 | return [ 77 | 'type' => 'application/json; charset=utf-8', 78 | 'body' => $result 79 | ]; 80 | 81 | default: 82 | return 404; 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/Registry.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the license.txt 9 | * file that was distributed with this source code. 10 | * @license MIT 11 | */ 12 | 13 | namespace Iconify\API; 14 | 15 | use \Iconify\JSONTools\Collection; 16 | 17 | class Registry { 18 | /** 19 | * @var int Version number used to bust old cache 20 | */ 21 | protected static $_version = 1; 22 | 23 | /** 24 | * @var null|array List of collections 25 | */ 26 | protected $_collections = null; 27 | 28 | /** 29 | * @var string|null 30 | */ 31 | protected $_cacheDir; 32 | 33 | /** 34 | * Registry constructor. 35 | * 36 | * @param string $cacheDir 37 | * @param callable $callback 38 | */ 39 | public function __construct($cacheDir, $callback) 40 | { 41 | $this->_cacheDir = $cacheDir; 42 | $cacheFile = $cacheDir ? $cacheDir . '/collections-' . self::$_version . '.php' : null; 43 | 44 | if ($cacheFile !== null && @file_exists($cacheFile)) { 45 | // Try to load from cache 46 | $this->_loadFromCache($cacheFile); 47 | if ($this->_collections) { 48 | return; 49 | } 50 | } 51 | 52 | // Get data from callback 53 | $this->_collections = $callback(); 54 | 55 | // Save cache 56 | if ($this->_collections && $cacheFile !== null) { 57 | $this->_saveCache($cacheFile); 58 | } 59 | } 60 | 61 | /** 62 | * Get collection 63 | * 64 | * @param string $prefix 65 | * @return null|Collection 66 | */ 67 | public function getCollection($prefix) 68 | { 69 | if (!$this->_collections || !isset($this->_collections[$prefix])) { 70 | return null; 71 | } 72 | 73 | $collection = new Collection($prefix); 74 | $collection->loadFromFile($this->_collections[$prefix], null, $this->_cacheDir ? $this->_cacheDir . '/collection-' . self::$_version . '-' . $prefix . '.php' : null); 75 | return $collection; 76 | } 77 | 78 | /** 79 | * Get collection file modification time 80 | * 81 | * @param string $prefix 82 | * @return bool|int 83 | */ 84 | public function getCollectionTime($prefix) 85 | { 86 | return @filemtime($this->_cacheDir ? $this->_cacheDir . '/collection-' . self::$_version . '-' . $prefix . '.php' : $this->_collections[$prefix]); 87 | } 88 | 89 | /** 90 | * Load collections list from cache 91 | * 92 | * @param string $filename 93 | */ 94 | protected function _loadFromCache($filename) 95 | { 96 | $cache_file = null; 97 | $cache_version = null; 98 | $cached_collections = null; 99 | 100 | try { 101 | /** @noinspection PhpIncludeInspection */ 102 | include $filename; 103 | } catch (\Exception $e) { 104 | return; 105 | } 106 | 107 | if ( 108 | $cache_file !== $filename || 109 | $cache_version !== self::$_version || 110 | $cached_collections === null 111 | ) { 112 | return; 113 | } 114 | 115 | $this->_collections = $cached_collections; 116 | } 117 | 118 | /** 119 | * Save cache 120 | * 121 | * @param string $filename Cache filename 122 | */ 123 | protected function _saveCache($filename) 124 | { 125 | $content = "_collections, true) . "; 129 | }"; 130 | file_put_contents($filename, $content); 131 | @chmod($filename, 0644); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /lib/Repositories.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the license.txt 9 | * file that was distributed with this source code. 10 | * @license MIT 11 | */ 12 | 13 | namespace Iconify\API; 14 | 15 | class Repositories { 16 | protected $_config; 17 | 18 | /** 19 | * Constructor 20 | * 21 | * @param $config 22 | */ 23 | public function __construct($config) 24 | { 25 | $this->_config = $config; 26 | } 27 | 28 | /** 29 | * Locate all collections 30 | * 31 | * @return array 32 | */ 33 | public function locateCollections() 34 | { 35 | $collections = []; 36 | 37 | $dirs = Dirs::instance($this->_config); 38 | $repos = $dirs->getRepos(); 39 | 40 | foreach ($repos as $repo) { 41 | $dir = $dirs->iconsDir($repo); 42 | if ($dir !== '') { 43 | $collections = array_merge($collections, $this->_scanDirectory($dir)); 44 | } 45 | } 46 | 47 | return $collections; 48 | } 49 | 50 | /** 51 | * Find all collections in directory 52 | * 53 | * @param string $dir 54 | * @return array 55 | */ 56 | protected function _scanDirectory($dir) 57 | { 58 | // List all json files 59 | $collections = []; 60 | $res = @opendir($dir); 61 | if ($res !== null) { 62 | while (($file = readdir($res)) !== false) { 63 | $dot = substr($file, 0, 1); 64 | if ($dot === '.' || $dot === '_') { 65 | continue; 66 | } 67 | $list = explode('.', $file); 68 | if (count($list) !== 2 || $list[1] !== 'json') { 69 | continue; 70 | } 71 | $collections[$list[0]] = $dir . '/' . $file; 72 | } 73 | closedir($res); 74 | } 75 | 76 | return $collections; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/Sync.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the license.txt 9 | * file that was distributed with this source code. 10 | * @license MIT 11 | */ 12 | 13 | namespace Iconify\API; 14 | 15 | class Sync { 16 | protected $_config; 17 | protected $_versions; 18 | 19 | /** 20 | * Constructor 21 | * 22 | * @param array $config 23 | */ 24 | public function __construct($config) 25 | { 26 | $this->_config = $config; 27 | } 28 | 29 | /** 30 | * Synchronize repository 31 | * 32 | * @param string $repo 33 | * @return bool True on success, false on failure 34 | */ 35 | public function sync($repo) 36 | { 37 | $dirs = Dirs::instance($this->_config); 38 | if (!in_array($repo, $dirs->getRepos()) || empty($this->_config['sync'][$repo])) { 39 | return false; 40 | } 41 | 42 | // Clean up old directories 43 | $this->cleanup(); 44 | 45 | // Start synchronizing 46 | $time = time(); 47 | $url = $this->_config['sync'][$repo]; 48 | $target = $this->_config['sync']['storage'] . '/' . $repo . '.' . $time; 49 | 50 | $cmd = strtr($this->_config['sync']['git'], [ 51 | '{repo}' => '"' . $url . '"', 52 | '{target}' => '"' . $target . '"' 53 | ]); 54 | 55 | exec($cmd); 56 | if (!is_dir($target)) { 57 | return false; 58 | } 59 | 60 | // Save new version 61 | $dirs->setRepositoryDir($repo, $time); 62 | $dirs->saveVersions(); 63 | 64 | $this->_purgeCache(); 65 | 66 | return true; 67 | } 68 | 69 | /** 70 | * Purge old cache 71 | */ 72 | protected function _purgeCache() 73 | { 74 | $dir = $this->_config['cache-dir']; 75 | 76 | foreach (new \DirectoryIterator($dir) as $entry) { 77 | if (!$entry->isFile()) { 78 | continue; 79 | } 80 | 81 | $file = $entry->getFilename(); 82 | $parts = explode('.', $file); 83 | 84 | if (count($parts) < 2 || $file === 'index.php') { 85 | continue; 86 | } 87 | 88 | $ext = array_pop($parts); 89 | if ($ext !== 'php') { 90 | continue; 91 | } 92 | 93 | @unlink($dir . '/' . $file); 94 | } 95 | } 96 | 97 | /** 98 | * Remove old repositories 99 | */ 100 | public function cleanup() 101 | { 102 | $base = $this->_config['sync']['storage']; 103 | $dirs = Dirs::instance($this->_config); 104 | $repos = $dirs->getLatestRepos(); 105 | 106 | // Find directories that require cleaning 107 | foreach (new \DirectoryIterator($base) as $entry) { 108 | if (!$entry->isDir() || $entry->isDot()) { 109 | continue; 110 | } 111 | 112 | $dir = $entry->getFilename(); 113 | $parts = explode('.', $dir); 114 | $repo = $parts[0]; 115 | if (count($parts) !== 2 || empty($repos[$repo])) { 116 | continue; 117 | } 118 | 119 | $time = intval($parts[1]); 120 | if ($time > ($repos[$repo] - 3600)) { 121 | // wait 1 hour before deleting old repository 122 | continue; 123 | } 124 | 125 | $this->rmdir($base . '/' . $dir); 126 | } 127 | } 128 | 129 | /** 130 | * Remove directory and contents 131 | * 132 | * @param string $dir 133 | */ 134 | protected function rmdir($dir) 135 | { 136 | foreach (new \DirectoryIterator($dir) as $entry) { 137 | if ($entry->isDot()) { 138 | continue; 139 | } 140 | $filename = $dir . '/' . $entry->getFilename(); 141 | if ($entry->isDir()) { 142 | $this->rmdir($filename); 143 | } else { 144 | unlink($filename); 145 | } 146 | } 147 | rmdir($dir); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "test": "bin/phpunit" 4 | }, 5 | "bugs": "https://github.com/iconify/api.php/issues", 6 | "homepage": "https://github.com/iconify/api.php", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+ssh://git@github.com/iconify/api.php.git" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Iconify API 2 | 3 | This software is no longer maintained. Use Node.js version of API: https://github.com/iconify/api 4 | 5 | PHP is no longer supported because: 6 | 7 | - It is much slower than Node version of API. 8 | - It has not been maintained for a while. 9 | - Updating it would require large effort, but it never be able to match Node version because of import and cleanup tools available in Node. 10 | 11 | ## About 12 | 13 | This code runs on api.iconify.design that is used to serve collections and SVG images. 14 | 15 | Make sure your server matches requirements below: 16 | 17 | - You must have PHP 5.6 or newer version. 18 | - Server must be running Apache with mod_rewrite enabled and AllowOverride enabled. 19 | - Files must be writable by same user that runs PHP scripts to allow writing to "cache" and "git-repos" directories. 20 | - If you are using function to synchronize repositories, make sure Git is installed and is accessable from command line. 21 | 22 | Node.js version is available at https://github.com/iconify/api.js 23 | 24 | ### How to use it 25 | 26 | Upload files in root directory of your website. Icons server requires its own domain or sub-domain. 27 | 28 | Add custom configuration to config.php. Default configuration is available as reference in config-default.php. See [config.md](config.md) 29 | 30 | ### Node vs PHP 31 | 32 | Node.js version of server is faster because it loads everything only once on startup. It is a bit harder to setup though because you need to install additional software and make sure server is running (using tools such as "pm2"). 33 | 34 | PHP process ends when HTTP request ends, so PHP has to reload lots of things for each request. PHP version has caching to minimize loading times, but it is still nowhere near as fast as Node.js version. The only upside of PHP version is it is easy to setup - simply upload files and you are done. 35 | -------------------------------------------------------------------------------- /robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / -------------------------------------------------------------------------------- /tests/query_test.php: -------------------------------------------------------------------------------- 1 | _collection1 = new Collection('test'); 13 | $this->_collection1->loadJSON([ 14 | 'prefix' => 'test', 15 | 'icons' => [ 16 | 'icon1' => [ 17 | 'body' => '', 18 | 'width' => 30 19 | ], 20 | 'icon2' => [ 21 | 'body' => '' 22 | ] 23 | ], 24 | 'aliases' => [ 25 | 'alias1' => [ 26 | 'parent' => 'icon2', 27 | 'hFlip' => true 28 | ] 29 | ], 30 | 'width' => 24, 31 | 'height' => 24 32 | ]); 33 | 34 | $this->_collection2 = new Collection('test2'); 35 | $this->_collection2->loadJSON([ 36 | 'icons' => [ 37 | 'test2-icon1' => [ 38 | 'body' => '', 39 | 'width' => 30 40 | ], 41 | 'test2-icon2' => [ 42 | 'body' => '' 43 | ], 44 | 'test2-icon3' => [ 45 | 'body' => '' 46 | ] 47 | ], 48 | 'aliases' => [ 49 | 'test2-alias1' => [ 50 | 'parent' => 'test2-icon2', 51 | 'hFlip' => true 52 | ] 53 | ], 54 | 'width' => 24, 55 | 'height' => 24 56 | ]); 57 | } 58 | 59 | public function testIconsList() 60 | { 61 | $this->assertEquals([ 62 | 'type' => 'application/javascript; charset=utf-8', 63 | 'body' => 'SimpleSVG._loaderCallback({"prefix":"test","icons":{"icon2":{"body":"","width":24,"height":24}},"aliases":{"alias1":{"parent":"icon2","hFlip":true}}})' 64 | ], Query::parse($this->_collection1, 'icons', 'js', [ 65 | 'icons' => 'alias1' 66 | ])); 67 | 68 | // Query collection without prefix, json 69 | $this->assertEquals([ 70 | 'type' => 'application/json; charset=utf-8', 71 | 'body' => '{"prefix":"test2","icons":{"icon2":{"body":"","width":24,"height":24}},"aliases":{"alias1":{"parent":"icon2","hFlip":true}}}' 72 | ], Query::parse($this->_collection2, 'icons', 'json', [ 73 | 'icons' => 'alias1' 74 | ])); 75 | 76 | // Custom callback 77 | $this->assertEquals([ 78 | 'type' => 'application/javascript; charset=utf-8', 79 | 'body' => 'console.log({"prefix":"test","icons":{"icon1":{"body":"","width":30,"height":24},"icon2":{"body":"","width":24,"height":24}}})' 80 | ], Query::parse($this->_collection1, 'icons', 'js', [ 81 | 'icons' => 'icon1,icon2', 82 | 'callback' => 'console.log' 83 | ])); 84 | } 85 | 86 | public function testSVG() 87 | { 88 | // Simple icon 89 | $this->assertEquals([ 90 | 'filename' => 'icon1.svg', 91 | 'type' => 'image/svg+xml; charset=utf-8', 92 | 'body' => '' 93 | ], Query::parse($this->_collection1, 'icon1', 'svg', [])); 94 | 95 | // Icon with custom attributes 96 | $this->assertEquals([ 97 | 'filename' => 'alias1.svg', 98 | 'type' => 'image/svg+xml; charset=utf-8', 99 | 'body' => '' 100 | ], Query::parse($this->_collection1, 'alias1', 'svg', [ 101 | 'color' => 'red' 102 | ])); 103 | 104 | // Icon with id replacement 105 | $result = Query::parse($this->_collection2, 'icon3', 'svg', [ 106 | 'color' => 'red', 107 | 'rotate' => '90deg' 108 | ]); 109 | $result = preg_replace('/IconifyId-[0-9a-f]+-[0-9a-f]+-[0-9]+/', 'some-id', $result['body']); 110 | $this->assertEquals('', $result); 111 | } 112 | } 113 | --------------------------------------------------------------------------------