├── CHANGELOG.md ├── LICENSE ├── README.md ├── blueprints.yaml ├── classes └── TwigReadingTimeFilters.php ├── composer.json ├── composer.lock ├── languages.yaml ├── readingtime.php ├── readingtime.yaml └── vendor ├── autoload.php └── composer ├── ClassLoader.php ├── InstalledVersions.php ├── LICENSE ├── autoload_classmap.php ├── autoload_namespaces.php ├── autoload_psr4.php ├── autoload_real.php ├── autoload_static.php ├── installed.json ├── installed.php └── platform_check.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v1.4.1 2 | ## 05/08/2023 3 | 4 | 1. [](#improved) 5 | * Fixed a deprecated message where `null` was being passed to `strip_tags()` function 6 | 7 | # v1.4.0 8 | ## 05/13/2020 9 | 10 | 1. [](#new) 11 | * Added optional features to include additional reading time for images [#16](https://github.com/getgrav/grav-plugin-readingtime/pull/16) 12 | * Started using composer 13 | 1. [](#improved) 14 | * Added hungarian translation [#12](https://github.com/getgrav/grav-plugin-readingtime/pull/12) 15 | * Added dutch translation [#14](https://github.com/getgrav/grav-plugin-readingtime/pull/14) 16 | * PHPStan 7 fixes 17 | 1. [](#bugfix) 18 | * Fixed issue with passed in labels [#18](https://github.com/getgrav/grav-plugin-readingtime/pull/18) 19 | * Fixed plugin initialization 20 | 21 | # v1.3.0 22 | ## 12/10/2018 23 | 24 | 1. [](#bugfix) 25 | * Handle languages with multibyte characters [#11](https://github.com/getgrav/grav-plugin-readingtime/issues/11) 26 | 27 | # v1.2.0 28 | ## 07/09/2017 29 | 30 | 1. [](#improved) 31 | * Added spanish translation [#6](https://github.com/getgrav/grav-plugin-readingtime/pull/6) 32 | * Added czech translation [#7](https://github.com/getgrav/grav-plugin-readingtime/pull/7) 33 | * Added french translation [#4](https://github.com/getgrav/grav-plugin-readingtime/pull/4) 34 | * Added italian translation 35 | 1. [](#bugfix) 36 | * Fix plural when rounding to minutes [#5](https://github.com/getgrav/grav-plugin-readingtime/issues/5) 37 | 38 | # v1.1.0 39 | ## 08/25/2016 40 | 41 | 1. [](#new) 42 | * Added admin blueprints 43 | * Changed strings to be based on languages codes 44 | 45 | # v1.0.7 46 | ## 04/14/2016 47 | 48 | 1. [](#new) 49 | * ChangeLog started... 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 RocketTheme 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Grav ReadingTime Plugin 2 | 3 | **ReadingTime** is a [Grav](http://github.com/getgrav/grav) plugin which allows Grav to display the reading time of a page's content. This is especially useful for blogs and other sites as it gives the reader a quick idea of how much time they will need to set aside to read the page in full. 4 | 5 | Enabling the plugin is very simple. Just install the plugin folder to `/user/plugins/` in your Grav install. By default, the plugin is enabled. 6 | 7 | # Installation 8 | 9 | To install this plugin, just download the zip version of this repository and unzip it under `/your/site/grav/user/plugins`. Then, rename the resulting folder to `readingtime`. 10 | 11 | >> It is important that the folder be named `readingtime` as this is the folder referenced in the plugin's code. 12 | 13 | The contents of the zipped folder should now be located in the `/your/site/grav/user/plugins/readingtime` directory. 14 | 15 | >> NOTE: This plugin is a modular component for Grav which requires [Grav](http://github.com/getgrav/grav), the [Error](https://github.com/getgrav/grav-plugin-error) and [Problems](https://github.com/getgrav/grav-plugin-problems) plugins, and a theme to be installed in order to operate. 16 | 17 | # Usage 18 | 19 | ### Initial Setup 20 | 21 | Place the following line of code in the theme file you wish to add the ReadingTime plugin for: 22 | 23 | ``` 24 | {{ page.content|readingtime }} 25 | ``` 26 | 27 | You need to pass a Twig Filter 'readingtime' to display the reading time values. You can translate the labels with this example: 28 | 29 | ``` 30 | {{ page.content|readingtime({'minutes_label': 'Minuti', 'minute_label': 'Minuto', 'seconds_label': 'Secondi', 'second_label': 'Secondo'}) }} 31 | ``` 32 | 33 | I used Italian translation for the labels, you can change with your language. 34 | 35 | If you need you can change the format with this avariable variables (the code is default format): 36 | 37 | ``` 38 | {{ page.content|readingtime({'format': '{minutes_short_count} {minutes_text}, {seconds_short_count} {seconds_text}'}) }} 39 | ``` 40 | 41 | Available variables: 42 | 43 | | Variable | Description | Example | 44 | | :---------------- | :---------------------- | :--------------------------------------------------------------------------- | 45 | | `{minute_label}` | Minute Label (Singular) | `minute` | 46 | | `{minutes_label}` | Minutes Label (Plural) | `minutes` | 47 | | `{second_label}` | Second Label (Singular) | `second` | 48 | | `{seconds_label}` | Second Label (Plural) | `seconds` | 49 | | `{format}` | Display Format | `{minutes_text} {minutes_short_count}, {seconds_text} {seconds_short_count}` | 50 | 51 | Not available to edit but used in the format variable: 52 | 53 | | Variable | Description | Example | 54 | | :---------------------- | :--------------------------------------- | :------ | 55 | | `{minutes_short_count}` | Displays Minutes with Abbreviated Digits | `2` | 56 | | `{seconds_short_count}` | Displays Seconds with Abbreviated Digits | `9` | 57 | | `{minutes_long_count}` | Displays Minutes in Double Digits | `02` | 58 | | `{seconds_long_count}` | Displays Seconds in Double Digits | `09` | 59 | 60 | Display variables for text labels: 61 | 62 | | Variable | Description | Example | 63 | | :--------------- | :------------------------------------------------------------------------------- | :-------- | 64 | | `{minutes_text}` | Displays the Minutes Text Label (Singular or Plural, Based on Number of Minutes) | `minute` | 65 | | `{seconds_text}` | Displays the Seconds Text Label (Singular or Plural, Based on Number of Seconds) | `seconds` | 66 | 67 | >> NOTE: Any time you are making alterations to a theme's files, you will want to duplicate the theme folder in the `user/themes/` directory, rename it, and set the new name as your active theme. This will ensure that you don't lose your customizations in the event that a theme is updated. Once you have tested the change thoroughly, you can delete or back up that folder elsewhere. 68 | 69 | ### Include image views 70 | The number of seconds to view images is added to the reading time of text when `include_image_views` is set to true. 71 | 72 | Images are identified by `` tags in `page.content()`. 73 | 74 | The default values for `seconds_per_image` (shown below) mean that the first image adds `12` seconds to the reading time, the second adds `11` seconds, the third adds `10` seconds, and so on. 75 | Only integers, whitespace, and commas are permitted in the string. 76 | 77 | ``` 78 | seconds_per_image: '12,11,10,9,8,7,6,5,4,3' 79 | ``` 80 | 81 | If there are more images in a page than what is defined in `seconds_per_image` (e.g., more than 10 images in the default shown above) then subsequent images take the last value (`3` seconds in the default shown above). 82 | 83 | The example below adds `5` seconds reading time for all images. 84 | 85 | ``` 86 | seconds_per_image: 5 87 | ``` 88 | -------------------------------------------------------------------------------- /blueprints.yaml: -------------------------------------------------------------------------------- 1 | name: Reading Time 2 | version: 1.4.1 3 | description: "Add human readable reading time to your pages." 4 | icon: clock-o 5 | author: 6 | name: Team Grav 7 | email: devs@getgrav.org 8 | url: http://getgrav.org 9 | homepage: https://github.com/getgrav/grav-plugin-readingtime 10 | demo: http://www.getgrav.org/blog 11 | keywords: readingtime, plugin 12 | bugs: https://github.com/getgrav/grav-plugin-readingtime/issues 13 | license: MIT 14 | 15 | form: 16 | validation: strict 17 | fields: 18 | enabled: 19 | type: toggle 20 | label: Plugin status 21 | highlight: 1 22 | default: 0 23 | options: 24 | 1: Enabled 25 | 0: Disabled 26 | validate: 27 | type: bool 28 | 29 | words_per_minute: 30 | type: text 31 | size: x-small 32 | append: wpm 33 | label: Words-per-minute 34 | validate: 35 | type: int 36 | min: 1 37 | max: 1000 38 | 39 | format: 40 | type: text 41 | label: Format 42 | size: large 43 | 44 | round: 45 | type: select 46 | size: small 47 | classes: fancy 48 | label: Round to nearest... 49 | options: 50 | seconds: second 51 | minutes: minute 52 | 53 | include_image_views: 54 | type: toggle 55 | label: Include time for viewing images 56 | highlight: 1 57 | default: 0 58 | options: 59 | 1: Enabled 60 | 0: Disabled 61 | validate: 62 | type: bool 63 | 64 | seconds_per_image: 65 | type: text 66 | label: Seconds to view images 67 | help: 'Comma-separated list of whole numbers where first number is the number of seconds to view the first image, second number for the second image, and so on. Example: 12,10,11' 68 | default: '12,11,10,9,8,7,6,5,4,3' 69 | validate: 70 | required: true 71 | pattern: '(^(\s*\d+\s*))?(,\s*\d+\s*)*' 72 | message: 'Must be a comma-separated list of whole numbers (e.g. 12,11,10) with at least one number' 73 | -------------------------------------------------------------------------------- /classes/TwigReadingTimeFilters.php: -------------------------------------------------------------------------------- 1 | grav = Grav::instance(); 16 | } 17 | 18 | public function getName() 19 | { 20 | return 'TwigReadingTimeFilters'; 21 | } 22 | 23 | public function getFilters() 24 | { 25 | return [ 26 | new \Twig_SimpleFilter( 'readingtime', [$this, 'getReadingTime'] ) 27 | ]; 28 | } 29 | 30 | public function validatePattern($seconds_per_image) 31 | { 32 | // Get regex that is used in the user interface 33 | $pattern = '/' . $this->grav['plugins']->get('readingtime')->blueprints()->schema()->get('seconds_per_image')['validate']['pattern'] . '/'; 34 | 35 | if (preg_match($pattern, $seconds_per_image, $matches) === false) { 36 | return false; 37 | } 38 | 39 | // Note: "$matches[0] will contain the text that matched the full pattern" 40 | // https://www.php.net/manual/en/function.preg-match.php 41 | return strlen($seconds_per_image) === strlen($matches[0]); 42 | } 43 | 44 | public function getReadingTime( $content, $params = array() ) 45 | { 46 | 47 | $this->mergeConfig($this->grav['page']); 48 | $language = $this->grav['language']; 49 | 50 | $options = array_merge($this->grav['config']->get('plugins.readingtime'), $params); 51 | 52 | $words = count(preg_split('/\s+/', strip_tags((string) $content)) ?: []); 53 | $wpm = $options['words_per_minute']; 54 | 55 | $minutes_short_count = floor($words / $wpm); 56 | $seconds_short_count = floor($words % $wpm / ($wpm / 60)); 57 | 58 | if ($options['include_image_views']) { 59 | $stripped = strip_tags($content, ""); 60 | $images_in_content = substr_count($stripped, " 0) { 63 | if ($this->validatePattern($options['seconds_per_image'])) { 64 | 65 | // assumes string only contains integers, commas, and whitespace 66 | $spi = preg_split('/\D+/', trim($options['seconds_per_image'])); 67 | $seconds_images = 0; 68 | 69 | for ($i = 0; $i < $images_in_content; ++$i) { 70 | $seconds_images += $i < count($spi) ? $spi[$i] : end($spi); 71 | } 72 | 73 | $minutes_short_count += floor($seconds_images / 60); 74 | $seconds_short_count += $seconds_images % 60; 75 | } else { 76 | $this->grav['log']->error("Plugin 'readingtime' - seconds_per_image failed regex vadation"); 77 | } 78 | } 79 | } 80 | 81 | $round = $options['round']; 82 | if ($round == 'minutes') { 83 | $minutes_short_count = round(($minutes_short_count*60 + $seconds_short_count) / 60); 84 | 85 | if ( $minutes_short_count < 1 ) { 86 | $minutes_short_count = 1; 87 | } 88 | 89 | $seconds_short_count = 0; 90 | } 91 | 92 | $minutes_long_count = number_format($minutes_short_count, 2); 93 | $seconds_long_count = number_format($seconds_short_count, 2); 94 | 95 | if (array_key_exists('minute_label', $options) and $minutes_short_count == 1) { 96 | $minutes_text = $options['minute_label']; 97 | } elseif (array_key_exists('minutes_label', $options) and $minutes_short_count > 1) { 98 | $minutes_text = $options['minutes_label']; 99 | } else { 100 | $minutes_text = $language->translate(( $minutes_short_count == 1 ) ? 'PLUGIN_READINGTIME.MINUTE' : 'PLUGIN_READINGTIME.MINUTES'); 101 | } 102 | 103 | if (array_key_exists('second_label', $options) and $seconds_short_count == 1) { 104 | $seconds_text = $options['second_label']; 105 | } elseif (array_key_exists('seconds_label', $options) and $seconds_short_count > 1) { 106 | $seconds_text = $options['seconds_label']; 107 | } else { 108 | $seconds_text = $language->translate(( $seconds_short_count == 1 ) ? 'PLUGIN_READINGTIME.SECOND' : 'PLUGIN_READINGTIME.SECONDS'); 109 | } 110 | 111 | $replace = [ 112 | 'minutes_short_count' => $minutes_short_count, 113 | 'seconds_short_count' => $seconds_short_count, 114 | 'minutes_long_count' => $minutes_long_count, 115 | 'seconds_long_count' => $seconds_long_count, 116 | 'minutes_text' => $minutes_text, 117 | 'seconds_text' => $seconds_text 118 | ]; 119 | 120 | $result = $options['format']; 121 | 122 | foreach ( $replace as $key => $value ) { 123 | $result = str_replace('{' . $key . '}', $value, $result); 124 | } 125 | 126 | return $result; 127 | } 128 | 129 | private function mergeConfig( Page $page ) 130 | { 131 | $defaults = (array) $this->grav['config']->get('plugins.readingtime'); 132 | if ( isset($page->header()->readingtime) ) { 133 | $this->grav['config']->set('plugins.readingtime', array_merge($defaults, $page->header()->readingtime)); 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "getgrav/grav-plugin-readingtime", 3 | "type": "grav-plugin", 4 | "description": "Grav Plugin Reading Time", 5 | "keywords": ["form"], 6 | "homepage": "https://github.com/getgrav/grav-plugin-readingtime", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Team Grav", 11 | "email": "devs@getgrav.org", 12 | "homepage": "http://getgrav.org", 13 | "role": "Developer" 14 | } 15 | ], 16 | "support": { 17 | "issues": "https://github.com/getgrav/grav-plugin-readingtime/issues", 18 | "irc": "https://chat.getgrav.org", 19 | "forum": "https://discourse.getgrav.org", 20 | "docs": "https://github.com/getgrav/grav-plugin-readingtime/blob/master/README.md" 21 | }, 22 | "require": { 23 | "php": ">=7.1.3" 24 | }, 25 | "autoload": { 26 | "psr-4": { 27 | "Grav\\Plugin\\ReadingTime\\": "classes/" 28 | }, 29 | "classmap": [ 30 | "readingtime.php" 31 | ] 32 | }, 33 | "config": { 34 | "platform": { 35 | "php": "7.1.3" 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "4021fd5856eeec39c6661b1492e2221d", 8 | "packages": [], 9 | "packages-dev": [], 10 | "aliases": [], 11 | "minimum-stability": "stable", 12 | "stability-flags": [], 13 | "prefer-stable": false, 14 | "prefer-lowest": false, 15 | "platform": { 16 | "php": ">=7.1.3" 17 | }, 18 | "platform-dev": [], 19 | "platform-overrides": { 20 | "php": "7.1.3" 21 | }, 22 | "plugin-api-version": "2.3.0" 23 | } 24 | -------------------------------------------------------------------------------- /languages.yaml: -------------------------------------------------------------------------------- 1 | en: 2 | PLUGIN_READINGTIME: 3 | SECOND: second 4 | SECONDS: seconds 5 | MINUTE: minute 6 | MINUTES: minutes 7 | 8 | cs: 9 | PLUGIN_READINGTIME: 10 | SECOND: sekunda 11 | SECONDS: sekundy 12 | MINUTE: minuta 13 | MINUTES: minuty 14 | 15 | es: 16 | PLUGIN_READINGTIME: 17 | SECOND: segundo 18 | SECONDS: segundos 19 | MINUTE: minuto 20 | MINUTES: minutos 21 | 22 | fr: 23 | PLUGIN_READINGTIME: 24 | SECOND: seconde 25 | SECONDS: secondes 26 | MINUTE: minute 27 | MINUTES: minutes 28 | 29 | hr: 30 | PLUGIN_READINGTIME: 31 | SECOND: sekunda 32 | SECONDS: sekundi 33 | MINUTE: minuta 34 | MINUTES: minuta 35 | 36 | it: 37 | PLUGIN_READINGTIME: 38 | SECOND: secondo 39 | SECONDS: secondi 40 | MINUTE: minuto 41 | MINUTES: minuti 42 | 43 | nl: 44 | PLUGIN_READINGTIME: 45 | SECOND: seconde 46 | SECONDS: seconden 47 | MINUTE: minuut 48 | MINUTES: minuten 49 | 50 | pt-BR: 51 | PLUGIN_READINGTIME: 52 | SECOND: segundo 53 | SECONDS: segundos 54 | MINUTE: minuto 55 | MINUTES: minutos 56 | 57 | de: 58 | PLUGIN_READINGTIME: 59 | SECOND: Sekunde 60 | SECONDS: Sekunden 61 | MINUTE: Minute 62 | MINUTES: Minuten 63 | -------------------------------------------------------------------------------- /readingtime.php: -------------------------------------------------------------------------------- 1 | [ 15 | ['autoload', 100000], 16 | ['onPluginsInitialized', 0] 17 | ] 18 | ]; 19 | } 20 | 21 | /** 22 | * [onPluginsInitialized:100000] Composer autoload. 23 | * 24 | * @return ClassLoader 25 | */ 26 | public function autoload() 27 | { 28 | return require __DIR__ . '/vendor/autoload.php'; 29 | } 30 | 31 | public function onPluginsInitialized() 32 | { 33 | if ($this->isAdmin()) { 34 | $this->active = false; 35 | return; 36 | } 37 | 38 | $this->enable([ 39 | 'onTwigExtensions' => [ 40 | ['onTwigExtensions', 0] 41 | ] 42 | ]); 43 | } 44 | 45 | public function onTwigExtensions() 46 | { 47 | $this->grav['twig']->twig->addExtension(new TwigReadingTimeFilters()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /readingtime.yaml: -------------------------------------------------------------------------------- 1 | enabled: true 2 | words_per_minute: 200 3 | format: "{minutes_short_count} {minutes_text}, {seconds_short_count} {seconds_text}" 4 | round: seconds 5 | include_image_views: false 6 | seconds_per_image: '12,11,10,9,8,7,6,5,4,3' 7 | -------------------------------------------------------------------------------- /vendor/autoload.php: -------------------------------------------------------------------------------- 1 | 7 | * Jordi Boggiano 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Composer\Autoload; 14 | 15 | /** 16 | * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. 17 | * 18 | * $loader = new \Composer\Autoload\ClassLoader(); 19 | * 20 | * // register classes with namespaces 21 | * $loader->add('Symfony\Component', __DIR__.'/component'); 22 | * $loader->add('Symfony', __DIR__.'/framework'); 23 | * 24 | * // activate the autoloader 25 | * $loader->register(); 26 | * 27 | * // to enable searching the include path (eg. for PEAR packages) 28 | * $loader->setUseIncludePath(true); 29 | * 30 | * In this example, if you try to use a class in the Symfony\Component 31 | * namespace or one of its children (Symfony\Component\Console for instance), 32 | * the autoloader will first look for the class under the component/ 33 | * directory, and it will then fallback to the framework/ directory if not 34 | * found before giving up. 35 | * 36 | * This class is loosely based on the Symfony UniversalClassLoader. 37 | * 38 | * @author Fabien Potencier 39 | * @author Jordi Boggiano 40 | * @see https://www.php-fig.org/psr/psr-0/ 41 | * @see https://www.php-fig.org/psr/psr-4/ 42 | */ 43 | class ClassLoader 44 | { 45 | /** @var \Closure(string):void */ 46 | private static $includeFile; 47 | 48 | /** @var ?string */ 49 | private $vendorDir; 50 | 51 | // PSR-4 52 | /** 53 | * @var array[] 54 | * @psalm-var array> 55 | */ 56 | private $prefixLengthsPsr4 = array(); 57 | /** 58 | * @var array[] 59 | * @psalm-var array> 60 | */ 61 | private $prefixDirsPsr4 = array(); 62 | /** 63 | * @var array[] 64 | * @psalm-var array 65 | */ 66 | private $fallbackDirsPsr4 = array(); 67 | 68 | // PSR-0 69 | /** 70 | * @var array[] 71 | * @psalm-var array> 72 | */ 73 | private $prefixesPsr0 = array(); 74 | /** 75 | * @var array[] 76 | * @psalm-var array 77 | */ 78 | private $fallbackDirsPsr0 = array(); 79 | 80 | /** @var bool */ 81 | private $useIncludePath = false; 82 | 83 | /** 84 | * @var string[] 85 | * @psalm-var array 86 | */ 87 | private $classMap = array(); 88 | 89 | /** @var bool */ 90 | private $classMapAuthoritative = false; 91 | 92 | /** 93 | * @var bool[] 94 | * @psalm-var array 95 | */ 96 | private $missingClasses = array(); 97 | 98 | /** @var ?string */ 99 | private $apcuPrefix; 100 | 101 | /** 102 | * @var self[] 103 | */ 104 | private static $registeredLoaders = array(); 105 | 106 | /** 107 | * @param ?string $vendorDir 108 | */ 109 | public function __construct($vendorDir = null) 110 | { 111 | $this->vendorDir = $vendorDir; 112 | self::initializeIncludeClosure(); 113 | } 114 | 115 | /** 116 | * @return string[] 117 | */ 118 | public function getPrefixes() 119 | { 120 | if (!empty($this->prefixesPsr0)) { 121 | return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); 122 | } 123 | 124 | return array(); 125 | } 126 | 127 | /** 128 | * @return array[] 129 | * @psalm-return array> 130 | */ 131 | public function getPrefixesPsr4() 132 | { 133 | return $this->prefixDirsPsr4; 134 | } 135 | 136 | /** 137 | * @return array[] 138 | * @psalm-return array 139 | */ 140 | public function getFallbackDirs() 141 | { 142 | return $this->fallbackDirsPsr0; 143 | } 144 | 145 | /** 146 | * @return array[] 147 | * @psalm-return array 148 | */ 149 | public function getFallbackDirsPsr4() 150 | { 151 | return $this->fallbackDirsPsr4; 152 | } 153 | 154 | /** 155 | * @return string[] Array of classname => path 156 | * @psalm-return array 157 | */ 158 | public function getClassMap() 159 | { 160 | return $this->classMap; 161 | } 162 | 163 | /** 164 | * @param string[] $classMap Class to filename map 165 | * @psalm-param array $classMap 166 | * 167 | * @return void 168 | */ 169 | public function addClassMap(array $classMap) 170 | { 171 | if ($this->classMap) { 172 | $this->classMap = array_merge($this->classMap, $classMap); 173 | } else { 174 | $this->classMap = $classMap; 175 | } 176 | } 177 | 178 | /** 179 | * Registers a set of PSR-0 directories for a given prefix, either 180 | * appending or prepending to the ones previously set for this prefix. 181 | * 182 | * @param string $prefix The prefix 183 | * @param string[]|string $paths The PSR-0 root directories 184 | * @param bool $prepend Whether to prepend the directories 185 | * 186 | * @return void 187 | */ 188 | public function add($prefix, $paths, $prepend = false) 189 | { 190 | if (!$prefix) { 191 | if ($prepend) { 192 | $this->fallbackDirsPsr0 = array_merge( 193 | (array) $paths, 194 | $this->fallbackDirsPsr0 195 | ); 196 | } else { 197 | $this->fallbackDirsPsr0 = array_merge( 198 | $this->fallbackDirsPsr0, 199 | (array) $paths 200 | ); 201 | } 202 | 203 | return; 204 | } 205 | 206 | $first = $prefix[0]; 207 | if (!isset($this->prefixesPsr0[$first][$prefix])) { 208 | $this->prefixesPsr0[$first][$prefix] = (array) $paths; 209 | 210 | return; 211 | } 212 | if ($prepend) { 213 | $this->prefixesPsr0[$first][$prefix] = array_merge( 214 | (array) $paths, 215 | $this->prefixesPsr0[$first][$prefix] 216 | ); 217 | } else { 218 | $this->prefixesPsr0[$first][$prefix] = array_merge( 219 | $this->prefixesPsr0[$first][$prefix], 220 | (array) $paths 221 | ); 222 | } 223 | } 224 | 225 | /** 226 | * Registers a set of PSR-4 directories for a given namespace, either 227 | * appending or prepending to the ones previously set for this namespace. 228 | * 229 | * @param string $prefix The prefix/namespace, with trailing '\\' 230 | * @param string[]|string $paths The PSR-4 base directories 231 | * @param bool $prepend Whether to prepend the directories 232 | * 233 | * @throws \InvalidArgumentException 234 | * 235 | * @return void 236 | */ 237 | public function addPsr4($prefix, $paths, $prepend = false) 238 | { 239 | if (!$prefix) { 240 | // Register directories for the root namespace. 241 | if ($prepend) { 242 | $this->fallbackDirsPsr4 = array_merge( 243 | (array) $paths, 244 | $this->fallbackDirsPsr4 245 | ); 246 | } else { 247 | $this->fallbackDirsPsr4 = array_merge( 248 | $this->fallbackDirsPsr4, 249 | (array) $paths 250 | ); 251 | } 252 | } elseif (!isset($this->prefixDirsPsr4[$prefix])) { 253 | // Register directories for a new namespace. 254 | $length = strlen($prefix); 255 | if ('\\' !== $prefix[$length - 1]) { 256 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 257 | } 258 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 259 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 260 | } elseif ($prepend) { 261 | // Prepend directories for an already registered namespace. 262 | $this->prefixDirsPsr4[$prefix] = array_merge( 263 | (array) $paths, 264 | $this->prefixDirsPsr4[$prefix] 265 | ); 266 | } else { 267 | // Append directories for an already registered namespace. 268 | $this->prefixDirsPsr4[$prefix] = array_merge( 269 | $this->prefixDirsPsr4[$prefix], 270 | (array) $paths 271 | ); 272 | } 273 | } 274 | 275 | /** 276 | * Registers a set of PSR-0 directories for a given prefix, 277 | * replacing any others previously set for this prefix. 278 | * 279 | * @param string $prefix The prefix 280 | * @param string[]|string $paths The PSR-0 base directories 281 | * 282 | * @return void 283 | */ 284 | public function set($prefix, $paths) 285 | { 286 | if (!$prefix) { 287 | $this->fallbackDirsPsr0 = (array) $paths; 288 | } else { 289 | $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; 290 | } 291 | } 292 | 293 | /** 294 | * Registers a set of PSR-4 directories for a given namespace, 295 | * replacing any others previously set for this namespace. 296 | * 297 | * @param string $prefix The prefix/namespace, with trailing '\\' 298 | * @param string[]|string $paths The PSR-4 base directories 299 | * 300 | * @throws \InvalidArgumentException 301 | * 302 | * @return void 303 | */ 304 | public function setPsr4($prefix, $paths) 305 | { 306 | if (!$prefix) { 307 | $this->fallbackDirsPsr4 = (array) $paths; 308 | } else { 309 | $length = strlen($prefix); 310 | if ('\\' !== $prefix[$length - 1]) { 311 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 312 | } 313 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 314 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 315 | } 316 | } 317 | 318 | /** 319 | * Turns on searching the include path for class files. 320 | * 321 | * @param bool $useIncludePath 322 | * 323 | * @return void 324 | */ 325 | public function setUseIncludePath($useIncludePath) 326 | { 327 | $this->useIncludePath = $useIncludePath; 328 | } 329 | 330 | /** 331 | * Can be used to check if the autoloader uses the include path to check 332 | * for classes. 333 | * 334 | * @return bool 335 | */ 336 | public function getUseIncludePath() 337 | { 338 | return $this->useIncludePath; 339 | } 340 | 341 | /** 342 | * Turns off searching the prefix and fallback directories for classes 343 | * that have not been registered with the class map. 344 | * 345 | * @param bool $classMapAuthoritative 346 | * 347 | * @return void 348 | */ 349 | public function setClassMapAuthoritative($classMapAuthoritative) 350 | { 351 | $this->classMapAuthoritative = $classMapAuthoritative; 352 | } 353 | 354 | /** 355 | * Should class lookup fail if not found in the current class map? 356 | * 357 | * @return bool 358 | */ 359 | public function isClassMapAuthoritative() 360 | { 361 | return $this->classMapAuthoritative; 362 | } 363 | 364 | /** 365 | * APCu prefix to use to cache found/not-found classes, if the extension is enabled. 366 | * 367 | * @param string|null $apcuPrefix 368 | * 369 | * @return void 370 | */ 371 | public function setApcuPrefix($apcuPrefix) 372 | { 373 | $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; 374 | } 375 | 376 | /** 377 | * The APCu prefix in use, or null if APCu caching is not enabled. 378 | * 379 | * @return string|null 380 | */ 381 | public function getApcuPrefix() 382 | { 383 | return $this->apcuPrefix; 384 | } 385 | 386 | /** 387 | * Registers this instance as an autoloader. 388 | * 389 | * @param bool $prepend Whether to prepend the autoloader or not 390 | * 391 | * @return void 392 | */ 393 | public function register($prepend = false) 394 | { 395 | spl_autoload_register(array($this, 'loadClass'), true, $prepend); 396 | 397 | if (null === $this->vendorDir) { 398 | return; 399 | } 400 | 401 | if ($prepend) { 402 | self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; 403 | } else { 404 | unset(self::$registeredLoaders[$this->vendorDir]); 405 | self::$registeredLoaders[$this->vendorDir] = $this; 406 | } 407 | } 408 | 409 | /** 410 | * Unregisters this instance as an autoloader. 411 | * 412 | * @return void 413 | */ 414 | public function unregister() 415 | { 416 | spl_autoload_unregister(array($this, 'loadClass')); 417 | 418 | if (null !== $this->vendorDir) { 419 | unset(self::$registeredLoaders[$this->vendorDir]); 420 | } 421 | } 422 | 423 | /** 424 | * Loads the given class or interface. 425 | * 426 | * @param string $class The name of the class 427 | * @return true|null True if loaded, null otherwise 428 | */ 429 | public function loadClass($class) 430 | { 431 | if ($file = $this->findFile($class)) { 432 | $includeFile = self::$includeFile; 433 | $includeFile($file); 434 | 435 | return true; 436 | } 437 | 438 | return null; 439 | } 440 | 441 | /** 442 | * Finds the path to the file where the class is defined. 443 | * 444 | * @param string $class The name of the class 445 | * 446 | * @return string|false The path if found, false otherwise 447 | */ 448 | public function findFile($class) 449 | { 450 | // class map lookup 451 | if (isset($this->classMap[$class])) { 452 | return $this->classMap[$class]; 453 | } 454 | if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { 455 | return false; 456 | } 457 | if (null !== $this->apcuPrefix) { 458 | $file = apcu_fetch($this->apcuPrefix.$class, $hit); 459 | if ($hit) { 460 | return $file; 461 | } 462 | } 463 | 464 | $file = $this->findFileWithExtension($class, '.php'); 465 | 466 | // Search for Hack files if we are running on HHVM 467 | if (false === $file && defined('HHVM_VERSION')) { 468 | $file = $this->findFileWithExtension($class, '.hh'); 469 | } 470 | 471 | if (null !== $this->apcuPrefix) { 472 | apcu_add($this->apcuPrefix.$class, $file); 473 | } 474 | 475 | if (false === $file) { 476 | // Remember that this class does not exist. 477 | $this->missingClasses[$class] = true; 478 | } 479 | 480 | return $file; 481 | } 482 | 483 | /** 484 | * Returns the currently registered loaders indexed by their corresponding vendor directories. 485 | * 486 | * @return self[] 487 | */ 488 | public static function getRegisteredLoaders() 489 | { 490 | return self::$registeredLoaders; 491 | } 492 | 493 | /** 494 | * @param string $class 495 | * @param string $ext 496 | * @return string|false 497 | */ 498 | private function findFileWithExtension($class, $ext) 499 | { 500 | // PSR-4 lookup 501 | $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; 502 | 503 | $first = $class[0]; 504 | if (isset($this->prefixLengthsPsr4[$first])) { 505 | $subPath = $class; 506 | while (false !== $lastPos = strrpos($subPath, '\\')) { 507 | $subPath = substr($subPath, 0, $lastPos); 508 | $search = $subPath . '\\'; 509 | if (isset($this->prefixDirsPsr4[$search])) { 510 | $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); 511 | foreach ($this->prefixDirsPsr4[$search] as $dir) { 512 | if (file_exists($file = $dir . $pathEnd)) { 513 | return $file; 514 | } 515 | } 516 | } 517 | } 518 | } 519 | 520 | // PSR-4 fallback dirs 521 | foreach ($this->fallbackDirsPsr4 as $dir) { 522 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { 523 | return $file; 524 | } 525 | } 526 | 527 | // PSR-0 lookup 528 | if (false !== $pos = strrpos($class, '\\')) { 529 | // namespaced class name 530 | $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) 531 | . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); 532 | } else { 533 | // PEAR-like class name 534 | $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; 535 | } 536 | 537 | if (isset($this->prefixesPsr0[$first])) { 538 | foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { 539 | if (0 === strpos($class, $prefix)) { 540 | foreach ($dirs as $dir) { 541 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 542 | return $file; 543 | } 544 | } 545 | } 546 | } 547 | } 548 | 549 | // PSR-0 fallback dirs 550 | foreach ($this->fallbackDirsPsr0 as $dir) { 551 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 552 | return $file; 553 | } 554 | } 555 | 556 | // PSR-0 include paths. 557 | if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { 558 | return $file; 559 | } 560 | 561 | return false; 562 | } 563 | 564 | /** 565 | * @return void 566 | */ 567 | private static function initializeIncludeClosure() 568 | { 569 | if (self::$includeFile !== null) { 570 | return; 571 | } 572 | 573 | /** 574 | * Scope isolated include. 575 | * 576 | * Prevents access to $this/self from included files. 577 | * 578 | * @param string $file 579 | * @return void 580 | */ 581 | self::$includeFile = \Closure::bind(static function($file) { 582 | include $file; 583 | }, null, null); 584 | } 585 | } 586 | -------------------------------------------------------------------------------- /vendor/composer/InstalledVersions.php: -------------------------------------------------------------------------------- 1 | 7 | * Jordi Boggiano 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Composer; 14 | 15 | use Composer\Autoload\ClassLoader; 16 | use Composer\Semver\VersionParser; 17 | 18 | /** 19 | * This class is copied in every Composer installed project and available to all 20 | * 21 | * See also https://getcomposer.org/doc/07-runtime.md#installed-versions 22 | * 23 | * To require its presence, you can require `composer-runtime-api ^2.0` 24 | * 25 | * @final 26 | */ 27 | class InstalledVersions 28 | { 29 | /** 30 | * @var mixed[]|null 31 | * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null 32 | */ 33 | private static $installed; 34 | 35 | /** 36 | * @var bool|null 37 | */ 38 | private static $canGetVendors; 39 | 40 | /** 41 | * @var array[] 42 | * @psalm-var array}> 43 | */ 44 | private static $installedByVendor = array(); 45 | 46 | /** 47 | * Returns a list of all package names which are present, either by being installed, replaced or provided 48 | * 49 | * @return string[] 50 | * @psalm-return list 51 | */ 52 | public static function getInstalledPackages() 53 | { 54 | $packages = array(); 55 | foreach (self::getInstalled() as $installed) { 56 | $packages[] = array_keys($installed['versions']); 57 | } 58 | 59 | if (1 === \count($packages)) { 60 | return $packages[0]; 61 | } 62 | 63 | return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); 64 | } 65 | 66 | /** 67 | * Returns a list of all package names with a specific type e.g. 'library' 68 | * 69 | * @param string $type 70 | * @return string[] 71 | * @psalm-return list 72 | */ 73 | public static function getInstalledPackagesByType($type) 74 | { 75 | $packagesByType = array(); 76 | 77 | foreach (self::getInstalled() as $installed) { 78 | foreach ($installed['versions'] as $name => $package) { 79 | if (isset($package['type']) && $package['type'] === $type) { 80 | $packagesByType[] = $name; 81 | } 82 | } 83 | } 84 | 85 | return $packagesByType; 86 | } 87 | 88 | /** 89 | * Checks whether the given package is installed 90 | * 91 | * This also returns true if the package name is provided or replaced by another package 92 | * 93 | * @param string $packageName 94 | * @param bool $includeDevRequirements 95 | * @return bool 96 | */ 97 | public static function isInstalled($packageName, $includeDevRequirements = true) 98 | { 99 | foreach (self::getInstalled() as $installed) { 100 | if (isset($installed['versions'][$packageName])) { 101 | return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; 102 | } 103 | } 104 | 105 | return false; 106 | } 107 | 108 | /** 109 | * Checks whether the given package satisfies a version constraint 110 | * 111 | * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: 112 | * 113 | * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') 114 | * 115 | * @param VersionParser $parser Install composer/semver to have access to this class and functionality 116 | * @param string $packageName 117 | * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package 118 | * @return bool 119 | */ 120 | public static function satisfies(VersionParser $parser, $packageName, $constraint) 121 | { 122 | $constraint = $parser->parseConstraints((string) $constraint); 123 | $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); 124 | 125 | return $provided->matches($constraint); 126 | } 127 | 128 | /** 129 | * Returns a version constraint representing all the range(s) which are installed for a given package 130 | * 131 | * It is easier to use this via isInstalled() with the $constraint argument if you need to check 132 | * whether a given version of a package is installed, and not just whether it exists 133 | * 134 | * @param string $packageName 135 | * @return string Version constraint usable with composer/semver 136 | */ 137 | public static function getVersionRanges($packageName) 138 | { 139 | foreach (self::getInstalled() as $installed) { 140 | if (!isset($installed['versions'][$packageName])) { 141 | continue; 142 | } 143 | 144 | $ranges = array(); 145 | if (isset($installed['versions'][$packageName]['pretty_version'])) { 146 | $ranges[] = $installed['versions'][$packageName]['pretty_version']; 147 | } 148 | if (array_key_exists('aliases', $installed['versions'][$packageName])) { 149 | $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); 150 | } 151 | if (array_key_exists('replaced', $installed['versions'][$packageName])) { 152 | $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); 153 | } 154 | if (array_key_exists('provided', $installed['versions'][$packageName])) { 155 | $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); 156 | } 157 | 158 | return implode(' || ', $ranges); 159 | } 160 | 161 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 162 | } 163 | 164 | /** 165 | * @param string $packageName 166 | * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present 167 | */ 168 | public static function getVersion($packageName) 169 | { 170 | foreach (self::getInstalled() as $installed) { 171 | if (!isset($installed['versions'][$packageName])) { 172 | continue; 173 | } 174 | 175 | if (!isset($installed['versions'][$packageName]['version'])) { 176 | return null; 177 | } 178 | 179 | return $installed['versions'][$packageName]['version']; 180 | } 181 | 182 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 183 | } 184 | 185 | /** 186 | * @param string $packageName 187 | * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present 188 | */ 189 | public static function getPrettyVersion($packageName) 190 | { 191 | foreach (self::getInstalled() as $installed) { 192 | if (!isset($installed['versions'][$packageName])) { 193 | continue; 194 | } 195 | 196 | if (!isset($installed['versions'][$packageName]['pretty_version'])) { 197 | return null; 198 | } 199 | 200 | return $installed['versions'][$packageName]['pretty_version']; 201 | } 202 | 203 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 204 | } 205 | 206 | /** 207 | * @param string $packageName 208 | * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference 209 | */ 210 | public static function getReference($packageName) 211 | { 212 | foreach (self::getInstalled() as $installed) { 213 | if (!isset($installed['versions'][$packageName])) { 214 | continue; 215 | } 216 | 217 | if (!isset($installed['versions'][$packageName]['reference'])) { 218 | return null; 219 | } 220 | 221 | return $installed['versions'][$packageName]['reference']; 222 | } 223 | 224 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 225 | } 226 | 227 | /** 228 | * @param string $packageName 229 | * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. 230 | */ 231 | public static function getInstallPath($packageName) 232 | { 233 | foreach (self::getInstalled() as $installed) { 234 | if (!isset($installed['versions'][$packageName])) { 235 | continue; 236 | } 237 | 238 | return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; 239 | } 240 | 241 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 242 | } 243 | 244 | /** 245 | * @return array 246 | * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} 247 | */ 248 | public static function getRootPackage() 249 | { 250 | $installed = self::getInstalled(); 251 | 252 | return $installed[0]['root']; 253 | } 254 | 255 | /** 256 | * Returns the raw installed.php data for custom implementations 257 | * 258 | * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. 259 | * @return array[] 260 | * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} 261 | */ 262 | public static function getRawData() 263 | { 264 | @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); 265 | 266 | if (null === self::$installed) { 267 | // only require the installed.php file if this file is loaded from its dumped location, 268 | // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 269 | if (substr(__DIR__, -8, 1) !== 'C') { 270 | self::$installed = include __DIR__ . '/installed.php'; 271 | } else { 272 | self::$installed = array(); 273 | } 274 | } 275 | 276 | return self::$installed; 277 | } 278 | 279 | /** 280 | * Returns the raw data of all installed.php which are currently loaded for custom implementations 281 | * 282 | * @return array[] 283 | * @psalm-return list}> 284 | */ 285 | public static function getAllRawData() 286 | { 287 | return self::getInstalled(); 288 | } 289 | 290 | /** 291 | * Lets you reload the static array from another file 292 | * 293 | * This is only useful for complex integrations in which a project needs to use 294 | * this class but then also needs to execute another project's autoloader in process, 295 | * and wants to ensure both projects have access to their version of installed.php. 296 | * 297 | * A typical case would be PHPUnit, where it would need to make sure it reads all 298 | * the data it needs from this class, then call reload() with 299 | * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure 300 | * the project in which it runs can then also use this class safely, without 301 | * interference between PHPUnit's dependencies and the project's dependencies. 302 | * 303 | * @param array[] $data A vendor/composer/installed.php data set 304 | * @return void 305 | * 306 | * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data 307 | */ 308 | public static function reload($data) 309 | { 310 | self::$installed = $data; 311 | self::$installedByVendor = array(); 312 | } 313 | 314 | /** 315 | * @return array[] 316 | * @psalm-return list}> 317 | */ 318 | private static function getInstalled() 319 | { 320 | if (null === self::$canGetVendors) { 321 | self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); 322 | } 323 | 324 | $installed = array(); 325 | 326 | if (self::$canGetVendors) { 327 | foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { 328 | if (isset(self::$installedByVendor[$vendorDir])) { 329 | $installed[] = self::$installedByVendor[$vendorDir]; 330 | } elseif (is_file($vendorDir.'/composer/installed.php')) { 331 | /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ 332 | $required = require $vendorDir.'/composer/installed.php'; 333 | $installed[] = self::$installedByVendor[$vendorDir] = $required; 334 | if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { 335 | self::$installed = $installed[count($installed) - 1]; 336 | } 337 | } 338 | } 339 | } 340 | 341 | if (null === self::$installed) { 342 | // only require the installed.php file if this file is loaded from its dumped location, 343 | // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 344 | if (substr(__DIR__, -8, 1) !== 'C') { 345 | /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ 346 | $required = require __DIR__ . '/installed.php'; 347 | self::$installed = $required; 348 | } else { 349 | self::$installed = array(); 350 | } 351 | } 352 | 353 | if (self::$installed !== array()) { 354 | $installed[] = self::$installed; 355 | } 356 | 357 | return $installed; 358 | } 359 | } 360 | -------------------------------------------------------------------------------- /vendor/composer/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) Nils Adermann, Jordi Boggiano 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is furnished 9 | to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /vendor/composer/autoload_classmap.php: -------------------------------------------------------------------------------- 1 | $vendorDir . '/composer/InstalledVersions.php', 10 | 'Grav\\Plugin\\ReadingTimePlugin' => $baseDir . '/readingtime.php', 11 | ); 12 | -------------------------------------------------------------------------------- /vendor/composer/autoload_namespaces.php: -------------------------------------------------------------------------------- 1 | array($baseDir . '/classes'), 10 | ); 11 | -------------------------------------------------------------------------------- /vendor/composer/autoload_real.php: -------------------------------------------------------------------------------- 1 | register(true); 35 | 36 | return $loader; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /vendor/composer/autoload_static.php: -------------------------------------------------------------------------------- 1 | 11 | array ( 12 | 'Grav\\Plugin\\ReadingTime\\' => 24, 13 | ), 14 | ); 15 | 16 | public static $prefixDirsPsr4 = array ( 17 | 'Grav\\Plugin\\ReadingTime\\' => 18 | array ( 19 | 0 => __DIR__ . '/../..' . '/classes', 20 | ), 21 | ); 22 | 23 | public static $classMap = array ( 24 | 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 25 | 'Grav\\Plugin\\ReadingTimePlugin' => __DIR__ . '/../..' . '/readingtime.php', 26 | ); 27 | 28 | public static function getInitializer(ClassLoader $loader) 29 | { 30 | return \Closure::bind(function () use ($loader) { 31 | $loader->prefixLengthsPsr4 = ComposerStaticInitba8cc29b2b127a27a57127d33945f8aa::$prefixLengthsPsr4; 32 | $loader->prefixDirsPsr4 = ComposerStaticInitba8cc29b2b127a27a57127d33945f8aa::$prefixDirsPsr4; 33 | $loader->classMap = ComposerStaticInitba8cc29b2b127a27a57127d33945f8aa::$classMap; 34 | 35 | }, null, ClassLoader::class); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /vendor/composer/installed.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [], 3 | "dev": true, 4 | "dev-package-names": [] 5 | } 6 | -------------------------------------------------------------------------------- /vendor/composer/installed.php: -------------------------------------------------------------------------------- 1 | array( 3 | 'name' => 'getgrav/grav-plugin-readingtime', 4 | 'pretty_version' => 'dev-develop', 5 | 'version' => 'dev-develop', 6 | 'reference' => 'd4d840380af104fa5d31deb6822431a32a2a62a3', 7 | 'type' => 'grav-plugin', 8 | 'install_path' => __DIR__ . '/../../', 9 | 'aliases' => array(), 10 | 'dev' => true, 11 | ), 12 | 'versions' => array( 13 | 'getgrav/grav-plugin-readingtime' => array( 14 | 'pretty_version' => 'dev-develop', 15 | 'version' => 'dev-develop', 16 | 'reference' => 'd4d840380af104fa5d31deb6822431a32a2a62a3', 17 | 'type' => 'grav-plugin', 18 | 'install_path' => __DIR__ . '/../../', 19 | 'aliases' => array(), 20 | 'dev_requirement' => false, 21 | ), 22 | ), 23 | ); 24 | -------------------------------------------------------------------------------- /vendor/composer/platform_check.php: -------------------------------------------------------------------------------- 1 | = 70103)) { 8 | $issues[] = 'Your Composer dependencies require a PHP version ">= 7.1.3". You are running ' . PHP_VERSION . '.'; 9 | } 10 | 11 | if ($issues) { 12 | if (!headers_sent()) { 13 | header('HTTP/1.1 500 Internal Server Error'); 14 | } 15 | if (!ini_get('display_errors')) { 16 | if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { 17 | fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); 18 | } elseif (!headers_sent()) { 19 | echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; 20 | } 21 | } 22 | trigger_error( 23 | 'Composer detected issues in your platform: ' . implode(' ', $issues), 24 | E_USER_ERROR 25 | ); 26 | } 27 | --------------------------------------------------------------------------------