├── 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 |
--------------------------------------------------------------------------------