Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7960678db | ||
|
|
2d7fac02bb | ||
|
|
1141a4e382 | ||
|
|
392aec8529 | ||
|
|
f9487a3336 | ||
|
|
305f2cc0dd | ||
|
|
bde7a6a279 | ||
|
|
88bbf916b5 | ||
|
|
19bc5c38dc | ||
|
|
2c768ff5d4 | ||
|
|
36f60cbb3c | ||
|
|
356030865a | ||
|
|
4a1cd76cf5 | ||
|
|
2bf960028e | ||
|
|
7f01dcb9fa | ||
|
|
1c02eabb7c | ||
|
|
b2f0562a7a | ||
|
|
5fab953fbd | ||
|
|
abe0ae741f | ||
|
|
55238aa169 | ||
|
|
fdb3658cd0 | ||
|
|
bfddb60d6d | ||
|
|
aee28b3450 | ||
|
|
db87a5d45f | ||
|
|
6efcb252e1 | ||
|
|
0c54185f1f | ||
|
|
b325fff158 | ||
|
|
6d3092c736 | ||
|
|
236bdc258d | ||
|
|
8721901494 | ||
|
|
a34a951b97 | ||
|
|
0c48352677 | ||
|
|
d6ae53bba4 | ||
|
|
7c0f6298da | ||
|
|
98d9dc2ad8 | ||
|
|
36ff41a474 |
No files matched your search
@@ -0,0 +1 @@
|
|||||||
|
github: Mattitja AB
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
name: Bug Report
|
||||||
|
description: Report an Issue or Bug with the Package
|
||||||
|
title: "[Bug]: "
|
||||||
|
labels: ["bug"]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
We're sorry to hear you have a problem. Can you help us solve it by providing the following details.
|
||||||
|
- type: textarea
|
||||||
|
id: what-happened
|
||||||
|
attributes:
|
||||||
|
label: What happened?
|
||||||
|
description: What did you expect to happen?
|
||||||
|
placeholder: I cannot currently do X thing because when I do, it breaks X thing.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: how-to-reproduce
|
||||||
|
attributes:
|
||||||
|
label: How to reproduce the bug
|
||||||
|
description: How did this occur, please add any config values used and provide a set of reliable steps if possible.
|
||||||
|
placeholder: When I do X I see Y.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: package-version
|
||||||
|
attributes:
|
||||||
|
label: Package Version
|
||||||
|
description: What version of our Package are you running? Please be as specific as possible
|
||||||
|
placeholder: 2.0.0
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: php-version
|
||||||
|
attributes:
|
||||||
|
label: PHP Version
|
||||||
|
description: What version of PHP are you running? Please be as specific as possible
|
||||||
|
placeholder: 8.2.0
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: laravel-version
|
||||||
|
attributes:
|
||||||
|
label: Laravel Version
|
||||||
|
description: What version of Laravel are you running? Please be as specific as possible
|
||||||
|
placeholder: 9.0.0
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: operating-systems
|
||||||
|
attributes:
|
||||||
|
label: Which operating systems does this happen with?
|
||||||
|
description: You may select more than one.
|
||||||
|
multiple: true
|
||||||
|
options:
|
||||||
|
- macOS
|
||||||
|
- Windows
|
||||||
|
- Linux
|
||||||
|
- type: textarea
|
||||||
|
id: notes
|
||||||
|
attributes:
|
||||||
|
label: Notes
|
||||||
|
description: Use this field to provide any other notes that you feel might be relevant to the issue.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: Ask a question
|
||||||
|
url: https://github.com/mattitjaab/laravel-cision-feed/discussions/new?category=q-a
|
||||||
|
about: Ask the community for help
|
||||||
|
- name: Request a feature
|
||||||
|
url: https://github.com/mattitjaab/laravel-cision-feed/discussions/new?category=ideas
|
||||||
|
about: Share ideas for new features
|
||||||
|
- name: Report a security issue
|
||||||
|
url: https://github.com/mattitjaab/laravel-cision-feed/security/policy
|
||||||
|
about: Learn how to notify us for sensitive bugs
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Please see the documentation for all configuration options:
|
||||||
|
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
labels:
|
||||||
|
- "dependencies"
|
||||||
|
|
||||||
|
- package-ecosystem: "composer"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
labels:
|
||||||
|
- "dependencies"
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
name: dependabot-auto-merge
|
||||||
|
on: pull_request_target
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
dependabot:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 5
|
||||||
|
if: ${{ github.actor == 'dependabot[bot]' }}
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- name: Dependabot metadata
|
||||||
|
id: metadata
|
||||||
|
uses: dependabot/fetch-metadata@v2.5.0
|
||||||
|
with:
|
||||||
|
github-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
|
|
||||||
|
- name: Auto-merge Dependabot PRs for semver-minor updates
|
||||||
|
if: ${{steps.metadata.outputs.update-type == 'version-update:semver-minor'}}
|
||||||
|
run: gh pr merge --auto --merge "$PR_URL"
|
||||||
|
env:
|
||||||
|
PR_URL: ${{github.event.pull_request.html_url}}
|
||||||
|
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||||
|
|
||||||
|
- name: Auto-merge Dependabot PRs for semver-patch updates
|
||||||
|
if: ${{steps.metadata.outputs.update-type == 'version-update:semver-patch'}}
|
||||||
|
run: gh pr merge --auto --merge "$PR_URL"
|
||||||
|
env:
|
||||||
|
PR_URL: ${{github.event.pull_request.html_url}}
|
||||||
|
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
name: Fix PHP code style issues
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- '**.php'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
php-code-styling:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 5
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.head_ref }}
|
||||||
|
|
||||||
|
- name: Fix PHP code style issues
|
||||||
|
uses: aglipanci/laravel-pint-action@2.6
|
||||||
|
|
||||||
|
- name: Commit changes
|
||||||
|
uses: stefanzweifel/git-auto-commit-action@v6
|
||||||
|
with:
|
||||||
|
commit_message: Fix styling
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
name: PHPStan
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- '**.php'
|
||||||
|
- 'phpstan.neon.dist'
|
||||||
|
- '.github/workflows/phpstan.yml'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
phpstan:
|
||||||
|
name: phpstan
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 5
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: '8.4'
|
||||||
|
coverage: none
|
||||||
|
|
||||||
|
- name: Install composer dependencies
|
||||||
|
uses: ramsey/composer-install@v3
|
||||||
|
|
||||||
|
- name: Run PHPStan
|
||||||
|
run: ./vendor/bin/phpstan --error-format=github
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
name: run-tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- '**.php'
|
||||||
|
- '.github/workflows/run-tests.yml'
|
||||||
|
- 'phpunit.xml.dist'
|
||||||
|
- 'composer.json'
|
||||||
|
- 'composer.lock'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
timeout-minutes: 5
|
||||||
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest]
|
||||||
|
php: [8.3, 8.4]
|
||||||
|
laravel: [11.*, 12.*]
|
||||||
|
stability: [prefer-lowest, prefer-stable]
|
||||||
|
include:
|
||||||
|
- laravel: 11.*
|
||||||
|
php: 8.3
|
||||||
|
testbench: 9.*
|
||||||
|
carbon: ^2.63
|
||||||
|
- laravel: 11.*
|
||||||
|
php: 8.4
|
||||||
|
testbench: 9.*
|
||||||
|
carbon: ^2.63
|
||||||
|
- laravel: 12.*
|
||||||
|
php: 8.3
|
||||||
|
testbench: 10.*
|
||||||
|
carbon: ^3.8.4
|
||||||
|
- laravel: 12.*
|
||||||
|
php: 8.4
|
||||||
|
testbench: 10.*
|
||||||
|
carbon: ^3.8.4
|
||||||
|
|
||||||
|
name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php }}
|
||||||
|
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
|
||||||
|
coverage: none
|
||||||
|
|
||||||
|
- name: Setup problem matchers
|
||||||
|
run: |
|
||||||
|
echo "::add-matcher::${{ runner.tool_cache }}/php.json"
|
||||||
|
echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
|
||||||
|
|
||||||
|
- name: Remove Larastan if Laravel 12
|
||||||
|
if: startsWith(matrix.laravel, '12.')
|
||||||
|
run: composer remove larastan/larastan --dev --no-update || true
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
composer require \
|
||||||
|
"laravel/framework:${{ matrix.laravel }}" \
|
||||||
|
"orchestra/testbench:${{ matrix.testbench }}" \
|
||||||
|
"nesbot/carbon:${{ matrix.carbon }}" \
|
||||||
|
--no-interaction --no-update
|
||||||
|
|
||||||
|
composer update --${{ matrix.stability }} --prefer-dist --no-interaction
|
||||||
|
|
||||||
|
- name: List Installed Dependencies
|
||||||
|
run: composer show -D
|
||||||
|
|
||||||
|
- name: Execute tests
|
||||||
|
run: vendor/bin/pest --ci
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
name: "Update Changelog"
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [released]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 5
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: main
|
||||||
|
|
||||||
|
- name: Update Changelog
|
||||||
|
uses: stefanzweifel/changelog-updater-action@v1
|
||||||
|
with:
|
||||||
|
latest-version: ${{ github.event.release.name }}
|
||||||
|
release-notes: ${{ github.event.release.body }}
|
||||||
|
|
||||||
|
- name: Commit updated CHANGELOG
|
||||||
|
uses: stefanzweifel/git-auto-commit-action@v6
|
||||||
|
with:
|
||||||
|
branch: main
|
||||||
|
commit_message: Update CHANGELOG
|
||||||
|
file_pattern: CHANGELOG.md
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
vendor
|
||||||
|
composer.lock
|
||||||
|
.phpunit.cache
|
||||||
|
build
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to `laravel-cision-feed` will be documented in this file.
|
||||||
|
|
||||||
|
## v2.1 - 2025-07-04
|
||||||
|
|
||||||
|
**Full Changelog**: https://github.com/MattitjaAB/laravel-cision-feed/compare/v2.0...v2.1
|
||||||
|
|
||||||
|
## v2.0 - 2025-07-04
|
||||||
|
|
||||||
|
### What's Changed
|
||||||
|
|
||||||
|
* 2.0 by @mathiaspalmqvist in https://github.com/MattitjaAB/laravel-cision-feed/pull/1
|
||||||
|
* Bump stefanzweifel/git-auto-commit-action from 5 to 6 by @dependabot in https://github.com/MattitjaAB/laravel-cision-feed/pull/2
|
||||||
|
|
||||||
|
### New Contributors
|
||||||
|
|
||||||
|
* @mathiaspalmqvist made their first contribution in https://github.com/MattitjaAB/laravel-cision-feed/pull/1
|
||||||
|
* @dependabot made their first contribution in https://github.com/MattitjaAB/laravel-cision-feed/pull/2
|
||||||
|
|
||||||
|
**Full Changelog**: https://github.com/MattitjaAB/laravel-cision-feed/compare/1.0.2...v2.0
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) Nils Adermann, Jordi Boggiano
|
Copyright (c) Mattitja AB <hej@mattitja.se>
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
in the Software without restriction, including without limitation the rights
|
in the Software without restriction, including without limitation the rights
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
copies of the Software, and to permit persons to whom the Software is furnished
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
to do so, subject to the following conditions:
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
The above copyright notice and this permission notice shall be included in
|
||||||
copies or substantial portions of the Software.
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
@@ -18,4 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
|
|
||||||
+66
-9
@@ -1,24 +1,81 @@
|
|||||||
{
|
{
|
||||||
"name": "mathiaspalmqvist/laravel-cision-feed",
|
"name": "mattitja/laravel-cision-feed",
|
||||||
"description": "Fetches press releases and financial reports from any public company on cision.",
|
"description": "Fetch and parse Cision press releases via RSS and article HTML in Laravel.",
|
||||||
|
"keywords": [
|
||||||
|
"laravel",
|
||||||
|
"cision",
|
||||||
|
"press releases",
|
||||||
|
"rss",
|
||||||
|
"html parsing",
|
||||||
|
"package"
|
||||||
|
],
|
||||||
|
"homepage": "https://github.com/mattitjaab/laravel-cision-feed",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
"name": "Mathias Palmqvist",
|
"name": "Mattitja AB",
|
||||||
"email": "mathias@mattitja.se"
|
"email": "hej@mattitja.se",
|
||||||
|
"role": "Developer"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {},
|
"require": {
|
||||||
|
"php": "^8.3",
|
||||||
|
"illuminate/contracts": "^10.0 || ^11.0 || ^12.0",
|
||||||
|
"spatie/laravel-package-tools": "^1.16",
|
||||||
|
"symfony/css-selector": "^6.4 || ^7.0 || ^8.0",
|
||||||
|
"symfony/dom-crawler": "^6.4 || ^7.0 || ^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"orchestra/testbench": "^8.22 || ^9.0 || ^10.0",
|
||||||
|
"pestphp/pest": "^3.0",
|
||||||
|
"pestphp/pest-plugin-laravel": "^3.0",
|
||||||
|
"pestphp/pest-plugin-arch": "^3.0",
|
||||||
|
"laravel/pint": "^1.14",
|
||||||
|
"nunomaduro/collision": "^8.1 || ^7.10",
|
||||||
|
"larastan/larastan": "^2.9 || ^3.0",
|
||||||
|
"phpstan/phpstan-phpunit": "^1.3 || ^2.0",
|
||||||
|
"phpstan/phpstan-deprecation-rules": "^1.1 || ^2.0",
|
||||||
|
"phpstan/extension-installer": "^1.3 || ^2.0",
|
||||||
|
"spatie/laravel-ray": "^1.35"
|
||||||
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"Mathiaspalmqvist\\LaravelCisionFeed\\": "src/"
|
"Mattitja\\Cision\\": "src/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"Mattitja\\Cision\\Tests\\": "tests/",
|
||||||
|
"Workbench\\App\\": "workbench/app/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "vendor/bin/pest",
|
||||||
|
"test-coverage": "vendor/bin/pest --coverage",
|
||||||
|
"analyse": "vendor/bin/phpstan analyse",
|
||||||
|
"format": "vendor/bin/pint"
|
||||||
|
},
|
||||||
"extra": {
|
"extra": {
|
||||||
"laravel": {
|
"laravel": {
|
||||||
"providers": [
|
"providers": [
|
||||||
"Mathiaspalmqvist\\LaravelCisionFeed\\LaravelCisionFeedProvider"
|
"Mattitja\\Cision\\CisionServiceProvider"
|
||||||
]
|
],
|
||||||
|
"aliases": {
|
||||||
|
"Cision": "Mattitja\\Cision\\Facades\\Cision"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"config": {
|
||||||
|
"sort-packages": true,
|
||||||
|
"allow-plugins": {
|
||||||
|
"phpstan/extension-installer": true,
|
||||||
|
"pestphp/pest-plugin": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/mattitjaab/laravel-cision-feed/issues",
|
||||||
|
"source": "https://github.com/mattitjaab/laravel-cision-feed"
|
||||||
|
},
|
||||||
|
"minimum-stability": "dev",
|
||||||
|
"prefer-stable": true
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The Cision newsroom slug, e.g. "example-company" in
|
||||||
|
* https://news.cision.com/se/example-company
|
||||||
|
*/
|
||||||
|
'slug' => env('LARAVEL_CISION_FEED_SLUG'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Timezone used when formatting the "created_at" value of feed items.
|
||||||
|
*/
|
||||||
|
'timezone' => env('LARAVEL_CISION_FEED_TIMEZONE', 'Europe/Stockholm'),
|
||||||
|
|
||||||
|
];
|
||||||
Whitespace-only changes.
@@ -0,0 +1,10 @@
|
|||||||
|
includes:
|
||||||
|
- phpstan-baseline.neon
|
||||||
|
|
||||||
|
parameters:
|
||||||
|
level: 5
|
||||||
|
paths:
|
||||||
|
- src
|
||||||
|
tmpDir: build/phpstan
|
||||||
|
checkOctaneCompatibility: true
|
||||||
|
checkModelProperties: true
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<phpunit
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.3/phpunit.xsd"
|
||||||
|
backupGlobals="false"
|
||||||
|
bootstrap="vendor/autoload.php"
|
||||||
|
colors="true"
|
||||||
|
processIsolation="false"
|
||||||
|
stopOnFailure="false"
|
||||||
|
executionOrder="random"
|
||||||
|
failOnWarning="true"
|
||||||
|
failOnRisky="true"
|
||||||
|
failOnEmptyTestSuite="true"
|
||||||
|
beStrictAboutOutputDuringTests="true"
|
||||||
|
cacheDirectory=".phpunit.cache"
|
||||||
|
backupStaticProperties="false"
|
||||||
|
>
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="MattitjaAB Test Suite">
|
||||||
|
<directory>tests</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
<logging>
|
||||||
|
<junit outputFile="build/report.junit.xml"/>
|
||||||
|
</logging>
|
||||||
|
<source>
|
||||||
|
<include>
|
||||||
|
<directory suffix=".php">./src</directory>
|
||||||
|
</include>
|
||||||
|
</source>
|
||||||
|
</phpunit>
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
# Laravel Cision Feed
|
||||||
|
|
||||||
|
[](https://packagist.org/packages/mattitja/laravel-cision-feed)
|
||||||
|
[](https://github.com/mattitjaab/laravel-cision-feed/actions?query=workflow%3Arun-tests+branch%3Amain)
|
||||||
|
[](https://github.com/mattitjaab/laravel-cision-feed/actions?query=workflow%3A%22Fix+PHP+code+style+issues%22+branch%3Amain)
|
||||||
|
[](https://packagist.org/packages/mattitja/laravel-cision-feed)
|
||||||
|
|
||||||
|
A Laravel package for retrieving and parsing data from [Cision News](https://news.cision.com/se). Fetch press releases, financial reports, media posts, and structured article content using a simple and expressive API.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Retrieve Cision RSS feeds by type (Press, News, Financial, Media)
|
||||||
|
- Parse article pages and extract clean structured HTML
|
||||||
|
- Automatically handles encoding, formatting, and clean-up of content
|
||||||
|
- Fully testable and extensible
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer require mattitja/laravel-cision-feed
|
||||||
|
```
|
||||||
|
|
||||||
|
Set your Cision newsroom slug in your `.env`:
|
||||||
|
|
||||||
|
```
|
||||||
|
LARAVEL_CISION_FEED_SLUG=your-cision-slug
|
||||||
|
```
|
||||||
|
|
||||||
|
Optionally publish the config file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php artisan vendor:publish --tag="laravel-cision-feed-config"
|
||||||
|
```
|
||||||
|
|
||||||
|
Feed timestamps (`created_at`) are converted to `Europe/Stockholm` by default. Override with `LARAVEL_CISION_FEED_TIMEZONE`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```php
|
||||||
|
use Mattitja\Cision\Cision;
|
||||||
|
|
||||||
|
$cision = new Cision(); // uses config('cision.slug')
|
||||||
|
$cision = new Cision('other-company'); // or pass a slug explicitly
|
||||||
|
|
||||||
|
// Fetch RSS entries
|
||||||
|
$items = $cision->press(); // or ->news(), ->financial(), ->media()
|
||||||
|
|
||||||
|
// Fetch full content for a specific article
|
||||||
|
$article = $cision->fetchContent('https://news.cision.com/se/example-company/r/your-article-slug,c1234567');
|
||||||
|
|
||||||
|
echo $article['header']; // Article headline
|
||||||
|
echo $article['published_at']; // ISO timestamp
|
||||||
|
echo $article['content']; // Clean HTML
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use the facade:
|
||||||
|
|
||||||
|
```php
|
||||||
|
use Mattitja\Cision\Facades\Cision;
|
||||||
|
|
||||||
|
$items = Cision::press();
|
||||||
|
```
|
||||||
|
|
||||||
|
All methods return `false` if the request fails or the response cannot be parsed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer test
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
See [CHANGELOG](CHANGELOG.md) for recent changes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Contributions are welcome! Please open an issue or pull request.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
If you discover any security-related issues, please refer to our [security policy](../../security/policy).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
- [Mattitja AB](https://github.com/MattitjaAB)
|
||||||
|
- [All Contributors](../../contributors)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
The MIT License (MIT). See [LICENSE](LICENSE.md) for details.
|
||||||
Executable
+179
@@ -0,0 +1,179 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Mattitja\Cision;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Http\Client\Response;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Mattitja\Cision\Support\HtmlCleaner;
|
||||||
|
use Symfony\Component\DomCrawler\Crawler;
|
||||||
|
|
||||||
|
class Cision
|
||||||
|
{
|
||||||
|
protected string $slug;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new instance for the given newsroom slug. When no slug is
|
||||||
|
* given, the "cision.slug" config value is used.
|
||||||
|
*/
|
||||||
|
public function __construct(?string $slug = null)
|
||||||
|
{
|
||||||
|
$this->slug = (string) ($slug ?? config('cision.slug', ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function all(int $page = 1): array|false
|
||||||
|
{
|
||||||
|
return $this->fetch($page);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function press(int $page = 1): array|false
|
||||||
|
{
|
||||||
|
return $this->fetch($page, 'Press');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function financial(int $page = 1): array|false
|
||||||
|
{
|
||||||
|
return $this->fetch($page, 'Financial');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function news(int $page = 1): array|false
|
||||||
|
{
|
||||||
|
return $this->fetch($page, 'News');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function media(int $page = 1): array|false
|
||||||
|
{
|
||||||
|
return $this->fetch($page, 'Media');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function fetch(int $page = 1, ?string $type = null): array|false
|
||||||
|
{
|
||||||
|
$response = $this->makeRequest($page, $type);
|
||||||
|
|
||||||
|
if ($response === false || $response->failed()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->parseResponse($response->body(), $page);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch and parse the HTML content of a Cision article URL.
|
||||||
|
*/
|
||||||
|
public function fetchContent(string $url): array|false
|
||||||
|
{
|
||||||
|
$response = Http::get($url);
|
||||||
|
|
||||||
|
if ($response->failed() || trim($response->body()) === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$crawler = new Crawler($response->body());
|
||||||
|
|
||||||
|
$article = $crawler->filter('article')->first();
|
||||||
|
$heading = $article->filter('h1');
|
||||||
|
|
||||||
|
if ($article->count() === 0 || $heading->count() === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$header = trim($heading->text());
|
||||||
|
$time = $article->filter('time');
|
||||||
|
$publishedAt = $time->count() > 0 ? $time->attr('datetime') : null;
|
||||||
|
|
||||||
|
$allowedTags = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol', 'li'];
|
||||||
|
$allowedInline = ['strong', 'b', 'em', 'i', 'br'];
|
||||||
|
|
||||||
|
$content = [];
|
||||||
|
|
||||||
|
foreach ($article as $node) {
|
||||||
|
foreach ($node->childNodes as $child) {
|
||||||
|
$content[] = HtmlCleaner::renderNode($child, $allowedTags, $allowedInline);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$cleaned = HtmlCleaner::clean(implode("\n", array_filter($content)));
|
||||||
|
|
||||||
|
return [
|
||||||
|
'header' => $header,
|
||||||
|
'published_at' => $publishedAt,
|
||||||
|
'content' => $cleaned,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function makeRequest(int $page, ?string $type = null): Response|false
|
||||||
|
{
|
||||||
|
if (empty($this->slug)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = [
|
||||||
|
'n' => $this->slug,
|
||||||
|
'pageIx' => $page,
|
||||||
|
'format' => 'rss',
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($type) {
|
||||||
|
$query['m'] = $type;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Http::get('https://news.cision.com/se/ListItems?'.http_build_query($query));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function parseResponse(string $data, int $page): array|false
|
||||||
|
{
|
||||||
|
if (empty($data)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Suppress XML parsing warnings
|
||||||
|
libxml_use_internal_errors(true);
|
||||||
|
$xml = simplexml_load_string($data, 'SimpleXMLElement', LIBXML_NOCDATA);
|
||||||
|
libxml_clear_errors();
|
||||||
|
|
||||||
|
if ($xml === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert XML to JSON then to array
|
||||||
|
$json = json_encode($xml);
|
||||||
|
$rows = json_decode($json, true);
|
||||||
|
|
||||||
|
// Validate structure
|
||||||
|
if (! isset($rows['channel']['item'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure 'item' is always an array
|
||||||
|
$items = $rows['channel']['item'];
|
||||||
|
$items = isset($items[0]) ? $items : [$items];
|
||||||
|
|
||||||
|
// Format the result
|
||||||
|
$timezone = config('cision.timezone', 'Europe/Stockholm');
|
||||||
|
|
||||||
|
$formatted = array_map(fn ($item) => [
|
||||||
|
'guid' => $this->stringValue($item['guid'] ?? null),
|
||||||
|
'title' => $this->stringValue($item['title'] ?? null),
|
||||||
|
'description' => $this->stringValue($item['description'] ?? null),
|
||||||
|
'link' => $this->stringValue($item['link'] ?? null),
|
||||||
|
'created_at' => Carbon::parse($this->stringValue($item['pubDate'] ?? null))
|
||||||
|
->setTimezone($timezone)
|
||||||
|
->format('Y-m-d H:i:s'),
|
||||||
|
], $items);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'items' => $formatted,
|
||||||
|
'previous_page' => $page > 1 ? $page - 1 : false,
|
||||||
|
'next_page' => count($formatted) === 24 ? $page + 1 : false,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Empty XML elements are converted to empty arrays by the JSON round
|
||||||
|
* trip, so normalize every value to a string.
|
||||||
|
*/
|
||||||
|
protected function stringValue(mixed $value): string
|
||||||
|
{
|
||||||
|
return is_string($value) ? $value : '';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Mattitja\Cision;
|
||||||
|
|
||||||
|
use Spatie\LaravelPackageTools\Package;
|
||||||
|
use Spatie\LaravelPackageTools\PackageServiceProvider;
|
||||||
|
|
||||||
|
class CisionServiceProvider extends PackageServiceProvider
|
||||||
|
{
|
||||||
|
public function configurePackage(Package $package): void
|
||||||
|
{
|
||||||
|
$package
|
||||||
|
->name('laravel-cision-feed')
|
||||||
|
->hasConfigFile('cision');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function packageRegistered(): void
|
||||||
|
{
|
||||||
|
$this->app->bind(Cision::class, fn () => new Cision);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Mattitja\Cision\Facades;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Facade;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @method static array|false all(int $page = 1)
|
||||||
|
* @method static array|false press(int $page = 1)
|
||||||
|
* @method static array|false financial(int $page = 1)
|
||||||
|
* @method static array|false news(int $page = 1)
|
||||||
|
* @method static array|false media(int $page = 1)
|
||||||
|
* @method static array|false fetchContent(string $url)
|
||||||
|
*
|
||||||
|
* @see \Mattitja\Cision\Cision
|
||||||
|
*/
|
||||||
|
class Cision extends Facade
|
||||||
|
{
|
||||||
|
protected static function getFacadeAccessor(): string
|
||||||
|
{
|
||||||
|
return \Mattitja\Cision\Cision::class;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Mathiaspalmqvist\LaravelCisionFeed;
|
|
||||||
|
|
||||||
use Carbon\Carbon;
|
|
||||||
use Illuminate\Support\Facades\Http;
|
|
||||||
|
|
||||||
class LaravelCisionFeed
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @param int $page
|
|
||||||
* @return array|false
|
|
||||||
*/
|
|
||||||
public static function all(int $page = 1): bool|array
|
|
||||||
{
|
|
||||||
$request = self::request($page);
|
|
||||||
|
|
||||||
if ($request->failed()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return self::parse($request->body());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int $page
|
|
||||||
* @return array|false
|
|
||||||
*/
|
|
||||||
public static function press(int $page = 1): bool|array
|
|
||||||
{
|
|
||||||
$request = self::request($page, 'Press');
|
|
||||||
|
|
||||||
if ($request->failed()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return self::parse($request->body());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int $page
|
|
||||||
* @return array|false
|
|
||||||
*/
|
|
||||||
public static function financial(int $page = 1): bool|array
|
|
||||||
{
|
|
||||||
$request = self::request($page, 'Financial');
|
|
||||||
|
|
||||||
if ($request->failed()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return self::parse($request->body());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int $page
|
|
||||||
* @return array|false
|
|
||||||
*/
|
|
||||||
public static function news(int $page = 1): bool|array
|
|
||||||
{
|
|
||||||
$request = self::request($page, 'News');
|
|
||||||
|
|
||||||
if ($request->failed()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return self::parse($request->body());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int $page
|
|
||||||
* @return array|false
|
|
||||||
*/
|
|
||||||
public static function media(int $page = 1): bool|array
|
|
||||||
{
|
|
||||||
$request = self::request($page, 'Media');
|
|
||||||
|
|
||||||
if ($request->failed()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return self::parse($request->body());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int $page
|
|
||||||
* @param string|null $m
|
|
||||||
* @return false|\Illuminate\Http\Client\Response
|
|
||||||
*/
|
|
||||||
private static function request(int $page = 1, string $m = null): bool|\Illuminate\Http\Client\Response
|
|
||||||
{
|
|
||||||
if (env('LARAVEL_CISION_FEED_SLUG') === null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$query = [
|
|
||||||
'n' => env('LARAVEL_CISION_FEED_SLUG'),
|
|
||||||
'pageIx' => $page,
|
|
||||||
'format' => 'rss',
|
|
||||||
];
|
|
||||||
|
|
||||||
if ($m) {
|
|
||||||
$query['m'] = $m;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Http::get('https://news.cision.com/se/ListItems?'.http_build_query($query));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string|null $data
|
|
||||||
* @return array|false
|
|
||||||
*/
|
|
||||||
private static function parse(string $data = null): bool|array
|
|
||||||
{
|
|
||||||
if ($data === null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$xml = simplexml_load_string($data, "SimpleXMLElement", LIBXML_NOCDATA);
|
|
||||||
$json = json_encode($xml);
|
|
||||||
$rows = json_decode($json, true);
|
|
||||||
|
|
||||||
if (! isset($rows['channel']['item'])) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$return = [];
|
|
||||||
|
|
||||||
foreach ($rows['channel']['item'] as $row) {
|
|
||||||
$return[] = [
|
|
||||||
'guid' => $row['guid'],
|
|
||||||
'title' => $row['title'],
|
|
||||||
'description' => $row['description'],
|
|
||||||
'link' => $row['link'],
|
|
||||||
'created_at' => Carbon::parse($row['pubDate'])->addHours(2)->format('Y-m-d H:i:s'),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Mathiaspalmqvist\LaravelCisionFeed;
|
|
||||||
|
|
||||||
use Illuminate\Support\ServiceProvider;
|
|
||||||
|
|
||||||
class LaravelCisionFeedProvider extends ServiceProvider
|
|
||||||
{
|
|
||||||
public function boot()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public function register()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Mattitja\Cision\Support;
|
||||||
|
|
||||||
|
use DOMElement;
|
||||||
|
use DOMNode;
|
||||||
|
use DOMText;
|
||||||
|
|
||||||
|
class HtmlCleaner
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Renders a DOM node into safe HTML, preserving only allowed tags.
|
||||||
|
*/
|
||||||
|
public static function renderNode(DOMNode $node, array $allowedTags, array $allowedInline, string $parentTag = ''): ?string
|
||||||
|
{
|
||||||
|
if ($node instanceof DOMText) {
|
||||||
|
return $node->wholeText;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! ($node instanceof DOMElement)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tag = $node->nodeName;
|
||||||
|
|
||||||
|
if ($tag === 'br') {
|
||||||
|
if ($parentTag === 'p') {
|
||||||
|
$prev = $node->previousSibling;
|
||||||
|
$next = $node->nextSibling;
|
||||||
|
|
||||||
|
$hasTextBefore = $prev instanceof DOMText && trim($prev->textContent) !== '';
|
||||||
|
$hasTextAfter = $next instanceof DOMText && trim($next->textContent) !== '';
|
||||||
|
|
||||||
|
if ($hasTextBefore && $hasTextAfter) {
|
||||||
|
return '<br>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$inner = '';
|
||||||
|
|
||||||
|
foreach ($node->childNodes as $child) {
|
||||||
|
$rendered = self::renderNode($child, $allowedTags, $allowedInline, $tag);
|
||||||
|
if ($rendered !== null) {
|
||||||
|
$inner .= $rendered;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array($tag, $allowedTags)) {
|
||||||
|
if ($tag === 'p') {
|
||||||
|
$class = self::startsWithItalic($node) ? ' class="italic"' : '';
|
||||||
|
|
||||||
|
return "<p{$class}>".trim($inner).'</p>';
|
||||||
|
}
|
||||||
|
|
||||||
|
return "<{$tag}>".trim($inner)."</{$tag}>";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array($tag, $allowedInline)) {
|
||||||
|
return "<{$tag}>{$inner}</{$tag}>";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $inner;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans a raw HTML string: removes unwanted content, empty tags, and excess whitespace.
|
||||||
|
*/
|
||||||
|
public static function clean(string $html): string
|
||||||
|
{
|
||||||
|
$html = preg_replace('/<h1\b[^>]*>.*?<\/h1>/i', '', $html, 1);
|
||||||
|
$html = preg_replace('/<(\w+)[^>]*>\s*<\/\1>/', '', $html);
|
||||||
|
|
||||||
|
$unwantedStrings = [
|
||||||
|
'Report this content',
|
||||||
|
'<p><strong>Taggar:</strong></p>',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($unwantedStrings as $str) {
|
||||||
|
$html = str_replace($str, '', $html);
|
||||||
|
}
|
||||||
|
|
||||||
|
$html = preg_replace('/\b[a-z]{3},\s[a-z]{3}\s\d{2},\s\d{4}\s\d{2}:\d{2}\sCET\b/i', '', $html);
|
||||||
|
$html = str_replace(["\u{A0}", "\t", "\r"], ' ', $html);
|
||||||
|
$html = preg_replace('/[ ]{2,}/', ' ', $html);
|
||||||
|
$html = preg_replace("/(\n\s*){2,}/", "\n", $html);
|
||||||
|
$html = preg_replace('/[ ]+\n/', "\n", $html);
|
||||||
|
|
||||||
|
return trim($html);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the first meaningful child of the element is an <em> or <i>.
|
||||||
|
*/
|
||||||
|
protected static function startsWithItalic(DOMElement $node): bool
|
||||||
|
{
|
||||||
|
foreach ($node->childNodes as $child) {
|
||||||
|
if ($child instanceof DOMText && trim($child->textContent) === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $child instanceof DOMElement && in_array($child->nodeName, ['em', 'i'], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
arch('it will not use debugging functions')
|
||||||
|
->expect(['dd', 'dump', 'ray'])
|
||||||
|
->each->not->toBeUsed();
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Mattitja\Cision\Cision;
|
||||||
|
|
||||||
|
it('can fetch press releases from the RSS feed', function () {
|
||||||
|
Http::fake([
|
||||||
|
'news.cision.com/*' => Http::response(file_get_contents(__DIR__.'/Fixtures/rss-press.xml')),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$cision = new Cision('test-company');
|
||||||
|
$data = $cision->press();
|
||||||
|
|
||||||
|
expect($data)->toBeArray();
|
||||||
|
expect($data)->toHaveKey('items');
|
||||||
|
expect($data['items'])->not->toBeEmpty();
|
||||||
|
|
||||||
|
$item = $data['items'][0];
|
||||||
|
expect($item)->toHaveKeys(['guid', 'title', 'description', 'link', 'created_at']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can fetch and parse full article content', function () {
|
||||||
|
Http::fake([
|
||||||
|
'news.cision.com/*' => Http::response(file_get_contents(__DIR__.'/Fixtures/article.html')),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$cision = new Cision('test-company');
|
||||||
|
$content = $cision->fetchContent('https://news.cision.com/se/example/r/test,c123456');
|
||||||
|
|
||||||
|
expect($content)->toHaveKeys(['header', 'published_at', 'content']);
|
||||||
|
expect($content['header'])->not->toBeEmpty();
|
||||||
|
expect($content['content'])->toContain('<p');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('converts feed timestamps to Stockholm time in summer and winter', function () {
|
||||||
|
$rss = fn (string $date) => <<<XML
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<rss version="2.0"><channel>
|
||||||
|
<item><guid>a</guid><title>A</title><description>D</description><link>L</link><pubDate>{$date}</pubDate></item>
|
||||||
|
</channel></rss>
|
||||||
|
XML;
|
||||||
|
|
||||||
|
Http::fakeSequence()
|
||||||
|
->push($rss('Fri, 04 Jul 2025 07:55:00 GMT'))
|
||||||
|
->push($rss('Wed, 15 Jan 2025 07:55:00 GMT'));
|
||||||
|
|
||||||
|
$cision = new Cision('test-company');
|
||||||
|
|
||||||
|
expect($cision->press()['items'][0]['created_at'])->toBe('2025-07-04 09:55:00');
|
||||||
|
expect($cision->press()['items'][0]['created_at'])->toBe('2025-01-15 08:55:00');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns empty strings for empty feed elements', function () {
|
||||||
|
Http::fake([
|
||||||
|
'news.cision.com/*' => Http::response(<<<'XML'
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<rss version="2.0"><channel>
|
||||||
|
<item><guid>a</guid><title>A</title><description></description><link>L</link><pubDate>Fri, 04 Jul 2025 07:55:00 GMT</pubDate></item>
|
||||||
|
</channel></rss>
|
||||||
|
XML),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect((new Cision('test-company'))->press()['items'][0]['description'])->toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false when the feed request fails', function () {
|
||||||
|
Http::fake(['news.cision.com/*' => Http::response('', 500)]);
|
||||||
|
|
||||||
|
expect((new Cision('test-company'))->press())->toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false when the article request fails', function () {
|
||||||
|
Http::fake(['news.cision.com/*' => Http::response('', 404)]);
|
||||||
|
|
||||||
|
expect((new Cision('test-company'))->fetchContent('https://news.cision.com/se/x'))->toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false when the page has no article', function () {
|
||||||
|
Http::fake(['news.cision.com/*' => Http::response('<html><body><p>Nope</p></body></html>')]);
|
||||||
|
|
||||||
|
expect((new Cision('test-company'))->fetchContent('https://news.cision.com/se/x'))->toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('marks paragraphs that start with italic text', function () {
|
||||||
|
Http::fake([
|
||||||
|
'news.cision.com/*' => Http::response(file_get_contents(__DIR__.'/Fixtures/article.html')),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$content = (new Cision('test-company'))->fetchContent('https://news.cision.com/se/x')['content'];
|
||||||
|
|
||||||
|
expect($content)
|
||||||
|
->toContain('<p class="italic"><em>Italic sentence.</em></p>')
|
||||||
|
->toContain('<p><strong>Lead:</strong> This is a test article paragraph.</p>')
|
||||||
|
->not->toContain('Test Article Header');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses the configured slug when none is given', function () {
|
||||||
|
config()->set('cision.slug', 'configured-company');
|
||||||
|
|
||||||
|
Http::fake([
|
||||||
|
'news.cision.com/*' => Http::response(file_get_contents(__DIR__.'/Fixtures/rss-press.xml')),
|
||||||
|
]);
|
||||||
|
|
||||||
|
Mattitja\Cision\Facades\Cision::press();
|
||||||
|
|
||||||
|
Http::assertSent(fn ($request) => str_contains($request->url(), 'n=configured-company'));
|
||||||
|
});
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
<article>
|
||||||
|
<div>
|
||||||
|
<h1>Test Article Header</h1>
|
||||||
|
<time pubdate="2025-07-04 07:55:00Z" datetime="2025-07-04 07:55:00Z">fre, jul 04, 2025 09:55 CET</time>
|
||||||
|
<p><strong>Lead:</strong> This is a test article paragraph.</p>
|
||||||
|
<p><em>Italic sentence.</em></p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Key:</strong> Value</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<rss version="2.0">
|
||||||
|
<channel>
|
||||||
|
<title>Test Press Releases</title>
|
||||||
|
<item>
|
||||||
|
<guid>test-guid</guid>
|
||||||
|
<title>Test Press Title</title>
|
||||||
|
<description>This is a test press release description.</description>
|
||||||
|
<link>https://news.cision.com/se/example/r/test,c123456</link>
|
||||||
|
<pubDate>Fri, 04 Jul 2025 07:55:00 GMT</pubDate>
|
||||||
|
</item>
|
||||||
|
</channel>
|
||||||
|
</rss>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Mattitja\Cision\Support\HtmlCleaner;
|
||||||
|
|
||||||
|
it('removes trailing spaces before newlines without joining lines', function () {
|
||||||
|
expect(HtmlCleaner::clean("<p>One</p> \n<p>Two</p>"))->toBe("<p>One</p>\n<p>Two</p>");
|
||||||
|
});
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Mattitja\Cision\Tests\TestCase;
|
||||||
|
|
||||||
|
uses(TestCase::class)->in(__DIR__);
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Mattitja\Cision\Tests;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
use Mattitja\Cision\CisionServiceProvider;
|
||||||
|
use Orchestra\Testbench\TestCase as Orchestra;
|
||||||
|
|
||||||
|
class TestCase extends Orchestra
|
||||||
|
{
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
Factory::guessFactoryNamesUsing(
|
||||||
|
fn (string $modelName) => 'Mattitja\\Cision\\Database\\Factories\\'.class_basename($modelName).'Factory'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getPackageProviders($app)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
CisionServiceProvider::class,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEnvironmentSetUp($app)
|
||||||
|
{
|
||||||
|
config()->set('database.default', 'testing');
|
||||||
|
|
||||||
|
/*
|
||||||
|
foreach (\Illuminate\Support\Facades\File::allFiles(__DIR__ . '/database/migrations') as $migration) {
|
||||||
|
(include $migration->getRealPath())->up();
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
-7
@@ -1,7 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
// autoload.php @generated by Composer
|
|
||||||
|
|
||||||
require_once __DIR__ . '/composer/autoload_real.php';
|
|
||||||
|
|
||||||
return ComposerAutoloaderInita551a78ff0c5533943a66fc25192491f::getLoader();
|
|
||||||
Vendored
-572
@@ -1,572 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of Composer.
|
|
||||||
*
|
|
||||||
* (c) Nils Adermann <naderman@naderman.de>
|
|
||||||
* Jordi Boggiano <j.boggiano@seld.be>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Composer\Autoload;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
|
|
||||||
*
|
|
||||||
* $loader = new \Composer\Autoload\ClassLoader();
|
|
||||||
*
|
|
||||||
* // register classes with namespaces
|
|
||||||
* $loader->add('Symfony\Component', __DIR__.'/component');
|
|
||||||
* $loader->add('Symfony', __DIR__.'/framework');
|
|
||||||
*
|
|
||||||
* // activate the autoloader
|
|
||||||
* $loader->register();
|
|
||||||
*
|
|
||||||
* // to enable searching the include path (eg. for PEAR packages)
|
|
||||||
* $loader->setUseIncludePath(true);
|
|
||||||
*
|
|
||||||
* In this example, if you try to use a class in the Symfony\Component
|
|
||||||
* namespace or one of its children (Symfony\Component\Console for instance),
|
|
||||||
* the autoloader will first look for the class under the component/
|
|
||||||
* directory, and it will then fallback to the framework/ directory if not
|
|
||||||
* found before giving up.
|
|
||||||
*
|
|
||||||
* This class is loosely based on the Symfony UniversalClassLoader.
|
|
||||||
*
|
|
||||||
* @author Fabien Potencier <fabien@symfony.com>
|
|
||||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
|
||||||
* @see https://www.php-fig.org/psr/psr-0/
|
|
||||||
* @see https://www.php-fig.org/psr/psr-4/
|
|
||||||
*/
|
|
||||||
class ClassLoader
|
|
||||||
{
|
|
||||||
/** @var ?string */
|
|
||||||
private $vendorDir;
|
|
||||||
|
|
||||||
// PSR-4
|
|
||||||
/**
|
|
||||||
* @var array[]
|
|
||||||
* @psalm-var array<string, array<string, int>>
|
|
||||||
*/
|
|
||||||
private $prefixLengthsPsr4 = array();
|
|
||||||
/**
|
|
||||||
* @var array[]
|
|
||||||
* @psalm-var array<string, array<int, string>>
|
|
||||||
*/
|
|
||||||
private $prefixDirsPsr4 = array();
|
|
||||||
/**
|
|
||||||
* @var array[]
|
|
||||||
* @psalm-var array<string, string>
|
|
||||||
*/
|
|
||||||
private $fallbackDirsPsr4 = array();
|
|
||||||
|
|
||||||
// PSR-0
|
|
||||||
/**
|
|
||||||
* @var array[]
|
|
||||||
* @psalm-var array<string, array<string, string[]>>
|
|
||||||
*/
|
|
||||||
private $prefixesPsr0 = array();
|
|
||||||
/**
|
|
||||||
* @var array[]
|
|
||||||
* @psalm-var array<string, string>
|
|
||||||
*/
|
|
||||||
private $fallbackDirsPsr0 = array();
|
|
||||||
|
|
||||||
/** @var bool */
|
|
||||||
private $useIncludePath = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string[]
|
|
||||||
* @psalm-var array<string, string>
|
|
||||||
*/
|
|
||||||
private $classMap = array();
|
|
||||||
|
|
||||||
/** @var bool */
|
|
||||||
private $classMapAuthoritative = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var bool[]
|
|
||||||
* @psalm-var array<string, bool>
|
|
||||||
*/
|
|
||||||
private $missingClasses = array();
|
|
||||||
|
|
||||||
/** @var ?string */
|
|
||||||
private $apcuPrefix;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var self[]
|
|
||||||
*/
|
|
||||||
private static $registeredLoaders = array();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param ?string $vendorDir
|
|
||||||
*/
|
|
||||||
public function __construct($vendorDir = null)
|
|
||||||
{
|
|
||||||
$this->vendorDir = $vendorDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string[]
|
|
||||||
*/
|
|
||||||
public function getPrefixes()
|
|
||||||
{
|
|
||||||
if (!empty($this->prefixesPsr0)) {
|
|
||||||
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
|
|
||||||
}
|
|
||||||
|
|
||||||
return array();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array[]
|
|
||||||
* @psalm-return array<string, array<int, string>>
|
|
||||||
*/
|
|
||||||
public function getPrefixesPsr4()
|
|
||||||
{
|
|
||||||
return $this->prefixDirsPsr4;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array[]
|
|
||||||
* @psalm-return array<string, string>
|
|
||||||
*/
|
|
||||||
public function getFallbackDirs()
|
|
||||||
{
|
|
||||||
return $this->fallbackDirsPsr0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array[]
|
|
||||||
* @psalm-return array<string, string>
|
|
||||||
*/
|
|
||||||
public function getFallbackDirsPsr4()
|
|
||||||
{
|
|
||||||
return $this->fallbackDirsPsr4;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string[] Array of classname => path
|
|
||||||
* @psalm-return array<string, string>
|
|
||||||
*/
|
|
||||||
public function getClassMap()
|
|
||||||
{
|
|
||||||
return $this->classMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string[] $classMap Class to filename map
|
|
||||||
* @psalm-param array<string, string> $classMap
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function addClassMap(array $classMap)
|
|
||||||
{
|
|
||||||
if ($this->classMap) {
|
|
||||||
$this->classMap = array_merge($this->classMap, $classMap);
|
|
||||||
} else {
|
|
||||||
$this->classMap = $classMap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers a set of PSR-0 directories for a given prefix, either
|
|
||||||
* appending or prepending to the ones previously set for this prefix.
|
|
||||||
*
|
|
||||||
* @param string $prefix The prefix
|
|
||||||
* @param string[]|string $paths The PSR-0 root directories
|
|
||||||
* @param bool $prepend Whether to prepend the directories
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function add($prefix, $paths, $prepend = false)
|
|
||||||
{
|
|
||||||
if (!$prefix) {
|
|
||||||
if ($prepend) {
|
|
||||||
$this->fallbackDirsPsr0 = array_merge(
|
|
||||||
(array) $paths,
|
|
||||||
$this->fallbackDirsPsr0
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$this->fallbackDirsPsr0 = array_merge(
|
|
||||||
$this->fallbackDirsPsr0,
|
|
||||||
(array) $paths
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$first = $prefix[0];
|
|
||||||
if (!isset($this->prefixesPsr0[$first][$prefix])) {
|
|
||||||
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ($prepend) {
|
|
||||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
|
||||||
(array) $paths,
|
|
||||||
$this->prefixesPsr0[$first][$prefix]
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
|
||||||
$this->prefixesPsr0[$first][$prefix],
|
|
||||||
(array) $paths
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers a set of PSR-4 directories for a given namespace, either
|
|
||||||
* appending or prepending to the ones previously set for this namespace.
|
|
||||||
*
|
|
||||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
|
||||||
* @param string[]|string $paths The PSR-4 base directories
|
|
||||||
* @param bool $prepend Whether to prepend the directories
|
|
||||||
*
|
|
||||||
* @throws \InvalidArgumentException
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function addPsr4($prefix, $paths, $prepend = false)
|
|
||||||
{
|
|
||||||
if (!$prefix) {
|
|
||||||
// Register directories for the root namespace.
|
|
||||||
if ($prepend) {
|
|
||||||
$this->fallbackDirsPsr4 = array_merge(
|
|
||||||
(array) $paths,
|
|
||||||
$this->fallbackDirsPsr4
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$this->fallbackDirsPsr4 = array_merge(
|
|
||||||
$this->fallbackDirsPsr4,
|
|
||||||
(array) $paths
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
|
|
||||||
// Register directories for a new namespace.
|
|
||||||
$length = strlen($prefix);
|
|
||||||
if ('\\' !== $prefix[$length - 1]) {
|
|
||||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
|
||||||
}
|
|
||||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
|
||||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
|
||||||
} elseif ($prepend) {
|
|
||||||
// Prepend directories for an already registered namespace.
|
|
||||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
|
||||||
(array) $paths,
|
|
||||||
$this->prefixDirsPsr4[$prefix]
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Append directories for an already registered namespace.
|
|
||||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
|
||||||
$this->prefixDirsPsr4[$prefix],
|
|
||||||
(array) $paths
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers a set of PSR-0 directories for a given prefix,
|
|
||||||
* replacing any others previously set for this prefix.
|
|
||||||
*
|
|
||||||
* @param string $prefix The prefix
|
|
||||||
* @param string[]|string $paths The PSR-0 base directories
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function set($prefix, $paths)
|
|
||||||
{
|
|
||||||
if (!$prefix) {
|
|
||||||
$this->fallbackDirsPsr0 = (array) $paths;
|
|
||||||
} else {
|
|
||||||
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers a set of PSR-4 directories for a given namespace,
|
|
||||||
* replacing any others previously set for this namespace.
|
|
||||||
*
|
|
||||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
|
||||||
* @param string[]|string $paths The PSR-4 base directories
|
|
||||||
*
|
|
||||||
* @throws \InvalidArgumentException
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function setPsr4($prefix, $paths)
|
|
||||||
{
|
|
||||||
if (!$prefix) {
|
|
||||||
$this->fallbackDirsPsr4 = (array) $paths;
|
|
||||||
} else {
|
|
||||||
$length = strlen($prefix);
|
|
||||||
if ('\\' !== $prefix[$length - 1]) {
|
|
||||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
|
||||||
}
|
|
||||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
|
||||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Turns on searching the include path for class files.
|
|
||||||
*
|
|
||||||
* @param bool $useIncludePath
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function setUseIncludePath($useIncludePath)
|
|
||||||
{
|
|
||||||
$this->useIncludePath = $useIncludePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Can be used to check if the autoloader uses the include path to check
|
|
||||||
* for classes.
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function getUseIncludePath()
|
|
||||||
{
|
|
||||||
return $this->useIncludePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Turns off searching the prefix and fallback directories for classes
|
|
||||||
* that have not been registered with the class map.
|
|
||||||
*
|
|
||||||
* @param bool $classMapAuthoritative
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function setClassMapAuthoritative($classMapAuthoritative)
|
|
||||||
{
|
|
||||||
$this->classMapAuthoritative = $classMapAuthoritative;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Should class lookup fail if not found in the current class map?
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function isClassMapAuthoritative()
|
|
||||||
{
|
|
||||||
return $this->classMapAuthoritative;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
|
|
||||||
*
|
|
||||||
* @param string|null $apcuPrefix
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function setApcuPrefix($apcuPrefix)
|
|
||||||
{
|
|
||||||
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The APCu prefix in use, or null if APCu caching is not enabled.
|
|
||||||
*
|
|
||||||
* @return string|null
|
|
||||||
*/
|
|
||||||
public function getApcuPrefix()
|
|
||||||
{
|
|
||||||
return $this->apcuPrefix;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers this instance as an autoloader.
|
|
||||||
*
|
|
||||||
* @param bool $prepend Whether to prepend the autoloader or not
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function register($prepend = false)
|
|
||||||
{
|
|
||||||
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
|
|
||||||
|
|
||||||
if (null === $this->vendorDir) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($prepend) {
|
|
||||||
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
|
|
||||||
} else {
|
|
||||||
unset(self::$registeredLoaders[$this->vendorDir]);
|
|
||||||
self::$registeredLoaders[$this->vendorDir] = $this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unregisters this instance as an autoloader.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function unregister()
|
|
||||||
{
|
|
||||||
spl_autoload_unregister(array($this, 'loadClass'));
|
|
||||||
|
|
||||||
if (null !== $this->vendorDir) {
|
|
||||||
unset(self::$registeredLoaders[$this->vendorDir]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the given class or interface.
|
|
||||||
*
|
|
||||||
* @param string $class The name of the class
|
|
||||||
* @return true|null True if loaded, null otherwise
|
|
||||||
*/
|
|
||||||
public function loadClass($class)
|
|
||||||
{
|
|
||||||
if ($file = $this->findFile($class)) {
|
|
||||||
includeFile($file);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds the path to the file where the class is defined.
|
|
||||||
*
|
|
||||||
* @param string $class The name of the class
|
|
||||||
*
|
|
||||||
* @return string|false The path if found, false otherwise
|
|
||||||
*/
|
|
||||||
public function findFile($class)
|
|
||||||
{
|
|
||||||
// class map lookup
|
|
||||||
if (isset($this->classMap[$class])) {
|
|
||||||
return $this->classMap[$class];
|
|
||||||
}
|
|
||||||
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (null !== $this->apcuPrefix) {
|
|
||||||
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
|
|
||||||
if ($hit) {
|
|
||||||
return $file;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$file = $this->findFileWithExtension($class, '.php');
|
|
||||||
|
|
||||||
// Search for Hack files if we are running on HHVM
|
|
||||||
if (false === $file && defined('HHVM_VERSION')) {
|
|
||||||
$file = $this->findFileWithExtension($class, '.hh');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null !== $this->apcuPrefix) {
|
|
||||||
apcu_add($this->apcuPrefix.$class, $file);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false === $file) {
|
|
||||||
// Remember that this class does not exist.
|
|
||||||
$this->missingClasses[$class] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $file;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the currently registered loaders indexed by their corresponding vendor directories.
|
|
||||||
*
|
|
||||||
* @return self[]
|
|
||||||
*/
|
|
||||||
public static function getRegisteredLoaders()
|
|
||||||
{
|
|
||||||
return self::$registeredLoaders;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $class
|
|
||||||
* @param string $ext
|
|
||||||
* @return string|false
|
|
||||||
*/
|
|
||||||
private function findFileWithExtension($class, $ext)
|
|
||||||
{
|
|
||||||
// PSR-4 lookup
|
|
||||||
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
|
|
||||||
|
|
||||||
$first = $class[0];
|
|
||||||
if (isset($this->prefixLengthsPsr4[$first])) {
|
|
||||||
$subPath = $class;
|
|
||||||
while (false !== $lastPos = strrpos($subPath, '\\')) {
|
|
||||||
$subPath = substr($subPath, 0, $lastPos);
|
|
||||||
$search = $subPath . '\\';
|
|
||||||
if (isset($this->prefixDirsPsr4[$search])) {
|
|
||||||
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
|
|
||||||
foreach ($this->prefixDirsPsr4[$search] as $dir) {
|
|
||||||
if (file_exists($file = $dir . $pathEnd)) {
|
|
||||||
return $file;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PSR-4 fallback dirs
|
|
||||||
foreach ($this->fallbackDirsPsr4 as $dir) {
|
|
||||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
|
|
||||||
return $file;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PSR-0 lookup
|
|
||||||
if (false !== $pos = strrpos($class, '\\')) {
|
|
||||||
// namespaced class name
|
|
||||||
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
|
|
||||||
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
|
|
||||||
} else {
|
|
||||||
// PEAR-like class name
|
|
||||||
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($this->prefixesPsr0[$first])) {
|
|
||||||
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
|
|
||||||
if (0 === strpos($class, $prefix)) {
|
|
||||||
foreach ($dirs as $dir) {
|
|
||||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
|
||||||
return $file;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PSR-0 fallback dirs
|
|
||||||
foreach ($this->fallbackDirsPsr0 as $dir) {
|
|
||||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
|
||||||
return $file;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PSR-0 include paths.
|
|
||||||
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
|
|
||||||
return $file;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scope isolated include.
|
|
||||||
*
|
|
||||||
* Prevents access to $this/self from included files.
|
|
||||||
*
|
|
||||||
* @param string $file
|
|
||||||
* @return void
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
function includeFile($file)
|
|
||||||
{
|
|
||||||
include $file;
|
|
||||||
}
|
|
||||||
-10
@@ -1,10 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
// autoload_classmap.php @generated by Composer
|
|
||||||
|
|
||||||
$vendorDir = dirname(dirname(__FILE__));
|
|
||||||
$baseDir = dirname($vendorDir);
|
|
||||||
|
|
||||||
return array(
|
|
||||||
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
|
||||||
);
|
|
||||||
-9
@@ -1,9 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
// autoload_namespaces.php @generated by Composer
|
|
||||||
|
|
||||||
$vendorDir = dirname(dirname(__FILE__));
|
|
||||||
$baseDir = dirname($vendorDir);
|
|
||||||
|
|
||||||
return array(
|
|
||||||
);
|
|
||||||
Vendored
-10
@@ -1,10 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
// autoload_psr4.php @generated by Composer
|
|
||||||
|
|
||||||
$vendorDir = dirname(dirname(__FILE__));
|
|
||||||
$baseDir = dirname($vendorDir);
|
|
||||||
|
|
||||||
return array(
|
|
||||||
'Mathiaspalmqvist\\LaravelCisionFeed\\' => array($baseDir . '/src'),
|
|
||||||
);
|
|
||||||
Vendored
-55
@@ -1,55 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
// autoload_real.php @generated by Composer
|
|
||||||
|
|
||||||
class ComposerAutoloaderInita551a78ff0c5533943a66fc25192491f
|
|
||||||
{
|
|
||||||
private static $loader;
|
|
||||||
|
|
||||||
public static function loadClassLoader($class)
|
|
||||||
{
|
|
||||||
if ('Composer\Autoload\ClassLoader' === $class) {
|
|
||||||
require __DIR__ . '/ClassLoader.php';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return \Composer\Autoload\ClassLoader
|
|
||||||
*/
|
|
||||||
public static function getLoader()
|
|
||||||
{
|
|
||||||
if (null !== self::$loader) {
|
|
||||||
return self::$loader;
|
|
||||||
}
|
|
||||||
|
|
||||||
spl_autoload_register(array('ComposerAutoloaderInita551a78ff0c5533943a66fc25192491f', 'loadClassLoader'), true, true);
|
|
||||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
|
|
||||||
spl_autoload_unregister(array('ComposerAutoloaderInita551a78ff0c5533943a66fc25192491f', 'loadClassLoader'));
|
|
||||||
|
|
||||||
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
|
|
||||||
if ($useStaticLoader) {
|
|
||||||
require __DIR__ . '/autoload_static.php';
|
|
||||||
|
|
||||||
call_user_func(\Composer\Autoload\ComposerStaticInita551a78ff0c5533943a66fc25192491f::getInitializer($loader));
|
|
||||||
} else {
|
|
||||||
$map = require __DIR__ . '/autoload_namespaces.php';
|
|
||||||
foreach ($map as $namespace => $path) {
|
|
||||||
$loader->set($namespace, $path);
|
|
||||||
}
|
|
||||||
|
|
||||||
$map = require __DIR__ . '/autoload_psr4.php';
|
|
||||||
foreach ($map as $namespace => $path) {
|
|
||||||
$loader->setPsr4($namespace, $path);
|
|
||||||
}
|
|
||||||
|
|
||||||
$classMap = require __DIR__ . '/autoload_classmap.php';
|
|
||||||
if ($classMap) {
|
|
||||||
$loader->addClassMap($classMap);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$loader->register(true);
|
|
||||||
|
|
||||||
return $loader;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vendored
-36
@@ -1,36 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
// autoload_static.php @generated by Composer
|
|
||||||
|
|
||||||
namespace Composer\Autoload;
|
|
||||||
|
|
||||||
class ComposerStaticInita551a78ff0c5533943a66fc25192491f
|
|
||||||
{
|
|
||||||
public static $prefixLengthsPsr4 = array (
|
|
||||||
'M' =>
|
|
||||||
array (
|
|
||||||
'Mathiaspalmqvist\\LaravelCisionFeed\\' => 35,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
public static $prefixDirsPsr4 = array (
|
|
||||||
'Mathiaspalmqvist\\LaravelCisionFeed\\' =>
|
|
||||||
array (
|
|
||||||
0 => __DIR__ . '/../..' . '/src',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
public static $classMap = array (
|
|
||||||
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
|
||||||
);
|
|
||||||
|
|
||||||
public static function getInitializer(ClassLoader $loader)
|
|
||||||
{
|
|
||||||
return \Closure::bind(function () use ($loader) {
|
|
||||||
$loader->prefixLengthsPsr4 = ComposerStaticInita551a78ff0c5533943a66fc25192491f::$prefixLengthsPsr4;
|
|
||||||
$loader->prefixDirsPsr4 = ComposerStaticInita551a78ff0c5533943a66fc25192491f::$prefixDirsPsr4;
|
|
||||||
$loader->classMap = ComposerStaticInita551a78ff0c5533943a66fc25192491f::$classMap;
|
|
||||||
|
|
||||||
}, null, ClassLoader::class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user