1<?php 2 3/** 4 * e107 website system 5 * 6 * Copyright (C) 2008-2017 e107 Inc (e107.org) 7 * Released under the terms and conditions of the 8 * GNU General Public License (http://www.gnu.org/licenses/gpl.txt) 9 * 10 * @file 11 * External library handling for e107 core/plugins/themes. 12 * 13 * TODO: 14 * - Provide the ability to use third-party callbacks (are defined in e_library.php files) for groups: 15 * 'info', 'pre_detect', 'post_detect', 'pre_dependencies_load', 'pre_load', 'post_load' 16 */ 17 18// [e_LANGUAGEDIR]/[e_LANGUAGE]/lan_library_manager.php 19e107::lan('core', 'library_manager'); 20 21 22 23/** 24 * Class core_library. 25 */ 26class core_library 27{ 28 29 /** 30 * Provides information about external libraries. 31 * 32 * Provides information about: 33 * - jQuery (CDN). 34 * - jQuery (local). 35 * - jQuery Once (CDN) 36 * - jQuery Once (local) 37 * - jQuery UI (CDN) 38 * - jQuery UI (local) 39 * - Bootstrap (CDN) 40 * - Bootstrap (local) 41 * - Bootstrap Editable (CDN) 42 * - Bootstrap Editable (local) 43 * - Font-Awesome (CDN) 44 * - Font-Awesome (local) 45 */ 46 public function config() 47 { 48 $libraries = array(); 49 50 // jQuery (CDN). 51 $libraries['cdn.jquery'] = array( 52 'name' => 'jQuery (CDN)', 53 'vendor_url' => 'https://jquery.com/', 54 'version_arguments' => array( 55 'file' => 'jquery.min.js', 56 'pattern' => '/jQuery\s+v(\d\.\d\.\d+)/', 57 'lines' => 5, 58 ), 59 'files' => array( 60 'js' => array( 61 'jquery.min.js' => array( 62 'zone' => 1, 63 'type' => 'url', 64 ), 65 ), 66 ), 67 'variants' => array( 68 // 'unminified' version for debugging. 69 'dev' => array( 70 'files' => array( 71 'js' => array( 72 'jquery.js' => array( 73 'zone' => 1, 74 'type' => 'url', 75 ), 76 ), 77 ), 78 ), 79 ), 80 // Override library path to CDN. 81 'library_path' => 'https://cdn.jsdelivr.net/jquery', 82 'path' => '2.2.4', 83 ); 84 85 // jQuery (local). 86 $libraries['jquery'] = array( 87 'name' => 'jQuery (local)', 88 'vendor_url' => 'https://jquery.com/', 89 'version_arguments' => array( 90 'file' => 'dist/jquery.min.js', 91 'pattern' => '/v(\d\.\d\.\d+)/', 92 'lines' => 5, 93 ), 94 'files' => array( 95 'js' => array( 96 'dist/jquery.min.js' => array( 97 'zone' => 1, 98 'type' => 'url', 99 ), 100 ), 101 ), 102 'variants' => array( 103 // 'unminified' version for debugging. 104 'dev' => array( 105 'files' => array( 106 'js' => array( 107 'dist/jquery.js' => array( 108 'zone' => 1, 109 'type' => 'url', 110 ), 111 ), 112 ), 113 ), 114 ), 115 'library_path' => '{e_WEB}lib/jquery', 116 'path' => '2.2.4', 117 ); 118 119 // jQuery Once (CDN). 120 $libraries['cdn.jquery.once'] = array( 121 'name' => 'jQuery Once (CDN)', 122 'vendor_url' => 'https://plugins.jquery.com/once/', 123 'version_arguments' => array( 124 'file' => 'jquery.once.min.js', 125 'pattern' => '/jQuery\sOnce\s+v(\d\.\d\.\d+)/', 126 'lines' => 5, 127 ), 128 'files' => array( 129 'js' => array( 130 'jquery.once.min.js' => array( 131 'zone' => 2, 132 'type' => 'footer', 133 ), 134 ), 135 ), 136 'variants' => array( 137 // 'unminified' version for debugging. 138 'dev' => array( 139 'files' => array( 140 'js' => array( 141 // There is no non-minified version. 142 'jquery.once.min.js' => array( 143 'zone' => 2, 144 'type' => 'footer', 145 ), 146 ), 147 ), 148 ), 149 ), 150 // Override library path to CDN. 151 'library_path' => 'https://cdn.jsdelivr.net/jquery.once', 152 'path' => '2.1.2', 153 ); 154 155 // jQuery Once (local). 156 $libraries['jquery.once'] = array( 157 'name' => 'jQuery Once (local)', 158 'vendor_url' => 'https://plugins.jquery.com/once/', 159 'version_arguments' => array( 160 'file' => 'jquery.once.min.js', 161 'pattern' => '/jQuery\sOnce\s+v(\d\.\d\.\d+)/', 162 'lines' => 5, 163 ), 164 'files' => array( 165 'js' => array( 166 'jquery.once.min.js' => array( 167 'zone' => 2, 168 'type' => 'footer', 169 ), 170 ), 171 ), 172 'variants' => array( 173 // 'unminified' version for debugging. 174 'dev' => array( 175 'files' => array( 176 'js' => array( 177 // There is no non-minified version. 178 'jquery.once.min.js' => array( 179 'zone' => 2, 180 'type' => 'footer', 181 ), 182 ), 183 ), 184 ), 185 ), 186 // Override library path. 187 'library_path' => '{e_WEB}lib/jquery-once', 188 ); 189 190 // jQuery UI (CDN). 191 $libraries['cdn.jquery.ui'] = array( 192 'name' => 'jQuery UI (CDN)', 193 'vendor_url' => 'https://jqueryui.com/', 194 'version_arguments' => array( 195 'file' => 'jquery-ui.min.js', 196 'pattern' => '/v(\d\.\d+\.\d+)/', 197 'lines' => 5, 198 ), 199 'files' => array( 200 'js' => array( 201 'jquery-ui.min.js' => array( 202 'zone' => 2, 203 'type' => 'footer', 204 ), 205 ), 206 'css' => array( 207 'jquery-ui.min.css' => array( 208 'zone' => 2, 209 ), 210 ), 211 ), 212 'variants' => array( 213 // 'unminified' version for debugging. 214 'dev' => array( 215 'files' => array( 216 'js' => array( 217 // There is no non-minified version. 218 'jquery-ui.min.js' => array( 219 'zone' => 2, 220 'type' => 'footer', 221 ), 222 ), 223 'css' => array( 224 // There is no non-minified version. 225 'jquery-ui.min.css' => array( 226 'zone' => 2, 227 ), 228 ), 229 ), 230 ), 231 ), 232 // Override library path to CDN. 233 'library_path' => 'https://cdn.jsdelivr.net/jquery.ui', 234 'path' => '1.11.4', 235 ); 236 237 // jQuery UI (local). 238 $libraries['jquery.ui'] = array( 239 'name' => 'jQuery UI (local)', 240 'vendor_url' => 'https://jqueryui.com/', 241 'version_arguments' => array( 242 'file' => 'jquery-ui.js', 243 'pattern' => '/v(\d\.\d+\.\d+)/', 244 'lines' => 5, 245 ), 246 'files' => array( 247 'js' => array( 248 'jquery-ui.min.js' => array( 249 'zone' => 2, 250 'type' => 'footer', 251 ), 252 ), 253 'css' => array( 254 'jquery-ui.min.css' => array( 255 'zone' => 2, 256 ), 257 ), 258 ), 259 'variants' => array( 260 // 'unminified' version for debugging. 261 'dev' => array( 262 'files' => array( 263 'js' => array( 264 'jquery-ui.js' => array( 265 'zone' => 2, 266 'type' => 'footer', 267 ), 268 ), 269 'css' => array( 270 'jquery-ui.css' => array( 271 'zone' => 2, 272 ), 273 ), 274 ), 275 ), 276 ), 277 // Override library path. 278 'library_path' => '{e_WEB}lib/jquery-ui', 279 ); 280 281 282 283 // ----------------- Bootstrap 4 ---------------------------// 284 285 // Bootstrap (CDN). 286 $libraries['cdn.bootstrap4'] = array( 287 'name' => 'Bootstrap 4 (CDN)', 288 'vendor_url' => 'http://getbootstrap.com/', 289 'version_arguments' => array( 290 'file' => 'dist/js/bootstrap.min.js', 291 'pattern' => '/Bootstrap\s+v(\d\.\d\.\d+)/', 292 'lines' => 5, 293 ), 294 'files' => array( 295 'js' => array( 296 'dist/js/bootstrap.bundle.min.js' => array( 297 'zone' => 2, 298 'type' => 'footer', 299 ), 300 ), 301 'css' => array( 302 'dist/css/bootstrap.min.css' => array( 303 'zone' => 1, 304 ), 305 ), 306 ), 307 'variants' => array( 308 // 'unminified' version for debugging. 309 /*'dev' => array( 310 'files' => array( 311 'js' => array( 312 'js/bootstrap.js' => array( 313 'zone' => 2, 314 'type' => 'footer', 315 ), 316 ), 317 'css' => array( 318 'css/bootstrap.css' => array( 319 'zone' => 2, 320 ), 321 ), 322 ), 323 ),*/ 324 325 326 ), 327 // Override library path to CDN. 328 // https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/js/bootstrap.bundle.min.js 329 'library_path' => 'https://cdn.jsdelivr.net/npm/bootstrap@4.3.1', 330 'path' => '', 331 ); 332 333 // Bootstrap (local). 334 $libraries['bootstrap4'] = array( 335 'name' => 'Bootstrap 4 (local)', 336 'vendor_url' => 'http://getbootstrap.com/', 337 'version_arguments' => array( 338 'file' => 'js/bootstrap.bundle.min.js', 339 'pattern' => '/Bootstrap\s+v(\d\.\d\.\d+)/', 340 'lines' => 5, 341 ), 342 'files' => array( 343 'js' => array( 344 'js/bootstrap.bundle.min.js' => array( 345 'zone' => 2, 346 'type' => 'footer', 347 ), 348 ), 349 'css' => array( 350 'css/bootstrap.min.css' => array( 351 'zone' => 2, 352 ), 353 ), 354 ), 355 'variants' => array( 356 // 'unminified' version for debugging. 357 'dev' => array( 358 'files' => array( 359 'js' => array( 360 'js/bootstrap.bundle.js' => array( 361 'zone' => 2, 362 'type' => 'footer', 363 ), 364 ), 365 'css' => array( 366 'css/bootstrap.css' => array( 367 'zone' => 2, 368 ), 369 ), 370 ), 371 ), 372 ), 373 'library_path' => '{e_WEB}lib/bootstrap', 374 'path' => '4', 375 ); 376 377 378 // ----------------------------------------------------- // 379 380 381 382 // Bootstrap (CDN). 383 $libraries['cdn.bootstrap'] = array( 384 'name' => 'Bootstrap (CDN)', 385 'vendor_url' => 'http://getbootstrap.com/', 386 'version_arguments' => array( 387 'file' => 'js/bootstrap.min.js', 388 'pattern' => '/Bootstrap\s+v(\d\.\d\.\d+)/', 389 'lines' => 5, 390 ), 391 'files' => array( 392 'js' => array( 393 'js/bootstrap.min.js' => array( 394 'zone' => 2, 395 'type' => 'footer', 396 ), 397 ), 398 'css' => array( 399 'css/bootstrap.min.css' => array( 400 'zone' => 2, 401 ), 402 ), 403 ), 404 'variants' => array( 405 // 'unminified' version for debugging. 406 'dev' => array( 407 'files' => array( 408 'js' => array( 409 'js/bootstrap.js' => array( 410 'zone' => 2, 411 'type' => 'footer', 412 ), 413 ), 414 'css' => array( 415 'css/bootstrap.css' => array( 416 'zone' => 2, 417 ), 418 ), 419 ), 420 ), 421 422 423 ), 424 // Override library path to CDN. 425 // 'library_path' => 'https://cdn.jsdelivr.net/bootstrap', 426 'library_path' => 'https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap', 427 'path' => '3.4.1', 428 ); 429 430 // Bootstrap (local). 431 $libraries['bootstrap'] = array( 432 'name' => 'Bootstrap (local)', 433 'vendor_url' => 'http://getbootstrap.com/', 434 'version_arguments' => array( 435 'file' => 'js/bootstrap.min.js', 436 'pattern' => '/Bootstrap\s+v(\d\.\d\.\d+)/', 437 'lines' => 5, 438 ), 439 'files' => array( 440 'js' => array( 441 'js/bootstrap.min.js' => array( 442 'zone' => 2, 443 'type' => 'footer', 444 ), 445 ), 446 'css' => array( 447 'css/bootstrap.min.css' => array( 448 'zone' => 2, 449 ), 450 ), 451 ), 452 'variants' => array( 453 // 'unminified' version for debugging. 454 'dev' => array( 455 'files' => array( 456 'js' => array( 457 'js/bootstrap.js' => array( 458 'zone' => 2, 459 'type' => 'footer', 460 ), 461 ), 462 'css' => array( 463 'css/bootstrap.css' => array( 464 'zone' => 2, 465 ), 466 ), 467 ), 468 ), 469 ), 470 'library_path' => '{e_WEB}lib/bootstrap', 471 'path' => '3', 472 ); 473 474 // Bootstrap Editable (CDN). 475 $libraries['cdn.bootstrap.editable'] = array( 476 'name' => 'Bootstrap Editable (CDN)', 477 'vendor_url' => 'https://vitalets.github.io/bootstrap-editable/', 478 'version_arguments' => array( 479 'file' => 'js/bootstrap-editable.min.js', 480 'pattern' => '/v(\d\.\d\.\d+)/', 481 'lines' => 5, 482 ), 483 'files' => array( 484 'js' => array( 485 'js/bootstrap-editable.min.js' => array( 486 'zone' => 2, 487 'type' => 'footer', 488 ), 489 ), 490 'css' => array( 491 'css/bootstrap-editable.min.css' => array( 492 'zone' => 2, 493 ), 494 ), 495 ), 496 'variants' => array( 497 // 'unminified' version for debugging. 498 'dev' => array( 499 'files' => array( 500 'js' => array( 501 'js/bootstrap-editable.js' => array( 502 'zone' => 2, 503 'type' => 'footer', 504 ), 505 ), 506 'css' => array( 507 'css/bootstrap-editable.css' => array( 508 'zone' => 2, 509 ), 510 ), 511 ), 512 ), 513 ), 514 // Override library path to CDN. 515 'library_path' => 'https://cdn.jsdelivr.net/bootstrap.editable', 516 'path' => '1.5.1', 517 ); 518 519 // Bootstrap Editable (local). 520 $libraries['bootstrap.editable'] = array( 521 'name' => 'Bootstrap Editable (local)', 522 'vendor_url' => 'https://vitalets.github.io/bootstrap-editable/', 523 'version_arguments' => array( 524 'file' => 'js/bootstrap-editable.min.js', 525 'pattern' => '/v(\d\.\d\.\d+)/', 526 'lines' => 5, 527 ), 528 'files' => array( 529 'js' => array( 530 'js/bootstrap-editable.min.js' => array( 531 'zone' => 2, 532 'type' => 'footer', 533 ), 534 ), 535 'css' => array( 536 'css/bootstrap-editable.min.css' => array( 537 'zone' => 2, 538 ), 539 ), 540 ), 541 'variants' => array( 542 // 'unminified' version for debugging. 543 'dev' => array( 544 'files' => array( 545 'js' => array( 546 'js/bootstrap-editable.js' => array( 547 'zone' => 2, 548 'type' => 'footer', 549 ), 550 ), 551 'css' => array( 552 'css/bootstrap-editable.css' => array( 553 'zone' => 2, 554 ), 555 ), 556 ), 557 ), 558 ), 559 // Override library path. 560 'library_path' => '{e_WEB}js/bootstrap3-editable', 561 ); 562 563 // Bootstrap Switch (CDN). 564 $libraries['cdn.bootstrap.switch'] = array( 565 'name' => 'Bootstrap Switch (CDN)', 566 'vendor_url' => 'http://www.bootstrap-switch.org', 567 'version_arguments' => array( 568 'file' => 'js/bootstrap-switch.min.js', 569 'pattern' => '/v(\d\.\d\.\d)/', 570 'lines' => 5, 571 ), 572 'files' => array( 573 'js' => array( 574 'js/bootstrap-switch.min.js' => array( 575 'zone' => 2, 576 'type' => 'footer', 577 ), 578 ), 579 'css' => array( 580 'css/bootstrap3/bootstrap-switch.min.css' => array( 581 'zone' => 2, 582 ), 583 ), 584 ), 585 'variants' => array( 586 // 'unminified' version for debugging. 587 'dev' => array( 588 'files' => array( 589 'js' => array( 590 'js/bootstrap-switch.js' => array( 591 'zone' => 2, 592 'type' => 'footer', 593 ), 594 ), 595 'css' => array( 596 'css/bootstrap3/bootstrap-switch.css' => array( 597 'zone' => 2, 598 ), 599 ), 600 ), 601 ), 602 ), 603 // Override library path to CDN. 604 'library_path' => 'https://cdn.jsdelivr.net/bootstrap.switch', 605 'path' => '3.3.2', 606 ); 607 608 // Bootstrap Switch (local). 609 $libraries['bootstrap.switch'] = array( 610 'name' => 'Bootstrap Switch (local)', 611 'vendor_url' => 'http://www.bootstrap-switch.org', 612 'version_arguments' => array( 613 'file' => 'dist/js/bootstrap-switch.min.js', 614 'pattern' => '/v(\d\.\d\.\d)/', 615 'lines' => 5, 616 ), 617 'files' => array( 618 'js' => array( 619 'dist/js/bootstrap-switch.min.js' => array( 620 'zone' => 2, 621 'type' => 'footer', 622 ), 623 ), 624 'css' => array( 625 'dist/css/bootstrap3/bootstrap-switch.min.css' => array( 626 'zone' => 2, 627 ), 628 ), 629 ), 630 'variants' => array( 631 // 'unminified' version for debugging. 632 'dev' => array( 633 'files' => array( 634 'js' => array( 635 'dist/js/bootstrap-switch.js' => array( 636 'zone' => 2, 637 'type' => 'footer', 638 ), 639 ), 640 'css' => array( 641 'dist/css/bootstrap3/bootstrap-switch.css' => array( 642 'zone' => 2, 643 ), 644 ), 645 ), 646 ), 647 ), 648 // Override library path. 649 'library_path' => '{e_WEB}lib/bootstrap-switch', 650 ); 651 652 // Font-Awesome 4 (CDN). 653 $libraries['cdn.fontawesome'] = array( 654 'name' => 'Font-Awesome 4 (CDN)', 655 'vendor_url' => 'http://fontawesome.io/', 656 'version_arguments' => array( 657 'file' => 'css/font-awesome.min.css', 658 'pattern' => '/(\d\.\d\.\d+)/', 659 'lines' => 10, 660 ), 661 'files' => array( 662 'css' => array( 663 'css/font-awesome.min.css' => array( 664 'zone' => 2, 665 ), 666 ), 667 ), 668 'variants' => array( 669 // 'unminified' version for debugging. 670 'dev' => array( 671 'files' => array( 672 'css' => array( 673 'css/font-awesome.css' => array( 674 'zone' => 2, 675 ), 676 ), 677 ), 678 ), 679 ), 680 // Override library path to CDN. 681 'library_path' => 'https://cdn.jsdelivr.net/fontawesome', 682 'path' => '4.7.0', 683 ); 684 685 // Font-Awesome (local). 686 $libraries['fontawesome'] = array( 687 'name' => 'Font-Awesome 4 (local)', 688 'vendor_url' => 'http://fontawesome.io/', 689 'version_arguments' => array( 690 'file' => 'css/font-awesome.min.css', 691 'pattern' => '/(\d\.\d\.\d+)/', 692 'lines' => 10, 693 ), 694 'files' => array( 695 'css' => array( 696 'css/font-awesome.min.css' => array( 697 'zone' => 2, 698 ), 699 ), 700 ), 701 'variants' => array( 702 // 'unminified' version for debugging. 703 'dev' => array( 704 'files' => array( 705 'css' => array( 706 'css/font-awesome.css' => array( 707 'zone' => 2, 708 ), 709 ), 710 ), 711 ), 712 ), 713 // Override library path. 714 'library_path' => '{e_WEB}lib/font-awesome', 715 'path' => '4.7.0', 716 ); 717 718 719 720 721 722 // Font-Awesome 5 (CDN). 723 $libraries['cdn.fontawesome5'] = array( 724 'name' => 'Font-Awesome 5 (CDN)', 725 'vendor_url' => 'https://fontawesome.com/', 726 'version_arguments' => array( 727 'file' => 'css/all.css', 728 'pattern' => '/(\d\.\d\.\d+)/', 729 'lines' => 10, 730 ), 731 'files' => array( 732 'css' => array( 733 'css/all.css' => array( 734 'zone' => 2, 735 ), 736 'css/v4-shims.css' => array( 737 'zone' => 2, 738 ), 739 ), 740 ), 741 /* 'variants' => array( 742 // 'unminified' version for debugging. 743 'dev' => array( 744 'files' => array( 745 'css' => array( 746 'css/font-awesome.css' => array( 747 'zone' => 2, 748 ), 749 ), 750 ), 751 ), 752 ),*/ 753 // Override library path to CDN. 754 'library_path' => 'https://use.fontawesome.com/releases', 755 'path' => 'v5.8.1', 756 ); 757 758 // Font-Awesome (local). 759 $libraries['fontawesome5'] = array( 760 'name' => 'Font-Awesome 5 (local)', 761 'vendor_url' => 'https://fontawesome.com/', 762 'version_arguments' => array( 763 'file' => 'css/all.css', 764 'pattern' => '/(\d\.\d\.\d+)/', 765 'lines' => 3, 766 ), 767 'files' => array( 768 'css' => array( 769 'css/all.min.css' => array( 770 'zone' => 2, 771 ), 772 'css/v4-shims.min.css' => array( 773 'zone' => 2, 774 ), 775 ), 776 ), 777 'variants' => array( 778 // 'unminified' version for debugging. 779 'dev' => array( 780 'files' => array( 781 'css' => array( 782 'css/all.css' => array( 783 'zone' => 2, 784 ), 785 'css/v4-shims.css' => array( 786 'zone' => 2, 787 ), 788 ), 789 ), 790 ), 791 ), 792 // Override library path. 793 'library_path' => '{e_WEB}lib/font-awesome', 794 'path' => '5', 795 ); 796 797 798 799 // Animate (local). 800 $libraries['animate.css'] = array( 801 'name' => 'Animate.css (local)', 802 'vendor_url' => 'https://daneden.github.io/animate.css/', 803 'version_arguments' => array( 804 'file' => 'animate.min.css', 805 'pattern' => '/(\d\.\d\.\d+)/', 806 'lines' => 5, 807 ), 808 'files' => array( 809 'css' => array( 810 'animate.min.css' => array( 811 'zone' => 2, 812 ), 813 ), 814 ), 815 /* 'variants' => array( 816 // 'unminified' version for debugging. 817 'dev' => array( 818 'files' => array( 819 'css' => array( 820 'css/font-awesome.css' => array( 821 'zone' => 2, 822 ), 823 ), 824 ), 825 ), 826 ),*/ 827 // Override library path. 828 'library_path' => '{e_WEB}lib/animate.css', 829 // 'path' => '3.5.2', 830 ); 831 832 833 834 835 return $libraries; 836 } 837 838 /** 839 * Alters library information before detection and caching takes place. 840 */ 841 function config_alter(&$libraries) 842 { 843 $pref = e107::pref('core'); 844 $cdnProvider = varset($pref['e_jslib_cdn_provider'], 'jsdelivr'); 845 846 // If CDNJS is the selected provider, we alter core CDN libraries to use it 847 // instead of jsDelivr. 848 if($cdnProvider == 'cdnjs') 849 { 850 $libraries['cdn.jquery']['library_path'] = str_replace('https://cdn.jsdelivr.net/jquery', 'https://cdnjs.cloudflare.com/ajax/libs/jquery', $libraries['cdn.jquery']['library_path']); 851 $libraries['cdn.jquery.once']['library_path'] = str_replace('https://cdn.jsdelivr.net/jquery.once', 'https://cdnjs.cloudflare.com/ajax/libs/jquery-once', $libraries['cdn.jquery.once']['library_path']); 852 $libraries['cdn.jquery.ui']['library_path'] = str_replace('https://cdn.jsdelivr.net/jquery.ui', 'https://cdnjs.cloudflare.com/ajax/libs/jqueryui', $libraries['cdn.jquery.ui']['library_path']); 853 $libraries['cdn.bootstrap']['library_path'] = str_replace('https://cdn.jsdelivr.net/bootstrap', 'https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap', $libraries['cdn.bootstrap']['library_path']); 854 855 $libraries['cdn.bootstrap.editable']['library_path'] = str_replace('https://cdn.jsdelivr.net/bootstrap.editable', 'https://cdnjs.cloudflare.com/ajax/libs/x-editable', $libraries['cdn.bootstrap.editable']['library_path']); 856 $libraries['cdn.bootstrap.editable']['path'] .= '/bootstrap-editable'; 857 858 $libraries['cdn.bootstrap.switch']['library_path'] = str_replace('https://cdn.jsdelivr.net/bootstrap.switch', 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap-switch', $libraries['cdn.bootstrap.switch']['library_path']); 859 $libraries['cdn.fontawesome']['library_path'] = str_replace('https://cdn.jsdelivr.net/fontawesome', 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome', $libraries['cdn.fontawesome']['library_path']); 860 } 861 } 862 863} 864 865 866/** 867 * Class e_library_manager. 868 */ 869class e_library_manager 870{ 871 872 /** 873 * Constructor 874 * Use {@link getInstance()}, direct instantiating is not possible for signleton objects. 875 */ 876 public function __construct() 877 { 878 } 879 880 /** 881 * @return void 882 */ 883 protected function _init() 884 { 885 } 886 887 /** 888 * Cloning is not allowed. 889 */ 890 private function __clone() 891 { 892 } 893 894 /** 895 * Tries to detect a library and its installed version. 896 * 897 * @param $name 898 * The machine name of a library to return registered information for. 899 * 900 * @return array|false 901 * An associative array containing registered information for the library specified by $name, or FALSE if the 902 * library $name is not registered. In addition to the keys returned by info(), the following keys are 903 * contained: 904 * - installed: A boolean indicating whether the library is installed. Note that not only the top-level library, 905 * but also each variant contains this key. 906 * - version: If the version could be detected, the full version string. 907 * - error: If an error occurred during library detection, one of the following error statuses: 908 * "not found", "not detected", "not supported". 909 * - error_message: If an error occurred during library detection, a detailed error_message. 910 */ 911 public function detect($name) 912 { 913 // Re-use the statically cached value of info() to save memory. 914 $library = &$this->info($name); 915 916 // Exit early if the library was not found. 917 if($library === false) 918 { 919 return $library; 920 } 921 922 // If 'installed' is set, library detection ran already. 923 if(isset($library['installed'])) 924 { 925 return $library; 926 } 927 928 $library['installed'] = false; 929 930 // Check whether the library exists. 931 if(!isset($library['library_path'])) 932 { 933 $library['library_path'] = $this->detectPath($library['machine_name']); 934 } 935 936 $libraryPath = e107::getParser()->replaceConstants($library['library_path']); 937 if($library['library_path'] === false || (!file_exists($libraryPath) && substr($libraryPath, 0, 4) != 'http')) 938 { 939 $library['error'] = LAN_NOT_FOUND; 940 941 $replace_with = array($library['name']); 942 $library['error_message'] = e107::getParser()->lanVars(LAN_LIBRARY_MANAGER_03, $replace_with, true); 943 944 return $library; 945 } 946 947 // TODO: 948 // Invoke callbacks in the 'pre_detect' group. 949 $this->invoke('pre_detect', $library); 950 951 // Detect library version, if not hardcoded. 952 if(!isset($library['version'])) 953 { 954 // If version_callback is a method in $this class. 955 if(method_exists($this, $library['version_callback'])) 956 { 957 // We support both a single parameter, which is an associative array, and an indexed array of multiple 958 // parameters. 959 if(isset($library['version_arguments'][0])) 960 { 961 // Add the library as the first argument. 962 $classMethod = array($this, $library['version_callback']); 963 $params = array_merge(array($library), $library['version_arguments']); 964 $library['version'] = call_user_func_array($classMethod, $params); 965 } 966 else 967 { 968 $method = $library['version_callback']; 969 $library['version'] = $this->$method($library, $library['version_arguments']); 970 } 971 } 972 // If version_callback is a method in e_library.php file. 973 else 974 { 975 $library['version'] = ''; 976 $class = false; 977 978 if(varset($library['plugin'], false)) 979 { 980 $class = e107::getAddon($library['plugin'], 'e_library'); 981 } 982 elseif(varset($library['theme'], false)) 983 { 984 // e107::getAddon() does not support theme folders. 985 if(is_readable(e_THEME . $library['theme'] . '/theme_library.php')) 986 { 987 e107_require_once(e_THEME . $library['theme'] . '/theme_library.php'); 988 $addonClass = 'theme_library'; 989 990 if(class_exists($addonClass)) 991 { 992 $class = new $addonClass(); 993 } 994 } 995 996 // e107::getAddon() does not support theme folders. 997 if(is_readable(e_THEME . $library['theme'] . '/admin_theme_library.php')) 998 { 999 e107_require_once(e_THEME . $library['theme'] . '/admin_theme_library.php'); 1000 $addonClass = 'admin_theme_library'; 1001 1002 if(class_exists($addonClass)) 1003 { 1004 $class = new $addonClass(); 1005 } 1006 } 1007 } 1008 1009 // We support both a single parameter, which is an associative array, and an 1010 // indexed array of multiple parameters. 1011 if(isset($library['version_arguments'][0])) 1012 { 1013 if($class) 1014 { 1015 $params = array_merge(array($library), $library['version_arguments']); 1016 $library['version'] = e107::callMethod($class, $library['version_callback'], $params); 1017 } 1018 } 1019 else 1020 { 1021 if($class) 1022 { 1023 $library['version'] = e107::callMethod($class, $library['version_callback'], $library, $library['version_arguments']); 1024 } 1025 } 1026 } 1027 1028 if(empty($library['version'])) 1029 { 1030 $library['error'] = LAN_LIBRARY_MANAGER_10; 1031 1032 $replace_with = array($library['name']); 1033 $library['error_message'] = e107::getParser()->lanVars(LAN_LIBRARY_MANAGER_04, $replace_with, true); 1034 1035 return $library; 1036 } 1037 } 1038 1039 // Determine to which supported version the installed version maps. 1040 if(!empty($library['versions'])) 1041 { 1042 ksort($library['versions']); 1043 $version = 0; 1044 foreach($library['versions'] as $supported_version => $version_properties) 1045 { 1046 if(version_compare($library['version'], $supported_version, '>=')) 1047 { 1048 $version = $supported_version; 1049 } 1050 } 1051 if(!$version) 1052 { 1053 $library['error'] = LAN_LIBRARY_MANAGER_11; 1054 1055 $replace_with = array($library['version'], $library['name']); 1056 $library['error_message'] = e107::getParser()->lanVars(LAN_LIBRARY_MANAGER_05, $replace_with, true); 1057 1058 return $library; 1059 } 1060 1061 // Apply version specific definitions and overrides. 1062 $library = array_merge($library, $library['versions'][$version]); 1063 unset($library['versions']); 1064 } 1065 1066 // Check each variant if it is installed. 1067 if(!empty($library['variants'])) 1068 { 1069 foreach($library['variants'] as $variant_name => &$variant) 1070 { 1071 // If no variant callback has been set, assume the variant to be installed. 1072 if(!isset($variant['variant_callback'])) 1073 { 1074 $variant['installed'] = true; 1075 } 1076 else 1077 { 1078 $variant['installed'] = false; 1079 $class = false; 1080 1081 if(varset($library['plugin'], false)) 1082 { 1083 $class = e107::getAddon($library['plugin'], 'e_library'); 1084 } 1085 elseif(varset($library['theme'], false)) 1086 { 1087 // e107::getAddon() does not support theme folders. 1088 if(is_readable(e_THEME . $library['theme'] . '/theme_library.php')) 1089 { 1090 e107_require_once(e_THEME . $library['theme'] . '/theme_library.php'); 1091 $addonClass = 'theme_library'; 1092 1093 if(class_exists($addonClass)) 1094 { 1095 $class = new $addonClass(); 1096 } 1097 } 1098 1099 // e107::getAddon() does not support theme folders. 1100 if(is_readable(e_THEME . $library['theme'] . '/admin_theme_library.php')) 1101 { 1102 e107_require_once(e_THEME . $library['theme'] . '/admin_theme_library.php'); 1103 $addonClass = 'admin_theme_library'; 1104 1105 if(class_exists($addonClass)) 1106 { 1107 $class = new $addonClass(); 1108 } 1109 } 1110 } 1111 1112 // We support both a single parameter, which is an associative array, and an indexed array of 1113 // multiple parameters. 1114 if(isset($variant['variant_arguments'][0])) 1115 { 1116 if($class) 1117 { 1118 $params = array_merge(array($library, $variant_name), $variant['variant_arguments']); 1119 $variant['installed'] = e107::callMethod($class, $library['variant_callback'], $params); 1120 } 1121 } 1122 else 1123 { 1124 if($class) 1125 { 1126 // Can't use e107::callMethod(), because it only supports 2 params. 1127 if(method_exists($class, $variant['variant_callback'])) 1128 { 1129 // Call PLUGIN/THEME_library::VARIANT_CALLBACK(). 1130 $method = $variant['variant_callback']; 1131 $variant['installed'] = $class->$method($library, $variant_name, $variant['variant_arguments']); 1132 } 1133 } 1134 } 1135 1136 if(!$variant['installed']) 1137 { 1138 $variant['error'] = LAN_NOT_FOUND; 1139 1140 $replace_with = array($variant_name, $library['name']); 1141 $variant['error_message'] = e107::getParser()->lanVars(LAN_LIBRARY_MANAGER_06, $replace_with, true); 1142 } 1143 } 1144 } 1145 } 1146 1147 // If we end up here, the library should be usable. 1148 $library['installed'] = true; 1149 1150 // Invoke callbacks in the 'post_detect' group. 1151 $this->invoke('post_detect', $library); 1152 1153 return $library; 1154 } 1155 1156 /** 1157 * Loads a library. 1158 * 1159 * @param $name 1160 * The name of the library to load. 1161 * @param $variant 1162 * The name of the variant to load. Note that only one variant of a library can be loaded within a single 1163 * request. The variant that has been passed first is used; different variant names in subsequent calls are 1164 * ignored. 1165 * 1166 * @return mixed 1167 * An associative array of the library information as returned from config(). The top-level properties 1168 * contain the effective definition of the library (variant) that has been loaded. Additionally: 1169 * - installed: Whether the library is installed, as determined by detect(). 1170 * - loaded: Either the amount of library files that have been loaded, or FALSE if the library could not be 1171 * loaded. See MYPLUGIN_library::config() for more information. 1172 */ 1173 public function load($name, $variant = null) 1174 { 1175 // Re-use the statically cached value to save memory. 1176 static $loaded; 1177 1178 if(!isset($loaded[$name])) 1179 { 1180 $cache = e107::getCache(); 1181 $cache_context = (defset('e_ADMIN_AREA', false) == true) ? 'AdminArea' : 'UserArea'; 1182 $cacheID = 'Library_' . $cache_context . '_' . e107::getParser()->filter($name, 'file'); 1183 $cached = $cache->retrieve($cacheID, false, true, true); 1184 1185 if($cached) 1186 { 1187 $library = e107::unserialize($cached); 1188 } 1189 1190 if(!varset($library, false)) 1191 { 1192 $library = $this->detect($name); 1193 $cacheData = e107::serialize($library, 'json'); 1194 $cache->set($cacheID, $cacheData, true, true, true); 1195 } 1196 1197 // Exit early if the library was not found. 1198 if($library === false) 1199 { 1200 $loaded[$name] = $library; 1201 return $loaded[$name]; 1202 } 1203 1204 // If a variant was specified, override the top-level properties with the variant properties. 1205 if(isset($variant)) 1206 { 1207 // Ensure that the $variant key exists, and if it does not, set its 'installed' property to FALSE by 1208 // default. This will prevent the loading of the library files below. 1209 $library['variants'] += array($variant => array('installed' => false)); 1210 $library = array_merge($library, $library['variants'][$variant]); 1211 } 1212 // Regardless of whether a specific variant was requested or not, there can only be one variant of a 1213 // library within a single request. 1214 unset($library['variants']); 1215 1216 // TODO: 1217 // Invoke callbacks in the 'pre_dependencies_load' group. 1218 $this->invoke('pre_dependencies_load', $library); 1219 1220 // If the library (variant) is installed, load it. 1221 $library['loaded'] = false; 1222 if($library['installed']) 1223 { 1224 // Load library dependencies. 1225 if(isset($library['dependencies'])) 1226 { 1227 foreach($library['dependencies'] as $dependency) 1228 { 1229 $this->load($dependency); 1230 } 1231 } 1232 1233 // TODO: 1234 // Invoke callbacks in the 'pre_load' group. 1235 $this->invoke('pre_load', $library); 1236 1237 // Load all the files associated with the library. 1238 $library['loaded'] = $this->loadFiles($library); 1239 1240 // TODO: 1241 // Invoke callbacks in the 'post_load' group. 1242 $this->invoke('post_load', $library); 1243 } 1244 $loaded[$name] = $library; 1245 } 1246 1247 return $loaded[$name]; 1248 } 1249 1250 /** 1251 * Gets the path of a library. 1252 * 1253 * @param $name 1254 * The machine name of a library to return the path for. 1255 * 1256 * @return string 1257 * The path to the specified library or FALSE if the library wasn't found. 1258 */ 1259 private function detectPath($name) 1260 { 1261 static $libraries; 1262 1263 if(!isset($libraries)) 1264 { 1265 $libraries = $this->getLibraries(); 1266 } 1267 1268 $path = ''; 1269 if(!isset($libraries[$name])) 1270 { 1271 return false; 1272 } 1273 else 1274 { 1275 $path .= $libraries[$name]; 1276 } 1277 1278 return $path; 1279 } 1280 1281 /** 1282 * Returns an array of library directories. 1283 * 1284 * @return array 1285 * A list of library directories. 1286 */ 1287 private function getLibraries() 1288 { 1289 $dir = e_WEB . 'lib'; 1290 $directories = array(); 1291 1292 // Retrieve list of directories. 1293 $file = e107::getFile(); 1294 $dirs = $file->get_dirs($dir); 1295 1296 foreach($dirs as $dirName) 1297 { 1298 $directories[$dirName] = "{e_WEB}lib/$dirName"; 1299 } 1300 1301 return $directories; 1302 } 1303 1304 /** 1305 * Returns with the selected property of a library. 1306 * 1307 * @param string $library 1308 * Library machine name. For example: bootstrap 1309 * 1310 * @param string $property 1311 * The property name. For example: library_path 1312 * 1313 * @return mixed 1314 */ 1315 public function getProperty($library, $property) 1316 { 1317 $lib = self::info($library); 1318 return varset($lib[$property], false); 1319 } 1320 1321 1322 /** 1323 * Return full path to a library in different formats. 1324 * @param string $library 1325 * The library name eg. bootstrap 1326 * 1327 * @param null $mode 1328 * The mode: null | 'full' | 'abs' 1329 * 1330 * @return string 1331 */ 1332 public function getPath($library, $mode=null) 1333 { 1334 $path = self::getProperty($library, 'library_path').'/'. self::getProperty($library, 'path'); 1335 return e107::getParser()->replaceConstants($path,$mode).'/'; 1336 } 1337 1338 1339 /** 1340 * Returns information about registered libraries. 1341 * 1342 * The returned information is unprocessed; i.e., as registered by plugins. 1343 * 1344 * @param $library 1345 * (optional) The machine name of a library to return registered information for. If omitted, information 1346 * about all registered libraries is returned. 1347 * 1348 * @return array|false 1349 * An associative array containing registered information for all libraries, the registered information for the 1350 * library specified by $name, or FALSE if the library $name is not registered. 1351 */ 1352 public function &info($library = null) 1353 { 1354 // This static cache is re-used by detect() to save memory. 1355 static $libraries; 1356 1357 if(!isset($libraries)) 1358 { 1359 $libraries = array(); 1360 1361 $coreLibrary = new core_library(); 1362 $info = $coreLibrary->config(); 1363 if(is_array($info)) 1364 { 1365 foreach($info as $machine_name => $properties) 1366 { 1367 $properties['info_type'] = 'core'; 1368 $libraries[$machine_name] = $properties; 1369 } 1370 } 1371 1372 $plugins = array(); 1373 $themes = array(); 1374 1375 // Gather information from PLUGIN_library::config(). 1376 $pluginInfo = e107::getAddonConfig('e_library', 'library'); // 'config' is the default. 1377 foreach($pluginInfo as $plugin => $info) 1378 { 1379 foreach($info as $machine_name => $properties) 1380 { 1381 $properties['info_type'] = 'plugin'; 1382 $properties['plugin'] = $plugin; 1383 $libraries[$machine_name] = $properties; 1384 $plugins[] = $plugin; // This plugin has a valid e_library implementation. 1385 } 1386 } 1387 1388 $themes[] = array( 1389 'name' => e107::getPref('sitetheme'), 1390 'file' => 'theme_library', 1391 'class' => 'theme_library', 1392 ); 1393 1394 $themes[] = array( 1395 'name' => e107::getPref('admintheme'), 1396 'file' => 'admin_theme_library', 1397 'class' => 'admin_theme_library', 1398 ); 1399 1400 foreach($themes as $theme) 1401 { 1402 if(is_readable(e_THEME . $theme['name'] . '/' . $theme['file'] . '.php')) 1403 { 1404 e107_require_once(e_THEME . $theme['name'] . '/' . $theme['file'] . '.php'); 1405 1406 $info = e107::callMethod($theme['class'], 'config'); 1407 if(is_array($info)) 1408 { 1409 foreach($info as $machine_name => $properties) 1410 { 1411 $properties['info_type'] = 'theme'; 1412 $properties['theme'] = $theme['name']; 1413 $libraries[$machine_name] = $properties; 1414 } 1415 } 1416 } 1417 } 1418 1419 // Provide defaults. 1420 foreach($libraries as $machine_name => &$properties) 1421 { 1422 $this->infoDefaults($properties, $machine_name); 1423 } 1424 1425 // Alter config array. For example, change CDN provider. 1426 $coreLibrary->config_alter($libraries); 1427 1428 // Allow enabled plugins (with e_library.php file) to alter the registered libraries. 1429 foreach($plugins as $plugin) 1430 { 1431 $class = e107::getAddon($plugin, 'e_library'); 1432 if($class && method_exists($class, 'config_alter')) 1433 { 1434 // The library definitions are passed by reference. 1435 $class->config_alter($libraries); 1436 } 1437 } 1438 1439 // Allow enabled themes to alter the registered libraries. 1440 foreach($themes as $theme) 1441 { 1442 if(is_readable(e_THEME . $theme['name'] . '/' . $theme['file'] . '.php')) 1443 { 1444 e107_require_once(e_THEME . $theme['name'] . '/' . $theme['file'] . '.php'); 1445 1446 if(class_exists($theme['class'])) 1447 { 1448 $class = new $theme['class'](); 1449 if(method_exists($class, 'config_alter')) 1450 { 1451 // We cannot use e107::callMethod() because need to pass variable by reference. 1452 $class->config_alter($libraries); 1453 } 1454 } 1455 } 1456 } 1457 1458 // TODO: 1459 // Invoke callbacks in the 'info' group. 1460 foreach($libraries as &$properties) 1461 { 1462 $this->invoke('info', $properties); 1463 } 1464 } 1465 1466 if(isset($library)) 1467 { 1468 if(!empty($libraries[$library])) 1469 { 1470 return $libraries[$library]; 1471 } 1472 else 1473 { 1474 $false = false; 1475 return $false; 1476 } 1477 } 1478 1479 return $libraries; 1480 } 1481 1482 /** 1483 * Applies default properties to a library definition. 1484 * 1485 * @param array $library 1486 * An array of library information, passed by reference. 1487 * @param string $name 1488 * The machine name of the passed-in library. 1489 * 1490 * @return array 1491 */ 1492 private function infoDefaults(&$library, $name) 1493 { 1494 $library += array( 1495 'machine_name' => $name, 1496 'name' => $name, 1497 'vendor_url' => '', 1498 'download_url' => '', 1499 'path' => '', 1500 'library_path' => null, 1501 'version_callback' => 'getVersion', 1502 'version_arguments' => array(), 1503 'files' => array(), 1504 'dependencies' => array(), 1505 'variants' => array(), 1506 'versions' => array(), 1507 'integration_files' => array(), 1508 'callbacks' => array(), 1509 ); 1510 1511 $library['callbacks'] += array( 1512 'info' => array(), 1513 'pre_detect' => array('preDetect'), 1514 'post_detect' => array(), 1515 'pre_dependencies_load' => array(), 1516 'pre_load' => array('preLoad'), 1517 'post_load' => array(), 1518 ); 1519 1520 // Add our own callbacks before any others. 1521 array_unshift($library['callbacks']['info'], 'prepareFiles'); 1522 array_unshift($library['callbacks']['post_detect'], 'detectDependencies'); 1523 1524 return $library; 1525 } 1526 1527 /** 1528 * Library info callback to make all 'files' properties consistent. 1529 * 1530 * This turns libraries' file information declared as e.g. 1531 * @code 1532 * $library['files']['js'] = array('example_1.js', 'example_2.js'); 1533 * @endcode 1534 * into 1535 * @code 1536 * $library['files']['js'] = array( 1537 * 'example_1.js' => array(), 1538 * 'example_2.js' => array(), 1539 * ); 1540 * @endcode 1541 * It does the same for the 'integration_files' property. 1542 * 1543 * @param $library 1544 * An associative array of library information or a part of it, passed by reference. 1545 * @param $version 1546 * If the library information belongs to a specific version, the version string. NULL otherwise. 1547 * @param $variant 1548 * If the library information belongs to a specific variant, the variant name. NULL otherwise. 1549 */ 1550 private function prepareFiles(&$library, $version = null, $variant = null) 1551 { 1552 // Both the 'files' property and the 'integration_files' property contain file declarations, and we want to make 1553 // both consistent. 1554 $file_types = array(); 1555 if(isset($library['files'])) 1556 { 1557 $file_types[] = &$library['files']; 1558 } 1559 if(isset($library['integration_files'])) 1560 { 1561 // Integration files are additionally keyed by plugin. 1562 foreach($library['integration_files'] as &$integration_files) 1563 { 1564 $file_types[] = &$integration_files; 1565 } 1566 } 1567 foreach($file_types as &$files) 1568 { 1569 // Go through all supported types of files. 1570 foreach(array('js', 'css', 'php') as $type) 1571 { 1572 if(isset($files[$type])) 1573 { 1574 foreach($files[$type] as $key => $value) 1575 { 1576 // Unset numeric keys and turn the respective values into keys. 1577 if(is_numeric($key)) 1578 { 1579 $files[$type][$value] = array(); 1580 unset($files[$type][$key]); 1581 } 1582 } 1583 } 1584 } 1585 } 1586 } 1587 1588 /** 1589 * Library post detect callback to process and detect dependencies. 1590 * 1591 * It checks whether each of the dependencies of a library are installed and available in a compatible version. 1592 * 1593 * @param $library 1594 * An associative array of library information or a part of it, passed by reference. 1595 * @param $version 1596 * If the library information belongs to a specific version, the version string. NULL otherwise. 1597 * @param $variant 1598 * If the library information belongs to a specific variant, the variant name. NULL otherwise. 1599 */ 1600 private function detectDependencies(&$library, $version = null, $variant = null) 1601 { 1602 if(isset($library['dependencies'])) 1603 { 1604 foreach($library['dependencies'] as &$dependency_string) 1605 { 1606 $dependency_info = $this->parseDependency($dependency_string); 1607 $dependency = $this->detect($dependency_info['name']); 1608 if(!$dependency['installed']) 1609 { 1610 $library['installed'] = false; 1611 $library['error'] = LAN_LIBRARY_MANAGER_07; 1612 1613 $replace_with = array($dependency['name'], $library['name']); 1614 $library['error_message'] = e107::getParser()->lanVars(LAN_LIBRARY_MANAGER_01, $replace_with, true); 1615 } 1616 elseif($this->checkIncompatibility($dependency_info, $dependency['version'])) 1617 { 1618 $library['installed'] = false; 1619 $library['error'] = LAN_LIBRARY_MANAGER_08; 1620 1621 $replace_with = array($dependency['version'], $library['name'], $library['name']); 1622 $library['error_message'] = e107::getParser()->lanVars(LAN_LIBRARY_MANAGER_02, $replace_with, true); 1623 } 1624 1625 // Remove the version string from the dependency, so load() can load the libraries directly. 1626 $dependency_string = $dependency_info['name']; 1627 } 1628 } 1629 } 1630 1631 /** 1632 * Invokes library callbacks. 1633 * 1634 * @param $group 1635 * A string containing the group of callbacks that is to be applied. Should be either 'info', 'post_detect'. 1636 * @param $library 1637 * An array of library information, passed by reference. 1638 */ 1639 private function invoke($group, &$library) 1640 { 1641 // When introducing new callback groups in newer versions, stale cached library information somehow reaches 1642 // this point during the database update before clearing the library cache. 1643 if(empty($library['callbacks'][$group])) 1644 { 1645 return; 1646 } 1647 1648 foreach($library['callbacks'][$group] as $callback) 1649 { 1650 $this->traverseLibrary($library, $callback); 1651 } 1652 } 1653 1654 /** 1655 * Helper function to apply a callback to all parts of a library. 1656 * 1657 * Because library declarations can include variants and versions, and those version declarations can in turn 1658 * include variants, modifying e.g. the 'files' property everywhere it is declared can be quite cumbersome, in 1659 * which case this helper function is useful. 1660 * 1661 * @param $library 1662 * An array of library information, passed by reference. 1663 * @param $callback 1664 * A string containing the callback to apply to all parts of a library. 1665 */ 1666 private function traverseLibrary(&$library, $callback) 1667 { 1668 // If callback belongs to $this class. 1669 if(method_exists($this, $callback)) 1670 { 1671 // Always apply the callback to the top-level library. 1672 // Params: $library, $version, $variant 1673 $this->{$callback}($library, null, null); 1674 1675 // Apply the callback to versions. 1676 if(isset($library['versions'])) 1677 { 1678 foreach($library['versions'] as $version_string => &$version) 1679 { 1680 $this->{$callback}($version, $version_string, null); 1681 1682 // Versions can include variants as well. 1683 if(isset($version['variants'])) 1684 { 1685 foreach($version['variants'] as $version_variant_name => &$version_variant) 1686 { 1687 $this->{$callback}($version_variant, $version_string, $version_variant_name); 1688 } 1689 } 1690 } 1691 } 1692 1693 // Apply the callback to variants. 1694 if(isset($library['variants'])) 1695 { 1696 foreach($library['variants'] as $variant_name => &$variant) 1697 { 1698 $this->$callback($variant, null, $variant_name); 1699 } 1700 } 1701 } 1702 else 1703 { 1704 // TODO: Provide the ability to use third-party callbacks (are defined in e_library.php files) for groups: 1705 // 'info', 'pre_detect', 'post_detect', 'pre_dependencies_load', 'pre_load', 'post_load' 1706 } 1707 } 1708 1709 /** 1710 * Loads a library's files. 1711 * 1712 * @param $library 1713 * An array of library information as returned by info(). 1714 * 1715 * @return int 1716 * The number of loaded files. 1717 */ 1718 private function loadFiles($library) 1719 { 1720 $siteTheme = e107::getPref('sitetheme'); 1721 $adminTheme = e107::getPref('admintheme'); 1722 1723 // Load integration_files. 1724 if(!empty($library['post_load_integration_files']) && !$library['post_load_integration_files'] && !empty($library['integration_files'])) 1725 { 1726 foreach($library['integration_files'] as $provider => $files) 1727 { 1728 // If provider is an installed plugin. 1729 if(e107::isInstalled($provider)) 1730 { 1731 $this->loadFiles(array( 1732 'files' => $files, 1733 'path' => '', 1734 'library_path' => e_PLUGIN . $provider, 1735 'post_load_integration_files' => false, 1736 )); 1737 } 1738 // If provider is the admin theme, we only allow it for admin pages. 1739 elseif(e_ADMIN_AREA && $provider == $adminTheme) 1740 { 1741 $this->loadFiles(array( 1742 'files' => $files, 1743 'path' => '', 1744 'library_path' => e_THEME . $provider, 1745 'post_load_integration_files' => false, 1746 )); 1747 } 1748 // If provider is the site theme, we only allow it on user areas. 1749 elseif(!deftrue(e_ADMIN_AREA, false) && $provider == $siteTheme) 1750 { 1751 $this->loadFiles(array( 1752 'files' => $files, 1753 'path' => '', 1754 'library_path' => e_THEME . $provider, 1755 'post_load_integration_files' => false, 1756 )); 1757 } 1758 } 1759 } 1760 1761 // Construct the full path to the library for later use. 1762 $path = e107::getParser()->replaceConstants($library['library_path']); 1763 $path = ($library['path'] !== '' ? rtrim($path, '/') . '/' . $library['path'] : $path); 1764 $path = rtrim($path, '/'); 1765 1766 // Count the number of loaded files for the return value. 1767 $count = 0; 1768 1769 // Load both the JavaScript and the CSS files. 1770 foreach(array('js', 'css') as $type) 1771 { 1772 if(!empty($library['files'][$type])) 1773 { 1774 foreach($library['files'][$type] as $data => $options) 1775 { 1776 // If the value is not an array, it's a filename and passed as first (and only) argument. 1777 if(!is_array($options)) 1778 { 1779 $data = $options; 1780 $options = array(); 1781 } 1782 // In some cases, the first parameter ($data) is an array. Arrays can't be passed as keys in PHP, 1783 // so we have to get $data from the value array. 1784 if(is_numeric($data)) 1785 { 1786 $data = $options['data']; 1787 unset($options['data']); 1788 } 1789 // Prepend the library_path to the file name. 1790 $data = "$path/$data"; 1791 // Apply the default zone if the zone isn't explicitly given. 1792 if(!isset($options['zone'])) 1793 { 1794 $options['zone'] = ($type == 'js') ? 2 : 2; // TODO: default zones. 1795 } 1796 // Apply the default type if the type isn't explicitly given. 1797 if(!isset($options['type'])) 1798 { 1799 $options['type'] = 'url'; 1800 } 1801 if($type == 'js') 1802 { 1803 e107::js($options['type'], $data, null, $options['zone']); 1804 } 1805 elseif($type == 'css') 1806 { 1807 e107::getJs()->libraryCSS($data); // load before others. 1808 // e107::css($options['type'], $data, null); 1809 } 1810 $count++; 1811 } 1812 } 1813 } 1814 1815 // Load PHP files. 1816 if(!empty($library['files']['php'])) 1817 { 1818 foreach($library['files']['php'] as $file => $array) 1819 { 1820 $file_path1 = $path . '/' . $file; 1821 $file_path2 = e_ROOT . $path . '/' . $file; 1822 1823 if(file_exists($file_path1)) 1824 { 1825 $this->_requireOnce($file_path1); 1826 $count++; 1827 } 1828 elseif(file_exists($file_path2)) 1829 { 1830 $this->_requireOnce($file_path2); 1831 $count++; 1832 } 1833 } 1834 } 1835 1836 // Load integration_files. 1837 if(!empty($library['post_load_integration_files']) && $library['post_load_integration_files'] && !empty($library['integration_files'])) 1838 { 1839 foreach($library['integration_files'] as $provider => $files) 1840 { 1841 // If provider is an installed plugin. 1842 if(e107::isInstalled($provider)) 1843 { 1844 $this->loadFiles(array( 1845 'files' => $files, 1846 'path' => '', 1847 'library_path' => e_PLUGIN . $provider, 1848 'post_load_integration_files' => false, 1849 )); 1850 } 1851 // If provider is the admin theme, we only allow it for admin pages. 1852 elseif(e_ADMIN_AREA && $provider == $adminTheme) 1853 { 1854 $this->loadFiles(array( 1855 'files' => $files, 1856 'path' => '', 1857 'library_path' => e_THEME . $provider, 1858 'post_load_integration_files' => false, 1859 )); 1860 } 1861 // If provider is the site theme, we only allow it on user areas. 1862 elseif(!deftrue(e_ADMIN_AREA, false) && $provider == $siteTheme) 1863 { 1864 $this->loadFiles(array( 1865 'files' => $files, 1866 'path' => '', 1867 'library_path' => e_THEME . $provider, 1868 'post_load_integration_files' => false, 1869 )); 1870 } 1871 } 1872 } 1873 1874 return $count; 1875 } 1876 1877 /** 1878 * Wrapper function for require_once. 1879 * 1880 * A library file could set a $path variable in file scope. Requiring such a file directly in loadFiles() 1881 * would lead to the local $path variable being overridden after the require_once statement. This would break 1882 * loading further files. Therefore we use this trivial wrapper which has no local state that can be tampered with. 1883 * 1884 * @param $file_path 1885 * The file path of the file to require. 1886 */ 1887 private function _requireOnce($file_path) 1888 { 1889 // TODO: use e107_require_once() instead? 1890 require_once $file_path; 1891 } 1892 1893 /** 1894 * Gets the version information from an arbitrary library. 1895 * 1896 * @param $library 1897 * An associative array containing all information about the library. 1898 * @param $options 1899 * An associative array containing with the following keys: 1900 * - file: The filename to parse for the version, relative to the library path. For example: 'docs/changelog.txt'. 1901 * - pattern: A string containing a regular expression (PCRE) to match the library version. For example: 1902 * '@version\s+([0-9a-zA-Z\.-]+)@'. Note that the returned version is not the match of the entire pattern (i.e. 1903 * '@version 1.2.3' in the above example) but the match of the first sub-pattern (i.e. '1.2.3' in the above example). 1904 * - lines: (optional) The maximum number of lines to search the pattern in. Defaults to 20. 1905 * - cols: (optional) The maximum number of characters per line to take into account. Defaults to 200. In case of 1906 * minified or compressed files, this prevents reading the entire file into memory. 1907 * 1908 * @return mixed 1909 * A string containing the version of the library. Or null. 1910 */ 1911 private function getVersion($library, $options) 1912 { 1913 // Provide defaults. 1914 $options += array( 1915 'file' => '', 1916 'pattern' => '', 1917 'lines' => 20, 1918 'cols' => 200, 1919 ); 1920 1921 $libraryPath = e107::getParser()->replaceConstants($library['library_path']); 1922 $libraryPath = ($library['path'] !== '' ? rtrim($libraryPath, '/') . '/' . $library['path'] : $libraryPath); 1923 $libraryPath = rtrim($libraryPath, '/'); 1924 1925 $file = $libraryPath . '/' . $options['file']; 1926 1927 if(empty($options['file'])) 1928 { 1929 return; 1930 } 1931 1932 // If remote file (e.g. CDN URL)... we download file to temp, and get version number. 1933 // The library will be cached with version number, so this only run once per library. 1934 if(substr($file, 0, 4) == 'http') 1935 { 1936 $content = e107::getFile()->getRemoteContent($file); 1937 $tmpFile = tempnam(sys_get_temp_dir(), 'lib_'); 1938 1939 if($tmpFile) 1940 { 1941 file_put_contents($tmpFile, $content); 1942 $file = $tmpFile; 1943 } 1944 } 1945 1946 if(!file_exists($file)) 1947 { 1948 return; 1949 } 1950 1951 $file = fopen($file, 'r'); 1952 while($options['lines'] && $line = fgets($file, $options['cols'])) 1953 { 1954 if(preg_match($options['pattern'], $line, $version)) 1955 { 1956 fclose($file); 1957 1958 // If downloaded file, we need to unlink it from temp. 1959 if(isset($tmpFile) && file_exists($tmpFile)) 1960 { 1961 unlink($tmpFile); 1962 } 1963 1964 return $version[1]; 1965 } 1966 $options['lines']--; 1967 } 1968 fclose($file); 1969 1970 // If downloaded file, we need to unlink it from temp. 1971 if(isset($tmpFile) && file_exists($tmpFile)) 1972 { 1973 unlink($tmpFile); 1974 } 1975 1976 return; 1977 } 1978 1979 /** 1980 * Parses a dependency for comparison by checkIncompatibility(). 1981 * 1982 * @param $dependency 1983 * A dependency string, which specifies a plugin dependency, and versions that are supported. Supported formats 1984 * include: 1985 * - 'plugin' 1986 * - 'plugin (>=version, version)' 1987 * 1988 * @return array 1989 * An associative array with three keys: 1990 * - 'name' includes the name of the thing to depend on (e.g. 'foo'). 1991 * - 'original_version' contains the original version string (which can be used in the UI for reporting 1992 * incompatibilities). 1993 * - 'versions' is a list of associative arrays, each containing the keys 'op' and 'version'. 'op' can be one of: 1994 * '=', '==', '!=', '<>', '<', '<=', '>', or '>='. 'version' is one piece like '4.5-beta3'. 1995 * Callers should pass this structure to checkIncompatibility(). 1996 */ 1997 private function parseDependency($dependency) 1998 { 1999 $value = array(); 2000 2001 // We use named subpatterns and support every op that version_compare supports. Also, op is optional and 2002 // defaults to equals. 2003 $p_op = '(?P<operation>!=|==|=|<|<=|>|>=|<>)?'; 2004 $p_major = '(?P<major>\d+)'; 2005 // By setting the minor version to x, branches can be matched. 2006 $p_minor = '(?P<minor>(?:\d+|x)(?:-[A-Za-z]+\d+)?)'; 2007 $parts = explode('(', $dependency, 2); 2008 $value['name'] = trim($parts[0]); 2009 2010 if(isset($parts[1])) 2011 { 2012 $value['original_version'] = ' (' . $parts[1]; 2013 foreach(explode(',', $parts[1]) as $version) 2014 { 2015 if(preg_match("/^\s*$p_op\s*$p_major\.$p_minor/", $version, $matches)) 2016 { 2017 $op = !empty($matches['operation']) ? $matches['operation'] : '='; 2018 if($matches['minor'] == 'x') 2019 { 2020 // "2.x" to mean any version that begins with "2" (e.g. 2.0, 2.9 are all "2.x"). 2021 // PHP's version_compare(), on the other hand, treats "x" as a string; so to version_compare(), 2022 // "2.x" is considered less than 2.0. This means that >=2.x and <2.x are handled by 2023 // version_compare() as we need, but > and <= are not. 2024 if($op == '>' || $op == '<=') 2025 { 2026 $matches['major']++; 2027 } 2028 // Equivalence can be checked by adding two restrictions. 2029 if($op == '=' || $op == '==') 2030 { 2031 $value['versions'][] = array('op' => '<', 'version' => ($matches['major'] + 1) . '.x'); 2032 $op = '>='; 2033 } 2034 } 2035 $value['versions'][] = array('op' => $op, 'version' => $matches['major'] . '.' . $matches['minor']); 2036 } 2037 } 2038 } 2039 2040 return $value; 2041 } 2042 2043 /** 2044 * Checks whether a version is compatible with a given dependency. 2045 * 2046 * @param $v 2047 * The parsed dependency structure from parseDependency(). 2048 * @param $current_version 2049 * The version to check against (like 4.2). 2050 * 2051 * @return mixed 2052 * NULL if compatible, otherwise the original dependency version string that caused the incompatibility. 2053 * 2054 * @see parseDependency() 2055 */ 2056 private function checkIncompatibility($v, $current_version) 2057 { 2058 if(!empty($v['versions'])) 2059 { 2060 foreach($v['versions'] as $required_version) 2061 { 2062 if((isset($required_version['op']))) 2063 { 2064 if(!version_compare($current_version, $required_version['version'], $required_version['op'])) 2065 { 2066 return $v['original_version']; 2067 } 2068 } 2069 } 2070 } 2071 } 2072 2073 /** 2074 * Alters library information before detecting. 2075 */ 2076 private function preDetect(&$library) 2077 { 2078 if(empty($library['machine_name'])) 2079 { 2080 return; 2081 } 2082 2083 // Prevent plugins/themes from altering libraries on Admin UI. 2084 if(defset('e_ADMIN_AREA', false) == true) 2085 { 2086 $coreLibrary = new core_library(); 2087 $coreLibs = $coreLibrary->config(); 2088 2089 if (isset($coreLibs[$library['machine_name']])) { 2090 $coreLib = $coreLibs[$library['machine_name']]; 2091 $library = array_replace_recursive($coreLib, array_replace_recursive($library, $coreLib)); 2092 } 2093 } 2094 } 2095 2096 /** 2097 * Alters library information before loading. 2098 */ 2099 private function preLoad(&$library) 2100 { 2101 if(empty($library['machine_name'])) 2102 { 2103 return; 2104 } 2105 2106 $excluded = $this->getExcludedLibraries(); 2107 2108 if(empty($excluded)) 2109 { 2110 return; 2111 } 2112 2113 // Make sure we have the name without cdn prefix. 2114 $basename = str_replace('cdn.', '', $library['machine_name']); 2115 2116 // If this library (or the CDN version of this library) is excluded 2117 // by the theme is currently used. 2118 if (in_array($basename, $excluded) || in_array('cdn.' . $basename, $excluded)) 2119 { 2120 unset($library['files']['css']); 2121 2122 if (!empty($library['variants'])) 2123 { 2124 foreach($library['variants'] as &$variant) 2125 { 2126 if(!empty($variant['files']['css'])) 2127 { 2128 unset($variant['files']['css']); 2129 } 2130 } 2131 } 2132 } 2133 } 2134 2135 /** 2136 * Get excluded libraries. 2137 * 2138 * @return array 2139 */ 2140 public function getExcludedLibraries() 2141 { 2142 // This static cache is re-used by preLoad() to save memory. 2143 static $excludedLibraries; 2144 2145 if(!isset($excludedLibraries)) 2146 { 2147 $excludedLibraries = array(); 2148 2149 $exclude = e107::getTheme('current', false)->cssAttribute('auto', 'exclude'); 2150 2151 if($exclude) 2152 { 2153 // Split string into array and remove whitespaces. 2154 $excludedLibraries = array_map('trim', explode(',', $exclude)); 2155 } 2156 } 2157 2158 return $excludedLibraries; 2159 } 2160 2161} 2162