From 4f75591060d95208a301bc6bf460d875631b29cc Mon Sep 17 00:00:00 2001 From: Dag Date: Fri, 1 Jul 2022 15:10:30 +0200 Subject: [PATCH] Reformat codebase v4 (#2872) Reformat code base to PSR12 Co-authored-by: rssbridge --- actions/ConnectivityAction.php | 136 +- actions/DetectAction.php | 62 +- actions/DisplayAction.php | 353 +- actions/ListAction.php | 70 +- bridges/ABCNewsBridge.php | 79 +- bridges/AO3Bridge.php | 208 +- bridges/ARDMediathekBridge.php | 175 +- bridges/ASRockNewsBridge.php | 75 +- bridges/AcrimedBridge.php | 61 +- bridges/AirBreizhBridge.php | 97 +- bridges/AlbionOnlineBridge.php | 133 +- bridges/AlfaBankByBridge.php | 146 +- bridges/AllocineFRBridge.php | 198 +- bridges/AmazonBridge.php | 173 +- bridges/AmazonPriceTrackerBridge.php | 414 +- bridges/AnidexBridge.php | 409 +- bridges/AnimeUltimeBridge.php | 242 +- bridges/AppleAppStoreBridge.php | 248 +- bridges/AppleMusicBridge.php | 98 +- bridges/ArtStationBridge.php | 157 +- bridges/Arte7Bridge.php | 289 +- bridges/AsahiShimbunAJWBridge.php | 134 +- bridges/AskfmBridge.php | 119 +- bridges/AssociatedPressNewsBridge.php | 422 +- bridges/AstrophysicsDataSystemBridge.php | 89 +- bridges/AtmoNouvelleAquitaineBridge.php | 9209 +++++++++-------- bridges/AtmoOccitanieBridge.php | 94 +- bridges/AutoJMBridge.php | 236 +- bridges/AwwwardsBridge.php | 93 +- bridges/BAEBridge.php | 504 +- bridges/BadDragonBridge.php | 790 +- bridges/BakaUpdatesMangaReleasesBridge.php | 321 +- bridges/BandcampBridge.php | 746 +- bridges/BandcampDailyBridge.php | 289 +- bridges/BastaBridge.php | 48 +- bridges/BinanceBridge.php | 66 +- bridges/BlaguesDeMerdeBridge.php | 65 +- bridges/BleepingComputerBridge.php | 45 +- bridges/BlizzardNewsBridge.php | 110 +- bridges/BookMyShowBridge.php | 2754 ++--- bridges/BooruprojectBridge.php | 124 +- bridges/BrutBridge.php | 192 +- bridges/BugzillaBridge.php | 320 +- bridges/BukowskisBridge.php | 406 +- bridges/BundesbankBridge.php | 128 +- bridges/BundestagParteispendenBridge.php | 131 +- bridges/CBCEditorsBlogBridge.php | 60 +- bridges/CNETBridge.php | 196 +- bridges/CNETFranceBridge.php | 101 +- bridges/CVEDetailsBridge.php | 231 +- bridges/CachetBridge.php | 240 +- bridges/CarThrottleBridge.php | 61 +- bridges/CastorusBridge.php | 201 +- bridges/CdactionBridge.php | 108 +- bridges/CeskaTelevizeBridge.php | 137 +- bridges/CodebergBridge.php | 656 +- bridges/CollegeDeFranceBridge.php | 146 +- bridges/ComboiosDePortugalBridge.php | 41 +- bridges/ComicsKingdomBridge.php | 105 +- bridges/CommonDreamsBridge.php | 44 +- bridges/CopieDoubleBridge.php | 56 +- bridges/CourrierInternationalBridge.php | 41 +- bridges/CraigslistBridge.php | 184 +- bridges/CrewbayBridge.php | 416 +- bridges/CryptomeBridge.php | 77 +- bridges/CubariBridge.php | 150 +- bridges/CuriousCatBridge.php | 150 +- bridges/CyanideAndHappinessBridge.php | 65 +- bridges/DailymotionBridge.php | 334 +- bridges/DanbooruBridge.php | 117 +- bridges/DansTonChatBridge.php | 45 +- bridges/DarkReadingBridge.php | 157 +- bridges/DauphineLibereBridge.php | 100 +- bridges/DavesTrailerPageBridge.php | 57 +- bridges/DealabsBridge.php | 3908 ++++--- bridges/DemoBridge.php | 81 +- bridges/DerpibooruBridge.php | 208 +- bridges/DesoutterBridge.php | 421 +- bridges/DevToBridge.php | 160 +- bridges/DeveloppezDotComBridge.php | 743 +- bridges/DiarioDeNoticiasBridge.php | 147 +- bridges/DiarioDoAlentejoBridge.php | 105 +- bridges/DiceBridge.php | 236 +- bridges/DilbertBridge.php | 59 +- bridges/DiscogsBridge.php | 216 +- bridges/DockerHubBridge.php | 249 +- bridges/DonnonsBridge.php | 186 +- bridges/DribbbleBridge.php | 161 +- bridges/Drive2ruBridge.php | 411 +- bridges/DuckDuckGoBridge.php | 72 +- bridges/EZTVBridge.php | 195 +- bridges/EconomistBridge.php | 270 +- bridges/EconomistWorldInBriefBridge.php | 260 +- bridges/EliteDangerousGalnetBridge.php | 86 +- bridges/ElloBridge.php | 249 +- bridges/ElsevierBridge.php | 71 +- bridges/EngadgetBridge.php | 46 +- bridges/EpicgamesBridge.php | 162 +- bridges/EsquerdaNetBridge.php | 132 +- bridges/EstCeQuonMetEnProdBridge.php | 42 +- bridges/EtsyBridge.php | 130 +- bridges/EuronewsBridge.php | 392 +- bridges/ExecuteProgramBridge.php | 54 +- bridges/ExplosmBridge.php | 110 +- bridges/ExtremeDownloadBridge.php | 195 +- bridges/FB2Bridge.php | 536 +- bridges/FDroidBridge.php | 143 +- bridges/FDroidRepoBridge.php | 330 +- bridges/FM4Bridge.php | 105 +- bridges/FSecureBlogBridge.php | 212 +- bridges/FabriceBellardBridge.php | 55 +- bridges/FacebookBridge.php | 1379 +-- bridges/FeedExpanderExampleBridge.php | 115 +- bridges/FeedMergeBridge.php | 106 +- bridges/FeedReducerBridge.php | 102 +- bridges/FicbookBridge.php | 321 +- bridges/FilterBridge.php | 255 +- bridges/FindACrewBridge.php | 158 +- bridges/FirefoxAddonsBridge.php | 156 +- bridges/FirstLookMediaTechBridge.php | 83 +- bridges/FlashbackBridge.php | 350 +- bridges/FlickrBridge.php | 558 +- bridges/FolhaDeSaoPauloBridge.php | 130 +- bridges/ForGifsBridge.php | 63 +- bridges/Formula1Bridge.php | 119 +- bridges/FourchanBridge.php | 133 +- bridges/FreeCodeCampBridge.php | 48 +- bridges/FunkBridge.php | 152 +- bridges/FurAffinityBridge.php | 1775 ++-- bridges/FurAffinityUserBridge.php | 99 +- bridges/FuturaSciencesBridge.php | 302 +- bridges/GBAtempBridge.php | 274 +- bridges/GOGBridge.php | 97 +- bridges/GQMagazineBridge.php | 228 +- bridges/GatesNotesBridge.php | 85 +- bridges/GelbooruBridge.php | 153 +- bridges/GenshinImpactBridge.php | 118 +- bridges/GettrBridge.php | 186 +- bridges/GiphyBridge.php | 172 +- bridges/GitHubGistBridge.php | 214 +- bridges/GiteaBridge.php | 537 +- bridges/GithubIssueBridge.php | 519 +- bridges/GithubPullRequestBridge.php | 79 +- bridges/GithubSearchBridge.php | 112 +- bridges/GithubTrendingBridge.php | 1234 +-- bridges/GitlabIssueBridge.php | 357 +- bridges/GizmodoBridge.php | 120 +- bridges/GlassdoorBridge.php | 318 +- bridges/GlowficBridge.php | 172 +- bridges/GoComicsBridge.php | 97 +- bridges/GogsBridge.php | 359 +- bridges/GolemBridge.php | 216 +- bridges/GoodreadsBridge.php | 156 +- bridges/GoogleGroupsBridge.php | 118 +- bridges/GooglePlayStoreBridge.php | 100 +- bridges/GoogleSearchBridge.php | 175 +- bridges/GrandComicsDatabaseBridge.php | 100 +- bridges/GroupBundNaturschutzBridge.php | 199 +- bridges/HDWallpapersBridge.php | 145 +- bridges/HackerNewsUserThreadsBridge.php | 80 +- bridges/HardwareInfoBridge.php | 101 +- bridges/HashnodeBridge.php | 82 +- bridges/HaveIBeenPwnedBridge.php | 234 +- bridges/HeiseBridge.php | 136 +- bridges/HotUKDealsBridge.php | 6657 ++++++------ bridges/IGNBridge.php | 107 +- bridges/IKWYDBridge.php | 212 +- bridges/IPBBridge.php | 502 +- bridges/IdenticaBridge.php | 82 +- bridges/IndeedBridge.php | 397 +- bridges/IndiegogoBridge.php | 265 +- bridges/InstagramBridge.php | 556 +- bridges/InstructablesBridge.php | 655 +- bridges/InternetArchiveBridge.php | 453 +- bridges/ItchioBridge.php | 78 +- bridges/IvooxBridge.php | 216 +- bridges/JapanExpoBridge.php | 186 +- bridges/JornalDeNoticiasBridge.php | 93 +- bridges/JustETFBridge.php | 614 +- bridges/Kanali6Bridge.php | 32 +- bridges/KernelBugTrackerBridge.php | 259 +- bridges/KhinsiderBridge.php | 66 +- bridges/KilledbyGoogleBridge.php | 117 +- bridges/KonachanBridge.php | 13 +- bridges/KoreusBridge.php | 33 +- bridges/KununuBridge.php | 243 +- bridges/LWNprevBridge.php | 460 +- bridges/LaCentraleBridge.php | 927 +- bridges/LaTeX3ProjectNewslettersBridge.php | 52 +- bridges/LeBonCoinBridge.php | 992 +- bridges/LeMondeInformatiqueBridge.php | 64 +- bridges/LegifranceJOBridge.php | 126 +- bridges/LegoIdeasBridge.php | 169 +- bridges/LesJoiesDuCodeBridge.php | 56 +- bridges/ListverseBridge.php | 37 +- bridges/LolibooruBridge.php | 13 +- bridges/MallTvBridge.php | 113 +- bridges/MangaDexBridge.php | 431 +- bridges/MarktplaatsBridge.php | 253 +- bridges/MastodonBridge.php | 364 +- bridges/MediapartBlogsBridge.php | 83 +- bridges/MediapartBridge.php | 114 +- bridges/MilbooruBridge.php | 13 +- bridges/MixCloudBridge.php | 97 +- bridges/ModelKarteiBridge.php | 180 +- bridges/MoebooruBridge.php | 96 +- bridges/MoinMoinBridge.php | 583 +- bridges/MondeDiploBridge.php | 47 +- bridges/MozillaBugTrackerBridge.php | 257 +- bridges/MozillaSecurityBridge.php | 52 +- bridges/MsnMondeBridge.php | 70 +- bridges/MspabooruBridge.php | 21 +- bridges/MydealsBridge.php | 4151 ++++---- bridges/N26Bridge.php | 58 +- bridges/NFLRUSBridge.php | 39 +- bridges/NYTBridge.php | 58 +- bridges/NasaApodBridge.php | 73 +- bridges/NationalGeographicBridge.php | 610 +- bridges/NewOnNetflixBridge.php | 94 +- bridges/NewgroundsBridge.php | 105 +- bridges/NextInpactBridge.php | 344 +- bridges/NextgovBridge.php | 115 +- bridges/NiceMatinBridge.php | 54 +- bridges/NikonDownloadCenterBridge.php | 63 +- bridges/NineGagBridge.php | 680 +- bridges/NordbayernBridge.php | 308 +- bridges/NotAlwaysBridge.php | 103 +- bridges/NovayaGazetaEuropeBridge.php | 264 +- bridges/NovelUpdatesBridge.php | 118 +- bridges/NpciBridge.php | 162 +- bridges/NyaaTorrentsBridge.php | 197 +- bridges/OnVaSortirBridge.php | 256 +- bridges/OneFortuneADayBridge.php | 142 +- bridges/OpenlyBridge.php | 438 +- bridges/OpenwhydBridge.php | 108 +- bridges/OpenwrtSecurityBridge.php | 58 +- bridges/OtrkeyFinderBridge.php | 309 +- bridges/PCGWNewsBridge.php | 56 +- bridges/PanacheDigitalGamesBridge.php | 75 +- bridges/ParksOnTheAirBridge.php | 53 +- bridges/ParlerBridge.php | 132 +- bridges/ParuVenduImmoBridge.php | 189 +- bridges/PatreonBridge.php | 371 +- bridges/PcGamerBridge.php | 67 +- bridges/PepperBridgeAbstract.php | 1235 +-- bridges/PhoronixBridge.php | 118 +- bridges/PicalaBridge.php | 120 +- bridges/PickyWallpapersBridge.php | 175 +- bridges/PicukiBridge.php | 151 +- bridges/PikabuBridge.php | 264 +- bridges/PillowfortBridge.php | 337 +- bridges/PinterestBridge.php | 105 +- bridges/PirateCommunityBridge.php | 146 +- bridges/PixivBridge.php | 396 +- bridges/PlantUMLReleasesBridge.php | 71 +- bridges/PokemonTVBridge.php | 272 +- bridges/PornhubBridge.php | 169 +- bridges/PresidenciaPTBridge.php | 145 +- bridges/RaceDepartmentBridge.php | 63 +- bridges/RadioMelodieBridge.php | 329 +- bridges/RainbowSixSiegeBridge.php | 75 +- bridges/RedditBridge.php | 501 +- bridges/Releases3DSBridge.php | 196 +- bridges/ReleasesSwitchBridge.php | 19 +- bridges/ReporterreBridge.php | 65 +- bridges/ReutersBridge.php | 1147 +- bridges/RoadAndTrackBridge.php | 109 +- bridges/RobinhoodSnacksBridge.php | 183 +- bridges/RoosterTeethBridge.php | 183 +- bridges/RtsBridge.php | 123 +- bridges/Rue89Bridge.php | 77 +- bridges/Rule34Bridge.php | 13 +- bridges/Rule34pahealBridge.php | 47 +- bridges/RutubeBridge.php | 165 +- bridges/SIMARBridge.php | 105 +- bridges/SafebooruBridge.php | 23 +- .../SchweinfurtBuergerinformationenBridge.php | 207 +- bridges/ScmbBridge.php | 66 +- bridges/ScoopItBridge.php | 68 +- bridges/ScribdBridge.php | 115 +- bridges/SensCritiqueBridge.php | 174 +- bridges/SeznamZpravyBridge.php | 218 +- bridges/ShanaprojectBridge.php | 303 +- bridges/Shimmie2Bridge.php | 62 +- bridges/SkimfeedBridge.php | 1463 ++- bridges/SlusheBridge.php | 323 +- bridges/SoundcloudBridge.php | 361 +- bridges/SplCenterBridge.php | 97 +- bridges/SpotifyBridge.php | 414 +- bridges/SpottschauBridge.php | 59 +- bridges/StanfordSIRbookreviewBridge.php | 76 +- bridges/SteamBridge.php | 190 +- bridges/SteamCommunityBridge.php | 308 +- bridges/StockFilingsBridge.php | 136 +- bridges/StripeAPIChangeLogBridge.php | 39 +- bridges/SummitsOnTheAirBridge.php | 69 +- bridges/SuperSmashBlogBridge.php | 73 +- bridges/SymfonyCastsBridge.php | 55 +- bridges/TbibBridge.php | 23 +- bridges/TebeoBridge.php | 74 +- bridges/TelegramBridge.php | 513 +- bridges/TheFarSideBridge.php | 63 +- bridges/TheGuardianBridge.php | 151 +- bridges/TheHackerNewsBridge.php | 136 +- bridges/ThePirateBayBridge.php | 554 +- bridges/TheWhiteboardBridge.php | 33 +- bridges/TheYeteeBridge.php | 58 +- bridges/TikTokBridge.php | 138 +- bridges/TinyLetterBridge.php | 88 +- bridges/TorrentGalaxyBridge.php | 213 +- bridges/TrelloBridge.php | 1348 +-- bridges/TwitScoopBridge.php | 258 +- bridges/TwitchBridge.php | 399 +- bridges/TwitterBridge.php | 1059 +- bridges/TwitterEngineeringBridge.php | 99 +- bridges/TwitterV2Bridge.php | 1140 +- bridges/UberNewsroomBridge.php | 330 +- bridges/UnogsBridge.php | 339 +- bridges/UnraidCommunityApplicationsBridge.php | 121 +- bridges/UnsplashBridge.php | 202 +- bridges/UrlebirdBridge.php | 131 +- bridges/UsbekEtRicaBridge.php | 186 +- bridges/UsenixBridge.php | 109 +- bridges/VarietyBridge.php | 47 +- bridges/ViadeoCompanyBridge.php | 65 +- bridges/ViceBridge.php | 70 +- bridges/VieDeMerdeBridge.php | 90 +- bridges/VimeoBridge.php | 292 +- bridges/VixenBridge.php | 180 +- bridges/VkBridge.php | 805 +- bridges/WallmineNewsBridge.php | 64 +- bridges/WallpaperflareBridge.php | 77 +- bridges/WeLiveSecurityBridge.php | 63 +- bridges/WebfailBridge.php | 287 +- bridges/WikiLeaksBridge.php | 215 +- bridges/WikipediaBridge.php | 593 +- bridges/WiredBridge.php | 179 +- bridges/WordPressBridge.php | 194 +- bridges/WordPressMadaraBridge.php | 231 +- bridges/WordPressPluginUpdateBridge.php | 104 +- bridges/WorldCosplayBridge.php | 266 +- bridges/WorldOfTanksBridge.php | 100 +- bridges/XPathBridge.php | 355 +- bridges/XbooruBridge.php | 21 +- bridges/XenForoBridge.php | 801 +- bridges/YGGTorrentBridge.php | 278 +- bridges/YandereBridge.php | 13 +- bridges/YeggiBridge.php | 168 +- bridges/YouTubeCommunityTabBridge.php | 425 +- bridges/YoutubeBridge.php | 781 +- bridges/ZDNetBridge.php | 395 +- bridges/ZenodoBridge.php | 86 +- caches/FileCache.php | 233 +- caches/MemcachedCache.php | 197 +- caches/SQLiteCache.php | 216 +- .../prepare_release/fetch_contributors.php | 56 +- formats/AtomFormat.php | 288 +- formats/HtmlFormat.php | 179 +- formats/JsonFormat.php | 209 +- formats/MrssFormat.php | 236 +- formats/PlaintextFormat.php | 31 +- index.php | 38 +- lib/ActionFactory.php | 43 +- lib/ActionInterface.php | 26 +- lib/Authentication.php | 108 +- lib/BridgeAbstract.php | 706 +- lib/BridgeCard.php | 602 +- lib/BridgeFactory.php | 144 +- lib/BridgeInterface.php | 162 +- lib/BridgeList.php | 245 +- lib/CacheFactory.php | 95 +- lib/CacheInterface.php | 96 +- lib/Configuration.php | 530 +- lib/Debug.php | 160 +- lib/Exceptions.php | 144 +- lib/FactoryAbstract.php | 99 +- lib/FeedExpander.php | 775 +- lib/FeedItem.php | 944 +- lib/FormatAbstract.php | 218 +- lib/FormatFactory.php | 102 +- lib/FormatInterface.php | 118 +- lib/ParameterValidator.php | 424 +- lib/XPathAbstract.php | 1048 +- lib/contents.php | 570 +- lib/error.php | 61 +- lib/html.php | 186 +- lib/php8backports.php | 28 +- lib/rssbridge.php | 33 +- phpcs.xml | 72 +- tests/Actions/ActionImplementationTest.php | 90 +- tests/Actions/ListActionTest.php | 131 +- tests/Bridges/BridgeImplementationTest.php | 373 +- tests/Caches/CacheImplementationTest.php | 65 +- tests/Formats/AtomFormatTest.php | 27 +- tests/Formats/BaseFormatTest.php | 102 +- tests/Formats/FormatImplementationTest.php | 67 +- tests/Formats/JsonFormatTest.php | 27 +- tests/Formats/MrssFormatTest.php | 27 +- 398 files changed, 58607 insertions(+), 56442 deletions(-) diff --git a/actions/ConnectivityAction.php b/actions/ConnectivityAction.php index 1018c4a2..c657f21b 100644 --- a/actions/ConnectivityAction.php +++ b/actions/ConnectivityAction.php @@ -1,4 +1,5 @@ userData['bridge'])) { + $this->returnEntryPage(); + return; + } - if(!isset($this->userData['bridge'])) { - $this->returnEntryPage(); - return; - } + $bridgeName = $this->userData['bridge']; - $bridgeName = $this->userData['bridge']; + $this->reportBridgeConnectivity($bridgeName); + } - $this->reportBridgeConnectivity($bridgeName); + /** + * Generates a report about the bridge connectivity status and sends it back + * to the user. + * + * The report is generated as Json-formatted string in the format + * { + * "bridge": "", + * "successful": true/false + * } + * + * @param string $bridgeName Name of the bridge to generate the report for + * @return void + */ + private function reportBridgeConnectivity($bridgeName) + { + $bridgeFac = new \BridgeFactory(); - } + if (!$bridgeFac->isWhitelisted($bridgeName)) { + header('Content-Type: text/html'); + returnServerError('Bridge is not whitelisted!'); + } - /** - * Generates a report about the bridge connectivity status and sends it back - * to the user. - * - * The report is generated as Json-formatted string in the format - * { - * "bridge": "", - * "successful": true/false - * } - * - * @param string $bridgeName Name of the bridge to generate the report for - * @return void - */ - private function reportBridgeConnectivity($bridgeName) { + header('Content-Type: text/json'); - $bridgeFac = new \BridgeFactory(); + $retVal = [ + 'bridge' => $bridgeName, + 'successful' => false, + 'http_code' => 200, + ]; - if(!$bridgeFac->isWhitelisted($bridgeName)) { - header('Content-Type: text/html'); - returnServerError('Bridge is not whitelisted!'); - } + $bridge = $bridgeFac->create($bridgeName); - header('Content-Type: text/json'); + if ($bridge === false) { + echo json_encode($retVal); + return; + } - $retVal = array( - 'bridge' => $bridgeName, - 'successful' => false, - 'http_code' => 200, - ); + $curl_opts = [ + CURLOPT_CONNECTTIMEOUT => 5 + ]; - $bridge = $bridgeFac->create($bridgeName); + try { + $reply = getContents($bridge::URI, [], $curl_opts, true); - if($bridge === false) { - echo json_encode($retVal); - return; - } + if ($reply['code'] === 200) { + $retVal['successful'] = true; + if (strpos(implode('', $reply['status_lines']), '301 Moved Permanently')) { + $retVal['http_code'] = 301; + } + } + } catch (Exception $e) { + $retVal['successful'] = false; + } - $curl_opts = array( - CURLOPT_CONNECTTIMEOUT => 5 - ); + echo json_encode($retVal); + } - try { - $reply = getContents($bridge::URI, array(), $curl_opts, true); - - if($reply['code'] === 200) { - $retVal['successful'] = true; - if (strpos(implode('', $reply['status_lines']), '301 Moved Permanently')) { - $retVal['http_code'] = 301; - } - } - } catch(Exception $e) { - $retVal['successful'] = false; - } - - echo json_encode($retVal); - - } - - private function returnEntryPage() { - echo << @@ -132,5 +132,5 @@ class ConnectivityAction implements ActionInterface EOD; - } + } } diff --git a/actions/DetectAction.php b/actions/DetectAction.php index d662d7aa..149b239d 100644 --- a/actions/DetectAction.php +++ b/actions/DetectAction.php @@ -1,4 +1,5 @@ userData['url'] - or returnClientError('You must specify a url!'); + public function execute() + { + $targetURL = $this->userData['url'] + or returnClientError('You must specify a url!'); - $format = $this->userData['format'] - or returnClientError('You must specify a format!'); + $format = $this->userData['format'] + or returnClientError('You must specify a format!'); - $bridgeFac = new \BridgeFactory(); + $bridgeFac = new \BridgeFactory(); - foreach($bridgeFac->getBridgeNames() as $bridgeName) { + foreach ($bridgeFac->getBridgeNames() as $bridgeName) { + if (!$bridgeFac->isWhitelisted($bridgeName)) { + continue; + } - if(!$bridgeFac->isWhitelisted($bridgeName)) { - continue; - } + $bridge = $bridgeFac->create($bridgeName); - $bridge = $bridgeFac->create($bridgeName); + if ($bridge === false) { + continue; + } - if($bridge === false) { - continue; - } + $bridgeParams = $bridge->detectParameters($targetURL); - $bridgeParams = $bridge->detectParameters($targetURL); + if (is_null($bridgeParams)) { + continue; + } - if(is_null($bridgeParams)) { - continue; - } + $bridgeParams['bridge'] = $bridgeName; + $bridgeParams['format'] = $format; - $bridgeParams['bridge'] = $bridgeName; - $bridgeParams['format'] = $format; + header('Location: ?action=display&' . http_build_query($bridgeParams), true, 301); + die(); + } - header('Location: ?action=display&' . http_build_query($bridgeParams), true, 301); - die(); - - } - - returnClientError('No bridge found for given URL: ' . $targetURL); - } + returnClientError('No bridge found for given URL: ' . $targetURL); + } } diff --git a/actions/DisplayAction.php b/actions/DisplayAction.php index e7031dab..721e9446 100644 --- a/actions/DisplayAction.php +++ b/actions/DisplayAction.php @@ -1,4 +1,5 @@ getCode(); - if ($returnCode === 301 || $returnCode === 302) { - # Don't pass redirect codes to the exterior - $returnCode = 508; - } - return $returnCode; - } + private function getReturnCode($error) + { + $returnCode = $error->getCode(); + if ($returnCode === 301 || $returnCode === 302) { + # Don't pass redirect codes to the exterior + $returnCode = 508; + } + return $returnCode; + } - public function execute() { - $bridge = array_key_exists('bridge', $this->userData) ? $this->userData['bridge'] : null; + public function execute() + { + $bridge = array_key_exists('bridge', $this->userData) ? $this->userData['bridge'] : null; - $format = $this->userData['format'] - or returnClientError('You must specify a format!'); + $format = $this->userData['format'] + or returnClientError('You must specify a format!'); - $bridgeFac = new \BridgeFactory(); + $bridgeFac = new \BridgeFactory(); - // whitelist control - if(!$bridgeFac->isWhitelisted($bridge)) { - throw new \Exception('This bridge is not whitelisted', 401); - die; - } + // whitelist control + if (!$bridgeFac->isWhitelisted($bridge)) { + throw new \Exception('This bridge is not whitelisted', 401); + die; + } - // Data retrieval - $bridge = $bridgeFac->create($bridge); - $bridge->loadConfiguration(); + // Data retrieval + $bridge = $bridgeFac->create($bridge); + $bridge->loadConfiguration(); - $noproxy = array_key_exists('_noproxy', $this->userData) - && filter_var($this->userData['_noproxy'], FILTER_VALIDATE_BOOLEAN); + $noproxy = array_key_exists('_noproxy', $this->userData) + && filter_var($this->userData['_noproxy'], FILTER_VALIDATE_BOOLEAN); - if(defined('PROXY_URL') && PROXY_BYBRIDGE && $noproxy) { - define('NOPROXY', true); - } + if (defined('PROXY_URL') && PROXY_BYBRIDGE && $noproxy) { + define('NOPROXY', true); + } - // Cache timeout - $cache_timeout = -1; - if(array_key_exists('_cache_timeout', $this->userData)) { + // Cache timeout + $cache_timeout = -1; + if (array_key_exists('_cache_timeout', $this->userData)) { + if (!CUSTOM_CACHE_TIMEOUT) { + unset($this->userData['_cache_timeout']); + $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) . '?' . http_build_query($this->userData); + header('Location: ' . $uri, true, 301); + die(); + } - if(!CUSTOM_CACHE_TIMEOUT) { - unset($this->userData['_cache_timeout']); - $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) . '?' . http_build_query($this->userData); - header('Location: ' . $uri, true, 301); - die(); - } + $cache_timeout = filter_var($this->userData['_cache_timeout'], FILTER_VALIDATE_INT); + } else { + $cache_timeout = $bridge->getCacheTimeout(); + } - $cache_timeout = filter_var($this->userData['_cache_timeout'], FILTER_VALIDATE_INT); + // Remove parameters that don't concern bridges + $bridge_params = array_diff_key( + $this->userData, + array_fill_keys( + [ + 'action', + 'bridge', + 'format', + '_noproxy', + '_cache_timeout', + '_error_time' + ], + '' + ) + ); - } else { - $cache_timeout = $bridge->getCacheTimeout(); - } + // Remove parameters that don't concern caches + $cache_params = array_diff_key( + $this->userData, + array_fill_keys( + [ + 'action', + 'format', + '_noproxy', + '_cache_timeout', + '_error_time' + ], + '' + ) + ); - // Remove parameters that don't concern bridges - $bridge_params = array_diff_key( - $this->userData, - array_fill_keys( - array( - 'action', - 'bridge', - 'format', - '_noproxy', - '_cache_timeout', - '_error_time' - ), '') - ); + // Initialize cache + $cacheFac = new CacheFactory(); - // Remove parameters that don't concern caches - $cache_params = array_diff_key( - $this->userData, - array_fill_keys( - array( - 'action', - 'format', - '_noproxy', - '_cache_timeout', - '_error_time' - ), '') - ); + $cache = $cacheFac->create(Configuration::getConfig('cache', 'type')); + $cache->setScope(''); + $cache->purgeCache(86400); // 24 hours + $cache->setKey($cache_params); - // Initialize cache - $cacheFac = new CacheFactory(); + $items = []; + $infos = []; + $mtime = $cache->getTime(); - $cache = $cacheFac->create(Configuration::getConfig('cache', 'type')); - $cache->setScope(''); - $cache->purgeCache(86400); // 24 hours - $cache->setKey($cache_params); + if ( + $mtime !== false + && (time() - $cache_timeout < $mtime) + && !Debug::isEnabled() + ) { // Load cached data + // Send "Not Modified" response if client supports it + // Implementation based on https://stackoverflow.com/a/10847262 + if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { + $stime = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']); - $items = array(); - $infos = array(); - $mtime = $cache->getTime(); + if ($mtime <= $stime) { // Cached data is older or same + header('Last-Modified: ' . gmdate('D, d M Y H:i:s ', $mtime) . 'GMT', true, 304); + die(); + } + } - if($mtime !== false - && (time() - $cache_timeout < $mtime) - && !Debug::isEnabled()) { // Load cached data + $cached = $cache->loadData(); - // Send "Not Modified" response if client supports it - // Implementation based on https://stackoverflow.com/a/10847262 - if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { - $stime = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']); + if (isset($cached['items']) && isset($cached['extraInfos'])) { + foreach ($cached['items'] as $item) { + $items[] = new \FeedItem($item); + } - if($mtime <= $stime) { // Cached data is older or same - header('Last-Modified: ' . gmdate('D, d M Y H:i:s ', $mtime) . 'GMT', true, 304); - die(); - } - } + $infos = $cached['extraInfos']; + } + } else { // Collect new data + try { + $bridge->setDatas($bridge_params); + $bridge->collectData(); - $cached = $cache->loadData(); + $items = $bridge->getItems(); - if(isset($cached['items']) && isset($cached['extraInfos'])) { - foreach($cached['items'] as $item) { - $items[] = new \FeedItem($item); - } + // Transform "legacy" items to FeedItems if necessary. + // Remove this code when support for "legacy" items ends! + if (isset($items[0]) && is_array($items[0])) { + $feedItems = []; - $infos = $cached['extraInfos']; - } + foreach ($items as $item) { + $feedItems[] = new \FeedItem($item); + } - } else { // Collect new data + $items = $feedItems; + } - try { - $bridge->setDatas($bridge_params); - $bridge->collectData(); + $infos = [ + 'name' => $bridge->getName(), + 'uri' => $bridge->getURI(), + 'donationUri' => $bridge->getDonationURI(), + 'icon' => $bridge->getIcon() + ]; + } catch (\Throwable $e) { + error_log($e); - $items = $bridge->getItems(); + if (logBridgeError($bridge::NAME, $e->getCode()) >= Configuration::getConfig('error', 'report_limit')) { + if (Configuration::getConfig('error', 'output') === 'feed') { + $item = new \FeedItem(); - // Transform "legacy" items to FeedItems if necessary. - // Remove this code when support for "legacy" items ends! - if(isset($items[0]) && is_array($items[0])) { - $feedItems = array(); + // Create "new" error message every 24 hours + $this->userData['_error_time'] = urlencode((int)(time() / 86400)); - foreach($items as $item) { - $feedItems[] = new \FeedItem($item); - } + $message = sprintf( + 'Bridge returned error %s! (%s)', + $e->getCode(), + $this->userData['_error_time'] + ); + $item->setTitle($message); - $items = $feedItems; - } + $item->setURI( + (isset($_SERVER['REQUEST_URI']) ? parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) : '') + . '?' + . http_build_query($this->userData) + ); - $infos = array( - 'name' => $bridge->getName(), - 'uri' => $bridge->getURI(), - 'donationUri' => $bridge->getDonationURI(), - 'icon' => $bridge->getIcon() - ); - } catch(\Throwable $e) { - error_log($e); + $item->setTimestamp(time()); + $item->setContent(buildBridgeException($e, $bridge)); - if(logBridgeError($bridge::NAME, $e->getCode()) >= Configuration::getConfig('error', 'report_limit')) { - if(Configuration::getConfig('error', 'output') === 'feed') { - $item = new \FeedItem(); + $items[] = $item; + } elseif (Configuration::getConfig('error', 'output') === 'http') { + header('Content-Type: text/html', true, $this->getReturnCode($e)); + die(buildTransformException($e, $bridge)); + } + } + } - // Create "new" error message every 24 hours - $this->userData['_error_time'] = urlencode((int)(time() / 86400)); + // Store data in cache + $cache->saveData([ + 'items' => array_map(function ($i) { + return $i->toArray(); + }, $items), + 'extraInfos' => $infos + ]); + } - $message = sprintf( - 'Bridge returned error %s! (%s)', - $e->getCode(), - $this->userData['_error_time'] - ); - $item->setTitle($message); + // Data transformation + try { + $formatFac = new FormatFactory(); + $format = $formatFac->create($format); + $format->setItems($items); + $format->setExtraInfos($infos); + $lastModified = $cache->getTime(); + $format->setLastModified($lastModified); + if ($lastModified) { + header('Last-Modified: ' . gmdate('D, d M Y H:i:s ', $lastModified) . 'GMT'); + } + header('Content-Type: ' . $format->getMimeType() . '; charset=' . $format->getCharset()); - $item->setURI( - (isset($_SERVER['REQUEST_URI']) ? parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) : '') - . '?' - . http_build_query($this->userData) - ); - - $item->setTimestamp(time()); - $item->setContent(buildBridgeException($e, $bridge)); - - $items[] = $item; - } elseif(Configuration::getConfig('error', 'output') === 'http') { - header('Content-Type: text/html', true, $this->getReturnCode($e)); - die(buildTransformException($e, $bridge)); - } - } - } - - // Store data in cache - $cache->saveData(array( - 'items' => array_map(function($i){ return $i->toArray(); }, $items), - 'extraInfos' => $infos - )); - - } - - // Data transformation - try { - $formatFac = new FormatFactory(); - $format = $formatFac->create($format); - $format->setItems($items); - $format->setExtraInfos($infos); - $lastModified = $cache->getTime(); - $format->setLastModified($lastModified); - if ($lastModified) { - header('Last-Modified: ' . gmdate('D, d M Y H:i:s ', $lastModified) . 'GMT'); - } - header('Content-Type: ' . $format->getMimeType() . '; charset=' . $format->getCharset()); - - echo $format->stringify(); - } catch(\Throwable $e) { - error_log($e); - header('Content-Type: text/html', true, $e->getCode()); - die(buildTransformException($e, $bridge)); - } - } + echo $format->stringify(); + } catch (\Throwable $e) { + error_log($e); + header('Content-Type: text/html', true, $e->getCode()); + die(buildTransformException($e, $bridge)); + } + } } diff --git a/actions/ListAction.php b/actions/ListAction.php index a778d846..7ddc42cb 100644 --- a/actions/ListAction.php +++ b/actions/ListAction.php @@ -1,4 +1,5 @@ bridges = array(); - $list->total = 0; + public function execute() + { + $list = new StdClass(); + $list->bridges = []; + $list->total = 0; - $bridgeFac = new \BridgeFactory(); + $bridgeFac = new \BridgeFactory(); - foreach($bridgeFac->getBridgeNames() as $bridgeName) { + foreach ($bridgeFac->getBridgeNames() as $bridgeName) { + $bridge = $bridgeFac->create($bridgeName); - $bridge = $bridgeFac->create($bridgeName); + if ($bridge === false) { // Broken bridge, show as inactive + $list->bridges[$bridgeName] = [ + 'status' => 'inactive' + ]; - if($bridge === false) { // Broken bridge, show as inactive + continue; + } - $list->bridges[$bridgeName] = array( - 'status' => 'inactive' - ); + $status = $bridgeFac->isWhitelisted($bridgeName) ? 'active' : 'inactive'; - continue; + $list->bridges[$bridgeName] = [ + 'status' => $status, + 'uri' => $bridge->getURI(), + 'donationUri' => $bridge->getDonationURI(), + 'name' => $bridge->getName(), + 'icon' => $bridge->getIcon(), + 'parameters' => $bridge->getParameters(), + 'maintainer' => $bridge->getMaintainer(), + 'description' => $bridge->getDescription() + ]; + } - } + $list->total = count($list->bridges); - $status = $bridgeFac->isWhitelisted($bridgeName) ? 'active' : 'inactive'; - - $list->bridges[$bridgeName] = array( - 'status' => $status, - 'uri' => $bridge->getURI(), - 'donationUri' => $bridge->getDonationURI(), - 'name' => $bridge->getName(), - 'icon' => $bridge->getIcon(), - 'parameters' => $bridge->getParameters(), - 'maintainer' => $bridge->getMaintainer(), - 'description' => $bridge->getDescription() - ); - - } - - $list->total = count($list->bridges); - - header('Content-Type: application/json'); - echo json_encode($list, JSON_PRETTY_PRINT); - } + header('Content-Type: application/json'); + echo json_encode($list, JSON_PRETTY_PRINT); + } } diff --git a/bridges/ABCNewsBridge.php b/bridges/ABCNewsBridge.php index 44208de1..94cd1fb3 100644 --- a/bridges/ABCNewsBridge.php +++ b/bridges/ABCNewsBridge.php @@ -1,45 +1,48 @@ array( - 'type' => 'list', - 'name' => 'Region', - 'title' => 'Choose state', - 'values' => array( - 'ACT' => 'act', - 'NSW' => 'nsw', - 'NT' => 'nt', - 'QLD' => 'qld', - 'SA' => 'sa', - 'TAS' => 'tas', - 'VIC' => 'vic', - 'WA' => 'wa' - ), - ) - ) - ); +class ABCNewsBridge extends BridgeAbstract +{ + const NAME = 'ABC News Bridge'; + const URI = 'https://www.abc.net.au'; + const DESCRIPTION = 'Topics of the Australian Broadcasting Corporation'; + const MAINTAINER = 'yue-dongchen'; - public function collectData() { - $url = 'https://www.abc.net.au/news/' . $this->getInput('topic'); - $html = getSimpleHTMLDOM($url)->find('.YAJzu._2FvRw.ZWhbj._3BZxh', 0); - $html = defaultLinkTo($html, $this->getURI()); + const PARAMETERS = [ + [ + 'topic' => [ + 'type' => 'list', + 'name' => 'Region', + 'title' => 'Choose state', + 'values' => [ + 'ACT' => 'act', + 'NSW' => 'nsw', + 'NT' => 'nt', + 'QLD' => 'qld', + 'SA' => 'sa', + 'TAS' => 'tas', + 'VIC' => 'vic', + 'WA' => 'wa' + ], + ] + ] + ]; - foreach($html->find('._2H7Su') as $article) { - $item = array(); + public function collectData() + { + $url = 'https://www.abc.net.au/news/' . $this->getInput('topic'); + $html = getSimpleHTMLDOM($url)->find('.YAJzu._2FvRw.ZWhbj._3BZxh', 0); + $html = defaultLinkTo($html, $this->getURI()); - $title = $article->find('._3T9Id.fmhNa.nsZdE._2c2Zy._1tOey._3EOTW', 0); - $item['title'] = $title->plaintext; - $item['uri'] = $title->href; - $item['content'] = $article->find('.rMkro._1cBaI._3PhF6._10YQT._1yL-m', 0)->plaintext; - $item['timestamp'] = strtotime($article->find('time', 0)->datetime); + foreach ($html->find('._2H7Su') as $article) { + $item = []; - $this->items[] = $item; - } - } + $title = $article->find('._3T9Id.fmhNa.nsZdE._2c2Zy._1tOey._3EOTW', 0); + $item['title'] = $title->plaintext; + $item['uri'] = $title->href; + $item['content'] = $article->find('.rMkro._1cBaI._3PhF6._10YQT._1yL-m', 0)->plaintext; + $item['timestamp'] = strtotime($article->find('time', 0)->datetime); + + $this->items[] = $item; + } + } } diff --git a/bridges/AO3Bridge.php b/bridges/AO3Bridge.php index 4a6f2ccf..f55c0d45 100644 --- a/bridges/AO3Bridge.php +++ b/bridges/AO3Bridge.php @@ -1,118 +1,130 @@ array( - 'url' => array( - 'name' => 'url', - 'required' => true, - // Example: F/F tag, complete works only - 'exampleValue' => 'https://archiveofourown.org/works?work_search[complete]=T&tag_id=F*s*F', - ), - ), - 'Bookmarks' => array( - 'user' => array( - 'name' => 'user', - 'required' => true, - // Example: Nyaaru's bookmarks - 'exampleValue' => 'Nyaaru', - ), - ), - 'Work' => array( - 'id' => array( - 'name' => 'id', - 'required' => true, - // Example: latest chapters from A Better Past by LysSerris - 'exampleValue' => '18181853', - ), - ) - ); +class AO3Bridge extends BridgeAbstract +{ + const NAME = 'AO3'; + const URI = 'https://archiveofourown.org/'; + const CACHE_TIMEOUT = 1800; + const DESCRIPTION = 'Returns works or chapters from Archive of Our Own'; + const MAINTAINER = 'Obsidienne'; + const PARAMETERS = [ + 'List' => [ + 'url' => [ + 'name' => 'url', + 'required' => true, + // Example: F/F tag, complete works only + 'exampleValue' => 'https://archiveofourown.org/works?work_search[complete]=T&tag_id=F*s*F', + ], + ], + 'Bookmarks' => [ + 'user' => [ + 'name' => 'user', + 'required' => true, + // Example: Nyaaru's bookmarks + 'exampleValue' => 'Nyaaru', + ], + ], + 'Work' => [ + 'id' => [ + 'name' => 'id', + 'required' => true, + // Example: latest chapters from A Better Past by LysSerris + 'exampleValue' => '18181853', + ], + ] + ]; - // Feed for lists of works (e.g. recent works, search results, filtered tags, - // bookmarks, series, collections). - private function collectList($url) { - $html = getSimpleHTMLDOM($url); - $html = defaultLinkTo($html, self::URI); + // Feed for lists of works (e.g. recent works, search results, filtered tags, + // bookmarks, series, collections). + private function collectList($url) + { + $html = getSimpleHTMLDOM($url); + $html = defaultLinkTo($html, self::URI); - foreach($html->find('.index.group > li') as $element) { - $item = array(); + foreach ($html->find('.index.group > li') as $element) { + $item = []; - $title = $element->find('div h4 a', 0); - if (!isset($title)) continue; // discard deleted works - $item['title'] = $title->plaintext; - $item['content'] = $element; - $item['uri'] = $title->href; + $title = $element->find('div h4 a', 0); + if (!isset($title)) { + continue; // discard deleted works + } + $item['title'] = $title->plaintext; + $item['content'] = $element; + $item['uri'] = $title->href; - $strdate = $element->find('div p.datetime', 0)->plaintext; - $item['timestamp'] = strtotime($strdate); + $strdate = $element->find('div p.datetime', 0)->plaintext; + $item['timestamp'] = strtotime($strdate); - $chapters = $element->find('dl dd.chapters', 0); - // bookmarked series and external works do not have a chapters count - $chapters = (isset($chapters) ? $chapters->plaintext : 0); - $item['uid'] = $item['uri'] . "/$strdate/$chapters"; + $chapters = $element->find('dl dd.chapters', 0); + // bookmarked series and external works do not have a chapters count + $chapters = (isset($chapters) ? $chapters->plaintext : 0); + $item['uid'] = $item['uri'] . "/$strdate/$chapters"; - $this->items[] = $item; - } - } + $this->items[] = $item; + } + } - // Feed for recent chapters of a specific work. - private function collectWork($id) { - $url = self::URI . "/works/$id/navigate"; - $html = getSimpleHTMLDOM($url); - $html = defaultLinkTo($html, self::URI); + // Feed for recent chapters of a specific work. + private function collectWork($id) + { + $url = self::URI . "/works/$id/navigate"; + $html = getSimpleHTMLDOM($url); + $html = defaultLinkTo($html, self::URI); - $this->title = $html->find('h2 a', 0)->plaintext; + $this->title = $html->find('h2 a', 0)->plaintext; - foreach($html->find('ol.index.group > li') as $element) { - $item = array(); + foreach ($html->find('ol.index.group > li') as $element) { + $item = []; - $item['title'] = $element->find('a', 0)->plaintext; - $item['content'] = $element; - $item['uri'] = $element->find('a', 0)->href; + $item['title'] = $element->find('a', 0)->plaintext; + $item['content'] = $element; + $item['uri'] = $element->find('a', 0)->href; - $strdate = $element->find('span.datetime', 0)->plaintext; - $strdate = str_replace('(', '', $strdate); - $strdate = str_replace(')', '', $strdate); - $item['timestamp'] = strtotime($strdate); + $strdate = $element->find('span.datetime', 0)->plaintext; + $strdate = str_replace('(', '', $strdate); + $strdate = str_replace(')', '', $strdate); + $item['timestamp'] = strtotime($strdate); - $item['uid'] = $item['uri'] . "/$strdate"; + $item['uid'] = $item['uri'] . "/$strdate"; - $this->items[] = $item; - } + $this->items[] = $item; + } - $this->items = array_reverse($this->items); - } + $this->items = array_reverse($this->items); + } - public function collectData() { - switch($this->queriedContext) { - case 'Bookmarks': - $user = $this->getInput('user'); - $this->title = $user; - $url = self::URI - . '/users/' . $user - . '/bookmarks?bookmark_search[sort_column]=bookmarkable_date'; - return $this->collectList($url); - case 'List': return $this->collectList( - $this->getInput('url') - ); - case 'Work': return $this->collectWork( - $this->getInput('id') - ); - } - } + public function collectData() + { + switch ($this->queriedContext) { + case 'Bookmarks': + $user = $this->getInput('user'); + $this->title = $user; + $url = self::URI + . '/users/' . $user + . '/bookmarks?bookmark_search[sort_column]=bookmarkable_date'; + return $this->collectList($url); + case 'List': + return $this->collectList( + $this->getInput('url') + ); + case 'Work': + return $this->collectWork( + $this->getInput('id') + ); + } + } - public function getName() { - $name = parent::getName() . " $this->queriedContext"; - if (isset($this->title)) $name .= " - $this->title"; - return $name; - } + public function getName() + { + $name = parent::getName() . " $this->queriedContext"; + if (isset($this->title)) { + $name .= " - $this->title"; + } + return $name; + } - public function getIcon() { - return self::URI . '/favicon.ico'; - } + public function getIcon() + { + return self::URI . '/favicon.ico'; + } } diff --git a/bridges/ARDMediathekBridge.php b/bridges/ARDMediathekBridge.php index 97250272..6de8dad7 100644 --- a/bridges/ARDMediathekBridge.php +++ b/bridges/ARDMediathekBridge.php @@ -1,95 +1,98 @@ array( - 'name' => 'Show Link or ID', - 'required' => true, - 'title' => 'Link to the show page or just its alphanumeric suffix', - 'defaultValue' => 'https://www.ardmediathek.de/sendung/45-min/Y3JpZDovL25kci5kZS8xMzkx/' - ) - ) - ); +class ARDMediathekBridge extends BridgeAbstract +{ + const NAME = 'ARD-Mediathek Bridge'; + const URI = 'https://www.ardmediathek.de'; + const DESCRIPTION = 'Feed of any series in the ARD-Mediathek, specified by its path'; + const MAINTAINER = 'yue-dongchen'; + /* + * Number of Items to be requested from ARDmediathek API + * 12 has been observed on the wild + * 29 is the highest successfully tested value + * More Items could be fetched via pagination + * The JSON-field pagination holds more information on that + * @const PAGESIZE number of requested items + */ + const PAGESIZE = 29; + /* + * The URL Prefix of the (Webapp-)API + * @const APIENDPOINT https-URL of the used endpoint + */ + const APIENDPOINT = 'https://api.ardmediathek.de/page-gateway/widgets/ard/asset/'; + /* + * The URL prefix of the video link + * URLs from the webapp include a slug containing titles of show, episode, and tv station. + * It seems to work without that. + * @const VIDEOLINKPREFIX https-URL prefix of video links + */ + const VIDEOLINKPREFIX = 'https://www.ardmediathek.de/video/'; + /* + * The requested width of the preview image + * 432 has been observed on the wild + * The webapp seems to also compute and add the height value + * It seems to works without that. + * @const IMAGEWIDTH width in px of the preview image + */ + const IMAGEWIDTH = 432; + /* + * Placeholder that will be replace by IMAGEWIDTH in the preview image URL + * @const IMAGEWIDTHPLACEHOLDER + */ + const IMAGEWIDTHPLACEHOLDER = '{width}'; - public function collectData() { - $oldTz = date_default_timezone_get(); + const PARAMETERS = [ + [ + 'path' => [ + 'name' => 'Show Link or ID', + 'required' => true, + 'title' => 'Link to the show page or just its alphanumeric suffix', + 'defaultValue' => 'https://www.ardmediathek.de/sendung/45-min/Y3JpZDovL25kci5kZS8xMzkx/' + ] + ] + ]; - date_default_timezone_set('Europe/Berlin'); + public function collectData() + { + $oldTz = date_default_timezone_get(); - $pathComponents = explode('/', $this->getInput('path')); - if (empty($pathComponents)) { - returnClientError('Path may not be empty'); - } - if (count($pathComponents) < 2) { - $showID = $pathComponents[0]; - } else { - $lastKey = count($pathComponents) - 1; - $showID = $pathComponents[$lastKey]; - if (strlen($showID) === 0) { - $showID = $pathComponents[$lastKey - 1]; - } - } + date_default_timezone_set('Europe/Berlin'); - $url = SELF::APIENDPOINT . $showID . '/?pageSize=' . SELF::PAGESIZE; - $rawJSON = getContents($url); - $processedJSON = json_decode($rawJSON); + $pathComponents = explode('/', $this->getInput('path')); + if (empty($pathComponents)) { + returnClientError('Path may not be empty'); + } + if (count($pathComponents) < 2) { + $showID = $pathComponents[0]; + } else { + $lastKey = count($pathComponents) - 1; + $showID = $pathComponents[$lastKey]; + if (strlen($showID) === 0) { + $showID = $pathComponents[$lastKey - 1]; + } + } - foreach($processedJSON->teasers as $video) { - $item = array(); - // there is also ->links->self->id, ->links->self->urlId, ->links->target->id, ->links->target->urlId - $item['uri'] = SELF::VIDEOLINKPREFIX . $video->id . '/'; - // there is also ->mediumTitle and ->shortTitle - $item['title'] = $video->longTitle; - // in the test, aspect16x9 was the only child of images, not sure whether that is always true - $item['enclosures'] = array( - str_replace(SELF::IMAGEWIDTHPLACEHOLDER, SELF::IMAGEWIDTH, $video->images->aspect16x9->src) - ); - $item['content'] = '

'; - $item['timestamp'] = $video->broadcastedOn; - $item['uid'] = $video->id; - $item['author'] = $video->publicationService->name; - $this->items[] = $item; - } + $url = self::APIENDPOINT . $showID . '/?pageSize=' . self::PAGESIZE; + $rawJSON = getContents($url); + $processedJSON = json_decode($rawJSON); - date_default_timezone_set($oldTz); - } + foreach ($processedJSON->teasers as $video) { + $item = []; + // there is also ->links->self->id, ->links->self->urlId, ->links->target->id, ->links->target->urlId + $item['uri'] = self::VIDEOLINKPREFIX . $video->id . '/'; + // there is also ->mediumTitle and ->shortTitle + $item['title'] = $video->longTitle; + // in the test, aspect16x9 was the only child of images, not sure whether that is always true + $item['enclosures'] = [ + str_replace(self::IMAGEWIDTHPLACEHOLDER, self::IMAGEWIDTH, $video->images->aspect16x9->src) + ]; + $item['content'] = '

'; + $item['timestamp'] = $video->broadcastedOn; + $item['uid'] = $video->id; + $item['author'] = $video->publicationService->name; + $this->items[] = $item; + } + + date_default_timezone_set($oldTz); + } } diff --git a/bridges/ASRockNewsBridge.php b/bridges/ASRockNewsBridge.php index 6c93798f..1b516377 100644 --- a/bridges/ASRockNewsBridge.php +++ b/bridges/ASRockNewsBridge.php @@ -1,55 +1,58 @@ find('div.inner > a') as $index => $a) { - $item = array(); + foreach ($html->find('div.inner > a') as $index => $a) { + $item = []; - $articlePath = $a->href; + $articlePath = $a->href; - $articlePageHtml = getSimpleHTMLDOMCached($articlePath, self::CACHE_TIMEOUT); + $articlePageHtml = getSimpleHTMLDOMCached($articlePath, self::CACHE_TIMEOUT); - $articlePageHtml = defaultLinkTo($articlePageHtml, self::URI); + $articlePageHtml = defaultLinkTo($articlePageHtml, self::URI); - $contents = $articlePageHtml->find('div.Contents', 0); + $contents = $articlePageHtml->find('div.Contents', 0); - $item['uri'] = $articlePath; - $item['title'] = $contents->find('h3', 0)->innertext; + $item['uri'] = $articlePath; + $item['title'] = $contents->find('h3', 0)->innertext; - $contents->find('h3', 0)->outertext = ''; + $contents->find('h3', 0)->outertext = ''; - $item['content'] = $contents->innertext; - $item['timestamp'] = $this->extractDate($a->plaintext); - $item['enclosures'][] = $a->find('img', 0)->src; - $this->items[] = $item; + $item['content'] = $contents->innertext; + $item['timestamp'] = $this->extractDate($a->plaintext); + $item['enclosures'][] = $a->find('img', 0)->src; + $this->items[] = $item; - if (count($this->items) >= 10) { - break; - } - } - } + if (count($this->items) >= 10) { + break; + } + } + } - private function extractDate($text) { - $dateRegex = '/^([0-9]{4}\/[0-9]{1,2}\/[0-9]{1,2})/'; + private function extractDate($text) + { + $dateRegex = '/^([0-9]{4}\/[0-9]{1,2}\/[0-9]{1,2})/'; - $text = trim($text); + $text = trim($text); - if (preg_match($dateRegex, $text, $matches)) { - return $matches[1]; - } + if (preg_match($dateRegex, $text, $matches)) { + return $matches[1]; + } - return ''; - } + return ''; + } } diff --git a/bridges/AcrimedBridge.php b/bridges/AcrimedBridge.php index 7bc73176..d37f3ce4 100644 --- a/bridges/AcrimedBridge.php +++ b/bridges/AcrimedBridge.php @@ -1,37 +1,40 @@ [ - 'name' => 'limit', - 'type' => 'number', - 'defaultValue' => -1, - ] - ] - ]; + const PARAMETERS = [ + [ + 'limit' => [ + 'name' => 'limit', + 'type' => 'number', + 'defaultValue' => -1, + ] + ] + ]; - public function collectData(){ - $this->collectExpandableDatas( - static::URI . 'spip.php?page=backend', - $this->getInput('limit') - ); - } + public function collectData() + { + $this->collectExpandableDatas( + static::URI . 'spip.php?page=backend', + $this->getInput('limit') + ); + } - protected function parseItem($newsItem){ - $item = parent::parseItem($newsItem); + protected function parseItem($newsItem) + { + $item = parent::parseItem($newsItem); - $articlePage = getSimpleHTMLDOM($newsItem->link); - $article = sanitize($articlePage->find('article.article1', 0)->innertext); - $article = defaultLinkTo($article, static::URI); - $item['content'] = $article; + $articlePage = getSimpleHTMLDOM($newsItem->link); + $article = sanitize($articlePage->find('article.article1', 0)->innertext); + $article = defaultLinkTo($article, static::URI); + $item['content'] = $article; - return $item; - } + return $item; + } } diff --git a/bridges/AirBreizhBridge.php b/bridges/AirBreizhBridge.php index 2d852da5..a822625f 100644 --- a/bridges/AirBreizhBridge.php +++ b/bridges/AirBreizhBridge.php @@ -1,54 +1,57 @@ array( - 'theme' => array( - 'name' => 'Thematique', - 'type' => 'list', - 'values' => array( - 'Tout' => '', - 'Rapport d\'activite' => 'rapport-dactivite', - 'Etude' => 'etudes', - 'Information' => 'information', - 'Autres documents' => 'autres-documents', - 'Plan Régional de Surveillance de la qualité de l’air' => 'prsqa', - 'Transport' => 'transport' - ) - ) - ) - ); +class AirBreizhBridge extends BridgeAbstract +{ + const MAINTAINER = 'fanch317'; + const NAME = 'Air Breizh'; + const URI = 'https://www.airbreizh.asso.fr/'; + const DESCRIPTION = 'Returns newests publications on Air Breizh'; + const PARAMETERS = [ + 'Publications' => [ + 'theme' => [ + 'name' => 'Thematique', + 'type' => 'list', + 'values' => [ + 'Tout' => '', + 'Rapport d\'activite' => 'rapport-dactivite', + 'Etude' => 'etudes', + 'Information' => 'information', + 'Autres documents' => 'autres-documents', + 'Plan Régional de Surveillance de la qualité de l’air' => 'prsqa', + 'Transport' => 'transport' + ] + ] + ] + ]; - public function getIcon() { - return 'https://www.airbreizh.asso.fr/voy_content/uploads/2017/11/favicon.png'; - } + public function getIcon() + { + return 'https://www.airbreizh.asso.fr/voy_content/uploads/2017/11/favicon.png'; + } - public function collectData(){ - $html = ''; - $html = getSimpleHTMLDOM(static::URI . 'publications/?fwp_publications_thematiques=' . $this->getInput('theme')) - or returnClientError('No results for this query.'); + public function collectData() + { + $html = ''; + $html = getSimpleHTMLDOM(static::URI . 'publications/?fwp_publications_thematiques=' . $this->getInput('theme')) + or returnClientError('No results for this query.'); - foreach ($html->find('article') as $article) { - $item = array(); - // Title - $item['title'] = $article->find('h2', 0)->plaintext; - // Author - $item['author'] = 'Air Breizh'; - // Image - $imagelink = $article->find('.card__image', 0)->find('img', 0)->getAttribute('src'); - // Content preview - $item['content'] = ' + foreach ($html->find('article') as $article) { + $item = []; + // Title + $item['title'] = $article->find('h2', 0)->plaintext; + // Author + $item['author'] = 'Air Breizh'; + // Image + $imagelink = $article->find('.card__image', 0)->find('img', 0)->getAttribute('src'); + // Content preview + $item['content'] = '
' - . $article->find('.card__text', 0)->plaintext; - // URL - $item['uri'] = $article->find('.publi__buttons', 0)->find('a', 0)->getAttribute('href'); - // ID - $item['id'] = $article->find('.publi__buttons', 0)->find('a', 0)->getAttribute('href'); - $this->items[] = $item; - } - } + . $article->find('.card__text', 0)->plaintext; + // URL + $item['uri'] = $article->find('.publi__buttons', 0)->find('a', 0)->getAttribute('href'); + // ID + $item['id'] = $article->find('.publi__buttons', 0)->find('a', 0)->getAttribute('href'); + $this->items[] = $item; + } + } } diff --git a/bridges/AlbionOnlineBridge.php b/bridges/AlbionOnlineBridge.php index f51b815b..4b191b18 100644 --- a/bridges/AlbionOnlineBridge.php +++ b/bridges/AlbionOnlineBridge.php @@ -1,73 +1,76 @@ array( - 'name' => 'Limit', - 'type' => 'number', - 'required' => true, - 'title' => 'Maximum number of items to return', - 'defaultValue' => 5, - ), - 'language' => array( - 'name' => 'Language', - 'type' => 'list', - 'values' => array( - 'English' => 'en', - 'Deutsch' => 'de', - 'Polski' => 'pl', - 'Français' => 'fr', - 'Русский' => 'ru', - 'Português' => 'pt', - 'Español' => 'es', - ), - 'title' => 'Language of changelog posts', - 'defaultValue' => 'en', - ), - 'full' => array( - 'name' => 'Full changelog', - 'type' => 'checkbox', - 'required' => false, - 'title' => 'Enable to receive the full changelog post for each item' - ), - )); + const PARAMETERS = [ [ + 'postcount' => [ + 'name' => 'Limit', + 'type' => 'number', + 'required' => true, + 'title' => 'Maximum number of items to return', + 'defaultValue' => 5, + ], + 'language' => [ + 'name' => 'Language', + 'type' => 'list', + 'values' => [ + 'English' => 'en', + 'Deutsch' => 'de', + 'Polski' => 'pl', + 'Français' => 'fr', + 'Русский' => 'ru', + 'Português' => 'pt', + 'Español' => 'es', + ], + 'title' => 'Language of changelog posts', + 'defaultValue' => 'en', + ], + 'full' => [ + 'name' => 'Full changelog', + 'type' => 'checkbox', + 'required' => false, + 'title' => 'Enable to receive the full changelog post for each item' + ], + ]]; - public function collectData() { - $api = 'https://albiononline.com/'; - // Example: https://albiononline.com/en/changelog/1/5 - $url = $api . $this->getInput('language') . '/changelog/1/' . $this->getInput('postcount'); + public function collectData() + { + $api = 'https://albiononline.com/'; + // Example: https://albiononline.com/en/changelog/1/5 + $url = $api . $this->getInput('language') . '/changelog/1/' . $this->getInput('postcount'); - $html = getSimpleHTMLDOM($url); + $html = getSimpleHTMLDOM($url); - foreach ($html->find('li') as $data) { - $item = array(); - $item['uri'] = self::URI . $data->find('a', 0)->getAttribute('href'); - $item['title'] = trim(explode('|', $data->find('span', 0)->plaintext)[0]); - // Time below work only with en lang. Need to think about solution. May be separate request like getFullChangelog, but to english list for all language - //print_r( date_parse_from_format( 'M j, Y' , 'Sep 9, 2020') ); - //$item['timestamp'] = $this->extractDate($a->plaintext); - $item['author'] = 'albiononline.com'; - if($this->getInput('full')) { - $item['content'] = $this->getFullChangelog($item['uri']); - } else { - //$item['content'] = trim(preg_replace('/\s+/', ' ', $data->find('span', 0)->plaintext)); - // Just use title, no info at all or use title and date, see above - $item['content'] = $item['title']; - } - $item['uid'] = hash('sha256', $item['title']); - $this->items[] = $item; - } - } + foreach ($html->find('li') as $data) { + $item = []; + $item['uri'] = self::URI . $data->find('a', 0)->getAttribute('href'); + $item['title'] = trim(explode('|', $data->find('span', 0)->plaintext)[0]); + // Time below work only with en lang. Need to think about solution. May be separate request like getFullChangelog, but to english list for all language + //print_r( date_parse_from_format( 'M j, Y' , 'Sep 9, 2020') ); + //$item['timestamp'] = $this->extractDate($a->plaintext); + $item['author'] = 'albiononline.com'; + if ($this->getInput('full')) { + $item['content'] = $this->getFullChangelog($item['uri']); + } else { + //$item['content'] = trim(preg_replace('/\s+/', ' ', $data->find('span', 0)->plaintext)); + // Just use title, no info at all or use title and date, see above + $item['content'] = $item['title']; + } + $item['uid'] = hash('sha256', $item['title']); + $this->items[] = $item; + } + } - private function getFullChangelog($url) { - $html = getSimpleHTMLDOMCached($url); - $html = defaultLinkTo($html, self::URI); - return $html->find('div.small-12.columns', 1)->innertext; - } + private function getFullChangelog($url) + { + $html = getSimpleHTMLDOMCached($url); + $html = defaultLinkTo($html, self::URI); + return $html->find('div.small-12.columns', 1)->innertext; + } } diff --git a/bridges/AlfaBankByBridge.php b/bridges/AlfaBankByBridge.php index 4b1ed48e..7c13c14d 100644 --- a/bridges/AlfaBankByBridge.php +++ b/bridges/AlfaBankByBridge.php @@ -1,83 +1,87 @@ array( - 'business' => array( - 'name' => 'Альфа Бизнес', - 'type' => 'list', - 'title' => 'В зависимости от выбора, возращает уведомления для" . +class AlfaBankByBridge extends BridgeAbstract +{ + const MAINTAINER = 'lassana'; + const NAME = 'AlfaBank.by Новости'; + const URI = 'https://www.alfabank.by'; + const DESCRIPTION = 'Уведомления Alfa-Now — новости от Альфа-Банка'; + const CACHE_TIMEOUT = 3600; // 1 hour + const PARAMETERS = [ + 'News' => [ + 'business' => [ + 'name' => 'Альфа Бизнес', + 'type' => 'list', + 'title' => 'В зависимости от выбора, возращает уведомления для" . " клиентов физ. лиц либо для клиентов-юридических лиц и ИП', - 'values' => array( - 'Новости' => 'news', - 'Новости бизнеса' => 'newsBusiness' - ), - 'defaultValue' => 'news' - ), - 'fullContent' => array( - 'name' => 'Включать содержимое', - 'type' => 'checkbox', - 'title' => 'Если выбрано, содержимое уведомлений вставляется в поток (работает медленно)' - ) - ) - ); + 'values' => [ + 'Новости' => 'news', + 'Новости бизнеса' => 'newsBusiness' + ], + 'defaultValue' => 'news' + ], + 'fullContent' => [ + 'name' => 'Включать содержимое', + 'type' => 'checkbox', + 'title' => 'Если выбрано, содержимое уведомлений вставляется в поток (работает медленно)' + ] + ] + ]; - public function collectData() { - $business = $this->getInput('business') == 'newsBusiness'; - $fullContent = $this->getInput('fullContent') == 'on'; + public function collectData() + { + $business = $this->getInput('business') == 'newsBusiness'; + $fullContent = $this->getInput('fullContent') == 'on'; - $mainPageUrl = self::URI . '/about/articles/uvedomleniya/'; - if($business) { - $mainPageUrl .= '?business=true'; - } - $html = getSimpleHTMLDOM($mainPageUrl); - $limit = 0; + $mainPageUrl = self::URI . '/about/articles/uvedomleniya/'; + if ($business) { + $mainPageUrl .= '?business=true'; + } + $html = getSimpleHTMLDOM($mainPageUrl); + $limit = 0; - foreach($html->find('a.notifications__item') as $element) { - if($limit < 10) { - $item = array(); - $item['uid'] = 'urn:sha1:' . hash('sha1', $element->getAttribute('data-notification-id')); - $item['title'] = $element->find('div.item-title', 0)->innertext; - $item['timestamp'] = DateTime::createFromFormat( - 'd M Y', - $this->ruMonthsToEn($element->find('div.item-date', 0)->innertext) - )->getTimestamp(); + foreach ($html->find('a.notifications__item') as $element) { + if ($limit < 10) { + $item = []; + $item['uid'] = 'urn:sha1:' . hash('sha1', $element->getAttribute('data-notification-id')); + $item['title'] = $element->find('div.item-title', 0)->innertext; + $item['timestamp'] = DateTime::createFromFormat( + 'd M Y', + $this->ruMonthsToEn($element->find('div.item-date', 0)->innertext) + )->getTimestamp(); - $itemUrl = self::URI . $element->href; - if($business) { - $itemUrl = str_replace('?business=true', '', $itemUrl); - } - $item['uri'] = $itemUrl; + $itemUrl = self::URI . $element->href; + if ($business) { + $itemUrl = str_replace('?business=true', '', $itemUrl); + } + $item['uri'] = $itemUrl; - if($fullContent) { - $itemHtml = getSimpleHTMLDOM($itemUrl); - if($itemHtml) { - $item['content'] = $itemHtml->find('div.now-p__content-text', 0)->innertext; - } - } + if ($fullContent) { + $itemHtml = getSimpleHTMLDOM($itemUrl); + if ($itemHtml) { + $item['content'] = $itemHtml->find('div.now-p__content-text', 0)->innertext; + } + } - $this->items[] = $item; - $limit++; - } - } - } + $this->items[] = $item; + $limit++; + } + } + } - public function getIcon() { - return static::URI . '/local/images/favicon.ico'; - } + public function getIcon() + { + return static::URI . '/local/images/favicon.ico'; + } - private function ruMonthsToEn($date) { - $ruMonths = array( - 'Января', 'Февраля', 'Марта', 'Апреля', 'Мая', 'Июня', - 'Июля', 'Августа', 'Сентября', 'Октября', 'Ноября', 'Декабря' ); - $enMonths = array( - 'January', 'February', 'March', 'April', 'May', 'June', - 'July', 'August', 'September', 'October', 'November', 'December' ); - return str_replace($ruMonths, $enMonths, $date); - } + private function ruMonthsToEn($date) + { + $ruMonths = [ + 'Января', 'Февраля', 'Марта', 'Апреля', 'Мая', 'Июня', + 'Июля', 'Августа', 'Сентября', 'Октября', 'Ноября', 'Декабря' ]; + $enMonths = [ + 'January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December' ]; + return str_replace($ruMonths, $enMonths, $date); + } } diff --git a/bridges/AllocineFRBridge.php b/bridges/AllocineFRBridge.php index 07d031d8..b93bccd2 100644 --- a/bridges/AllocineFRBridge.php +++ b/bridges/AllocineFRBridge.php @@ -1,113 +1,115 @@ array( - 'name' => 'Emission', - 'type' => 'list', - 'title' => 'Sélectionner l\'emission', - 'values' => array( - 'Faux Raccord' => 'faux-raccord', - 'Fanzone' => 'fanzone', - 'Game In Ciné' => 'game-in-cine', - 'Pour la faire courte' => 'pour-la-faire-courte', - 'Home Cinéma' => 'home-cinema', - 'PILS - Par Ici Les Sorties' => 'pils-par-ici-les-sorties', - 'AlloCiné : l\'émission, sur LeStream' => 'allocine-lemission-sur-lestream', - 'Give Me Five' => 'give-me-five', - 'Aviez-vous remarqué ?' => 'aviez-vous-remarque', - 'Et paf, il est mort' => 'et-paf-il-est-mort', - 'The Big Fan Theory' => 'the-big-fan-theory', - 'Clichés' => 'cliches', - 'Complètement...' => 'completement', - '#Fun Facts' => 'fun-facts', - 'Origin Story' => 'origin-story', - ) - ) - )); +class AllocineFRBridge extends BridgeAbstract +{ + const MAINTAINER = 'superbaillot.net'; + const NAME = 'Allo Cine Bridge'; + const CACHE_TIMEOUT = 25200; // 7h + const URI = 'https://www.allocine.fr'; + const DESCRIPTION = 'Bridge for allocine.fr'; + const PARAMETERS = [ [ + 'category' => [ + 'name' => 'Emission', + 'type' => 'list', + 'title' => 'Sélectionner l\'emission', + 'values' => [ + 'Faux Raccord' => 'faux-raccord', + 'Fanzone' => 'fanzone', + 'Game In Ciné' => 'game-in-cine', + 'Pour la faire courte' => 'pour-la-faire-courte', + 'Home Cinéma' => 'home-cinema', + 'PILS - Par Ici Les Sorties' => 'pils-par-ici-les-sorties', + 'AlloCiné : l\'émission, sur LeStream' => 'allocine-lemission-sur-lestream', + 'Give Me Five' => 'give-me-five', + 'Aviez-vous remarqué ?' => 'aviez-vous-remarque', + 'Et paf, il est mort' => 'et-paf-il-est-mort', + 'The Big Fan Theory' => 'the-big-fan-theory', + 'Clichés' => 'cliches', + 'Complètement...' => 'completement', + '#Fun Facts' => 'fun-facts', + 'Origin Story' => 'origin-story', + ] + ] + ]]; - public function getURI(){ - if(!is_null($this->getInput('category'))) { + public function getURI() + { + if (!is_null($this->getInput('category'))) { + $categories = [ + 'faux-raccord' => '/video/programme-12284/', + 'fanzone' => '/video/programme-12298/', + 'game-in-cine' => '/video/programme-12288/', + 'pour-la-faire-courte' => '/video/programme-20960/', + 'home-cinema' => '/video/programme-12287/', + 'pils-par-ici-les-sorties' => '/video/programme-25789/', + 'allocine-lemission-sur-lestream' => '/video/programme-25123/', + 'give-me-five' => '/video/programme-21919/saison-34518/', + 'aviez-vous-remarque' => '/video/programme-19518/', + 'et-paf-il-est-mort' => '/video/programme-25113/', + 'the-big-fan-theory' => '/video/programme-20403/', + 'cliches' => '/video/programme-24834/', + 'completement' => '/video/programme-23859/', + 'fun-facts' => '/video/programme-23040/', + 'origin-story' => '/video/programme-25667/' + ]; - $categories = array( - 'faux-raccord' => '/video/programme-12284/', - 'fanzone' => '/video/programme-12298/', - 'game-in-cine' => '/video/programme-12288/', - 'pour-la-faire-courte' => '/video/programme-20960/', - 'home-cinema' => '/video/programme-12287/', - 'pils-par-ici-les-sorties' => '/video/programme-25789/', - 'allocine-lemission-sur-lestream' => '/video/programme-25123/', - 'give-me-five' => '/video/programme-21919/saison-34518/', - 'aviez-vous-remarque' => '/video/programme-19518/', - 'et-paf-il-est-mort' => '/video/programme-25113/', - 'the-big-fan-theory' => '/video/programme-20403/', - 'cliches' => '/video/programme-24834/', - 'completement' => '/video/programme-23859/', - 'fun-facts' => '/video/programme-23040/', - 'origin-story' => '/video/programme-25667/' - ); + $category = $this->getInput('category'); + if (array_key_exists($category, $categories)) { + return static::URI . $this->getLastSeasonURI($categories[$category]); + } else { + returnClientError('Emission inconnue'); + } + } - $category = $this->getInput('category'); - if(array_key_exists($category, $categories)) { - return static::URI . $this->getLastSeasonURI($categories[$category]); - } else { - returnClientError('Emission inconnue'); - } - } + return parent::getURI(); + } - return parent::getURI(); - } + private function getLastSeasonURI($category) + { + $html = getSimpleHTMLDOMCached(static::URI . $category, 86400); + $seasonLink = $html->find('section[class=section-wrap section]', 0)->find('div[class=cf]', 0)->find('a', 0); + $URI = $seasonLink->href; + return $URI; + } - private function getLastSeasonURI($category) - { - $html = getSimpleHTMLDOMCached(static::URI . $category, 86400); - $seasonLink = $html->find('section[class=section-wrap section]', 0)->find('div[class=cf]', 0)->find('a', 0); - $URI = $seasonLink->href; - return $URI; - } + public function getName() + { + if (!is_null($this->getInput('category'))) { + return self::NAME . ' : ' + . array_search( + $this->getInput('category'), + self::PARAMETERS[$this->queriedContext]['category']['values'] + ); + } - public function getName(){ - if(!is_null($this->getInput('category'))) { - return self::NAME . ' : ' - . array_search( - $this->getInput('category'), - self::PARAMETERS[$this->queriedContext]['category']['values'] - ); - } + return parent::getName(); + } - return parent::getName(); - } + public function collectData() + { + $html = getSimpleHTMLDOM($this->getURI()); - public function collectData(){ + $category = array_search( + $this->getInput('category'), + self::PARAMETERS[$this->queriedContext]['category']['values'] + ); + foreach ($html->find('div[class=gd-col-left]', 0)->find('div[class*=video-card]') as $element) { + $item = []; - $html = getSimpleHTMLDOM($this->getURI()); + $title = $element->find('a[class*=meta-title-link]', 0); + $content = trim(defaultLinkTo($element->outertext, static::URI)); - $category = array_search( - $this->getInput('category'), - self::PARAMETERS[$this->queriedContext]['category']['values'] - ); - foreach($html->find('div[class=gd-col-left]', 0)->find('div[class*=video-card]') as $element) { - $item = array(); + // Replace image 'src' with the one in 'data-src' + $content = preg_replace('@src="data:image/gif;base64,[A-Za-z0-9+\/]*"@', '', $content); + $content = preg_replace('@data-src=@', 'src=', $content); - $title = $element->find('a[class*=meta-title-link]', 0); - $content = trim(defaultLinkTo($element->outertext, static::URI)); + // Remove date in the content to prevent content update while the video is getting older + $content = preg_replace('@

.*[^<]*[^<]*
@', '', $content); - // Replace image 'src' with the one in 'data-src' - $content = preg_replace('@src="data:image/gif;base64,[A-Za-z0-9+\/]*"@', '', $content); - $content = preg_replace('@data-src=@', 'src=', $content); - - // Remove date in the content to prevent content update while the video is getting older - $content = preg_replace('@
.*[^<]*[^<]*
@', '', $content); - - $item['content'] = $content; - $item['title'] = trim($title->innertext); - $item['uri'] = static::URI . '/' . substr($title->href, 1); - $this->items[] = $item; - } - } + $item['content'] = $content; + $item['title'] = trim($title->innertext); + $item['uri'] = static::URI . '/' . substr($title->href, 1); + $this->items[] = $item; + } + } } diff --git a/bridges/AmazonBridge.php b/bridges/AmazonBridge.php index ba440a59..40855a15 100644 --- a/bridges/AmazonBridge.php +++ b/bridges/AmazonBridge.php @@ -1,103 +1,104 @@ [ + 'name' => 'Keyword', + 'required' => true, + 'exampleValue' => 'watch', + ], + 'sort' => [ + 'name' => 'Sort by', + 'type' => 'list', + 'values' => [ + 'Relevance' => 'relevanceblender', + 'Price: Low to High' => 'price-asc-rank', + 'Price: High to Low' => 'price-desc-rank', + 'Average Customer Review' => 'review-rank', + 'Newest Arrivals' => 'date-desc-rank', + ], + 'defaultValue' => 'relevanceblender', + ], + 'tld' => [ + 'name' => 'Country', + 'type' => 'list', + 'values' => [ + 'Australia' => 'com.au', + 'Brazil' => 'com.br', + 'Canada' => 'ca', + 'China' => 'cn', + 'France' => 'fr', + 'Germany' => 'de', + 'India' => 'in', + 'Italy' => 'it', + 'Japan' => 'co.jp', + 'Mexico' => 'com.mx', + 'Netherlands' => 'nl', + 'Spain' => 'es', + 'Sweden' => 'se', + 'Turkey' => 'com.tr', + 'United Kingdom' => 'co.uk', + 'United States' => 'com', + ], + 'defaultValue' => 'com', + ], + ]]; - const PARAMETERS = array(array( - 'q' => array( - 'name' => 'Keyword', - 'required' => true, - 'exampleValue' => 'watch', - ), - 'sort' => array( - 'name' => 'Sort by', - 'type' => 'list', - 'values' => array( - 'Relevance' => 'relevanceblender', - 'Price: Low to High' => 'price-asc-rank', - 'Price: High to Low' => 'price-desc-rank', - 'Average Customer Review' => 'review-rank', - 'Newest Arrivals' => 'date-desc-rank', - ), - 'defaultValue' => 'relevanceblender', - ), - 'tld' => array( - 'name' => 'Country', - 'type' => 'list', - 'values' => array( - 'Australia' => 'com.au', - 'Brazil' => 'com.br', - 'Canada' => 'ca', - 'China' => 'cn', - 'France' => 'fr', - 'Germany' => 'de', - 'India' => 'in', - 'Italy' => 'it', - 'Japan' => 'co.jp', - 'Mexico' => 'com.mx', - 'Netherlands' => 'nl', - 'Spain' => 'es', - 'Sweden' => 'se', - 'Turkey' => 'com.tr', - 'United Kingdom' => 'co.uk', - 'United States' => 'com', - ), - 'defaultValue' => 'com', - ), - )); + public function collectData() + { + $baseUrl = sprintf('https://www.amazon.%s', $this->getInput('tld')); - public function collectData() { + $url = sprintf( + '%s/s/?field-keywords=%s&sort=%s', + $baseUrl, + urlencode($this->getInput('q')), + $this->getInput('sort') + ); - $baseUrl = sprintf('https://www.amazon.%s', $this->getInput('tld')); + $dom = getSimpleHTMLDOM($url); - $url = sprintf( - '%s/s/?field-keywords=%s&sort=%s', - $baseUrl, - urlencode($this->getInput('q')), - $this->getInput('sort') - ); + $elements = $dom->find('div.s-result-item'); - $dom = getSimpleHTMLDOM($url); + foreach ($elements as $element) { + $item = []; - $elements = $dom->find('div.s-result-item'); + $title = $element->find('h2', 0); + if (!$title) { + continue; + } - foreach($elements as $element) { - $item = []; + $item['title'] = $title->innertext; - $title = $element->find('h2', 0); - if (!$title) { - continue; - } + $itemUrl = $element->find('a', 0)->href; + $item['uri'] = urljoin($baseUrl, $itemUrl); - $item['title'] = $title->innertext; + $image = $element->find('img', 0); + if ($image) { + $item['content'] = '
'; + } - $itemUrl = $element->find('a', 0)->href; - $item['uri'] = urljoin($baseUrl, $itemUrl); + $price = $element->find('span.a-price > .a-offscreen', 0); + if ($price) { + $item['content'] .= $price->innertext; + } - $image = $element->find('img', 0); - if ($image) { - $item['content'] = '
'; - } + $this->items[] = $item; + } + } - $price = $element->find('span.a-price > .a-offscreen', 0); - if ($price) { - $item['content'] .= $price->innertext; - } + public function getName() + { + if (!is_null($this->getInput('tld')) && !is_null($this->getInput('q'))) { + return 'Amazon.' . $this->getInput('tld') . ': ' . $this->getInput('q'); + } - $this->items[] = $item; - } - } - - public function getName(){ - if(!is_null($this->getInput('tld')) && !is_null($this->getInput('q'))) { - return 'Amazon.' . $this->getInput('tld') . ': ' . $this->getInput('q'); - } - - return parent::getName(); - } + return parent::getName(); + } } diff --git a/bridges/AmazonPriceTrackerBridge.php b/bridges/AmazonPriceTrackerBridge.php index 3824c939..af8f4459 100644 --- a/bridges/AmazonPriceTrackerBridge.php +++ b/bridges/AmazonPriceTrackerBridge.php @@ -1,243 +1,257 @@ array( - 'name' => 'ASIN', - 'required' => true, - 'exampleValue' => 'B071GB1VMQ', - // https://stackoverflow.com/a/12827734 - 'pattern' => 'B[\dA-Z]{9}|\d{9}(X|\d)', - ), - 'tld' => array( - 'name' => 'Country', - 'type' => 'list', - 'values' => array( - 'Australia' => 'com.au', - 'Brazil' => 'com.br', - 'Canada' => 'ca', - 'China' => 'cn', - 'France' => 'fr', - 'Germany' => 'de', - 'India' => 'in', - 'Italy' => 'it', - 'Japan' => 'co.jp', - 'Mexico' => 'com.mx', - 'Netherlands' => 'nl', - 'Spain' => 'es', - 'Sweden' => 'se', - 'Turkey' => 'com.tr', - 'United Kingdom' => 'co.uk', - 'United States' => 'com', - ), - 'defaultValue' => 'com', - ), - )); + const PARAMETERS = [ + [ + 'asin' => [ + 'name' => 'ASIN', + 'required' => true, + 'exampleValue' => 'B071GB1VMQ', + // https://stackoverflow.com/a/12827734 + 'pattern' => 'B[\dA-Z]{9}|\d{9}(X|\d)', + ], + 'tld' => [ + 'name' => 'Country', + 'type' => 'list', + 'values' => [ + 'Australia' => 'com.au', + 'Brazil' => 'com.br', + 'Canada' => 'ca', + 'China' => 'cn', + 'France' => 'fr', + 'Germany' => 'de', + 'India' => 'in', + 'Italy' => 'it', + 'Japan' => 'co.jp', + 'Mexico' => 'com.mx', + 'Netherlands' => 'nl', + 'Spain' => 'es', + 'Sweden' => 'se', + 'Turkey' => 'com.tr', + 'United Kingdom' => 'co.uk', + 'United States' => 'com', + ], + 'defaultValue' => 'com', + ], + ]]; - const PRICE_SELECTORS = array( - '#priceblock_ourprice', - '.priceBlockBuyingPriceString', - '#newBuyBoxPrice', - '#tp_price_block_total_price_ww', - 'span.offer-price', - '.a-color-price', - ); + const PRICE_SELECTORS = [ + '#priceblock_ourprice', + '.priceBlockBuyingPriceString', + '#newBuyBoxPrice', + '#tp_price_block_total_price_ww', + 'span.offer-price', + '.a-color-price', + ]; - const WHITESPACE = " \t\n\r\0\x0B\xC2\xA0"; + const WHITESPACE = " \t\n\r\0\x0B\xC2\xA0"; - protected $title; + protected $title; - /** - * Generates domain name given a amazon TLD - */ - private function getDomainName() { - return 'https://www.amazon.' . $this->getInput('tld'); - } + /** + * Generates domain name given a amazon TLD + */ + private function getDomainName() + { + return 'https://www.amazon.' . $this->getInput('tld'); + } - /** - * Generates URI for a Amazon product page - */ - public function getURI() { - if (!is_null($this->getInput('asin'))) { - return $this->getDomainName() . '/dp/' . $this->getInput('asin'); - } - return parent::getURI(); - } + /** + * Generates URI for a Amazon product page + */ + public function getURI() + { + if (!is_null($this->getInput('asin'))) { + return $this->getDomainName() . '/dp/' . $this->getInput('asin'); + } + return parent::getURI(); + } - /** - * Scrapes the product title from the html page - * returns the default title if scraping fails - */ - private function getTitle($html) { - $titleTag = $html->find('#productTitle', 0); + /** + * Scrapes the product title from the html page + * returns the default title if scraping fails + */ + private function getTitle($html) + { + $titleTag = $html->find('#productTitle', 0); - if (!$titleTag) { - return $this->getDefaultTitle(); - } else { - return trim(html_entity_decode($titleTag->innertext, ENT_QUOTES)); - } - } + if (!$titleTag) { + return $this->getDefaultTitle(); + } else { + return trim(html_entity_decode($titleTag->innertext, ENT_QUOTES)); + } + } - /** - * Title used by the feed if none could be found - */ - private function getDefaultTitle() { - return 'Amazon.' . $this->getInput('tld') . ': ' . $this->getInput('asin'); - } + /** + * Title used by the feed if none could be found + */ + private function getDefaultTitle() + { + return 'Amazon.' . $this->getInput('tld') . ': ' . $this->getInput('asin'); + } - /** - * Returns name for the feed - * Uses title (already scraped) if it has one - */ - public function getName() { - if (isset($this->title)) { - return $this->title; - } else { - return parent::getName(); - } - } + /** + * Returns name for the feed + * Uses title (already scraped) if it has one + */ + public function getName() + { + if (isset($this->title)) { + return $this->title; + } else { + return parent::getName(); + } + } - private function parseDynamicImage($attribute) { - $json = json_decode(html_entity_decode($attribute), true); + private function parseDynamicImage($attribute) + { + $json = json_decode(html_entity_decode($attribute), true); - if ($json and count($json) > 0) { - return array_keys($json)[0]; - } - } + if ($json and count($json) > 0) { + return array_keys($json)[0]; + } + } - /** - * Returns a generated image tag for the product - */ - private function getImage($html) { - $imageSrc = $html->find('#main-image-container img', 0); + /** + * Returns a generated image tag for the product + */ + private function getImage($html) + { + $imageSrc = $html->find('#main-image-container img', 0); - if ($imageSrc) { - $hiresImage = $imageSrc->getAttribute('data-old-hires'); - $dynamicImageAttribute = $imageSrc->getAttribute('data-a-dynamic-image'); - $image = $hiresImage ?: $this->parseDynamicImage($dynamicImageAttribute); - } - $image = $image ?: 'https://placekitten.com/200/300'; + if ($imageSrc) { + $hiresImage = $imageSrc->getAttribute('data-old-hires'); + $dynamicImageAttribute = $imageSrc->getAttribute('data-a-dynamic-image'); + $image = $hiresImage ?: $this->parseDynamicImage($dynamicImageAttribute); + } + $image = $image ?: 'https://placekitten.com/200/300'; - return << EOT; - } + } - /** - * Return \simple_html_dom object - * for the entire html of the product page - */ - private function getHtml() { - $uri = $this->getURI(); + /** + * Return \simple_html_dom object + * for the entire html of the product page + */ + private function getHtml() + { + $uri = $this->getURI(); - return getSimpleHTMLDOM($uri) ?: returnServerError('Could not request Amazon.'); - } + return getSimpleHTMLDOM($uri) ?: returnServerError('Could not request Amazon.'); + } - private function scrapePriceFromMetrics($html) { - $asinData = $html->find('#cerberus-data-metrics', 0); + private function scrapePriceFromMetrics($html) + { + $asinData = $html->find('#cerberus-data-metrics', 0); - // QUOTE; - $this->item['content'] .= $quote_html; - } + $this->item['content'] .= $quote_html; + } - $this->item['content'] = htmlspecialchars_decode($this->item['content'], ENT_QUOTES); + $this->item['content'] = htmlspecialchars_decode($this->item['content'], ENT_QUOTES); - // Add current Item to Items array - $this->items[] = $this->item; - } + // Add current Item to Items array + $this->items[] = $this->item; + } - // Sort all tweets in array by date - usort($this->items, array('TwitterV2Bridge', 'compareTweetDate')); - } + // Sort all tweets in array by date + usort($this->items, ['TwitterV2Bridge', 'compareTweetDate']); + } - private static function compareTweetDate($tweet1, $tweet2) { - return (strtotime($tweet1['timestamp']) < strtotime($tweet2['timestamp']) ? 1 : -1); - } + private static function compareTweetDate($tweet1, $tweet2) + { + return (strtotime($tweet1['timestamp']) < strtotime($tweet2['timestamp']) ? 1 : -1); + } - /** - * Tries to make an API call to Twitter. - * @param $api string API entry point - * @param $params array additional URI parmaeters - * @return object json data - */ - private function makeApiCall($api, $authHeaders, $params) { - $uri = self::API_URI . $api . '?' . http_build_query($params); - $result = getContents($uri, $authHeaders, array(), false); - $data = json_decode($result); - return $data; - } + /** + * Tries to make an API call to Twitter. + * @param $api string API entry point + * @param $params array additional URI parmaeters + * @return object json data + */ + private function makeApiCall($api, $authHeaders, $params) + { + $uri = self::API_URI . $api . '?' . http_build_query($params); + $result = getContents($uri, $authHeaders, [], false); + $data = json_decode($result); + return $data; + } - /** - * Change format of URLs in tweet text - * @param $tweetObject object current Tweet JSON - * @param $tweetText string current Tweet text - * @return string modified tweet text - */ - private function replaceTweetURLs($tweetObject, $tweetText) { - $foundUrls = false; - // Rewrite URL links, based on URL list in tweet object - if(isset($tweetObject->entities->urls)) { - foreach($tweetObject->entities->urls as $url) { - $tweetText = str_replace($url->url, - '' . $url->display_url . '', - $tweetText); - } - $foundUrls = true; - } - // Regex fallback for rewriting URL links. Should never trigger? - if($foundUrls === false) { - $reg_ex = '/(http|https|ftp|ftps)\:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(\/\S*)?/'; - if(preg_match($reg_ex, $tweetText, $url)) { - $tweetText = preg_replace($reg_ex, - "{$url[0]} ", - $tweetText); - } - } - // Fix back-to-back URLs by adding a
- $reg_ex = '/\/a>\s*
entities->urls)) { + foreach ($tweetObject->entities->urls as $url) { + $tweetText = str_replace( + $url->url, + '
' . $url->display_url . '', + $tweetText + ); + } + $foundUrls = true; + } + // Regex fallback for rewriting URL links. Should never trigger? + if ($foundUrls === false) { + $reg_ex = '/(http|https|ftp|ftps)\:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(\/\S*)?/'; + if (preg_match($reg_ex, $tweetText, $url)) { + $tweetText = preg_replace( + $reg_ex, + "{$url[0]} ", + $tweetText + ); + } + } + // Fix back-to-back URLs by adding a
+ $reg_ex = '/\/a>\s*
id === $tweetObject->author_id) { - $matchedUser = $retweetedUser; - Debug::log('Found author_id match in $retweetedUsers'); - break; - } - } - } - if(!isset($matchedUser->username) && isset($includesUsers)) { - Debug::log('Searching for tweet author_id in $includesUsers'); - foreach($includesUsers as $includesUser) { - if($includesUser->id === $tweetObject->author_id) { - $matchedUser = $includesUser; - Debug::log('Found author_id match in $includesUsers'); - break; - } - } - } - return $matchedUser; - } + /** + * Find User object for Retweeted/Quoted tweet + * @param $tweetObject object current Tweet JSON + * @param $retweetedUsers + * @param $includesUsers + * @return object found User + */ + private function getTweetUser($tweetObject, $retweetedUsers, $includesUsers) + { + $originalUser = new stdClass(); // make the linters stop complaining + if (isset($retweetedUsers)) { + Debug::log('Searching for tweet author_id in $retweetedUsers'); + foreach ($retweetedUsers as $retweetedUser) { + if ($retweetedUser->id === $tweetObject->author_id) { + $matchedUser = $retweetedUser; + Debug::log('Found author_id match in $retweetedUsers'); + break; + } + } + } + if (!isset($matchedUser->username) && isset($includesUsers)) { + Debug::log('Searching for tweet author_id in $includesUsers'); + foreach ($includesUsers as $includesUser) { + if ($includesUser->id === $tweetObject->author_id) { + $matchedUser = $includesUser; + Debug::log('Found author_id match in $includesUsers'); + break; + } + } + } + return $matchedUser; + } - /** - * Generates HTML for embedded media - * @param $tweetObject object current Tweet JSON - * @param $includesMedia - * @param $retweetedMedia - * @return string modified tweet text - */ - private function createTweetMediaHTML($tweetObject, $includesMedia, $retweetedMedia){ - $media_html = ''; - // Match media_keys in tweet to media list from, put matches into new array - $tweetMedia = array(); - // Start by checking the original list of tweet Media includes - if(isset($includesMedia)) { - Debug::log('Searching for media_key in $includesMedia'); - foreach($includesMedia as $includesMedium) { - if(in_array ($includesMedium->media_key, - $tweetObject->attachments->media_keys)) { - Debug::log('Found media_key in $includesMedia'); - $tweetMedia[] = $includesMedium; - } - } - } - // If no matches found, check the retweet Media includes - if(empty($tweetMedia) && isset($retweetedMedia)) { - Debug::log('Searching for media_key in $retweetedMedia'); - foreach($retweetedMedia as $retweetedMedium) { - if(in_array ($retweetedMedium->media_key, - $tweetObject->attachments->media_keys)) { - Debug::log('Found media_key in $retweetedMedia'); - $tweetMedia[] = $retweetedMedium; - } - } - } + /** + * Generates HTML for embedded media + * @param $tweetObject object current Tweet JSON + * @param $includesMedia + * @param $retweetedMedia + * @return string modified tweet text + */ + private function createTweetMediaHTML($tweetObject, $includesMedia, $retweetedMedia) + { + $media_html = ''; + // Match media_keys in tweet to media list from, put matches into new array + $tweetMedia = []; + // Start by checking the original list of tweet Media includes + if (isset($includesMedia)) { + Debug::log('Searching for media_key in $includesMedia'); + foreach ($includesMedia as $includesMedium) { + if ( + in_array( + $includesMedium->media_key, + $tweetObject->attachments->media_keys + ) + ) { + Debug::log('Found media_key in $includesMedia'); + $tweetMedia[] = $includesMedium; + } + } + } + // If no matches found, check the retweet Media includes + if (empty($tweetMedia) && isset($retweetedMedia)) { + Debug::log('Searching for media_key in $retweetedMedia'); + foreach ($retweetedMedia as $retweetedMedium) { + if ( + in_array( + $retweetedMedium->media_key, + $tweetObject->attachments->media_keys + ) + ) { + Debug::log('Found media_key in $retweetedMedia'); + $tweetMedia[] = $retweetedMedium; + } + } + } - foreach($tweetMedia as $media) { - switch($media->type) { - case 'photo': - if ($this->getInput('noimgscaling')) { - $image = $media->url; - $display_image = $media->url; - } else{ - $image = $media->url . '?name=orig'; - $display_image = $media->url; - } - // add enclosures - $this->item['enclosures'][] = $image; + foreach ($tweetMedia as $media) { + switch ($media->type) { + case 'photo': + if ($this->getInput('noimgscaling')) { + $image = $media->url; + $display_image = $media->url; + } else { + $image = $media->url . '?name=orig'; + $display_image = $media->url; + } + // add enclosures + $this->item['enclosures'][] = $image; - $media_html .= <<
EOD; - break; - case 'video': - // To Do: Is there a way to easily match this - // to a direct Video URL? - $display_image = $media->preview_image_url; + break; + case 'video': + // To Do: Is there a way to easily match this + // to a direct Video URL? + $display_image = $media->preview_image_url; - $media_html .= <<Video:

EOD; - break; - case 'animated_gif': - // To Do: Is there a way to easily match this to a - // direct animated Gif URL? - $display_image = $media->preview_image_url; + break; + case 'animated_gif': + // To Do: Is there a way to easily match this to a + // direct animated Gif URL? + $display_image = $media->preview_image_url; - $media_html .= <<Animated Gif:

EOD; - break; - default: - Debug::log('Missing support for media type: ' - . $media->type); - } - } + break; + default: + Debug::log('Missing support for media type: ' + . $media->type); + } + } - return $media_html; - } + return $media_html; + } } diff --git a/bridges/UberNewsroomBridge.php b/bridges/UberNewsroomBridge.php index 560998cd..333200cd 100644 --- a/bridges/UberNewsroomBridge.php +++ b/bridges/UberNewsroomBridge.php @@ -1,179 +1,185 @@ array( - 'name' => 'Region', - 'type' => 'list', - 'values' => array( - 'Africa' => array( - 'Egypt' => 'ar-EG', - 'Ghana' => 'en-GH', - 'Kenya' => 'en-KE', - 'Morocco' => 'fr-MA', - 'Nigeria' => 'en-NG', - 'South Africa' => 'en-ZA', - 'Tanzania' => 'en-TZ', - 'Uganda' => 'en-UG', - ), - 'Asia' => array( - 'Bangladesh' => 'en-BD', - 'Cambodia' => 'km-KH', - 'China' => 'zh-CN', - 'Hong Kong' => 'zh-HK', - 'India' => 'en-IN', - 'Indonesia' => 'en-ID', - 'Japan' => 'ja-JP', - 'Korea' => 'ko-KR', - 'Macau' => 'zh-MO', - 'Malaysia' => 'en-MY', - 'Myanmar' => 'en-MM', - 'Philippines' => 'en-PH', - 'Singapore' => 'en-SG', - 'Sri Lanka' => 'en-LK', - 'Taiwan' => 'zh-TW', - 'Thailand' => 'th-TH', - 'Vietnam' => 'vi-VN', - ), - 'Central America' => array( - 'Costa Rica' => 'es-CR', - 'Dominican Republic' => 'es-DO', - 'El Salvador' => 'es-SV', - 'Guatemala' => 'es-GT', - 'Honduras' => 'es-HN', - 'Mexico' => 'es-MX', - 'Nicaragua' => 'es-NI', - 'Panama' => 'es-PA', - 'Puerto Rico' => 'es-PR', - ), - 'Europe' => array( - 'Austria' => 'de-AT', - 'Azerbaijan' => 'az', - 'Belarus' => 'ru-BY', - 'Belgium' => 'fr-BE', - 'Bulgaria' => 'bg', - 'Croatia' => 'hr', - 'Czech Republic' => 'cs-CZ', - 'Denmark' => 'da-DK', - 'Estonia' => 'et-EE', - 'Finland' => 'fi', - 'France' => 'fr', - 'Germany' => 'de', - 'Greece' => 'el-GR', - 'Hungary' => 'hu', - 'Ireland' => 'en-IE', - 'Italy' => 'it', - 'Kazakhstan' => 'ru-KZ', - 'Lithuania' => 'lt', - 'Netherlands' => 'nl', - 'Norway' => 'nb-NO', - 'Poland' => 'pl', - 'Portugal' => 'pt', - 'Romania' => 'ro', - 'Russia' => 'ru', - 'Slovakia' => 'sk', - 'Spain' => 'es-ES', - 'Sweden' => 'sv-SE', - 'Switzerland' => 'fr-CH', - 'Turkey' => 'tr', - 'Ukraine' => 'uk-UA', - 'United Kingdom' => 'en-GB', - ), - 'Middle East' => array( - 'Bahrain' => 'en-BH', - 'Israel' => 'he-IL', - 'Jordan' => 'en-JO', - 'Kuwait' => 'en-KW', - 'Lebanon' => 'en-LB', - 'Pakistan' => 'en-PK', - 'Qatar' => 'en-QA', - 'Saudi Arabia' => 'ar-SA', - 'United Arab Emirates' => 'en-AE', - ), - 'North America' => array( - 'Canada' => 'en-CA', - 'United States' => 'en-US', - ), - 'Pacific' => array( - 'Australia' => 'en-AU', - 'New Zealand' => 'en-NZ', - ), - 'South America' => array( - 'Argentina' => 'es-AR', - 'Bolivia' => 'es-BO', - 'Brazil' => 'pt-BR', - 'Chile' => 'es-CL', - 'Colombia' => 'es-CO', - 'Ecuador' => 'es-EC', - 'Paraguay' => 'es-PY', - 'Peru' => 'es-PE', - 'Trinidad & Tobago' => 'en-TT', - 'Uruguay' => 'es-UY', - 'Venezuela' => 'es-VE', - ), - ), - 'defaultValue' => 'en-US', - ) - )); - const CACHE_TIMEOUT = 3600; +class UberNewsroomBridge extends BridgeAbstract +{ + const NAME = 'Uber Newsroom Bridge'; + const URI = 'https://www.uber.com'; + const URI_API_DATA = 'https://newsroomapi.uber.com/wp-json/newsroom/v1/data?locale='; + const URI_API_POST = 'https://newsroomapi.uber.com/wp-json/wp/v2/posts/'; + const DESCRIPTION = 'Returns news posts'; + const MAINTAINER = 'VerifiedJoseph'; + const PARAMETERS = [[ + 'region' => [ + 'name' => 'Region', + 'type' => 'list', + 'values' => [ + 'Africa' => [ + 'Egypt' => 'ar-EG', + 'Ghana' => 'en-GH', + 'Kenya' => 'en-KE', + 'Morocco' => 'fr-MA', + 'Nigeria' => 'en-NG', + 'South Africa' => 'en-ZA', + 'Tanzania' => 'en-TZ', + 'Uganda' => 'en-UG', + ], + 'Asia' => [ + 'Bangladesh' => 'en-BD', + 'Cambodia' => 'km-KH', + 'China' => 'zh-CN', + 'Hong Kong' => 'zh-HK', + 'India' => 'en-IN', + 'Indonesia' => 'en-ID', + 'Japan' => 'ja-JP', + 'Korea' => 'ko-KR', + 'Macau' => 'zh-MO', + 'Malaysia' => 'en-MY', + 'Myanmar' => 'en-MM', + 'Philippines' => 'en-PH', + 'Singapore' => 'en-SG', + 'Sri Lanka' => 'en-LK', + 'Taiwan' => 'zh-TW', + 'Thailand' => 'th-TH', + 'Vietnam' => 'vi-VN', + ], + 'Central America' => [ + 'Costa Rica' => 'es-CR', + 'Dominican Republic' => 'es-DO', + 'El Salvador' => 'es-SV', + 'Guatemala' => 'es-GT', + 'Honduras' => 'es-HN', + 'Mexico' => 'es-MX', + 'Nicaragua' => 'es-NI', + 'Panama' => 'es-PA', + 'Puerto Rico' => 'es-PR', + ], + 'Europe' => [ + 'Austria' => 'de-AT', + 'Azerbaijan' => 'az', + 'Belarus' => 'ru-BY', + 'Belgium' => 'fr-BE', + 'Bulgaria' => 'bg', + 'Croatia' => 'hr', + 'Czech Republic' => 'cs-CZ', + 'Denmark' => 'da-DK', + 'Estonia' => 'et-EE', + 'Finland' => 'fi', + 'France' => 'fr', + 'Germany' => 'de', + 'Greece' => 'el-GR', + 'Hungary' => 'hu', + 'Ireland' => 'en-IE', + 'Italy' => 'it', + 'Kazakhstan' => 'ru-KZ', + 'Lithuania' => 'lt', + 'Netherlands' => 'nl', + 'Norway' => 'nb-NO', + 'Poland' => 'pl', + 'Portugal' => 'pt', + 'Romania' => 'ro', + 'Russia' => 'ru', + 'Slovakia' => 'sk', + 'Spain' => 'es-ES', + 'Sweden' => 'sv-SE', + 'Switzerland' => 'fr-CH', + 'Turkey' => 'tr', + 'Ukraine' => 'uk-UA', + 'United Kingdom' => 'en-GB', + ], + 'Middle East' => [ + 'Bahrain' => 'en-BH', + 'Israel' => 'he-IL', + 'Jordan' => 'en-JO', + 'Kuwait' => 'en-KW', + 'Lebanon' => 'en-LB', + 'Pakistan' => 'en-PK', + 'Qatar' => 'en-QA', + 'Saudi Arabia' => 'ar-SA', + 'United Arab Emirates' => 'en-AE', + ], + 'North America' => [ + 'Canada' => 'en-CA', + 'United States' => 'en-US', + ], + 'Pacific' => [ + 'Australia' => 'en-AU', + 'New Zealand' => 'en-NZ', + ], + 'South America' => [ + 'Argentina' => 'es-AR', + 'Bolivia' => 'es-BO', + 'Brazil' => 'pt-BR', + 'Chile' => 'es-CL', + 'Colombia' => 'es-CO', + 'Ecuador' => 'es-EC', + 'Paraguay' => 'es-PY', + 'Peru' => 'es-PE', + 'Trinidad & Tobago' => 'en-TT', + 'Uruguay' => 'es-UY', + 'Venezuela' => 'es-VE', + ], + ], + 'defaultValue' => 'en-US', + ] + ]]; - private $regionName = ''; + const CACHE_TIMEOUT = 3600; - public function collectData() { - $json = getContents(self::URI_API_DATA . $this->getInput('region')); - $data = json_decode($json); + private $regionName = ''; - $this->regionName = $data->region->name; + public function collectData() + { + $json = getContents(self::URI_API_DATA . $this->getInput('region')); + $data = json_decode($json); - foreach ($data->articles as $article) { - $json = getContents(self::URI_API_POST . $article->id); - $post = json_decode($json); + $this->regionName = $data->region->name; - $item = array(); - $item['title'] = $post->title->rendered; - $item['timestamp'] = $post->date; - $item['uri'] = $post->link; - $item['content'] = $this->formatContent($post->content->rendered); - $item['enclosures'][] = $article->image_full; + foreach ($data->articles as $article) { + $json = getContents(self::URI_API_POST . $article->id); + $post = json_decode($json); - $this->items[] = $item; - } - } + $item = []; + $item['title'] = $post->title->rendered; + $item['timestamp'] = $post->date; + $item['uri'] = $post->link; + $item['content'] = $this->formatContent($post->content->rendered); + $item['enclosures'][] = $article->image_full; - public function getURI() { - if (is_null($this->getInput('region')) === false) { - return self::URI . '/' . $this->getInput('region') . '/newsroom'; - } + $this->items[] = $item; + } + } - return parent::getURI() . '/newsroom'; - } + public function getURI() + { + if (is_null($this->getInput('region')) === false) { + return self::URI . '/' . $this->getInput('region') . '/newsroom'; + } - public function getName() { - if (is_null($this->getInput('region')) === false) { - return $this->regionName . ' - Uber Newsroom'; - } + return parent::getURI() . '/newsroom'; + } - return parent::getName(); - } + public function getName() + { + if (is_null($this->getInput('region')) === false) { + return $this->regionName . ' - Uber Newsroom'; + } - private function formatContent($html) { - $html = str_get_html($html); + return parent::getName(); + } - foreach ($html->find('div.wp-video') as $div) { - $div->style = ''; - } + private function formatContent($html) + { + $html = str_get_html($html); - foreach ($html->find('video') as $video) { - $video->width = '100%'; - $video->height = ''; - } + foreach ($html->find('div.wp-video') as $div) { + $div->style = ''; + } - return $html; - } + foreach ($html->find('video') as $video) { + $video->width = '100%'; + $video->height = ''; + } + + return $html; + } } diff --git a/bridges/UnogsBridge.php b/bridges/UnogsBridge.php index f03555b4..021b75ed 100644 --- a/bridges/UnogsBridge.php +++ b/bridges/UnogsBridge.php @@ -1,191 +1,196 @@ [ + 'feed' => [ + 'name' => 'feed', + 'type' => 'list', + 'title' => 'Choose whether you want latest movies or removal on Netflix', + 'values' => [ + 'What\'s New' => 'new last 7 days', + 'Expiring' => 'expiring' + ] + ], + 'limit' => self::LIMIT, + ], + 'Global' => [], + 'Country' => [ + 'country_code' => [ + 'name' => 'Country', + 'type' => 'list', + 'title' => 'Choose your preferred country', + 'values' => [ + 'Argentina' => 21, + 'Australia' => 23, + 'Belgium' => 26, + 'Brazil' => 29, + 'Canada' => 33, + 'Colombia' => 36, + 'Czech Republic' => 307, + 'France' => 45, + 'Germany' => 39, + 'Greece' => 327, + 'Hong Kong' => 331, + 'Hungary' => 334, + 'Iceland' => 265, + 'India' => 337, + 'Israel' => 336, + 'Italy' => 269, + 'Japan' => 267, + 'Lithuania' => 357, + 'Malaysia' => 378, + 'Mexico' => 65, + 'Netherlands' => 67, + 'Philippines' => 390, + 'Poland' => 392, + 'Portugal' => 268, + 'Romania' => 400, + 'Russia' => 402, + 'Singapore' => 408, + 'Slovakia' => 412, + 'South Africa' => 447, + 'South Korea' => 348, + 'Spain' => 270, + 'Sweden' => 73, + 'Switzerland' => 34, + 'Thailand' => 425, + 'Turkey' => 432, + 'Ukraine' => 436, + 'United Kingdom' => 46, + 'United States' => 78 + ] + ] + ] + ]; - const PARAMETERS = array( - 'global' => array( - 'feed' => array( - 'name' => 'feed', - 'type' => 'list', - 'title' => 'Choose whether you want latest movies or removal on Netflix', - 'values' => array( - 'What\'s New' => 'new last 7 days', - 'Expiring' => 'expiring' - ) - ), - 'limit' => self::LIMIT, - ), - 'Global' => array(), - 'Country' => array( - 'country_code' => array( - 'name' => 'Country', - 'type' => 'list', - 'title' => 'Choose your preferred country', - 'values' => array( - 'Argentina' => 21, - 'Australia' => 23, - 'Belgium' => 26, - 'Brazil' => 29, - 'Canada' => 33, - 'Colombia' => 36, - 'Czech Republic' => 307, - 'France' => 45, - 'Germany' => 39, - 'Greece' => 327, - 'Hong Kong' => 331, - 'Hungary' => 334, - 'Iceland' => 265, - 'India' => 337, - 'Israel' => 336, - 'Italy' => 269, - 'Japan' => 267, - 'Lithuania' => 357, - 'Malaysia' => 378, - 'Mexico' => 65, - 'Netherlands' => 67, - 'Philippines' => 390, - 'Poland' => 392, - 'Portugal' => 268, - 'Romania' => 400, - 'Russia' => 402, - 'Singapore' => 408, - 'Slovakia' => 412, - 'South Africa' => 447, - 'South Korea' => 348, - 'Spain' => 270, - 'Sweden' => 73, - 'Switzerland' => 34, - 'Thailand' => 425, - 'Turkey' => 432, - 'Ukraine' => 436, - 'United Kingdom' => 46, - 'United States' => 78 - ) - ) - ) - ); + public function getName() + { + $feedName = ''; + if ($this->queriedContext == 'Global') { + $feedName .= 'Netflix Global - '; + } elseif ($this->queriedContext == 'Country') { + $feedName .= 'Netflix ' . $this->getParametersKey('country_code') . ' - '; + } + if ($this->getInput('feed') == 'expiring') { + $feedName .= 'Expiring title'; + } elseif ($this->getInput('feed') == 'new last 7 days') { + $feedName .= 'What\'s New'; + } else { + $feedName = self::NAME; + } + return $feedName; + } - public function getName() { - $feedName = ''; - if($this->queriedContext == 'Global') { - $feedName .= 'Netflix Global - '; - } elseif($this->queriedContext == 'Country') { - $feedName .= 'Netflix ' . $this->getParametersKey('country_code') . ' - '; - } - if($this->getInput('feed') == 'expiring') { - $feedName .= 'Expiring title'; - } elseif($this->getInput('feed') == 'new last 7 days') { - $feedName .= 'What\'s New'; - } else { - $feedName = self::NAME; - } - return $feedName; - } + private function getParametersKey($input = '') + { + $params = $this->getParameters(); + $tab = 'Country'; + if (!isset($params[$tab][$input])) { + return ''; + } - private function getParametersKey($input = '') { - $params = $this->getParameters(); - $tab = 'Country'; - if (!isset($params[$tab][$input])) { - return ''; - } + return array_search( + $this->getInput($input), + $params[$tab][$input]['values'] + ); + } - return array_search( - $this->getInput($input), - $params[$tab][$input]['values'] - ); + private function getJSON($url) + { + $header = [ + 'Referer: https://unogs.com/', + 'referrer: http://unogs.com' + ]; - } + $raw = getContents($url, $header); + return json_decode($raw, true); + } - private function getJSON($url) { - $header = array( - 'Referer: https://unogs.com/', - 'referrer: http://unogs.com' - ); + private function getImage($nfid) + { + $url = self::URI . '/api/title/bgimages?netflixid=' . $nfid; + $json = $this->getJSON($url); + $image_wrapper = ''; + if (isset($json['bo1280x448'])) { + $image_wrapper = 'bo1280x448'; + } else { + $image_wrapper = 'bo665x375'; + } + end($json[$image_wrapper]); + $position = key($json[$image_wrapper]); + $image_link = $json[$image_wrapper][$position]['url']; + return $image_link; + } - $raw = getContents($url, $header); - return json_decode($raw, true); - } + private function handleData($data) + { + $item = []; + $item['title'] = $data['title'] . ' - ' . $data['year']; + $item['timestamp'] = $data['titledate']; + $netflix_id = $data['nfid']; + $item['uri'] = 'https://www.netflix.com/title/' . $netflix_id; + $image_url = $this->getImage($netflix_id); + $netflix_synopsis = $data['synopsis']; + $expired_warning = ''; + if (isset($data['expires'])) { + $expired_warning .= '

Expired on: ' . $data['expires'] . '

'; + $item['timestamp'] = $data['expires']; + } + $unogs_url = self::URI . '/title/' . $netflix_id; - private function getImage($nfid) { - $url = self::URI . '/api/title/bgimages?netflixid=' . $nfid; - $json = $this->getJSON($url); - $image_wrapper = ''; - if(isset($json['bo1280x448'])) { - $image_wrapper = 'bo1280x448'; - } else { - $image_wrapper = 'bo665x375'; - } - end($json[$image_wrapper]); - $position = key($json[$image_wrapper]); - $image_link = $json[$image_wrapper][$position]['url']; - return $image_link; - } - - private function handleData($data) { - $item = array(); - $item['title'] = $data['title'] . ' - ' . $data['year']; - $item['timestamp'] = $data['titledate']; - $netflix_id = $data['nfid']; - $item['uri'] = 'https://www.netflix.com/title/' . $netflix_id; - $image_url = $this->getImage($netflix_id); - $netflix_synopsis = $data['synopsis']; - $expired_warning = ''; - if(isset($data['expires'])) { - $expired_warning .= '

Expired on: ' . $data['expires'] . '

'; - $item['timestamp'] = $data['expires']; - } - $unogs_url = self::URI . '/title/' . $netflix_id; - - $item['content'] = << $expired_warning

$netflix_synopsis

Details: $unogs_url

EOD; - $this->items[] = $item; - } + $this->items[] = $item; + } - public function collectData() { - $feed = $this->getInput('feed'); - $is_global = false; - $country_code = ''; + public function collectData() + { + $feed = $this->getInput('feed'); + $is_global = false; + $country_code = ''; - switch ($this->queriedContext) { - case 'Country': - $country_code = $this->getInput('country_code'); - break; - } + switch ($this->queriedContext) { + case 'Country': + $country_code = $this->getInput('country_code'); + break; + } - $limit = $this->getInput('limit') ?? 30; + $limit = $this->getInput('limit') ?? 30; - // https://rapidapi.com/unogs/api/unogsng/details - $api_url = sprintf( - '%s/api/search?query=%s%s&limit=%s', - self::URI, - urlencode($feed), - $country_code ? '&countrylist=' . $country_code : '', - $limit - ); + // https://rapidapi.com/unogs/api/unogsng/details + $api_url = sprintf( + '%s/api/search?query=%s%s&limit=%s', + self::URI, + urlencode($feed), + $country_code ? '&countrylist=' . $country_code : '', + $limit + ); - $json_data = $this->getJSON($api_url); - $movies = $json_data['results']; + $json_data = $this->getJSON($api_url); + $movies = $json_data['results']; - if($this->getInput('feed') == 'expiring') { - /* uNoGS API returns movies/series that going to remove - * today according to the day you fetch the data. - * They put items that going to remove in the future on the last - * so I reverse this to get those items, not to bothers those that already removed today. - */ - $movies = array_reverse($movies); - } + if ($this->getInput('feed') == 'expiring') { + /* uNoGS API returns movies/series that going to remove + * today according to the day you fetch the data. + * They put items that going to remove in the future on the last + * so I reverse this to get those items, not to bothers those that already removed today. + */ + $movies = array_reverse($movies); + } - foreach($movies as $movie) { - $this->handleData($movie); - } - } + foreach ($movies as $movie) { + $this->handleData($movie); + } + } } diff --git a/bridges/UnraidCommunityApplicationsBridge.php b/bridges/UnraidCommunityApplicationsBridge.php index c2cb3ace..5acd5049 100644 --- a/bridges/UnraidCommunityApplicationsBridge.php +++ b/bridges/UnraidCommunityApplicationsBridge.php @@ -1,70 +1,81 @@ apps = getContents(self::APPSURI); - $this->apps = json_decode($this->apps, true)['applist']; - } + private $apps = []; - private function sortApps() { - Debug::log('Sorting applications/plugins'); - usort($this->apps, function($app1, $app2) { - return $app1['FirstSeen'] < $app2['FirstSeen'] ? 1 : -1; - }); - } + private function fetchApps() + { + Debug::log('Fetching all applications/plugins'); + $this->apps = getContents(self::APPSURI); + $this->apps = json_decode($this->apps, true)['applist']; + } - public function collectData() { - $this->fetchApps(); - $this->sortApps(); + private function sortApps() + { + Debug::log('Sorting applications/plugins'); + usort($this->apps, function ($app1, $app2) { + return $app1['FirstSeen'] < $app2['FirstSeen'] ? 1 : -1; + }); + } - Debug::log('Building RSS feed'); - foreach($this->apps as $app) { - if(!array_key_exists('Language', $app)) { - $item = array(); - $item['title'] = $app['Name']; - $item['timestamp'] = $app['FirstSeen']; - $item['author'] = explode('\'', $app['Repo'])[0]; - $item['categories'] = explode(' ', $app['Category']); - $item['content'] = ''; + public function collectData() + { + $this->fetchApps(); + $this->sortApps(); - if(array_key_exists('Icon', $app)) - $item['content'] .= ''; + Debug::log('Building RSS feed'); + foreach ($this->apps as $app) { + if (!array_key_exists('Language', $app)) { + $item = []; + $item['title'] = $app['Name']; + $item['timestamp'] = $app['FirstSeen']; + $item['author'] = explode('\'', $app['Repo'])[0]; + $item['categories'] = explode(' ', $app['Category']); + $item['content'] = ''; - if(array_key_exists('Overview', $app)) - $item['content'] .= '

' - . $app['Overview'] - . '

'; + if (array_key_exists('Icon', $app)) { + $item['content'] .= ''; + } - if(array_key_exists('Project', $app)) - $item['uri'] = $app['Project']; + if (array_key_exists('Overview', $app)) { + $item['content'] .= '

' + . $app['Overview'] + . '

'; + } - if(array_key_exists('Registry', $app)) - $item['content'] .= '
Docker Hub'; + if (array_key_exists('Project', $app)) { + $item['uri'] = $app['Project']; + } - if(array_key_exists('Support', $app)) - $item['content'] .= '
Support'; + if (array_key_exists('Registry', $app)) { + $item['content'] .= '
Docker Hub'; + } - $this->items[] = $item; + if (array_key_exists('Support', $app)) { + $item['content'] .= '
Support'; + } - if(count($this->items) >= 15) - break; - } - } - } + $this->items[] = $item; + + if (count($this->items) >= 15) { + break; + } + } + } + } } diff --git a/bridges/UnsplashBridge.php b/bridges/UnsplashBridge.php index 876dfe9d..590d16ab 100644 --- a/bridges/UnsplashBridge.php +++ b/bridges/UnsplashBridge.php @@ -2,112 +2,118 @@ class UnsplashBridge extends BridgeAbstract { - const MAINTAINER = 'nel50n, langfingaz'; - const NAME = 'Unsplash Bridge'; - const URI = 'https://unsplash.com/'; - const CACHE_TIMEOUT = 43200; // 12h - const DESCRIPTION = 'Returns the latest photos from Unsplash'; + const MAINTAINER = 'nel50n, langfingaz'; + const NAME = 'Unsplash Bridge'; + const URI = 'https://unsplash.com/'; + const CACHE_TIMEOUT = 43200; // 12h + const DESCRIPTION = 'Returns the latest photos from Unsplash'; - const PARAMETERS = array(array( - 'u' => array( - 'name' => 'Filter by username (optional)', - 'type' => 'text', - 'defaultValue' => 'unsplash' - ), - 'm' => array( - 'name' => 'Max number of photos', - 'type' => 'number', - 'defaultValue' => 20, - 'required' => true - ), - 'prev_q' => array( - 'name' => 'Preview quality', - 'type' => 'list', - 'values' => array( - 'full' => 'full', - 'regular' => 'regular', - 'small' => 'small', - 'thumb' => 'thumb', - ), - 'defaultValue' => 'regular' - ), - 'w' => array( - 'name' => 'Max download width (optional)', - 'exampleValue' => 1920, - 'type' => 'number', - 'defaultValue' => 1920, - ), - 'jpg_q' => array( - 'name' => 'Max JPEG quality (optional)', - 'exampleValue' => 75, - 'type' => 'number', - 'defaultValue' => 75, - ) - )); + const PARAMETERS = [[ + 'u' => [ + 'name' => 'Filter by username (optional)', + 'type' => 'text', + 'defaultValue' => 'unsplash' + ], + 'm' => [ + 'name' => 'Max number of photos', + 'type' => 'number', + 'defaultValue' => 20, + 'required' => true + ], + 'prev_q' => [ + 'name' => 'Preview quality', + 'type' => 'list', + 'values' => [ + 'full' => 'full', + 'regular' => 'regular', + 'small' => 'small', + 'thumb' => 'thumb', + ], + 'defaultValue' => 'regular' + ], + 'w' => [ + 'name' => 'Max download width (optional)', + 'exampleValue' => 1920, + 'type' => 'number', + 'defaultValue' => 1920, + ], + 'jpg_q' => [ + 'name' => 'Max JPEG quality (optional)', + 'exampleValue' => 75, + 'type' => 'number', + 'defaultValue' => 75, + ] + ]]; - public function collectData() - { - $filteredUser = $this->getInput('u'); - $width = $this->getInput('w'); - $max = $this->getInput('m'); - $previewQuality = $this->getInput('prev_q'); - $jpgQuality = $this->getInput('jpg_q'); + public function collectData() + { + $filteredUser = $this->getInput('u'); + $width = $this->getInput('w'); + $max = $this->getInput('m'); + $previewQuality = $this->getInput('prev_q'); + $jpgQuality = $this->getInput('jpg_q'); - $url = 'https://unsplash.com/napi'; - if (strlen($filteredUser) > 0) $url .= '/users/' . $filteredUser; - $url .= '/photos?page=1&per_page=' . $max; - $api_response = getContents($url); + $url = 'https://unsplash.com/napi'; + if (strlen($filteredUser) > 0) { + $url .= '/users/' . $filteredUser; + } + $url .= '/photos?page=1&per_page=' . $max; + $api_response = getContents($url); - $json = json_decode($api_response, true); + $json = json_decode($api_response, true); - foreach ($json as $json_item) { - $item = array(); + foreach ($json as $json_item) { + $item = []; - // Get image URI - $uri = $json_item['urls']['raw'] . '&fm=jpg'; - if ($jpgQuality > 0) $uri .= '&q=' . $jpgQuality; - if ($width > 0) $uri .= '&w=' . $width . '&fit=max'; - $uri .= '.jpg'; // only for format hint - $item['uri'] = $uri; + // Get image URI + $uri = $json_item['urls']['raw'] . '&fm=jpg'; + if ($jpgQuality > 0) { + $uri .= '&q=' . $jpgQuality; + } + if ($width > 0) { + $uri .= '&w=' . $width . '&fit=max'; + } + $uri .= '.jpg'; // only for format hint + $item['uri'] = $uri; - // Get title from description - if (is_null($json_item['description'])) { - $item['title'] = 'Unsplash picture from ' . $json_item['user']['name']; - } else { - $item['title'] = $json_item['description']; - } + // Get title from description + if (is_null($json_item['description'])) { + $item['title'] = 'Unsplash picture from ' . $json_item['user']['name']; + } else { + $item['title'] = $json_item['description']; + } - $item['timestamp'] = $json_item['created_at']; - $content = 'User: @' - . $json_item['user']['username'] - . ''; - if (isset($json_item['location']['name'])) { - $content .= ' | Location: ' . $json_item['location']['name']; - } - $content .= ' | Image on Unsplash
Image from '
-				. $filteredUser
-				. ''; - $item['content'] = $content; + $item['timestamp'] = $json_item['created_at']; + $content = 'User: @' + . $json_item['user']['username'] + . ''; + if (isset($json_item['location']['name'])) { + $content .= ' | Location: ' . $json_item['location']['name']; + } + $content .= ' | Image on Unsplash
Image from '
+                . $filteredUser
+                . ''; + $item['content'] = $content; - $this->items[] = $item; - } - } + $this->items[] = $item; + } + } - public function getName() - { - $filteredUser = $this->getInput('u') ?? ''; - if (strlen($filteredUser) > 0) { - return $filteredUser . ' - ' . self::NAME; - } else { - return self::NAME; - } - } + public function getName() + { + $filteredUser = $this->getInput('u') ?? ''; + if (strlen($filteredUser) > 0) { + return $filteredUser . ' - ' . self::NAME; + } else { + return self::NAME; + } + } } diff --git a/bridges/UrlebirdBridge.php b/bridges/UrlebirdBridge.php index 98a16aae..429e93f5 100644 --- a/bridges/UrlebirdBridge.php +++ b/bridges/UrlebirdBridge.php @@ -1,72 +1,77 @@ array( - 'name' => '@username or #hashtag', - 'type' => 'text', - 'required' => true, - 'exampleValue' => '@willsmith', - 'title' => '@username or #hashtag' - ) - ) - ); +class UrlebirdBridge extends BridgeAbstract +{ + const MAINTAINER = 'dotter-ak'; + const NAME = 'urlebird.com'; + const URI = 'https://urlebird.com/'; + const DESCRIPTION = 'Bridge for urlebird.com'; + const CACHE_TIMEOUT = 10; + const PARAMETERS = [ + [ + 'query' => [ + 'name' => '@username or #hashtag', + 'type' => 'text', + 'required' => true, + 'exampleValue' => '@willsmith', + 'title' => '@username or #hashtag' + ] + ] + ]; - private $title; + private $title; - private function fixURI($uri) { - $path = parse_url($uri, PHP_URL_PATH); - $encoded_path = array_map('urlencode', explode('/', $path)); - return str_replace($path, implode('/', $encoded_path), $uri); - } + private function fixURI($uri) + { + $path = parse_url($uri, PHP_URL_PATH); + $encoded_path = array_map('urlencode', explode('/', $path)); + return str_replace($path, implode('/', $encoded_path), $uri); + } - public function collectData() { - switch($this->getInput('query')[0]) { - default: - returnServerError('Please, enter valid username or hashtag!'); - break; - case '@': - $url = 'https://urlebird.com/user/' . substr($this->getInput('query'), 1) . '/'; - break; - case '#': - $url = 'https://urlebird.com/hash/' . substr($this->getInput('query'), 1) . '/'; - break; - } + public function collectData() + { + switch ($this->getInput('query')[0]) { + default: + returnServerError('Please, enter valid username or hashtag!'); + break; + case '@': + $url = 'https://urlebird.com/user/' . substr($this->getInput('query'), 1) . '/'; + break; + case '#': + $url = 'https://urlebird.com/hash/' . substr($this->getInput('query'), 1) . '/'; + break; + } - $html = getSimpleHTMLDOM($url); - $this->title = $html->find('title', 0)->innertext; - $articles = $html->find('div.thumb'); - foreach ($articles as $article) { - $item = array(); - $item['uri'] = $this->fixURI($article->find('a', 2)->href); - $article_content = getSimpleHTMLDOM($item['uri']); - $item['author'] = $article->find('img', 0)->alt . ' (' . - $article_content->find('a.user-video', 1)->innertext . ')'; - $item['title'] = $article_content->find('title', 0)->innertext; - $item['enclosures'][] = $article_content->find('video', 0)->poster; - $video = $article_content->find('video', 0); - $video->autoplay = null; - $item['content'] = $video->outertext . '
' . - $article_content->find('div.music', 0) . '
' . - $article_content->find('div.info2', 0)->innertext . - '

Direct video link

Post link

'; - $this->items[] = $item; - } - } + $html = getSimpleHTMLDOM($url); + $this->title = $html->find('title', 0)->innertext; + $articles = $html->find('div.thumb'); + foreach ($articles as $article) { + $item = []; + $item['uri'] = $this->fixURI($article->find('a', 2)->href); + $article_content = getSimpleHTMLDOM($item['uri']); + $item['author'] = $article->find('img', 0)->alt . ' (' . + $article_content->find('a.user-video', 1)->innertext . ')'; + $item['title'] = $article_content->find('title', 0)->innertext; + $item['enclosures'][] = $article_content->find('video', 0)->poster; + $video = $article_content->find('video', 0); + $video->autoplay = null; + $item['content'] = $video->outertext . '
' . + $article_content->find('div.music', 0) . '
' . + $article_content->find('div.info2', 0)->innertext . + '

Direct video link

Post link

'; + $this->items[] = $item; + } + } - public function getName() { - return $this->title ?: parent::getName(); - } + public function getName() + { + return $this->title ?: parent::getName(); + } - public function getIcon() { - return 'https://urlebird.com/favicon.ico'; - } + public function getIcon() + { + return 'https://urlebird.com/favicon.ico'; + } } diff --git a/bridges/UsbekEtRicaBridge.php b/bridges/UsbekEtRicaBridge.php index d5fd507a..3dd432f0 100644 --- a/bridges/UsbekEtRicaBridge.php +++ b/bridges/UsbekEtRicaBridge.php @@ -1,111 +1,115 @@ array( - 'name' => 'Number of articles to return', - 'type' => 'number', - 'required' => false, - 'title' => 'Specifies the maximum number of articles to return', - 'defaultValue' => -1 - ), - 'fullarticle' => array( - 'name' => 'Load full article', - 'type' => 'checkbox', - 'required' => false, - 'title' => 'Activate to load full articles', - ) - ) - ); + const PARAMETERS = [ + [ + 'limit' => [ + 'name' => 'Number of articles to return', + 'type' => 'number', + 'required' => false, + 'title' => 'Specifies the maximum number of articles to return', + 'defaultValue' => -1 + ], + 'fullarticle' => [ + 'name' => 'Load full article', + 'type' => 'checkbox', + 'required' => false, + 'title' => 'Activate to load full articles', + ] + ] + ]; - public function collectData(){ - $limit = $this->getInput('limit'); - $fullarticle = $this->getInput('fullarticle'); - $html = getSimpleHTMLDOM($this->getURI()); + public function collectData() + { + $limit = $this->getInput('limit'); + $fullarticle = $this->getInput('fullarticle'); + $html = getSimpleHTMLDOM($this->getURI()); - $articles = $html->find('article'); + $articles = $html->find('article'); - foreach($articles as $article) { - $item = array(); + foreach ($articles as $article) { + $item = []; - $title = $article->find('h2', 0); - if($title) { - $item['title'] = $title->plaintext; - } else { - // Sometimes we get rubbish, ignore. - continue; - } + $title = $article->find('h2', 0); + if ($title) { + $item['title'] = $title->plaintext; + } else { + // Sometimes we get rubbish, ignore. + continue; + } - $author = $article->find('div.author span', 0); - if($author) { - $item['author'] = $author->plaintext; - } + $author = $article->find('div.author span', 0); + if ($author) { + $item['author'] = $author->plaintext; + } - $u = $article->find('a.card-img', 0); + $u = $article->find('a.card-img', 0); - $uri = $u->href; - if(substr($uri, 0, 1) === 'h') { // absolute uri - $item['uri'] = $uri; - } else { // relative uri - $item['uri'] = $this->getURI() . $uri; - } + $uri = $u->href; + if (substr($uri, 0, 1) === 'h') { // absolute uri + $item['uri'] = $uri; + } else { // relative uri + $item['uri'] = $this->getURI() . $uri; + } - if($fullarticle) { - $content = $this->loadFullArticle($item['uri']); - } + if ($fullarticle) { + $content = $this->loadFullArticle($item['uri']); + } - if($fullarticle && !is_null($content)) { - $item['content'] = $content; - } else { - $excerpt = $article->find('div.card-excerpt', 0); - if($excerpt) { - $item['content'] = $excerpt->plaintext; - } - } + if ($fullarticle && !is_null($content)) { + $item['content'] = $content; + } else { + $excerpt = $article->find('div.card-excerpt', 0); + if ($excerpt) { + $item['content'] = $excerpt->plaintext; + } + } - $image = $article->find('div.card-img img', 0); - if($image) { - $item['enclosures'] = array( - $image->src - ); - } + $image = $article->find('div.card-img img', 0); + if ($image) { + $item['enclosures'] = [ + $image->src + ]; + } - $this->items[] = $item; + $this->items[] = $item; - if($limit > 0 && count($this->items) >= $limit) { - break; - } - } - } + if ($limit > 0 && count($this->items) >= $limit) { + break; + } + } + } - /** - * Loads the full article and returns the contents - * @param $uri The article URI - * @return The article content - */ - private function loadFullArticle($uri){ - $html = getSimpleHTMLDOMCached($uri); + /** + * Loads the full article and returns the contents + * @param $uri The article URI + * @return The article content + */ + private function loadFullArticle($uri) + { + $html = getSimpleHTMLDOMCached($uri); - $content = $html->find('div.rich-text', 1); - if($content) { - return $this->replaceUriInHtmlElement($content); - } + $content = $html->find('div.rich-text', 1); + if ($content) { + return $this->replaceUriInHtmlElement($content); + } - return null; - } + return null; + } - /** - * Replaces all relative URIs with absolute ones - * @param $element A simplehtmldom element - * @return The $element->innertext with all URIs replaced - */ - private function replaceUriInHtmlElement($element){ - return str_replace('href="/', 'href="' . $this->getURI() . '/', $element->innertext); - } + /** + * Replaces all relative URIs with absolute ones + * @param $element A simplehtmldom element + * @return The $element->innertext with all URIs replaced + */ + private function replaceUriInHtmlElement($element) + { + return str_replace('href="/', 'href="' . $this->getURI() . '/', $element->innertext); + } } diff --git a/bridges/UsenixBridge.php b/bridges/UsenixBridge.php index 4f785a0e..659f012d 100644 --- a/bridges/UsenixBridge.php +++ b/bridges/UsenixBridge.php @@ -1,68 +1,69 @@ [ - ], - ]; + const NAME = 'USENIX'; + const URI = 'https://www.usenix.org/publications'; + const DESCRIPTION = 'Digital publications from USENIX (usenix.org)'; + const MAINTAINER = 'dvikan'; + const PARAMETERS = [ + 'USENIX ;login:' => [ + ], + ]; - public function collectData() - { - if ($this->queriedContext === 'USENIX ;login:') { - $this->collectLoginOnlineItems(); - return; - } - returnClientError('Illegal Context'); - } + public function collectData() + { + if ($this->queriedContext === 'USENIX ;login:') { + $this->collectLoginOnlineItems(); + return; + } + returnClientError('Illegal Context'); + } - private function collectLoginOnlineItems(): void - { - $url = 'https://www.usenix.org/publications/loginonline'; - $dom = getSimpleHTMLDOMCached($url); - $items = $dom->find('div.view-content > div'); + private function collectLoginOnlineItems(): void + { + $url = 'https://www.usenix.org/publications/loginonline'; + $dom = getSimpleHTMLDOMCached($url); + $items = $dom->find('div.view-content > div'); - foreach ($items as $item) { - $title = $item->find('.views-field-title > span', 0); - $author = $item->find('.views-field-pseudo-author-list > span.field-content', 0); - $relativeUrl = $item->find('.views-field-nothing-1 > span > a', 0); - $uri = sprintf('https://www.usenix.org%s', $relativeUrl->href); - // June 2, 2022 - $createdAt = $item->find('div.views-field-field-lv2-publication-date > div > span', 0); + foreach ($items as $item) { + $title = $item->find('.views-field-title > span', 0); + $author = $item->find('.views-field-pseudo-author-list > span.field-content', 0); + $relativeUrl = $item->find('.views-field-nothing-1 > span > a', 0); + $uri = sprintf('https://www.usenix.org%s', $relativeUrl->href); + // June 2, 2022 + $createdAt = $item->find('div.views-field-field-lv2-publication-date > div > span', 0); - $item = [ - 'title' => $title->innertext, - 'author' => strstr($author->plaintext, ',', true) ?: $author->plaintext, - 'uri' => $uri, - 'timestamp' => $createdAt->innertext, - ]; + $item = [ + 'title' => $title->innertext, + 'author' => strstr($author->plaintext, ',', true) ?: $author->plaintext, + 'uri' => $uri, + 'timestamp' => $createdAt->innertext, + ]; - $this->items[] = array_merge($item, $this->getItemContent($uri)); - } - } + $this->items[] = array_merge($item, $this->getItemContent($uri)); + } + } - private function getItemContent(string $uri) : array - { - $html = getSimpleHTMLDOMCached($uri); - $content = $html->find('.paragraphs-items-full', 0)->innertext; - $extra = $html->find('fieldset', 0); - if (!empty($extra)) { - $content .= $extra->innertext; - } + private function getItemContent(string $uri): array + { + $html = getSimpleHTMLDOMCached($uri); + $content = $html->find('.paragraphs-items-full', 0)->innertext; + $extra = $html->find('fieldset', 0); + if (!empty($extra)) { + $content .= $extra->innertext; + } - $tags = []; - foreach($html->find('.field-name-field-lv2-tags div.field-item') as $tag) { - $tags[] = $tag->plaintext; - } + $tags = []; + foreach ($html->find('.field-name-field-lv2-tags div.field-item') as $tag) { + $tags[] = $tag->plaintext; + } - return [ - 'content' => $content, - 'categories' => $tags - ]; - } + return [ + 'content' => $content, + 'categories' => $tags + ]; + } } diff --git a/bridges/VarietyBridge.php b/bridges/VarietyBridge.php index 8bc48f46..23d1df3f 100644 --- a/bridges/VarietyBridge.php +++ b/bridges/VarietyBridge.php @@ -1,30 +1,33 @@ collectExpandableDatas('https://feeds.feedburner.com/variety/headlines', 15); - } + public function collectData() + { + $this->collectExpandableDatas('https://feeds.feedburner.com/variety/headlines', 15); + } - protected function parseItem($newsItem){ - $item = parent::parseItem($newsItem); - // $articlePage gets the entire page's contents - $articlePage = getSimpleHTMLDOM($newsItem->link); + protected function parseItem($newsItem) + { + $item = parent::parseItem($newsItem); + // $articlePage gets the entire page's contents + $articlePage = getSimpleHTMLDOM($newsItem->link); - // Remove Script tags - foreach($articlePage->find('script') as $script_tag) { - $script_tag->remove(); - } - $article = $articlePage->find('div.c-featured-media', 0); - $article = $article . $articlePage->find('.c-content', 0); + // Remove Script tags + foreach ($articlePage->find('script') as $script_tag) { + $script_tag->remove(); + } + $article = $articlePage->find('div.c-featured-media', 0); + $article = $article . $articlePage->find('.c-content', 0); - $item['content'] = $article; + $item['content'] = $article; - return $item; - } + return $item; + } } diff --git a/bridges/ViadeoCompanyBridge.php b/bridges/ViadeoCompanyBridge.php index fd1a29b6..3b147c41 100644 --- a/bridges/ViadeoCompanyBridge.php +++ b/bridges/ViadeoCompanyBridge.php @@ -1,40 +1,43 @@ apple)'; - const PARAMETERS = array( array( - 'c' => array( - 'name' => 'Company name', - 'exampleValue' => 'apple', - 'required' => true - ) - )); + const PARAMETERS = [ [ + 'c' => [ + 'name' => 'Company name', + 'exampleValue' => 'apple', + 'required' => true + ] + ]]; - public function collectData(){ - // Redirects to https://emploi.lefigaro.fr/recherche/entreprises - $url = sprintf('%sfr/company/%s', self::URI, $this->getInput('c')); + public function collectData() + { + // Redirects to https://emploi.lefigaro.fr/recherche/entreprises + $url = sprintf('%sfr/company/%s', self::URI, $this->getInput('c')); - $html = getSimpleHTMLDOM($url); + $html = getSimpleHTMLDOM($url); - // TODO: Fix broken xpath selector - $elements = $html->find('//*[@id="company-newsfeed"]/ul/li'); + // TODO: Fix broken xpath selector + $elements = $html->find('//*[@id="company-newsfeed"]/ul/li'); - foreach($elements as $element) { - $title = $element->find('p', 0)->innertext; - if(!$title) { - continue; - } - $item = array(); - $item['uri'] = $url; - $item['title'] = mb_substr($element->find('p', 0)->innertext, 0, 100); - $item['content'] = $element->find('p', 0)->innertext;; - $this->items[] = $item; - } - } + foreach ($elements as $element) { + $title = $element->find('p', 0)->innertext; + if (!$title) { + continue; + } + $item = []; + $item['uri'] = $url; + $item['title'] = mb_substr($element->find('p', 0)->innertext, 0, 100); + $item['content'] = $element->find('p', 0)->innertext; + ; + $this->items[] = $item; + } + } } diff --git a/bridges/ViceBridge.php b/bridges/ViceBridge.php index 4dccb8ef..14272517 100644 --- a/bridges/ViceBridge.php +++ b/bridges/ViceBridge.php @@ -1,38 +1,42 @@ array( - 'name' => 'Feed', - 'type' => 'list', - 'values' => array( - 'Vice News' => 'rss', - 'Motherboard - Tech' => 'en_us/rss/topic/tech', - 'Entertainment' => 'en_us/rss/topic/entertainment', - 'Noisey - Music' => 'en_us/rss/topic/music', - 'Munchies - Food' => 'en_us/rss/topic/food' - ) - ) - )); - public function collectData(){ - $feed = $this->getInput('feed'); - $feedURL = 'https://www.vice.com/' . $feed; - $this->collectExpandableDatas($feedURL, 10); - } +class ViceBridge extends FeedExpander +{ + const MAINTAINER = 'IceWreck'; + const NAME = 'Vice Bridge'; + const URI = 'https://www.vice.com/'; + const CACHE_TIMEOUT = 3600; // This is a news site, so don't cache for more than 10 mins + const DESCRIPTION = 'RSS feed for vice publications like Vice News, Munchies, Motherboard, etc.'; + const PARAMETERS = [ [ + 'feed' => [ + 'name' => 'Feed', + 'type' => 'list', + 'values' => [ + 'Vice News' => 'rss', + 'Motherboard - Tech' => 'en_us/rss/topic/tech', + 'Entertainment' => 'en_us/rss/topic/entertainment', + 'Noisey - Music' => 'en_us/rss/topic/music', + 'Munchies - Food' => 'en_us/rss/topic/food' + ] + ] + ]]; - protected function parseItem($newsItem){ - $item = parent::parseItem($newsItem); - // $articlePage gets the entire page's contents - $articlePage = getSimpleHTMLDOM($newsItem->link); - // text and embedded content - $article = $article . $articlePage->find('.article__body', 0); - $item['content'] = $article; + public function collectData() + { + $feed = $this->getInput('feed'); + $feedURL = 'https://www.vice.com/' . $feed; + $this->collectExpandableDatas($feedURL, 10); + } - return $item; - } + protected function parseItem($newsItem) + { + $item = parent::parseItem($newsItem); + // $articlePage gets the entire page's contents + $articlePage = getSimpleHTMLDOM($newsItem->link); + // text and embedded content + $article = $article . $articlePage->find('.article__body', 0); + $item['content'] = $article; + + return $item; + } } diff --git a/bridges/VieDeMerdeBridge.php b/bridges/VieDeMerdeBridge.php index d9c906e5..9e6166fb 100644 --- a/bridges/VieDeMerdeBridge.php +++ b/bridges/VieDeMerdeBridge.php @@ -1,56 +1,58 @@ array( - 'name' => 'Limit number of returned items', - 'type' => 'number', - 'defaultValue' => 20 - ) - )); + const PARAMETERS = [[ + 'item_limit' => [ + 'name' => 'Limit number of returned items', + 'type' => 'number', + 'defaultValue' => 20 + ] + ]]; - public function collectData() { - $limit = $this->getInput('item_limit'); + public function collectData() + { + $limit = $this->getInput('item_limit'); - if ($limit < 1) { - $limit = 20; - } + if ($limit < 1) { + $limit = 20; + } - $html = getSimpleHTMLDOM(self::URI, array()); - $quotes = $html->find('article.bg-white'); - if(sizeof($quotes) === 0) { - return; - } + $html = getSimpleHTMLDOM(self::URI, []); + $quotes = $html->find('article.bg-white'); + if (sizeof($quotes) === 0) { + return; + } - foreach($quotes as $quote) { - $item = array(); - $item['uri'] = self::URI . $quote->find('a', 0)->href; - $titleContent = $quote->find('h2', 0); + foreach ($quotes as $quote) { + $item = []; + $item['uri'] = self::URI . $quote->find('a', 0)->href; + $titleContent = $quote->find('h2', 0); - if($titleContent) { - $item['title'] = html_entity_decode($titleContent->plaintext, ENT_QUOTES); - } else { - continue; - } + if ($titleContent) { + $item['title'] = html_entity_decode($titleContent->plaintext, ENT_QUOTES); + } else { + continue; + } - $quoteText = $quote->find('a', 1)->plaintext; - $isAVDM = $quote->find('.vote-btn', 0)->plaintext; - $isNotAVDM = $quote->find('.vote-btn', 1)->plaintext; - $item['content'] = $quoteText . '
' . $isAVDM . '
' . $isNotAVDM; - $item['author'] = $quote->find('p', 0)->plaintext; - $item['uid'] = hash('sha256', $item['title']); + $quoteText = $quote->find('a', 1)->plaintext; + $isAVDM = $quote->find('.vote-btn', 0)->plaintext; + $isNotAVDM = $quote->find('.vote-btn', 1)->plaintext; + $item['content'] = $quoteText . '
' . $isAVDM . '
' . $isNotAVDM; + $item['author'] = $quote->find('p', 0)->plaintext; + $item['uid'] = hash('sha256', $item['title']); - $this->items[] = $item; + $this->items[] = $item; - if (count($this->items) >= $limit) { - break; - } - } - } + if (count($this->items) >= $limit) { + break; + } + } + } } diff --git a/bridges/VimeoBridge.php b/bridges/VimeoBridge.php index d97026d6..80bfb8ac 100644 --- a/bridges/VimeoBridge.php +++ b/bridges/VimeoBridge.php @@ -1,175 +1,197 @@ [ + 'name' => 'Search Query', + 'type' => 'text', + 'exampleValue' => 'birds', + 'required' => true + ], + 'type' => [ + 'name' => 'Show results for', + 'type' => 'list', + 'defaultValue' => 'Videos', + 'values' => [ + 'Videos' => 'search', + 'On Demand' => 'search/ondemand', + 'People' => 'search/people', + 'Channels' => 'search/channels', + 'Groups' => 'search/groups' + ] + ] + ] + ]; - const PARAMETERS = array( - array( - 'q' => array( - 'name' => 'Search Query', - 'type' => 'text', - 'exampleValue' => 'birds', - 'required' => true - ), - 'type' => array( - 'name' => 'Show results for', - 'type' => 'list', - 'defaultValue' => 'Videos', - 'values' => array( - 'Videos' => 'search', - 'On Demand' => 'search/ondemand', - 'People' => 'search/people', - 'Channels' => 'search/channels', - 'Groups' => 'search/groups' - ) - ) - ) - ); + public function getURI() + { + if ( + ($query = $this->getInput('q')) + && ($type = $this->getInput('type')) + ) { + return self::URI . $type . '/sort:latest?q=' . $query; + } - public function getURI() { - if(($query = $this->getInput('q')) - && ($type = $this->getInput('type'))) { - return self::URI . $type . '/sort:latest?q=' . $query; - } + return parent::getURI(); + } - return parent::getURI(); - } + public function collectData() + { + $html = getSimpleHTMLDOM( + $this->getURI(), + $header = [], + $opts = [], + $lowercase = true, + $forceTagsClosed = true, + $target_charset = DEFAULT_TARGET_CHARSET, + $stripRN = false, // We want to keep newline characters + $defaultBRText = DEFAULT_BR_TEXT, + $defaultSpanText = DEFAULT_SPAN_TEXT + ); - public function collectData() { + $json = null; // Holds the JSON data - $html = getSimpleHTMLDOM($this->getURI(), - $header = array(), - $opts = array(), - $lowercase = true, - $forceTagsClosed = true, - $target_charset = DEFAULT_TARGET_CHARSET, - $stripRN = false, // We want to keep newline characters - $defaultBRText = DEFAULT_BR_TEXT, - $defaultSpanText = DEFAULT_SPAN_TEXT); + /** + * Search results are included as JSON formatted string inside a script + * tag that has the variable 'vimeo.config'. The data is condensed into + * a single line of code, so we can just search for the newline. + * + * Everything after "vimeo.config = _extend((vimeo.config || {}), " is + * the JSON formatted string. + */ + foreach ($html->find('script') as $script) { + foreach (explode("\n", $script) as $line) { + $line = trim($line); - $json = null; // Holds the JSON data + if (strpos($line, 'vimeo.config') !== 0) { + continue; + } - /** - * Search results are included as JSON formatted string inside a script - * tag that has the variable 'vimeo.config'. The data is condensed into - * a single line of code, so we can just search for the newline. - * - * Everything after "vimeo.config = _extend((vimeo.config || {}), " is - * the JSON formatted string. - */ - foreach($html->find('script') as $script) { - foreach(explode("\n", $script) as $line) { - $line = trim($line); + // 45 = strlen("vimeo.config = _extend((vimeo.config || {}), "); + // 47 = 45 + 2, because we don't want the final ");" + $json = json_decode(substr($line, 45, strlen($line) - 47)); + } + } - if(strpos($line, 'vimeo.config') !== 0) - continue; + if (is_null($json)) { + returnClientError('No results for this query!'); + } - // 45 = strlen("vimeo.config = _extend((vimeo.config || {}), "); - // 47 = 45 + 2, because we don't want the final ");" - $json = json_decode(substr($line, 45, strlen($line) - 47)); - } - } + foreach ($json->api->initial_json->data as $element) { + switch ($element->type) { + case 'clip': + $this->addClip($element); + break; + case 'ondemand': + $this->addOnDemand($element); + break; + case 'people': + $this->addPeople($element); + break; + case 'channel': + $this->addChannel($element); + break; + case 'group': + $this->addGroup($element); + break; - if(is_null($json)) { - returnClientError('No results for this query!'); - } + default: + returnServerError('Unknown type: ' . $element->type); + } + } + } - foreach($json->api->initial_json->data as $element) { - switch($element->type) { - case 'clip': $this->addClip($element); break; - case 'ondemand': $this->addOnDemand($element); break; - case 'people': $this->addPeople($element); break; - case 'channel': $this->addChannel($element); break; - case 'group': $this->addGroup($element); break; + private function addClip($element) + { + $item = []; - default: returnServerError('Unknown type: ' . $element->type); - } - } + $item['uri'] = $element->clip->link; + $item['title'] = $element->clip->name; + $item['author'] = $element->clip->user->name; + $item['timestamp'] = strtotime($element->clip->created_time); - } + $item['enclosures'] = [ + end($element->clip->pictures->sizes)->link + ]; - private function addClip($element) { - $item = array(); + $item['content'] = ""; - $item['uri'] = $element->clip->link; - $item['title'] = $element->clip->name; - $item['author'] = $element->clip->user->name; - $item['timestamp'] = strtotime($element->clip->created_time); + $this->items[] = $item; + } - $item['enclosures'] = array( - end($element->clip->pictures->sizes)->link - ); + private function addOnDemand($element) + { + $item = []; - $item['content'] = ""; + $item['uri'] = $element->ondemand->link; + $item['title'] = $element->ondemand->name; - $this->items[] = $item; - } + // Only for films + if (isset($element->ondemand->film)) { + $item['timestamp'] = strtotime($element->ondemand->film->release_time); + } - private function addOnDemand($element) { - $item = array(); + $item['enclosures'] = [ + end($element->ondemand->pictures->sizes)->link + ]; - $item['uri'] = $element->ondemand->link; - $item['title'] = $element->ondemand->name; + $item['content'] = ""; - // Only for films - if(isset($element->ondemand->film)) - $item['timestamp'] = strtotime($element->ondemand->film->release_time); + $this->items[] = $item; + } - $item['enclosures'] = array( - end($element->ondemand->pictures->sizes)->link - ); + private function addPeople($element) + { + $item = []; - $item['content'] = ""; + $item['uri'] = $element->people->link; + $item['title'] = $element->people->name; - $this->items[] = $item; - } + $item['enclosures'] = [ + end($element->people->pictures->sizes)->link + ]; - private function addPeople($element) { - $item = array(); + $item['content'] = ""; - $item['uri'] = $element->people->link; - $item['title'] = $element->people->name; + $this->items[] = $item; + } - $item['enclosures'] = array( - end($element->people->pictures->sizes)->link - ); + private function addChannel($element) + { + $item = []; - $item['content'] = ""; + $item['uri'] = $element->channel->link; + $item['title'] = $element->channel->name; - $this->items[] = $item; - } + $item['enclosures'] = [ + end($element->channel->pictures->sizes)->link + ]; - private function addChannel($element) { - $item = array(); + $item['content'] = ""; - $item['uri'] = $element->channel->link; - $item['title'] = $element->channel->name; + $this->items[] = $item; + } - $item['enclosures'] = array( - end($element->channel->pictures->sizes)->link - ); + private function addGroup($element) + { + $item = []; - $item['content'] = ""; + $item['uri'] = $element->group->link; + $item['title'] = $element->group->name; - $this->items[] = $item; - } + $item['enclosures'] = [ + end($element->group->pictures->sizes)->link + ]; - private function addGroup($element) { - $item = array(); + $item['content'] = ""; - $item['uri'] = $element->group->link; - $item['title'] = $element->group->name; - - $item['enclosures'] = array( - end($element->group->pictures->sizes)->link - ); - - $item['content'] = ""; - - $this->items[] = $item; - } + $this->items[] = $item; + } } diff --git a/bridges/VixenBridge.php b/bridges/VixenBridge.php index 721524e9..048b9a7b 100644 --- a/bridges/VixenBridge.php +++ b/bridges/VixenBridge.php @@ -1,99 +1,111 @@ array( - 'type' => 'list', - 'name' => 'Site', - 'title' => 'Choose site of interest', - 'values' => array( - 'Blacked' => 'Blacked', - 'BlackedRaw' => 'BlackedRaw', - 'Tushy' => 'Tushy', - 'TushyRaw' => 'TushyRaw', - 'Vixen' => 'Vixen', - 'Slayed' => 'Slayed', - 'Deeper' => 'Deeper' - ), - ) - ) - ); + /** + * The pictures on the pages are referenced with temporary links with + * limited validity. Greater cache timeout results in invalid links in + * the feed + */ + const CACHE_TIMEOUT = 60; - public function collectData() { - $videosURL = $this->getURI() . '/videos'; + const PARAMETERS = [ + [ + 'site' => [ + 'type' => 'list', + 'name' => 'Site', + 'title' => 'Choose site of interest', + 'values' => [ + 'Blacked' => 'Blacked', + 'BlackedRaw' => 'BlackedRaw', + 'Tushy' => 'Tushy', + 'TushyRaw' => 'TushyRaw', + 'Vixen' => 'Vixen', + 'Slayed' => 'Slayed', + 'Deeper' => 'Deeper' + ], + ] + ] + ]; - $website = getSimpleHTMLDOM($videosURL); - $json = $website->getElementById('__NEXT_DATA__'); - $data = json_decode($json->innertext(), true); - $nodes = array_column($data['props']['pageProps']['edges'], 'node'); + public function collectData() + { + $videosURL = $this->getURI() . '/videos'; - foreach($nodes as $n) { - $imageURL = $n['images']['listing'][2]['highdpi']['triple']; + $website = getSimpleHTMLDOM($videosURL); + $json = $website->getElementById('__NEXT_DATA__'); + $data = json_decode($json->innertext(), true); + $nodes = array_column($data['props']['pageProps']['edges'], 'node'); - $item = [ - 'title' => $n['title'], - 'uri' => "$videosURL/$n[slug]", - 'uid' => $n['videoId'], - 'timestamp' => strtotime($n['releaseDate']), - 'enclosures' => [ $imageURL ], - 'author' => implode(' & ', array_column($n['modelsSlugged'], 'name')), - ]; + foreach ($nodes as $n) { + $imageURL = $n['images']['listing'][2]['highdpi']['triple']; - /* - * No images retrieved from here. Should be cached for as long as - * possible to avoid rate throttling - */ - $target = getSimpleHtmlDOMCached($item['uri'], 86400); - $item['content'] = $this->generateContent($imageURL, - $target->find('meta[name=description]', 0)->content, - $n['modelsSlugged']); + $item = [ + 'title' => $n['title'], + 'uri' => "$videosURL/$n[slug]", + 'uid' => $n['videoId'], + 'timestamp' => strtotime($n['releaseDate']), + 'enclosures' => [ $imageURL ], + 'author' => implode(' & ', array_column($n['modelsSlugged'], 'name')), + ]; - $item['categories'] = array_map('ucwords', - explode(',', $target->find('meta[name=keywords]', 0)->content)); + /* + * No images retrieved from here. Should be cached for as long as + * possible to avoid rate throttling + */ + $target = getSimpleHtmlDOMCached($item['uri'], 86400); + $item['content'] = $this->generateContent( + $imageURL, + $target->find('meta[name=description]', 0)->content, + $n['modelsSlugged'] + ); - $this->items[] = $item; - } - } + $item['categories'] = array_map( + 'ucwords', + explode(',', $target->find('meta[name=keywords]', 0)->content) + ); - public function getURI() { - $param = $this->getInput('site'); - return $param ? "https://www.$param.com" : self::URI; - } + $this->items[] = $item; + } + } - /** - * Return name of the bridge. Default is needed for bridge index list - */ - public function getName() { - $param = $this->getInput('site'); - return $param ? "$param Bridge" : self::NAME; - } + public function getURI() + { + $param = $this->getInput('site'); + return $param ? "https://www.$param.com" : self::URI; + } - private static function makeLink($URI, $text) { - return "$text"; - } + /** + * Return name of the bridge. Default is needed for bridge index list + */ + public function getName() + { + $param = $this->getInput('site'); + return $param ? "$param Bridge" : self::NAME; + } - private function generateContent($imageURI, $description, $models) { - $content = "

$description

"; - $modelLinks = array_map( - function($model) { - return self::makeLink( - $this->getURI() . "/models/$model[slugged]", - $model['name']); - }, - $models - ); - return $content . '

Starring: ' . implode(' & ', $modelLinks) . '

'; - } + private static function makeLink($URI, $text) + { + return "$text"; + } + + private function generateContent($imageURI, $description, $models) + { + $content = "

$description

"; + $modelLinks = array_map( + function ($model) { + return self::makeLink( + $this->getURI() . "/models/$model[slugged]", + $model['name'] + ); + }, + $models + ); + return $content . '

Starring: ' . implode(' & ', $modelLinks) . '

'; + } } diff --git a/bridges/VkBridge.php b/bridges/VkBridge.php index 1d0f65b0..bb554bc1 100644 --- a/bridges/VkBridge.php +++ b/bridges/VkBridge.php @@ -2,463 +2,476 @@ class VkBridge extends BridgeAbstract { + const MAINTAINER = 'em92'; + // const MAINTAINER = 'pmaziere'; + // const MAINTAINER = 'ahiles3005'; + const NAME = 'VK.com'; + const URI = 'https://vk.com/'; + const CACHE_TIMEOUT = 300; // 5min + const DESCRIPTION = 'Working with open pages'; + const PARAMETERS = [ + [ + 'u' => [ + 'name' => 'Group or user name', + 'exampleValue' => 'elonmusk_tech', + 'required' => true + ], + 'hide_reposts' => [ + 'name' => 'Hide reposts', + 'type' => 'checkbox', + ] + ] + ]; - const MAINTAINER = 'em92'; - // const MAINTAINER = 'pmaziere'; - // const MAINTAINER = 'ahiles3005'; - const NAME = 'VK.com'; - const URI = 'https://vk.com/'; - const CACHE_TIMEOUT = 300; // 5min - const DESCRIPTION = 'Working with open pages'; - const PARAMETERS = array( - array( - 'u' => array( - 'name' => 'Group or user name', - 'exampleValue' => 'elonmusk_tech', - 'required' => true - ), - 'hide_reposts' => array( - 'name' => 'Hide reposts', - 'type' => 'checkbox', - ) - ) - ); + protected $videos = []; + protected $pageName; - protected $videos = array(); - protected $pageName; + protected function getAccessToken() + { + return 'e69b2db9f6cd4a97c0716893232587165c18be85bc1af1834560125c1d3c8ec281eb407a78cca0ae16776'; + } - protected function getAccessToken() - { - return 'e69b2db9f6cd4a97c0716893232587165c18be85bc1af1834560125c1d3c8ec281eb407a78cca0ae16776'; - } + public function getURI() + { + if (!is_null($this->getInput('u'))) { + return static::URI . urlencode($this->getInput('u')); + } - public function getURI() - { - if (!is_null($this->getInput('u'))) { - return static::URI . urlencode($this->getInput('u')); - } + return parent::getURI(); + } - return parent::getURI(); - } + public function getName() + { + if ($this->pageName) { + return $this->pageName; + } - public function getName() - { - if ($this->pageName) { - return $this->pageName; - } + return parent::getName(); + } - return parent::getName(); - } + public function collectData() + { + $text_html = $this->getContents(); - public function collectData() - { - $text_html = $this->getContents(); + $text_html = iconv('windows-1251', 'utf-8//ignore', $text_html); + // makes album link generating work correctly + $text_html = str_replace('"class="page_album_link">', '" class="page_album_link">', $text_html); + $html = str_get_html($text_html); + $pageName = $html->find('.page_name', 0); + if (is_object($pageName)) { + $pageName = $pageName->plaintext; + $this->pageName = htmlspecialchars_decode($pageName); + } + foreach ($html->find('div.replies') as $comment_block) { + $comment_block->outertext = ''; + } + $html->load($html->save()); - $text_html = iconv('windows-1251', 'utf-8//ignore', $text_html); - // makes album link generating work correctly - $text_html = str_replace('"class="page_album_link">', '" class="page_album_link">', $text_html); - $html = str_get_html($text_html); - $pageName = $html->find('.page_name', 0); - if (is_object($pageName)) { - $pageName = $pageName->plaintext; - $this->pageName = htmlspecialchars_decode($pageName); - } - foreach ($html->find('div.replies') as $comment_block) { - $comment_block->outertext = ''; - } - $html->load($html->save()); + $pinned_post_item = null; + $last_post_id = 0; - $pinned_post_item = null; - $last_post_id = 0; + foreach ($html->find('.post') as $post) { + if ($post->find('.wall_post_text_deleted')) { + // repost of deleted post + continue; + } - foreach ($html->find('.post') as $post) { + defaultLinkTo($post, self::URI); - if ($post->find('.wall_post_text_deleted')) { - // repost of deleted post - continue; - } + $post_videos = []; - defaultLinkTo($post, self::URI); + $is_pinned_post = false; + if (strpos($post->getAttribute('class'), 'post_fixed') !== false) { + $is_pinned_post = true; + } - $post_videos = array(); + if (is_object($post->find('a.wall_post_more', 0))) { + //delete link "show full" in content + $post->find('a.wall_post_more', 0)->outertext = ''; + } - $is_pinned_post = false; - if (strpos($post->getAttribute('class'), 'post_fixed') !== false) { - $is_pinned_post = true; - } + $content_suffix = ''; - if (is_object($post->find('a.wall_post_more', 0))) { - //delete link "show full" in content - $post->find('a.wall_post_more', 0)->outertext = ''; - } + // looking for external links + $external_link_selectors = [ + 'a.page_media_link_title', + 'div.page_media_link_title > a', + 'div.media_desc > a.lnk', + ]; - $content_suffix = ''; + foreach ($external_link_selectors as $sel) { + if (is_object($post->find($sel, 0))) { + $a = $post->find($sel, 0); + $innertext = $a->innertext; + $parsed_url = parse_url($a->getAttribute('href')); + if (strpos($parsed_url['path'], '/away.php') !== 0) { + continue; + } + parse_str($parsed_url['query'], $parsed_query); + $content_suffix .= "
External link: $innertext"; + } + } - // looking for external links - $external_link_selectors = array( - 'a.page_media_link_title', - 'div.page_media_link_title > a', - 'div.media_desc > a.lnk', - ); + // remove external link from content + $external_link_selectors_to_remove = [ + 'div.page_media_thumbed_link', + 'div.page_media_link_desc_wrap', + 'div.media_desc > a.lnk', + ]; - foreach($external_link_selectors as $sel) { - if (is_object($post->find($sel, 0))) { - $a = $post->find($sel, 0); - $innertext = $a->innertext; - $parsed_url = parse_url($a->getAttribute('href')); - if (strpos($parsed_url['path'], '/away.php') !== 0) continue; - parse_str($parsed_url['query'], $parsed_query); - $content_suffix .= "
External link: $innertext"; - } - } + foreach ($external_link_selectors_to_remove as $sel) { + if (is_object($post->find($sel, 0))) { + $post->find($sel, 0)->outertext = ''; + } + } - // remove external link from content - $external_link_selectors_to_remove = array( - 'div.page_media_thumbed_link', - 'div.page_media_link_desc_wrap', - 'div.media_desc > a.lnk', - ); - - foreach($external_link_selectors_to_remove as $sel) { - if (is_object($post->find($sel, 0))) { - $post->find($sel, 0)->outertext = ''; - } - } - - // looking for article - $article = $post->find('a.article_snippet', 0); - if (is_object($article)) { - if (strpos($article->getAttribute('class'), 'article_snippet_mini') !== false) { - $article_title_selector = 'div.article_snippet_mini_title'; - $article_author_selector = 'div.article_snippet_mini_info > .mem_link, + // looking for article + $article = $post->find('a.article_snippet', 0); + if (is_object($article)) { + if (strpos($article->getAttribute('class'), 'article_snippet_mini') !== false) { + $article_title_selector = 'div.article_snippet_mini_title'; + $article_author_selector = 'div.article_snippet_mini_info > .mem_link, div.article_snippet_mini_info > .group_link'; - $article_thumb_selector = 'div.article_snippet_mini_thumb'; - } else { - $article_title_selector = 'div.article_snippet__title'; - $article_author_selector = 'div.article_snippet__author'; - $article_thumb_selector = 'div.article_snippet__image'; - } - $article_title = $article->find($article_title_selector, 0)->innertext; - $article_author = $article->find($article_author_selector, 0)->innertext; - $article_link = $article->getAttribute('href'); - $article_img_element_style = $article->find($article_thumb_selector, 0)->getAttribute('style'); - preg_match('/background-image: url\((.*)\)/', $article_img_element_style, $matches); - if (count($matches) > 0) { - $content_suffix .= "
"; - } - $content_suffix .= "
Article: $article_title ($article_author)"; - $article->outertext = ''; - } + $article_thumb_selector = 'div.article_snippet_mini_thumb'; + } else { + $article_title_selector = 'div.article_snippet__title'; + $article_author_selector = 'div.article_snippet__author'; + $article_thumb_selector = 'div.article_snippet__image'; + } + $article_title = $article->find($article_title_selector, 0)->innertext; + $article_author = $article->find($article_author_selector, 0)->innertext; + $article_link = $article->getAttribute('href'); + $article_img_element_style = $article->find($article_thumb_selector, 0)->getAttribute('style'); + preg_match('/background-image: url\((.*)\)/', $article_img_element_style, $matches); + if (count($matches) > 0) { + $content_suffix .= "
"; + } + $content_suffix .= "
Article: $article_title ($article_author)"; + $article->outertext = ''; + } - // get video on post - $video = $post->find('div.post_video_desc', 0); - $main_video_link = ''; - if (is_object($video)) { - $video_title = $video->find('div.post_video_title', 0)->plaintext; - $video_link = $video->find('a.lnk', 0)->getAttribute('href'); - $this->appendVideo($video_title, $video_link, $content_suffix, $post_videos); - $video->outertext = ''; - $main_video_link = $video_link; - } + // get video on post + $video = $post->find('div.post_video_desc', 0); + $main_video_link = ''; + if (is_object($video)) { + $video_title = $video->find('div.post_video_title', 0)->plaintext; + $video_link = $video->find('a.lnk', 0)->getAttribute('href'); + $this->appendVideo($video_title, $video_link, $content_suffix, $post_videos); + $video->outertext = ''; + $main_video_link = $video_link; + } - // get all other videos - foreach($post->find('a.page_post_thumb_video') as $a) { - $video_title = htmlspecialchars_decode($a->getAttribute('aria-label')); - $video_link = $a->getAttribute('href'); - if ($video_link != $main_video_link) $this->appendVideo($video_title, $video_link, $content_suffix, $post_videos); - $a->outertext = ''; - } + // get all other videos + foreach ($post->find('a.page_post_thumb_video') as $a) { + $video_title = htmlspecialchars_decode($a->getAttribute('aria-label')); + $video_link = $a->getAttribute('href'); + if ($video_link != $main_video_link) { + $this->appendVideo($video_title, $video_link, $content_suffix, $post_videos); + } + $a->outertext = ''; + } - // get all photos - foreach($post->find('div.wall_text a.page_post_thumb_wrap') as $a) { - $result = $this->getPhoto($a); - if ($result == null) continue; - $a->outertext = ''; - $content_suffix .= "
$result"; - } + // get all photos + foreach ($post->find('div.wall_text a.page_post_thumb_wrap') as $a) { + $result = $this->getPhoto($a); + if ($result == null) { + continue; + } + $a->outertext = ''; + $content_suffix .= "
$result"; + } - // get albums - foreach($post->find('.page_album_wrap') as $el) { - $a = $el->find('.page_album_link', 0); - $album_title = $a->find('.page_album_title_text', 0)->getAttribute('title'); - $album_link = $a->getAttribute('href'); - $el->outertext = ''; - $content_suffix .= "
Album: $album_title"; - } + // get albums + foreach ($post->find('.page_album_wrap') as $el) { + $a = $el->find('.page_album_link', 0); + $album_title = $a->find('.page_album_title_text', 0)->getAttribute('title'); + $album_link = $a->getAttribute('href'); + $el->outertext = ''; + $content_suffix .= "
Album: $album_title"; + } - // get photo documents - foreach($post->find('a.page_doc_photo_href') as $a) { - $doc_link = $a->getAttribute('href'); - $doc_gif_label_element = $a->find('.page_gif_label', 0); - $doc_title_element = $a->find('.doc_label', 0); + // get photo documents + foreach ($post->find('a.page_doc_photo_href') as $a) { + $doc_link = $a->getAttribute('href'); + $doc_gif_label_element = $a->find('.page_gif_label', 0); + $doc_title_element = $a->find('.doc_label', 0); - if (is_object($doc_gif_label_element)) { - $gif_preview_img = backgroundToImg($a->find('.page_doc_photo', 0)); - $content_suffix .= "
Gif: $gif_preview_img"; + if (is_object($doc_gif_label_element)) { + $gif_preview_img = backgroundToImg($a->find('.page_doc_photo', 0)); + $content_suffix .= "
Gif: $gif_preview_img"; + } elseif (is_object($doc_title_element)) { + $doc_title = $doc_title_element->innertext; + $content_suffix .= "
Doc: $doc_title"; + } else { + continue; + } - } else if (is_object($doc_title_element)) { - $doc_title = $doc_title_element->innertext; - $content_suffix .= "
Doc: $doc_title"; + $a->outertext = ''; + } - } else { - continue; + // get other documents + foreach ($post->find('div.page_doc_row') as $div) { + $doc_title_element = $div->find('a.page_doc_title', 0); - } + if (is_object($doc_title_element)) { + $doc_title = $doc_title_element->innertext; + $doc_link = $doc_title_element->getAttribute('href'); + $content_suffix .= "
Doc: $doc_title"; + } else { + continue; + } - $a->outertext = ''; - } + $div->outertext = ''; + } - // get other documents - foreach($post->find('div.page_doc_row') as $div) { - $doc_title_element = $div->find('a.page_doc_title', 0); + // get polls + foreach ($post->find('div.page_media_poll_wrap') as $div) { + $poll_title = $div->find('.page_media_poll_title', 0)->innertext; + $content_suffix .= "
Poll: $poll_title"; + foreach ($div->find('div.page_poll_text') as $poll_stat_title) { + $content_suffix .= '
- ' . $poll_stat_title->innertext; + } + $div->outertext = ''; + } - if (is_object($doc_title_element)) { - $doc_title = $doc_title_element->innertext; - $doc_link = $doc_title_element->getAttribute('href'); - $content_suffix .= "
Doc: $doc_title"; + // get sign / post author + $post_author = $pageName; + $author_selectors = ['a.wall_signed_by', 'a.author']; + foreach ($author_selectors as $author_selector) { + $a = $post->find($author_selector, 0); + if (is_object($a)) { + $post_author = $a->innertext; + $a->outertext = ''; + break; + } + } - } else { - continue; + // fix links and get post hashtags + $hashtags = []; + foreach ($post->find('a') as $a) { + $href = $a->getAttribute('href'); + $innertext = $a->innertext; - } + $hashtag_prefix = '/feed?section=search&q=%23'; + $hashtag = null; - $div->outertext = ''; - } + if ($href && substr($href, 0, strlen($hashtag_prefix)) === $hashtag_prefix) { + $hashtag = urldecode(substr($href, strlen($hashtag_prefix))); + } elseif (substr($innertext, 0, 1) == '#') { + $hashtag = $innertext; + } - // get polls - foreach($post->find('div.page_media_poll_wrap') as $div) { - $poll_title = $div->find('.page_media_poll_title', 0)->innertext; - $content_suffix .= "
Poll: $poll_title"; - foreach($div->find('div.page_poll_text') as $poll_stat_title) { - $content_suffix .= '
- ' . $poll_stat_title->innertext; - } - $div->outertext = ''; - } + if ($hashtag) { + $a->outertext = $innertext; + $hashtags[] = $hashtag; + continue; + } - // get sign / post author - $post_author = $pageName; - $author_selectors = array('a.wall_signed_by', 'a.author'); - foreach($author_selectors as $author_selector) { - $a = $post->find($author_selector, 0); - if (is_object($a)) { - $post_author = $a->innertext; - $a->outertext = ''; - break; - } - } + $parsed_url = parse_url($href); - // fix links and get post hashtags - $hashtags = array(); - foreach($post->find('a') as $a) { - $href = $a->getAttribute('href'); - $innertext = $a->innertext; + if (array_key_exists('path', $parsed_url) === false) { + continue; + } - $hashtag_prefix = '/feed?section=search&q=%23'; - $hashtag = null; + if (strpos($parsed_url['path'], '/away.php') === 0) { + parse_str($parsed_url['query'], $parsed_query); + $a->setAttribute('href', iconv( + 'windows-1251', + 'utf-8//ignore', + $parsed_query['to'] + )); + } + } - if ($href && substr($href, 0, strlen($hashtag_prefix)) === $hashtag_prefix) { - $hashtag = urldecode(substr($href, strlen($hashtag_prefix))); - } else if (substr($innertext, 0, 1) == '#') { - $hashtag = $innertext; - } + $copy_quote = $post->find('div.copy_quote', 0); + if (is_object($copy_quote)) { + if ($this->getInput('hide_reposts') === true) { + continue; + } + if ($copy_post_header = $copy_quote->find('div.copy_post_header', 0)) { + $copy_post_header->outertext = ''; + } - if ($hashtag) { - $a->outertext = $innertext; - $hashtags[] = $hashtag; - continue; - } + $second_copy_quote = $copy_quote->find('div.published_sec_quote', 0); + if (is_object($second_copy_quote)) { + $second_copy_quote_author = $second_copy_quote->find('a.copy_author', 0)->outertext; + $second_copy_quote_content = $second_copy_quote->find('div.copy_post_date', 0)->outertext; + $second_copy_quote->outertext = "
Reposted ($second_copy_quote_author): $second_copy_quote_content"; + } + $copy_quote_author = $copy_quote->find('a.copy_author', 0)->outertext; + $copy_quote_content = $copy_quote->innertext; + $copy_quote->outertext = "
Reposted ($copy_quote_author):
$copy_quote_content"; + } - $parsed_url = parse_url($href); + $item = []; + $item['content'] = strip_tags(backgroundToImg($post->find('div.wall_text', 0)->innertext), '
'); + $item['content'] .= $content_suffix; + $item['categories'] = $hashtags; - if (array_key_exists('path', $parsed_url) === false) continue; + // get post link + $post_link = $post->find('a.post_link', 0)->getAttribute('href'); + preg_match('/wall-?\d+_(\d+)/', $post_link, $preg_match_result); + $item['post_id'] = intval($preg_match_result[1]); + $item['uri'] = $post_link; + $item['timestamp'] = $this->getTime($post); + $item['title'] = $this->getTitle($item['content']); + $item['author'] = $post_author; + $item['videos'] = $post_videos; + if ($is_pinned_post) { + // do not append it now + $pinned_post_item = $item; + } else { + $last_post_id = $item['post_id']; + $this->items[] = $item; + } + } - if (strpos($parsed_url['path'], '/away.php') === 0) { - parse_str($parsed_url['query'], $parsed_query); - $a->setAttribute('href', iconv( - 'windows-1251', - 'utf-8//ignore', - $parsed_query['to'] - )); - } - } + if (!is_null($pinned_post_item)) { + if (count($this->items) == 0) { + $this->items[] = $pinned_post_item; + } elseif ($last_post_id < $pinned_post_item['post_id']) { + $this->items[] = $pinned_post_item; + usort($this->items, function ($item1, $item2) { + return $item2['post_id'] - $item1['post_id']; + }); + } + } - $copy_quote = $post->find('div.copy_quote', 0); - if (is_object($copy_quote)) { - if ($this->getInput('hide_reposts') === true) { - continue; - } - if ($copy_post_header = $copy_quote->find('div.copy_post_header', 0)) { - $copy_post_header->outertext = ''; - } + $this->getCleanVideoLinks(); + } - $second_copy_quote = $copy_quote->find('div.published_sec_quote', 0); - if (is_object($second_copy_quote)) { - $second_copy_quote_author = $second_copy_quote->find('a.copy_author', 0)->outertext; - $second_copy_quote_content = $second_copy_quote->find('div.copy_post_date', 0)->outertext; - $second_copy_quote->outertext = "
Reposted ($second_copy_quote_author): $second_copy_quote_content"; - } - $copy_quote_author = $copy_quote->find('a.copy_author', 0)->outertext; - $copy_quote_content = $copy_quote->innertext; - $copy_quote->outertext = "
Reposted ($copy_quote_author):
$copy_quote_content"; - } + private function getPhoto($a) + { + $onclick = $a->getAttribute('onclick'); + preg_match('/return showPhoto\(.+?({.*})/', $onclick, $preg_match_result); + if (count($preg_match_result) == 0) { + return; + } - $item = array(); - $item['content'] = strip_tags(backgroundToImg($post->find('div.wall_text', 0)->innertext), '

'); - $item['content'] .= $content_suffix; - $item['categories'] = $hashtags; + $arg = htmlspecialchars_decode(str_replace('queue:1', '"queue":1', $preg_match_result[1])); + $data = json_decode($arg, true); + if ($data == null) { + return; + } - // get post link - $post_link = $post->find('a.post_link', 0)->getAttribute('href'); - preg_match('/wall-?\d+_(\d+)/', $post_link, $preg_match_result); - $item['post_id'] = intval($preg_match_result[1]); - $item['uri'] = $post_link; - $item['timestamp'] = $this->getTime($post); - $item['title'] = $this->getTitle($item['content']); - $item['author'] = $post_author; - $item['videos'] = $post_videos; - if ($is_pinned_post) { - // do not append it now - $pinned_post_item = $item; - } else { - $last_post_id = $item['post_id']; - $this->items[] = $item; - } + $thumb = $data['temp']['base'] . $data['temp']['x_'][0]; + $original = ''; + foreach (['y_', 'z_', 'w_'] as $key) { + if (!isset($data['temp'][$key])) { + continue; + } + if (!isset($data['temp'][$key][0])) { + continue; + } + if (substr($data['temp'][$key][0], 0, 4) == 'http') { + $base = ''; + } else { + $base = $data['temp']['base']; + } + $original = $base . $data['temp'][$key][0]; + } - } + if ($original) { + return "
"; + } else { + return ""; + } + } - if (!is_null($pinned_post_item)) { - if (count($this->items) == 0) { - $this->items[] = $pinned_post_item; - } else if ($last_post_id < $pinned_post_item['post_id']) { - $this->items[] = $pinned_post_item; - usort($this->items, function ($item1, $item2) { - return $item2['post_id'] - $item1['post_id']; - }); - } - } + private function getTitle($content) + { + preg_match('/^["\w\ \p{L}\(\)\?#«»-]+/mu', htmlspecialchars_decode($content), $result); + if (count($result) == 0) { + return 'untitled'; + } + return $result[0]; + } - $this->getCleanVideoLinks(); - } + private function getTime($post) + { + if ($time = $post->find('span.rel_date', 0)->getAttribute('time')) { + return $time; + } else { + $strdate = $post->find('span.rel_date', 0)->plaintext; + $strdate = preg_replace('/[\x00-\x1F\x7F-\xFF]/', ' ', $strdate); - private function getPhoto($a) { - $onclick = $a->getAttribute('onclick'); - preg_match('/return showPhoto\(.+?({.*})/', $onclick, $preg_match_result); - if (count($preg_match_result) == 0) return; + $date = date_parse($strdate); + if (!$date['year']) { + if (strstr($strdate, 'today') !== false) { + $strdate = date('d-m-Y') . ' ' . $strdate; + } elseif (strstr($strdate, 'yesterday ') !== false) { + $time = time() - 60 * 60 * 24; + $strdate = date('d-m-Y', $time) . ' ' . $strdate; + } elseif ($date['month'] && intval(date('m')) < $date['month']) { + $strdate = $strdate . ' ' . (date('Y') - 1); + } else { + $strdate = $strdate . ' ' . date('Y'); + } - $arg = htmlspecialchars_decode( str_replace('queue:1', '"queue":1', $preg_match_result[1]) ); - $data = json_decode($arg, true); - if ($data == null) return; + $date = date_parse($strdate); + } elseif ($date['hour'] === false) { + $date['hour'] = $date['minute'] = '00'; + } + return strtotime($date['day'] . '-' . $date['month'] . '-' . $date['year'] . ' ' . + $date['hour'] . ':' . $date['minute']); + } + } - $thumb = $data['temp']['base'] . $data['temp']['x_'][0]; - $original = ''; - foreach(array('y_', 'z_', 'w_') as $key) { - if (!isset($data['temp'][$key])) continue; - if (!isset($data['temp'][$key][0])) continue; - if (substr($data['temp'][$key][0], 0, 4) == 'http') { - $base = ''; - } else { - $base = $data['temp']['base']; - } - $original = $base . $data['temp'][$key][0]; - } + private function getContents() + { + $header = ['Accept-language: en', 'Cookie: remixlang=3']; - if ($original) { - return ""; - } else { - return ""; - } - } + return getContents($this->getURI(), $header); + } - private function getTitle($content) - { - preg_match('/^["\w\ \p{L}\(\)\?#«»-]+/mu', htmlspecialchars_decode($content), $result); - if (count($result) == 0) return 'untitled'; - return $result[0]; - } + protected function appendVideo($video_title, $video_link, &$content_suffix, array &$post_videos) + { + if (!$video_title) { + $video_title = '(empty)'; + } - private function getTime($post) - { - if ($time = $post->find('span.rel_date', 0)->getAttribute('time')) { - return $time; - } else { - $strdate = $post->find('span.rel_date', 0)->plaintext; - $strdate = preg_replace('/[\x00-\x1F\x7F-\xFF]/', ' ', $strdate); + preg_match('/video([0-9-]+_[0-9]+)/', $video_link, $preg_match_result); - $date = date_parse($strdate); - if (!$date['year']) { - if (strstr($strdate, 'today') !== false) { - $strdate = date('d-m-Y') . ' ' . $strdate; - } elseif (strstr($strdate, 'yesterday ') !== false) { - $time = time() - 60 * 60 * 24; - $strdate = date('d-m-Y', $time) . ' ' . $strdate; - } elseif ($date['month'] && intval(date('m')) < $date['month']) { - $strdate = $strdate . ' ' . (date('Y') - 1); - } else { - $strdate = $strdate . ' ' . date('Y'); - } + if (count($preg_match_result) > 1) { + $video_id = $preg_match_result[1]; + $this->videos[ $video_id ] = [ + 'url' => $video_link, + 'title' => $video_title, + ]; + $post_videos[] = $video_id; + } else { + $content_suffix .= '
Video: ' . $video_title . ''; + } + } - $date = date_parse($strdate); - } elseif ($date['hour'] === false) { - $date['hour'] = $date['minute'] = '00'; - } - return strtotime($date['day'] . '-' . $date['month'] . '-' . $date['year'] . ' ' . - $date['hour'] . ':' . $date['minute']); - } + protected function getCleanVideoLinks() + { + $result = $this->api('video.get', [ + 'videos' => implode(',', array_keys($this->videos)), + 'count' => 200 + ]); - } + if (!isset($result['error'])) { + foreach ($result['response']['items'] as $item) { + $video_id = strval($item['owner_id']) . '_' . strval($item['id']); + $this->videos[$video_id]['url'] = $item['player']; + } + } - private function getContents() - { - $header = array('Accept-language: en', 'Cookie: remixlang=3'); + foreach ($this->items as &$item) { + foreach ($item['videos'] as $video_id) { + $video_link = $this->videos[$video_id]['url']; + $video_title = $this->videos[$video_id]['title']; + $item['content'] .= '
Video: ' . $video_title . ''; + } + unset($item['videos']); + } + } - return getContents($this->getURI(), $header); - } - - protected function appendVideo($video_title, $video_link, &$content_suffix, array &$post_videos) - { - if (!$video_title) $video_title = '(empty)'; - - preg_match('/video([0-9-]+_[0-9]+)/', $video_link, $preg_match_result); - - if (count($preg_match_result) > 1) { - $video_id = $preg_match_result[1]; - $this->videos[ $video_id ] = array( - 'url' => $video_link, - 'title' => $video_title, - ); - $post_videos[] = $video_id; - } else { - $content_suffix .= '
Video: ' . $video_title . ''; - } - } - - protected function getCleanVideoLinks() { - $result = $this->api('video.get', array( - 'videos' => implode(',', array_keys($this->videos)), - 'count' => 200 - )); - - if (!isset($result['error'])) { - foreach($result['response']['items'] as $item) { - $video_id = strval($item['owner_id']) . '_' . strval($item['id']); - $this->videos[$video_id]['url'] = $item['player']; - } - } - - foreach($this->items as &$item) { - foreach($item['videos'] as $video_id) { - $video_link = $this->videos[$video_id]['url']; - $video_title = $this->videos[$video_id]['title']; - $item['content'] .= '
Video: ' . $video_title . ''; - } - unset($item['videos']); - } - } - - protected function api($method, array $params) - { - $params['v'] = '5.80'; - $params['access_token'] = $this->getAccessToken(); - return json_decode( getContents('https://api.vk.com/method/' . $method . '?' . http_build_query($params)), true ); - } + protected function api($method, array $params) + { + $params['v'] = '5.80'; + $params['access_token'] = $this->getAccessToken(); + return json_decode(getContents('https://api.vk.com/method/' . $method . '?' . http_build_query($params)), true); + } } diff --git a/bridges/WallmineNewsBridge.php b/bridges/WallmineNewsBridge.php index f21627a0..c5009172 100644 --- a/bridges/WallmineNewsBridge.php +++ b/bridges/WallmineNewsBridge.php @@ -1,48 +1,50 @@ getURI() . '/news/'); + const CACHE_TIMEOUT = 900; // 15 mins - $html = defaultLinkTo($html, self::URI); + public function collectData() + { + $html = getSimpleHTMLDOM($this->getURI() . '/news/'); - foreach($html->find('div.container.news-card') as $div) { - $item = array(); - $item['uri'] = $div->find('a', 0)->href; + $html = defaultLinkTo($html, self::URI); - $image = $div->find('img.img-fluid', 0)->src; + foreach ($html->find('div.container.news-card') as $div) { + $item = []; + $item['uri'] = $div->find('a', 0)->href; - $page = getSimpleHTMLDOMCached($item['uri'], 7200); + $image = $div->find('img.img-fluid', 0)->src; - $article = $page->find('div.container.article-container', 0); + $page = getSimpleHTMLDOMCached($item['uri'], 7200); - $item['title'] = $article->find('h1', 0)->plaintext; + $article = $page->find('div.container.article-container', 0); - $article->find('p.published-on', 0)->children(0)->outertext = ''; - $article->find('p.published-on', 0)->children(1)->outertext = ''; - $date = str_replace('at', '', $article->find('p.published-on', 0)->innertext); + $item['title'] = $article->find('h1', 0)->plaintext; - $item['timestamp'] = $date; + $article->find('p.published-on', 0)->children(0)->outertext = ''; + $article->find('p.published-on', 0)->children(1)->outertext = ''; + $date = str_replace('at', '', $article->find('p.published-on', 0)->innertext); - $article->find('h1', 0)->outertext = ''; - $article->find('p.published-on', 0)->outertext = ''; + $item['timestamp'] = $date; - $item['content'] = $article->innertext; - $item['enclosures'][] = $image; + $article->find('h1', 0)->outertext = ''; + $article->find('p.published-on', 0)->outertext = ''; - $this->items[] = $item; + $item['content'] = $article->innertext; + $item['enclosures'][] = $image; - if (count($this->items) >= 10) { - break; - } - } + $this->items[] = $item; - } + if (count($this->items) >= 10) { + break; + } + } + } } diff --git a/bridges/WallpaperflareBridge.php b/bridges/WallpaperflareBridge.php index 60486368..907288d0 100644 --- a/bridges/WallpaperflareBridge.php +++ b/bridges/WallpaperflareBridge.php @@ -1,41 +1,46 @@ array( - 'search' => array( - 'name' => 'Search', - 'exampleValue' => 'birds', - 'required' => true - ) - )); - const CACHE_TIMEOUT = 3600; //1 hour - const XPATH_EXPRESSION_ITEM = './/figure'; - const XPATH_EXPRESSION_ITEM_TITLE = './/img/@title'; - const XPATH_EXPRESSION_ITEM_CONTENT = ''; - const XPATH_EXPRESSION_ITEM_URI = './/a[@itemprop="url"]/@href'; - const XPATH_EXPRESSION_ITEM_AUTHOR = '/html[1]/body[1]/main[1]/section[1]/h1[1]'; - const XPATH_EXPRESSION_ITEM_TIMESTAMP = ''; - const XPATH_EXPRESSION_ITEM_ENCLOSURES = './/img/@data-src'; - const XPATH_EXPRESSION_ITEM_CATEGORIES = './/figcaption[@itemprop="caption description"]'; - const SETTING_FIX_ENCODING = false; - protected function getSourceUrl(){ - return 'https://www.wallpaperflare.com/search?wallpaper=' . $this->getInput('search'); - } +class WallpaperflareBridge extends XPathAbstract +{ + const NAME = 'Wallpaperflare'; + const URI = 'https://wallpaperflare.com'; + const DESCRIPTION = 'Wallpaperflare is a provider for Wallpapers on nearly every topic, especially for Anime'; + const MAINTAINER = 'dhuschde'; + const PARAMETERS = [ + '' => [ + 'search' => [ + 'name' => 'Search', + 'exampleValue' => 'birds', + 'required' => true + ] + ]]; + const CACHE_TIMEOUT = 3600; //1 hour + const XPATH_EXPRESSION_ITEM = './/figure'; + const XPATH_EXPRESSION_ITEM_TITLE = './/img/@title'; + const XPATH_EXPRESSION_ITEM_CONTENT = ''; + const XPATH_EXPRESSION_ITEM_URI = './/a[@itemprop="url"]/@href'; + const XPATH_EXPRESSION_ITEM_AUTHOR = '/html[1]/body[1]/main[1]/section[1]/h1[1]'; + const XPATH_EXPRESSION_ITEM_TIMESTAMP = ''; + const XPATH_EXPRESSION_ITEM_ENCLOSURES = './/img/@data-src'; + const XPATH_EXPRESSION_ITEM_CATEGORIES = './/figcaption[@itemprop="caption description"]'; + const SETTING_FIX_ENCODING = false; - public function getIcon() { - return 'https://www.google.com/s2/favicons?domain=wallpaperflare.com/'; - } + protected function getSourceUrl() + { + return 'https://www.wallpaperflare.com/search?wallpaper=' . $this->getInput('search'); + } - public function getName() { - if(!is_null($this->getInput('search'))) { - return 'Wallpaperflare - ' . $this->getInput('search'); - } else { - return 'Wallpaperflare'; - } - } + public function getIcon() + { + return 'https://www.google.com/s2/favicons?domain=wallpaperflare.com/'; + } + + public function getName() + { + if (!is_null($this->getInput('search'))) { + return 'Wallpaperflare - ' . $this->getInput('search'); + } else { + return 'Wallpaperflare'; + } + } } diff --git a/bridges/WeLiveSecurityBridge.php b/bridges/WeLiveSecurityBridge.php index 14af1ab3..6434a13a 100644 --- a/bridges/WeLiveSecurityBridge.php +++ b/bridges/WeLiveSecurityBridge.php @@ -1,38 +1,41 @@ self::LIMIT, - ], - ]; +class WeLiveSecurityBridge extends FeedExpander +{ + const MAINTAINER = 'ORelio'; + const NAME = 'We Live Security'; + const URI = 'https://www.welivesecurity.com/'; + const DESCRIPTION = 'Returns the newest articles.'; + const PARAMETERS = [ + [ + 'limit' => self::LIMIT, + ], + ]; - protected function parseItem($item){ - $item = parent::parseItem($item); + protected function parseItem($item) + { + $item = parent::parseItem($item); - $article_html = getSimpleHTMLDOMCached($item['uri']); - if(!$article_html) { - $item['content'] .= '

Could not request ' . $this->getName() . ': ' . $item['uri'] . '

'; - return $item; - } + $article_html = getSimpleHTMLDOMCached($item['uri']); + if (!$article_html) { + $item['content'] .= '

Could not request ' . $this->getName() . ': ' . $item['uri'] . '

'; + return $item; + } - $article_content = $article_html->find('div.formatted', 0)->innertext; - $article_content = stripWithDelimiters($article_content, ''); - $article_content = stripRecursiveHTMLSection($article_content, 'div', '
getInput('limit') ?? 10; - $this->collectExpandableDatas($feed, $limit); - } + public function collectData() + { + $feed = static::URI . 'feed/'; + $limit = $this->getInput('limit') ?? 10; + $this->collectExpandableDatas($feed, $limit); + } } diff --git a/bridges/WebfailBridge.php b/bridges/WebfailBridge.php index fefd539a..e55988da 100644 --- a/bridges/WebfailBridge.php +++ b/bridges/WebfailBridge.php @@ -1,156 +1,169 @@ array( - 'language' => array( - 'name' => 'Language', - 'type' => 'list', - 'title' => 'Select your language', - 'values' => array( - 'English' => 'en', - 'German' => 'de' - ), - 'defaultValue' => 'English' - ), - 'type' => array( - 'name' => 'Type', - 'type' => 'list', - 'title' => 'Select your content type', - 'values' => array( - 'None' => '/', - 'Facebook' => '/ffdts', - 'Images' => '/images', - 'Videos' => '/videos', - 'Gifs' => '/gifs' - ), - 'defaultValue' => 'None' - ) - ) - ); - public function getURI(){ - if(is_null($this->getInput('language'))) - return parent::getURI(); +class WebfailBridge extends BridgeAbstract +{ + const MAINTAINER = 'logmanoriginal'; + const URI = 'https://webfail.com'; + const NAME = 'Webfail'; + const DESCRIPTION = 'Returns the latest fails'; + const PARAMETERS = [ + 'By content type' => [ + 'language' => [ + 'name' => 'Language', + 'type' => 'list', + 'title' => 'Select your language', + 'values' => [ + 'English' => 'en', + 'German' => 'de' + ], + 'defaultValue' => 'English' + ], + 'type' => [ + 'name' => 'Type', + 'type' => 'list', + 'title' => 'Select your content type', + 'values' => [ + 'None' => '/', + 'Facebook' => '/ffdts', + 'Images' => '/images', + 'Videos' => '/videos', + 'Gifs' => '/gifs' + ], + 'defaultValue' => 'None' + ] + ] + ]; - // e.g.: https://en.webfail.com - return 'https://' . $this->getInput('language') . '.webfail.com'; - } + public function getURI() + { + if (is_null($this->getInput('language'))) { + return parent::getURI(); + } - public function collectData(){ - $html = getSimpleHTMLDOM($this->getURI() . $this->getInput('type')); + // e.g.: https://en.webfail.com + return 'https://' . $this->getInput('language') . '.webfail.com'; + } - $type = array_search($this->getInput('type'), - self::PARAMETERS[$this->queriedContext]['type']['values']); + public function collectData() + { + $html = getSimpleHTMLDOM($this->getURI() . $this->getInput('type')); - switch(strtolower($type)) { - case 'facebook': - case 'videos': - $this->extractNews($html, $type); - break; - case 'none': - case 'images': - case 'gifs': - $this->extractArticle($html); - break; - default: returnClientError('Unknown type: ' . $type); - } - } + $type = array_search( + $this->getInput('type'), + self::PARAMETERS[$this->queriedContext]['type']['values'] + ); - private function extractNews($html, $type){ - $news = $html->find('#main', 0)->find('a.wf-list-news'); - foreach($news as $element) { - $item = array(); - $item['title'] = $this->fixTitle($element->find('div.wf-news-title', 0)->innertext); - $item['uri'] = $this->getURI() . $element->href; + switch (strtolower($type)) { + case 'facebook': + case 'videos': + $this->extractNews($html, $type); + break; + case 'none': + case 'images': + case 'gifs': + $this->extractArticle($html); + break; + default: + returnClientError('Unknown type: ' . $type); + } + } - $img = $element->find('img.wf-image', 0)->src; - // Load high resolution image for 'facebook' - switch(strtolower($type)) { - case 'facebook': - $img = $this->getImageHiResUri($item['uri']); - break; - default: - } + private function extractNews($html, $type) + { + $news = $html->find('#main', 0)->find('a.wf-list-news'); + foreach ($news as $element) { + $item = []; + $item['title'] = $this->fixTitle($element->find('div.wf-news-title', 0)->innertext); + $item['uri'] = $this->getURI() . $element->href; - $description = ''; - if(!is_null($element->find('div.wf-news-description', 0))) { - $description = $element->find('div.wf-news-description', 0)->innertext; - } + $img = $element->find('img.wf-image', 0)->src; + // Load high resolution image for 'facebook' + switch (strtolower($type)) { + case 'facebook': + $img = $this->getImageHiResUri($item['uri']); + break; + default: + } - $infoElement = $element->find('div.wf-small', 0); - if (!is_null($infoElement)) { - if (preg_match('/(\d{2}\.\d{2}\.\d{4})/m', $infoElement->innertext, $matches) === 1 && count($matches) == 2) { - $dt = DateTime::createFromFormat('!d.m.Y', $matches[1]); - if ($dt !== false) { - $item['timestamp'] = $dt->getTimestamp(); - } - } - } + $description = ''; + if (!is_null($element->find('div.wf-news-description', 0))) { + $description = $element->find('div.wf-news-description', 0)->innertext; + } - $item['content'] = '

' - . $description - . '


'; + $infoElement = $element->find('div.wf-small', 0); + if (!is_null($infoElement)) { + if (preg_match('/(\d{2}\.\d{2}\.\d{4})/m', $infoElement->innertext, $matches) === 1 && count($matches) == 2) { + $dt = DateTime::createFromFormat('!d.m.Y', $matches[1]); + if ($dt !== false) { + $item['timestamp'] = $dt->getTimestamp(); + } + } + } - $this->items[] = $item; - } - } + $item['content'] = '

' + . $description + . '


'; - private function extractArticle($html){ - $articles = $html->find('article'); - foreach($articles as $article) { - $item = array(); - $item['title'] = $this->fixTitle($article->find('a', 1)->innertext); + $this->items[] = $item; + } + } - // Images, videos and gifs are provided in their own unique way - if(!is_null($article->find('img.wf-image', 0))) { // Image type - $item['uri'] = $this->getURI() . $article->find('a', 2)->href; - $item['content'] = ''; - } elseif(!is_null($article->find('div.wf-video', 0))) { // Video type - $videoId = $this->getVideoId($article->find('div.wf-play', 0)->onclick); - $item['uri'] = 'https://youtube.com/watch?v=' . $videoId; - $item['content'] = ''; - } elseif(!is_null($article->find('video[id*=gif-]', 0))) { // Gif type - $item['uri'] = $this->getURI() . $article->find('a', 2)->href; - $item['content'] = ''; - } + private function extractArticle($html) + { + $articles = $html->find('article'); + foreach ($articles as $article) { + $item = []; + $item['title'] = $this->fixTitle($article->find('a', 1)->innertext); - $this->items[] = $item; - } - } + // Images, videos and gifs are provided in their own unique way + if (!is_null($article->find('img.wf-image', 0))) { // Image type + $item['uri'] = $this->getURI() . $article->find('a', 2)->href; + $item['content'] = ''; + } elseif (!is_null($article->find('div.wf-video', 0))) { // Video type + $videoId = $this->getVideoId($article->find('div.wf-play', 0)->onclick); + $item['uri'] = 'https://youtube.com/watch?v=' . $videoId; + $item['content'] = ''; + } elseif (!is_null($article->find('video[id*=gif-]', 0))) { // Gif type + $item['uri'] = $this->getURI() . $article->find('a', 2)->href; + $item['content'] = ''; + } - private function fixTitle($title){ - // This fixes titles that include umlauts (in German language) - return html_entity_decode($title, ENT_QUOTES | ENT_HTML401, 'UTF-8'); - } + $this->items[] = $item; + } + } - private function getVideoId($onclick){ - return substr($onclick, 21, 11); - } + private function fixTitle($title) + { + // This fixes titles that include umlauts (in German language) + return html_entity_decode($title, ENT_QUOTES | ENT_HTML401, 'UTF-8'); + } - private function getImageHiResUri($url){ - // https://de.webfail.com/ef524fae509?tag=ffdt - // http://cdn.webfail.com/upl/img/ef524fae509/post2.jpg - $id = substr($url, strrpos($url, '/') + 1, strlen($url) - strrpos($url, '?') + 2); - return 'http://cdn.webfail.com/upl/img/' . $id . '/post2.jpg'; - } + private function getVideoId($onclick) + { + return substr($onclick, 21, 11); + } + + private function getImageHiResUri($url) + { + // https://de.webfail.com/ef524fae509?tag=ffdt + // http://cdn.webfail.com/upl/img/ef524fae509/post2.jpg + $id = substr($url, strrpos($url, '/') + 1, strlen($url) - strrpos($url, '?') + 2); + return 'http://cdn.webfail.com/upl/img/' . $id . '/post2.jpg'; + } } diff --git a/bridges/WikiLeaksBridge.php b/bridges/WikiLeaksBridge.php index cf44b066..512b1c30 100644 --- a/bridges/WikiLeaksBridge.php +++ b/bridges/WikiLeaksBridge.php @@ -1,127 +1,134 @@ array( - 'name' => 'Category', - 'type' => 'list', - 'title' => 'Select your category', - 'values' => array( - 'News' => '-News-', - 'Leaks' => array( - 'All' => '-Leaks-', - 'Intelligence' => '+-Intelligence-+', - 'Global Economy' => '+-Global-Economy-+', - 'International Politics' => '+-International-Politics-+', - 'Corporations' => '+-Corporations-+', - 'Government' => '+-Government-+', - 'War & Military' => '+-War-Military-+' - ) - ), - 'defaultValue' => 'news' - ), - 'teaser' => array( - 'name' => 'Show teaser', - 'type' => 'checkbox', - 'title' => 'If checked feeds will display the teaser', - 'defaultValue' => 'checked' - ) - ) - ); - public function collectData(){ - $html = getSimpleHTMLDOM($this->getURI()); +class WikiLeaksBridge extends BridgeAbstract +{ + const NAME = 'WikiLeaks'; + const URI = 'https://wikileaks.org'; + const DESCRIPTION = 'Returns the latest news or articles from WikiLeaks'; + const MAINTAINER = 'logmanoriginal'; + const PARAMETERS = [ + [ + 'category' => [ + 'name' => 'Category', + 'type' => 'list', + 'title' => 'Select your category', + 'values' => [ + 'News' => '-News-', + 'Leaks' => [ + 'All' => '-Leaks-', + 'Intelligence' => '+-Intelligence-+', + 'Global Economy' => '+-Global-Economy-+', + 'International Politics' => '+-International-Politics-+', + 'Corporations' => '+-Corporations-+', + 'Government' => '+-Government-+', + 'War & Military' => '+-War-Military-+' + ] + ], + 'defaultValue' => 'news' + ], + 'teaser' => [ + 'name' => 'Show teaser', + 'type' => 'checkbox', + 'title' => 'If checked feeds will display the teaser', + 'defaultValue' => 'checked' + ] + ] + ]; - // News are presented differently - switch($this->getInput('category')) { - case '-News-': - $this->loadNewsItems($html); - break; - default: - $this->loadLeakItems($html); - } - } + public function collectData() + { + $html = getSimpleHTMLDOM($this->getURI()); - public function getURI(){ - if(!is_null($this->getInput('category'))) { - return static::URI . '/' . $this->getInput('category') . '.html'; - } + // News are presented differently + switch ($this->getInput('category')) { + case '-News-': + $this->loadNewsItems($html); + break; + default: + $this->loadLeakItems($html); + } + } - return parent::getURI(); - } + public function getURI() + { + if (!is_null($this->getInput('category'))) { + return static::URI . '/' . $this->getInput('category') . '.html'; + } - public function getName(){ - if(!is_null($this->getInput('category'))) { - $category = array_search( - $this->getInput('category'), - static::PARAMETERS[0]['category']['values'] - ); + return parent::getURI(); + } - if($category === false) { - $category = array_search( - $this->getInput('category'), - static::PARAMETERS[0]['category']['values']['Leaks'] - ); - } + public function getName() + { + if (!is_null($this->getInput('category'))) { + $category = array_search( + $this->getInput('category'), + static::PARAMETERS[0]['category']['values'] + ); - return $category . ' - ' . static::NAME; - } + if ($category === false) { + $category = array_search( + $this->getInput('category'), + static::PARAMETERS[0]['category']['values']['Leaks'] + ); + } - return parent::getName(); - } + return $category . ' - ' . static::NAME; + } - private function loadNewsItems($html){ - $articles = $html->find('div.news-articles ul li'); + return parent::getName(); + } - if(is_null($articles) || count($articles) === 0) { - return; - } + private function loadNewsItems($html) + { + $articles = $html->find('div.news-articles ul li'); - foreach($articles as $article) { - $item = array(); + if (is_null($articles) || count($articles) === 0) { + return; + } - $item['title'] = $article->find('h3', 0)->plaintext; - $item['uri'] = static::URI . $article->find('h3 a', 0)->href; - $item['content'] = $article->find('div.introduction', 0)->plaintext; - $item['timestamp'] = strtotime($article->find('div.timestamp', 0)->plaintext); + foreach ($articles as $article) { + $item = []; - $this->items[] = $item; - } - } + $item['title'] = $article->find('h3', 0)->plaintext; + $item['uri'] = static::URI . $article->find('h3 a', 0)->href; + $item['content'] = $article->find('div.introduction', 0)->plaintext; + $item['timestamp'] = strtotime($article->find('div.timestamp', 0)->plaintext); - private function loadLeakItems($html){ - $articles = $html->find('li.tile'); + $this->items[] = $item; + } + } - if(is_null($articles) || count($articles) === 0) { - return; - } + private function loadLeakItems($html) + { + $articles = $html->find('li.tile'); - foreach($articles as $article) { - $item = array(); + if (is_null($articles) || count($articles) === 0) { + return; + } - $item['title'] = $article->find('h2', 0)->plaintext; - $item['uri'] = static::URI . $article->find('a', 0)->href; + foreach ($articles as $article) { + $item = []; - $teaser = static::URI . '/' . $article->find('div.teaser img', 0)->src; + $item['title'] = $article->find('h2', 0)->plaintext; + $item['uri'] = static::URI . $article->find('a', 0)->href; - if($this->getInput('teaser')) { - $item['content'] = '

' - . $article->find('div.intro', 0)->plaintext - . '

'; - } else { - $item['content'] = $article->find('div.intro', 0)->plaintext; - } + $teaser = static::URI . '/' . $article->find('div.teaser img', 0)->src; - $item['timestamp'] = strtotime($article->find('div.timestamp', 0)->plaintext); - $item['enclosures'] = array($teaser); + if ($this->getInput('teaser')) { + $item['content'] = '

' + . $article->find('div.intro', 0)->plaintext + . '

'; + } else { + $item['content'] = $article->find('div.intro', 0)->plaintext; + } - $this->items[] = $item; - } - } + $item['timestamp'] = strtotime($article->find('div.timestamp', 0)->plaintext); + $item['enclosures'] = [$teaser]; + + $this->items[] = $item; + } + } } diff --git a/bridges/WikipediaBridge.php b/bridges/WikipediaBridge.php index 1bdf2ddc..30e551ed 100644 --- a/bridges/WikipediaBridge.php +++ b/bridges/WikipediaBridge.php @@ -3,324 +3,347 @@ define('WIKIPEDIA_SUBJECT_TFA', 0); // Today's featured article define('WIKIPEDIA_SUBJECT_DYK', 1); // Did you know... -class WikipediaBridge extends BridgeAbstract { - const MAINTAINER = 'logmanoriginal'; - const NAME = 'Wikipedia bridge for many languages'; - const URI = 'https://www.wikipedia.org/'; - const DESCRIPTION = 'Returns articles for a language of your choice'; +class WikipediaBridge extends BridgeAbstract +{ + const MAINTAINER = 'logmanoriginal'; + const NAME = 'Wikipedia bridge for many languages'; + const URI = 'https://www.wikipedia.org/'; + const DESCRIPTION = 'Returns articles for a language of your choice'; - const PARAMETERS = array( array( - 'language' => array( - 'name' => 'Language', - 'type' => 'list', - 'title' => 'Select your language', - 'exampleValue' => 'English', - 'values' => array( - 'English' => 'en', - 'Русский' => 'ru', - 'Dutch' => 'nl', - 'Esperanto' => 'eo', - 'French' => 'fr', - 'German' => 'de', - ) - ), - 'subject' => array( - 'name' => 'Subject', - 'type' => 'list', - 'title' => 'What subject are you interested in?', - 'exampleValue' => 'Today\'s featured article', - 'values' => array( - 'Today\'s featured article' => 'tfa', - 'Did you know…' => 'dyk' - ) - ), - 'fullarticle' => array( - 'name' => 'Load full article', - 'type' => 'checkbox', - 'title' => 'Activate to always load the full article' - ) - )); + const PARAMETERS = [ [ + 'language' => [ + 'name' => 'Language', + 'type' => 'list', + 'title' => 'Select your language', + 'exampleValue' => 'English', + 'values' => [ + 'English' => 'en', + 'Русский' => 'ru', + 'Dutch' => 'nl', + 'Esperanto' => 'eo', + 'French' => 'fr', + 'German' => 'de', + ] + ], + 'subject' => [ + 'name' => 'Subject', + 'type' => 'list', + 'title' => 'What subject are you interested in?', + 'exampleValue' => 'Today\'s featured article', + 'values' => [ + 'Today\'s featured article' => 'tfa', + 'Did you know…' => 'dyk' + ] + ], + 'fullarticle' => [ + 'name' => 'Load full article', + 'type' => 'checkbox', + 'title' => 'Activate to always load the full article' + ] + ]]; - public function getURI(){ - if(!is_null($this->getInput('language'))) { - return 'https://' - . strtolower($this->getInput('language')) - . '.wikipedia.org'; - } + public function getURI() + { + if (!is_null($this->getInput('language'))) { + return 'https://' + . strtolower($this->getInput('language')) + . '.wikipedia.org'; + } - return parent::getURI(); - } + return parent::getURI(); + } - public function getName(){ - switch($this->getInput('subject')) { - case 'tfa': - $subject = WIKIPEDIA_SUBJECT_TFA; - break; - case 'dyk': - $subject = WIKIPEDIA_SUBJECT_DYK; - break; - default: return parent::getName(); - } + public function getName() + { + switch ($this->getInput('subject')) { + case 'tfa': + $subject = WIKIPEDIA_SUBJECT_TFA; + break; + case 'dyk': + $subject = WIKIPEDIA_SUBJECT_DYK; + break; + default: + return parent::getName(); + } - switch($subject) { - case WIKIPEDIA_SUBJECT_TFA: - $name = 'Today\'s featured article from ' - . strtolower($this->getInput('language')) - . '.wikipedia.org'; - break; - case WIKIPEDIA_SUBJECT_DYK: - $name = 'Did you know? - articles from ' - . strtolower($this->getInput('language')) - . '.wikipedia.org'; - break; - default: - $name = 'Articles from ' - . strtolower($this->getInput('language')) - . '.wikipedia.org'; - break; - } - return $name; - } + switch ($subject) { + case WIKIPEDIA_SUBJECT_TFA: + $name = 'Today\'s featured article from ' + . strtolower($this->getInput('language')) + . '.wikipedia.org'; + break; + case WIKIPEDIA_SUBJECT_DYK: + $name = 'Did you know? - articles from ' + . strtolower($this->getInput('language')) + . '.wikipedia.org'; + break; + default: + $name = 'Articles from ' + . strtolower($this->getInput('language')) + . '.wikipedia.org'; + break; + } + return $name; + } - public function collectData(){ + public function collectData() + { + switch ($this->getInput('subject')) { + case 'tfa': + $subject = WIKIPEDIA_SUBJECT_TFA; + break; + case 'dyk': + $subject = WIKIPEDIA_SUBJECT_DYK; + break; + default: + $subject = WIKIPEDIA_SUBJECT_TFA; + break; + } - switch($this->getInput('subject')) { - case 'tfa': - $subject = WIKIPEDIA_SUBJECT_TFA; - break; - case 'dyk': - $subject = WIKIPEDIA_SUBJECT_DYK; - break; - default: - $subject = WIKIPEDIA_SUBJECT_TFA; - break; - } + $fullArticle = $this->getInput('fullarticle'); - $fullArticle = $this->getInput('fullarticle'); + // This will automatically send us to the correct main page in any language (try it!) + $html = getSimpleHTMLDOM($this->getURI() . '/wiki'); - // This will automatically send us to the correct main page in any language (try it!) - $html = getSimpleHTMLDOM($this->getURI() . '/wiki'); + if (!$html) { + returnServerError('Could not load site: ' . $this->getURI() . '!'); + } - if(!$html) - returnServerError('Could not load site: ' . $this->getURI() . '!'); + /* + * Now read content depending on the language (make sure to create one function per language!) + * We build the function name automatically, just make sure you create a private function ending + * with your desired language code, where the language code is upper case! (en -> getContentsEN). + */ + $function = 'getContents' . ucfirst(strtolower($this->getInput('language'))); - /* - * Now read content depending on the language (make sure to create one function per language!) - * We build the function name automatically, just make sure you create a private function ending - * with your desired language code, where the language code is upper case! (en -> getContentsEN). - */ - $function = 'getContents' . ucfirst(strtolower($this->getInput('language'))); + if (!method_exists($this, $function)) { + returnServerError('A function to get the contents for your language is missing (\'' . $function . '\')!'); + } - if(!method_exists($this, $function)) - returnServerError('A function to get the contents for your language is missing (\'' . $function . '\')!'); + /* + * The method takes care of creating all items. + */ + $this->$function($html, $subject, $fullArticle); + } - /* - * The method takes care of creating all items. - */ - $this->$function($html, $subject, $fullArticle); - } + /** + * Replaces all relative URIs with absolute ones + * @param $element A simplehtmldom element + * @return The $element->innertext with all URIs replaced + */ + private function replaceUriInHtmlElement($element) + { + return str_replace('href="/', 'href="' . $this->getURI() . '/', $element->innertext); + } - /** - * Replaces all relative URIs with absolute ones - * @param $element A simplehtmldom element - * @return The $element->innertext with all URIs replaced - */ - private function replaceUriInHtmlElement($element){ - return str_replace('href="/', 'href="' . $this->getURI() . '/', $element->innertext); - } + /* + * Adds a new item to $items using a generic operation (should work for most + * (all?) wikis) $anchorText can be specified if the wiki in question doesn't + * use '...' (like Dutch, French and Italian) $anchorFallbackIndex can be + * used to specify a different fallback link than the first + * (e.g., -1 for the last) + */ + private function addTodaysFeaturedArticleGeneric( + $element, + $fullArticle, + $anchorText = '...', + $anchorFallbackIndex = 0 + ) { + // Clean the bottom of the featured article + if ($element->find('ul', -1)) { + $element->find('ul', -1)->outertext = ''; + } elseif ($element->find('div', -1)) { + $element->find('div', -1)->outertext = ''; + } - /* - * Adds a new item to $items using a generic operation (should work for most - * (all?) wikis) $anchorText can be specified if the wiki in question doesn't - * use '...' (like Dutch, French and Italian) $anchorFallbackIndex can be - * used to specify a different fallback link than the first - * (e.g., -1 for the last) - */ - private function addTodaysFeaturedArticleGeneric($element, - $fullArticle, - $anchorText = '...', - $anchorFallbackIndex = 0){ - // Clean the bottom of the featured article - if ($element->find('ul', -1)) - $element->find('ul', -1)->outertext = ''; - elseif ($element->find('div', -1)) { - $element->find('div', -1)->outertext = ''; - } + // The title and URI of the article can be found in an anchor containing + // the string '...' in most wikis ('full article ...') + $target = $element->find('p a', $anchorFallbackIndex); + foreach ($element->find('//a') as $anchor) { + if (strpos($anchor->innertext, $anchorText) !== false) { + $target = $anchor; + break; + } + } - // The title and URI of the article can be found in an anchor containing - // the string '...' in most wikis ('full article ...') - $target = $element->find('p a', $anchorFallbackIndex); - foreach($element->find('//a') as $anchor) { - if(strpos($anchor->innertext, $anchorText) !== false) { - $target = $anchor; - break; - } - } + $item = []; + $item['uri'] = $this->getURI() . $target->href; + $item['title'] = $target->title; - $item = array(); - $item['uri'] = $this->getURI() . $target->href; - $item['title'] = $target->title; + if (!$fullArticle) { + $item['content'] = strip_tags($this->replaceUriInHtmlElement($element), '


'); + } else { + $item['content'] = $this->loadFullArticle($item['uri']); + } - if(!$fullArticle) - $item['content'] = strip_tags($this->replaceUriInHtmlElement($element), '


'); - else - $item['content'] = $this->loadFullArticle($item['uri']); + $this->items[] = $item; + } - $this->items[] = $item; - } + /* + * Adds a new item to $items using a generic operation (should work for most (all?) wikis) + */ + private function addDidYouKnowGeneric($element, $fullArticle) + { + foreach ($element->find('ul', 0)->find('li') as $entry) { + $item = []; - /* - * Adds a new item to $items using a generic operation (should work for most (all?) wikis) - */ - private function addDidYouKnowGeneric($element, $fullArticle){ - foreach($element->find('ul', 0)->find('li') as $entry) { - $item = array(); + // We can only use the first anchor, there is no way of finding the 'correct' one if there are multiple + $item['uri'] = $this->getURI() . $entry->find('a', 0)->href; + $item['title'] = strip_tags($entry->innertext); - // We can only use the first anchor, there is no way of finding the 'correct' one if there are multiple - $item['uri'] = $this->getURI() . $entry->find('a', 0)->href; - $item['title'] = strip_tags($entry->innertext); + if (!$fullArticle) { + $item['content'] = $this->replaceUriInHtmlElement($entry); + } else { + $item['content'] = $this->loadFullArticle($item['uri']); + } - if(!$fullArticle) - $item['content'] = $this->replaceUriInHtmlElement($entry); - else - $item['content'] = $this->loadFullArticle($item['uri']); + $this->items[] = $item; + } + } - $this->items[] = $item; - } - } + /** + * Loads the full article from a given URI + */ + private function loadFullArticle($uri) + { + $content_html = getSimpleHTMLDOMCached($uri); - /** - * Loads the full article from a given URI - */ - private function loadFullArticle($uri){ - $content_html = getSimpleHTMLDOMCached($uri); + if (!$content_html) { + returnServerError('Could not load site: ' . $uri . '!'); + } - if(!$content_html) - returnServerError('Could not load site: ' . $uri . '!'); + $content = $content_html->find('#mw-content-text', 0); - $content = $content_html->find('#mw-content-text', 0); + if (!$content) { + returnServerError('Could not find content in page: ' . $uri . '!'); + } - if(!$content) - returnServerError('Could not find content in page: ' . $uri . '!'); + // Let's remove a couple of things from the article + $table = $content->find('#toc', 0); // Table of contents + if (!$table === false) { + $table->outertext = ''; + } - // Let's remove a couple of things from the article - $table = $content->find('#toc', 0); // Table of contents - if(!$table === false) - $table->outertext = ''; + foreach ($content->find('ol.references') as $reference) { // References + $reference->outertext = ''; + } - foreach($content->find('ol.references') as $reference) // References - $reference->outertext = ''; + return str_replace('href="/', 'href="' . $this->getURI() . '/', $content->innertext); + } - return str_replace('href="/', 'href="' . $this->getURI() . '/', $content->innertext); - } + /** + * Implementation for de.wikipedia.org + */ + private function getContentsDe($html, $subject, $fullArticle) + { + switch ($subject) { + case WIKIPEDIA_SUBJECT_TFA: + $element = $html->find('div[id=artikel] div.hauptseite-box-content', 0); + $this->addTodaysFeaturedArticleGeneric($element, $fullArticle); + break; + case WIKIPEDIA_SUBJECT_DYK: + $element = $html->find('div[id=wissenswertes]', 0); + $this->addDidYouKnowGeneric($element, $fullArticle); + break; + default: + break; + } + } - /** - * Implementation for de.wikipedia.org - */ - private function getContentsDe($html, $subject, $fullArticle){ - switch($subject) { - case WIKIPEDIA_SUBJECT_TFA: - $element = $html->find('div[id=artikel] div.hauptseite-box-content', 0); - $this->addTodaysFeaturedArticleGeneric($element, $fullArticle); - break; - case WIKIPEDIA_SUBJECT_DYK: - $element = $html->find('div[id=wissenswertes]', 0); - $this->addDidYouKnowGeneric($element, $fullArticle); - break; - default: - break; - } - } + /** + * Implementation for fr.wikipedia.org + */ + private function getContentsFr($html, $subject, $fullArticle) + { + switch ($subject) { + case WIKIPEDIA_SUBJECT_TFA: + $element = $html->find('div[class=accueil_2017_cadre]', 0); + $this->addTodaysFeaturedArticleGeneric($element, $fullArticle, 'Lire la suite'); + break; + case WIKIPEDIA_SUBJECT_DYK: + $element = $html->find('div[class=accueil_2017_cadre]', 2); + $this->addDidYouKnowGeneric($element, $fullArticle); + break; + default: + break; + } + } - /** - * Implementation for fr.wikipedia.org - */ - private function getContentsFr($html, $subject, $fullArticle){ - switch($subject) { - case WIKIPEDIA_SUBJECT_TFA: - $element = $html->find('div[class=accueil_2017_cadre]', 0); - $this->addTodaysFeaturedArticleGeneric($element, $fullArticle, 'Lire la suite'); - break; - case WIKIPEDIA_SUBJECT_DYK: - $element = $html->find('div[class=accueil_2017_cadre]', 2); - $this->addDidYouKnowGeneric($element, $fullArticle); - break; - default: - break; - } - } + /** + * Implementation for en.wikipedia.org + */ + private function getContentsEn($html, $subject, $fullArticle) + { + switch ($subject) { + case WIKIPEDIA_SUBJECT_TFA: + $element = $html->find('div[id=mp-tfa]', 0); + $this->addTodaysFeaturedArticleGeneric($element, $fullArticle, -1); + break; + case WIKIPEDIA_SUBJECT_DYK: + $element = $html->find('div[id=mp-dyk]', 0); + $this->addDidYouKnowGeneric($element, $fullArticle); + break; + default: + break; + } + } - /** - * Implementation for en.wikipedia.org - */ - private function getContentsEn($html, $subject, $fullArticle){ - switch($subject) { - case WIKIPEDIA_SUBJECT_TFA: - $element = $html->find('div[id=mp-tfa]', 0); - $this->addTodaysFeaturedArticleGeneric($element, $fullArticle, -1); - break; - case WIKIPEDIA_SUBJECT_DYK: - $element = $html->find('div[id=mp-dyk]', 0); - $this->addDidYouKnowGeneric($element, $fullArticle); - break; - default: - break; - } - } + /** + * Implementation for ru.wikipedia.org + */ + private function getContentsRu($html, $subject, $fullArticle) + { + switch ($subject) { + case WIKIPEDIA_SUBJECT_TFA: + $element = $html->find('div[id=main-tfa]', 0); + $this->addTodaysFeaturedArticleGeneric($element, $fullArticle, -1); + break; + case WIKIPEDIA_SUBJECT_DYK: + $element = $html->find('div[id=main-dyk]', 0); + $this->addDidYouKnowGeneric($element, $fullArticle); + break; + default: + break; + } + } - /** - * Implementation for ru.wikipedia.org - */ - private function getContentsRu($html, $subject, $fullArticle){ - switch($subject) { - case WIKIPEDIA_SUBJECT_TFA: - $element = $html->find('div[id=main-tfa]', 0); - $this->addTodaysFeaturedArticleGeneric($element, $fullArticle, -1); - break; - case WIKIPEDIA_SUBJECT_DYK: - $element = $html->find('div[id=main-dyk]', 0); - $this->addDidYouKnowGeneric($element, $fullArticle); - break; - default: - break; - } - } + /** + * Implementation for eo.wikipedia.org + */ + private function getContentsEo($html, $subject, $fullArticle) + { + switch ($subject) { + case WIKIPEDIA_SUBJECT_TFA: + $element = $html->find('div[id=mf-artikolo-de-la-monato]', 0); + $element->find('div', -2)->outertext = ''; + $this->addTodaysFeaturedArticleGeneric($element, $fullArticle); + break; + case WIKIPEDIA_SUBJECT_DYK: + $element = $html->find('div.hp', 1)->find('table', 4)->find('td', -1); + $this->addDidYouKnowGeneric($element, $fullArticle); + break; + default: + break; + } + } - /** - * Implementation for eo.wikipedia.org - */ - private function getContentsEo($html, $subject, $fullArticle){ - switch($subject) { - case WIKIPEDIA_SUBJECT_TFA: - $element = $html->find('div[id=mf-artikolo-de-la-monato]', 0); - $element->find('div', -2)->outertext = ''; - $this->addTodaysFeaturedArticleGeneric($element, $fullArticle); - break; - case WIKIPEDIA_SUBJECT_DYK: - $element = $html->find('div.hp', 1)->find('table', 4)->find('td', -1); - $this->addDidYouKnowGeneric($element, $fullArticle); - break; - default: - break; - } - } - - /** - * Implementation for nl.wikipedia.org - */ - private function getContentsNl($html, $subject, $fullArticle){ - switch($subject) { - case WIKIPEDIA_SUBJECT_TFA: - $element = $html->find('td[id=segment-Uitgelicht] div', 0); - $element->find('p', 1)->outertext = ''; - $this->addTodaysFeaturedArticleGeneric($element, $fullArticle, 'Lees verder'); - break; - case WIKIPEDIA_SUBJECT_DYK: - $element = $html->find('td[id=segment-Wist_je_dat] div', 0); - $this->addDidYouKnowGeneric($element, $fullArticle); - break; - default: - break; - } - } + /** + * Implementation for nl.wikipedia.org + */ + private function getContentsNl($html, $subject, $fullArticle) + { + switch ($subject) { + case WIKIPEDIA_SUBJECT_TFA: + $element = $html->find('td[id=segment-Uitgelicht] div', 0); + $element->find('p', 1)->outertext = ''; + $this->addTodaysFeaturedArticleGeneric($element, $fullArticle, 'Lees verder'); + break; + case WIKIPEDIA_SUBJECT_DYK: + $element = $html->find('td[id=segment-Wist_je_dat] div', 0); + $this->addDidYouKnowGeneric($element, $fullArticle); + break; + default: + break; + } + } } diff --git a/bridges/WiredBridge.php b/bridges/WiredBridge.php index b15f781f..d4c7cbbb 100644 --- a/bridges/WiredBridge.php +++ b/bridges/WiredBridge.php @@ -1,103 +1,110 @@ array( - 'name' => 'Feed', - 'type' => 'list', - 'values' => array( - 'WIRED Top Stories' => 'rss', // /feed/rss - 'Business' => 'business', // /feed/category/business/latest/rss - 'Culture' => 'culture', // /feed/category/culture/latest/rss - 'Gear' => 'gear', // /feed/category/gear/latest/rss - 'Ideas' => 'ideas', // /feed/category/ideas/latest/rss - 'Science' => 'science', // /feed/category/science/latest/rss - 'Security' => 'security', // /feed/category/security/latest/rss - 'Transportation' => 'transportation', // /feed/category/transportation/latest/rss - 'Backchannel' => 'backchannel', // /feed/category/backchannel/latest/rss - 'WIRED Guides' => 'wired-guide', // /feed/tag/wired-guide/latest/rss - 'Photo' => 'photo' // /feed/category/photo/latest/rss - ) - ), - 'limit' => self::LIMIT, - )); +class WiredBridge extends FeedExpander +{ + const MAINTAINER = 'ORelio'; + const NAME = 'WIRED Bridge'; + const URI = 'https://www.wired.com/'; + const DESCRIPTION = 'Returns the newest articles from WIRED'; - public function collectData(){ - $feed = $this->getInput('feed'); - if(empty($feed) || !ctype_alpha(str_replace('-', '', $feed))) { - returnClientError('Invalid feed, please check the "feed" parameter.'); - } + const PARAMETERS = [ [ + 'feed' => [ + 'name' => 'Feed', + 'type' => 'list', + 'values' => [ + 'WIRED Top Stories' => 'rss', // /feed/rss + 'Business' => 'business', // /feed/category/business/latest/rss + 'Culture' => 'culture', // /feed/category/culture/latest/rss + 'Gear' => 'gear', // /feed/category/gear/latest/rss + 'Ideas' => 'ideas', // /feed/category/ideas/latest/rss + 'Science' => 'science', // /feed/category/science/latest/rss + 'Security' => 'security', // /feed/category/security/latest/rss + 'Transportation' => 'transportation', // /feed/category/transportation/latest/rss + 'Backchannel' => 'backchannel', // /feed/category/backchannel/latest/rss + 'WIRED Guides' => 'wired-guide', // /feed/tag/wired-guide/latest/rss + 'Photo' => 'photo' // /feed/category/photo/latest/rss + ] + ], + 'limit' => self::LIMIT, + ]]; - $feed_url = $this->getURI() . 'feed/'; - if ($feed != 'rss') { - if ($feed != 'wired-guide') { - $feed_url .= 'category/'; - } else { - $feed_url .= 'tag/'; - } - $feed_url .= "$feed/latest/"; - } - $feed_url .= 'rss'; + public function collectData() + { + $feed = $this->getInput('feed'); + if (empty($feed) || !ctype_alpha(str_replace('-', '', $feed))) { + returnClientError('Invalid feed, please check the "feed" parameter.'); + } - $limit = $this->getInput('limit') ?? -1; - $this->collectExpandableDatas($feed_url, $limit); - } + $feed_url = $this->getURI() . 'feed/'; + if ($feed != 'rss') { + if ($feed != 'wired-guide') { + $feed_url .= 'category/'; + } else { + $feed_url .= 'tag/'; + } + $feed_url .= "$feed/latest/"; + } + $feed_url .= 'rss'; - protected function parseItem($newsItem){ - $item = parent::parseItem($newsItem); - $article = getSimpleHTMLDOMCached($item['uri']); - $item['content'] = $this->extractArticleContent($article); + $limit = $this->getInput('limit') ?? -1; + $this->collectExpandableDatas($feed_url, $limit); + } - $headline = strval($newsItem->description); - if(!empty($headline)) { - $item['content'] = '

' . $headline . '

' . $item['content']; - } + protected function parseItem($newsItem) + { + $item = parent::parseItem($newsItem); + $article = getSimpleHTMLDOMCached($item['uri']); + $item['content'] = $this->extractArticleContent($article); - $item_image = $article->find('meta[property="og:image"]', 0); - if(!empty($item_image)) { - $item['enclosures'] = array($item_image->content); - $item['content'] = '

' . $item['content']; - } + $headline = strval($newsItem->description); + if (!empty($headline)) { + $item['content'] = '

' . $headline . '

' . $item['content']; + } - return $item; - } + $item_image = $article->find('meta[property="og:image"]', 0); + if (!empty($item_image)) { + $item['enclosures'] = [$item_image->content]; + $item['content'] = '

' . $item['content']; + } - private function extractArticleContent($article){ - $content = $article->find('article', 0); - $truncate = true; + return $item; + } - if (empty($content)) { - $content = $article->find('div.listicle-main-component__container', 0); - $truncate = false; - } + private function extractArticleContent($article) + { + $content = $article->find('article', 0); + $truncate = true; - if (!empty($content)) { - $content = $content->innertext; - } + if (empty($content)) { + $content = $article->find('div.listicle-main-component__container', 0); + $truncate = false; + } - foreach (array( - '
/', '', $content); - return $content; - } + const PARAMETERS = [ [ + 'url' => [ + 'name' => 'Blog URL', + 'exampleValue' => 'https://www.wpbeginner.com/', + 'required' => true + ] + ]]; - protected function parseItem($newItem){ - $item = parent::parseItem($newItem); + private function cleanContent($content) + { + $content = stripWithDelimiters($content, ''); + $content = preg_replace('/
/', '', $content); + return $content; + } - $article_html = getSimpleHTMLDOMCached($item['uri']); + protected function parseItem($newItem) + { + $item = parent::parseItem($newItem); - $article = null; - switch(true) { + $article_html = getSimpleHTMLDOMCached($item['uri']); - // Custom fix for theme in https://jungefreiheit.de/politik/deutschland/2022/wahl-im-saarland/ - case !is_null($article_html->find('div[data-widget_type="theme-post-content.default"]', 0)): - $article = $article_html->find('div[data-widget_type="theme-post-content.default"]', 0); - break; - case !is_null($article_html->find('[itemprop=articleBody]', 0)): - // highest priority content div - $article = $article_html->find('[itemprop=articleBody]', 0); - break; - case !is_null($article_html->find('article', 0)): - // most common content div - $article = $article_html->find('article', 0); - break; - case !is_null($article_html->find('.single-content', 0)): - // another common content div - $article = $article_html->find('.single-content', 0); - break; - case !is_null($article_html->find('.post-content', 0)): - // another common content div - $article = $article_html->find('.post-content', 0); - break; - case !is_null($article_html->find('.post', 0)): - // for old WordPress themes without HTML5 - $article = $article_html->find('.post', 0); - break; - } + $article = null; + switch (true) { + // Custom fix for theme in https://jungefreiheit.de/politik/deutschland/2022/wahl-im-saarland/ + case !is_null($article_html->find('div[data-widget_type="theme-post-content.default"]', 0)): + $article = $article_html->find('div[data-widget_type="theme-post-content.default"]', 0); + break; + case !is_null($article_html->find('[itemprop=articleBody]', 0)): + // highest priority content div + $article = $article_html->find('[itemprop=articleBody]', 0); + break; + case !is_null($article_html->find('article', 0)): + // most common content div + $article = $article_html->find('article', 0); + break; + case !is_null($article_html->find('.single-content', 0)): + // another common content div + $article = $article_html->find('.single-content', 0); + break; + case !is_null($article_html->find('.post-content', 0)): + // another common content div + $article = $article_html->find('.post-content', 0); + break; + case !is_null($article_html->find('.post', 0)): + // for old WordPress themes without HTML5 + $article = $article_html->find('.post', 0); + break; + } - foreach ($article->find('h1.entry-title') as $title) - if ($title->plaintext == $item['title']) - $title->outertext = ''; + foreach ($article->find('h1.entry-title') as $title) { + if ($title->plaintext == $item['title']) { + $title->outertext = ''; + } + } - $article_image = $article_html->find('img.wp-post-image', 0); - if(!empty($item['content']) && (!is_object($article_image) || empty($article_image->src))) { - $article_image = str_get_html($item['content'])->find('img.wp-post-image', 0); - } - if(is_object($article_image) && !empty($article_image->src)) { - if(empty($article_image->getAttribute('data-lazy-src'))) { - $article_image = $article_image->src; - } else { - $article_image = $article_image->getAttribute('data-lazy-src'); - } - $mime_type = getMimeType($article_image); - if (strpos($mime_type, 'image') === false) - $article_image .= '#.image'; // force image - if (empty($item['enclosures'])) - $item['enclosures'] = array($article_image); - else - $item['enclosures'] = array_merge($item['enclosures'], $article_image); - } + $article_image = $article_html->find('img.wp-post-image', 0); + if (!empty($item['content']) && (!is_object($article_image) || empty($article_image->src))) { + $article_image = str_get_html($item['content'])->find('img.wp-post-image', 0); + } + if (is_object($article_image) && !empty($article_image->src)) { + if (empty($article_image->getAttribute('data-lazy-src'))) { + $article_image = $article_image->src; + } else { + $article_image = $article_image->getAttribute('data-lazy-src'); + } + $mime_type = getMimeType($article_image); + if (strpos($mime_type, 'image') === false) { + $article_image .= '#.image'; // force image + } + if (empty($item['enclosures'])) { + $item['enclosures'] = [$article_image]; + } else { + $item['enclosures'] = array_merge($item['enclosures'], $article_image); + } + } - if(!is_null($article)) { - $item['content'] = $this->cleanContent($article->innertext); - $item['content'] = defaultLinkTo($item['content'], $item['uri']); - } + if (!is_null($article)) { + $item['content'] = $this->cleanContent($article->innertext); + $item['content'] = defaultLinkTo($item['content'], $item['uri']); + } - return $item; - } + return $item; + } - public function getURI(){ - $url = $this->getInput('url'); - if(empty($url)) { - $url = parent::getURI(); - } - return $url; - } + public function getURI() + { + $url = $this->getInput('url'); + if (empty($url)) { + $url = parent::getURI(); + } + return $url; + } - public function collectData(){ - if($this->getInput('url') && substr($this->getInput('url'), 0, strlen('http')) !== 'http') { - // just in case someone find a way to access local files by playing with the url - returnClientError('The url parameter must either refer to http or https protocol.'); - } - try{ - $this->collectExpandableDatas($this->getURI() . '/feed/atom/', 20); - } catch (Exception $e) { - $this->collectExpandableDatas($this->getURI() . '/?feed=atom', 20); - } - - } + public function collectData() + { + if ($this->getInput('url') && substr($this->getInput('url'), 0, strlen('http')) !== 'http') { + // just in case someone find a way to access local files by playing with the url + returnClientError('The url parameter must either refer to http or https protocol.'); + } + try { + $this->collectExpandableDatas($this->getURI() . '/feed/atom/', 20); + } catch (Exception $e) { + $this->collectExpandableDatas($this->getURI() . '/?feed=atom', 20); + } + } } diff --git a/bridges/WordPressMadaraBridge.php b/bridges/WordPressMadaraBridge.php index 3170e119..4325075c 100644 --- a/bridges/WordPressMadaraBridge.php +++ b/bridges/WordPressMadaraBridge.php @@ -1,132 +1,145 @@ array( - 'url' => array( - 'name' => 'Manga URL', - 'exampleValue' => 'https://live.mangabooth.com/manga/manga-text-chapter/', - 'required' => true - ) - ) - ); + const PARAMETERS = [ + 'Manga Chapters' => [ + 'url' => [ + 'name' => 'Manga URL', + 'exampleValue' => 'https://live.mangabooth.com/manga/manga-text-chapter/', + 'required' => true + ] + ] + ]; - public function getName() { - switch($this->queriedContext) { - case 'Manga Chapters': - $mangaInfo = $this->getMangaInfo($this->getInput('url')); - return $mangaInfo['title']; - default: - return parent::getName(); - } - } + public function getName() + { + switch ($this->queriedContext) { + case 'Manga Chapters': + $mangaInfo = $this->getMangaInfo($this->getInput('url')); + return $mangaInfo['title']; + default: + return parent::getName(); + } + } - public function getURI() { - return $this->getInput('url') ?? self::URI; - } + public function getURI() + { + return $this->getInput('url') ?? self::URI; + } - public function collectData() { - $html = $this->queryAjaxChapters(); + public function collectData() + { + $html = $this->queryAjaxChapters(); - // Check if the list subcategorizes by volume - $volumes = $html->find('ul.volumns', 0); - if ($volumes) { - $this->parseVolumes($volumes); - } else { - $this->parseChapterList($html, null); - } - } + // Check if the list subcategorizes by volume + $volumes = $html->find('ul.volumns', 0); + if ($volumes) { + $this->parseVolumes($volumes); + } else { + $this->parseChapterList($html, null); + } + } - protected function queryAjaxChaptersNew() { - $uri = rtrim($this->getInput('url'), '/') . '/ajax/chapters/'; - $headers = array(); - $opts = array(CURLOPT_POST => 1); - return str_get_html(getContents($uri, $headers, $opts)); - } + protected function queryAjaxChaptersNew() + { + $uri = rtrim($this->getInput('url'), '/') . '/ajax/chapters/'; + $headers = []; + $opts = [CURLOPT_POST => 1]; + return str_get_html(getContents($uri, $headers, $opts)); + } - protected function queryAjaxChaptersOld() { - $mangaInfo = $this->getMangaInfo($this->getInput('url')); - $uri = rtrim($mangaInfo['root'], '/') . '/wp-admin/admin-ajax.php'; - $headers = array(); - $opts = array(CURLOPT_POSTFIELDS => array( - 'action' => 'manga_get_chapters', - 'manga' => $mangaInfo['id'] - )); - return str_get_html(getContents($uri, $headers, $opts)); - } + protected function queryAjaxChaptersOld() + { + $mangaInfo = $this->getMangaInfo($this->getInput('url')); + $uri = rtrim($mangaInfo['root'], '/') . '/wp-admin/admin-ajax.php'; + $headers = []; + $opts = [CURLOPT_POSTFIELDS => [ + 'action' => 'manga_get_chapters', + 'manga' => $mangaInfo['id'] + ]]; + return str_get_html(getContents($uri, $headers, $opts)); + } - protected function queryAjaxChapters() { - $new = $this->queryAjaxChaptersNew(); - if ($new->find('.wp-manga-chapter')) { - return $new; - } else { - return $this->queryAjaxChaptersOld(); - } - } + protected function queryAjaxChapters() + { + $new = $this->queryAjaxChaptersNew(); + if ($new->find('.wp-manga-chapter')) { + return $new; + } else { + return $this->queryAjaxChaptersOld(); + } + } - protected function parseVolumes($volumes) { - foreach($volumes->children(-1) as $volume) { - $volume_name = trim($volume->find('a.has-child', 0)->plaintext); - $this->parseChapterList($volume->find('ul', -1), $volume_name); - } - } + protected function parseVolumes($volumes) + { + foreach ($volumes->children(-1) as $volume) { + $volume_name = trim($volume->find('a.has-child', 0)->plaintext); + $this->parseChapterList($volume->find('ul', -1), $volume_name); + } + } - protected function parseChapterList($chapters, $volume) { - $mangaInfo = $this->getMangaInfo($this->getInput('url')); - foreach($chapters->find('li.wp-manga-chapter') as $chap) { - $link = $chap->find('a', 0); + protected function parseChapterList($chapters, $volume) + { + $mangaInfo = $this->getMangaInfo($this->getInput('url')); + foreach ($chapters->find('li.wp-manga-chapter') as $chap) { + $link = $chap->find('a', 0); - $item = array(); - $item['title'] = ($volume ?? '') . ' ' . trim($link->plaintext); - $item['uri'] = $link->href; - $item['uid'] = $link->href; - $item['timestamp'] = $chap->find('span.chapter-release-date', 0)->plaintext; - $item['author'] = $mangaInfo['author'] ?? null; - $item['categories'] = $mangaInfo['categories'] ?? null; - $this->items[] = $item; - } - } + $item = []; + $item['title'] = ($volume ?? '') . ' ' . trim($link->plaintext); + $item['uri'] = $link->href; + $item['uid'] = $link->href; + $item['timestamp'] = $chap->find('span.chapter-release-date', 0)->plaintext; + $item['author'] = $mangaInfo['author'] ?? null; + $item['categories'] = $mangaInfo['categories'] ?? null; + $this->items[] = $item; + } + } - /** - * Retrieves manga info from cache or title page. - * The returned array contains 'title', 'author', and 'categories' keys for use in feed items. - * The 'id' key contains the manga title id, used for the old ajax api. - * The 'root' key contains the website root. - * - * @param $url - * @return array - */ - protected function getMangaInfo($url) { - $url_cache = 'TitleInfo_' . preg_replace('/[^\w]/', '.', rtrim($url, '/')); - $cache = $this->loadCacheValue($url_cache); - if (isset($cache)) { - return $cache; - } + /** + * Retrieves manga info from cache or title page. + * The returned array contains 'title', 'author', and 'categories' keys for use in feed items. + * The 'id' key contains the manga title id, used for the old ajax api. + * The 'root' key contains the website root. + * + * @param $url + * @return array + */ + protected function getMangaInfo($url) + { + $url_cache = 'TitleInfo_' . preg_replace('/[^\w]/', '.', rtrim($url, '/')); + $cache = $this->loadCacheValue($url_cache); + if (isset($cache)) { + return $cache; + } - $info = array(); - $html = getSimpleHTMLDOMCached($url); + $info = []; + $html = getSimpleHTMLDOMCached($url); - $info['title'] = html_entity_decode($html->find('*[property=og:title]', 0)->content); - $author = $html->find('.author-content', 0); - if (!is_null($author)) - $info['author'] = trim($author->plaintext); - $cats = $html->find('.genres-content', 0); - if (!is_null($cats)) - $info['categories'] = explode(', ', trim($cats->plaintext)); + $info['title'] = html_entity_decode($html->find('*[property=og:title]', 0)->content); + $author = $html->find('.author-content', 0); + if (!is_null($author)) { + $info['author'] = trim($author->plaintext); + } + $cats = $html->find('.genres-content', 0); + if (!is_null($cats)) { + $info['categories'] = explode(', ', trim($cats->plaintext)); + } - $info['id'] = $html->find('#manga-chapters-holder', 0)->getAttribute('data-id'); - // It's possible to find this from the input parameters, but it is already available here. - $info['root'] = $html->find('a.logo', 0)->href; + $info['id'] = $html->find('#manga-chapters-holder', 0)->getAttribute('data-id'); + // It's possible to find this from the input parameters, but it is already available here. + $info['root'] = $html->find('a.logo', 0)->href; - $this->saveCacheValue($url_cache, $info); - return $info; - } + $this->saveCacheValue($url_cache, $info); + return $info; + } } diff --git a/bridges/WordPressPluginUpdateBridge.php b/bridges/WordPressPluginUpdateBridge.php index 272022dd..a092d72f 100644 --- a/bridges/WordPressPluginUpdateBridge.php +++ b/bridges/WordPressPluginUpdateBridge.php @@ -1,62 +1,64 @@ [ - 'name' => 'Plugin slug', - 'exampleValue' => 'akismet', - 'required' => true, - 'title' => 'Slug or url', - ] - ] - ]; + const PARAMETERS = [ + [ + // The incorrectly named pluginUrl is kept for BC + 'pluginUrl' => [ + 'name' => 'Plugin slug', + 'exampleValue' => 'akismet', + 'required' => true, + 'title' => 'Slug or url', + ] + ] + ]; - public function collectData() { - $input = trim($this->getInput('pluginUrl')); - if (preg_match('#https://wordpress\.org/plugins/([\w-]+)#', $input, $m)) { - $slug = $m[1]; - } else { - $slug = str_replace(['/'], '', $input); - } + public function collectData() + { + $input = trim($this->getInput('pluginUrl')); + if (preg_match('#https://wordpress\.org/plugins/([\w-]+)#', $input, $m)) { + $slug = $m[1]; + } else { + $slug = str_replace(['/'], '', $input); + } - $pluginData = self::fetchPluginData($slug); + $pluginData = self::fetchPluginData($slug); - if ($pluginData->versions === []) { - throw new \Exception('This plugin does not have versioning data'); - } + if ($pluginData->versions === []) { + throw new \Exception('This plugin does not have versioning data'); + } - // We don't need trunk. I think it's the latest commit. - unset($pluginData->versions->trunk); + // We don't need trunk. I think it's the latest commit. + unset($pluginData->versions->trunk); - foreach ($pluginData->versions as $version => $downloadUrl) { - $this->items[] = [ - 'title' => $version, - 'uri' => sprintf('https://wordpress.org/plugins/%s/#developers', $slug), - 'uid' => $downloadUrl, - ]; - } + foreach ($pluginData->versions as $version => $downloadUrl) { + $this->items[] = [ + 'title' => $version, + 'uri' => sprintf('https://wordpress.org/plugins/%s/#developers', $slug), + 'uid' => $downloadUrl, + ]; + } - usort($this->items, function($a, $b) { - return version_compare($b['title'], $a['title']); - }); - } + usort($this->items, function ($a, $b) { + return version_compare($b['title'], $a['title']); + }); + } - /** - * Fetch plugin data from wordpress.org json api - * - * https://codex.wordpress.org/WordPress.org_API#Plugins - * https://wordpress.org/support/topic/using-the-wordpress-org-api/ - */ - private static function fetchPluginData(string $slug): \stdClass - { - $api = 'https://api.wordpress.org/plugins/info/1.2/?action=plugin_information&request[slug]=%s'; - return json_decode(getContents(sprintf($api, $slug))); - } + /** + * Fetch plugin data from wordpress.org json api + * + * https://codex.wordpress.org/WordPress.org_API#Plugins + * https://wordpress.org/support/topic/using-the-wordpress-org-api/ + */ + private static function fetchPluginData(string $slug): \stdClass + { + $api = 'https://api.wordpress.org/plugins/info/1.2/?action=plugin_information&request[slug]=%s'; + return json_decode(getContents(sprintf($api, $slug))); + } } diff --git a/bridges/WorldCosplayBridge.php b/bridges/WorldCosplayBridge.php index b60d7948..cb28eee2 100644 --- a/bridges/WorldCosplayBridge.php +++ b/bridges/WorldCosplayBridge.php @@ -1,142 +1,146 @@ %s'; + const API_CHARACTER = 'api/photo/list.json?character_id=%u&limit=%u'; + const API_COSPLAYER = 'api/member/photos.json?member_id=%u&limit=%u'; + const API_SERIES = 'api/photo/list.json?title_id=%u&limit=%u'; + const API_TAG = 'api/tag/photo_list.json?id=%u&limit=%u'; - const ERR_CONTEXT = 'No context provided'; - const ERR_QUERY = 'Unable to query: %s'; + const CONTENT_HTML + = '%s'; - const LIMIT_MIN = 1; - const LIMIT_MAX = 24; + const ERR_CONTEXT = 'No context provided'; + const ERR_QUERY = 'Unable to query: %s'; - const PARAMETERS = array( - 'Character' => array( - 'cid' => array( - 'name' => 'Character ID', - 'type' => 'number', - 'required' => true, - 'title' => 'WorldCosplay character ID', - 'exampleValue' => 18204 - ) - ), - 'Cosplayer' => array( - 'uid' => array( - 'name' => 'Cosplayer ID', - 'type' => 'number', - 'required' => true, - 'title' => 'Cosplayer\'s WorldCosplay profile ID', - 'exampleValue' => 406782 - ) - ), - 'Series' => array( - 'sid' => array( - 'name' => 'Series ID', - 'type' => 'number', - 'required' => true, - 'title' => 'WorldCosplay series ID', - 'exampleValue' => 3139 - ) - ), - 'Tag' => array( - 'tid' => array( - 'name' => 'Tag ID', - 'type' => 'number', - 'required' => true, - 'title' => 'WorldCosplay tag ID', - 'exampleValue' => 33643 - ) - ), - 'global' => array( - 'limit' => array( - 'name' => 'Limit', - 'type' => 'number', - 'required' => false, - 'title' => 'Maximum number of photos to return', - 'exampleValue' => 5, - 'defaultValue' => 5 - ) - ) - ); + const LIMIT_MIN = 1; + const LIMIT_MAX = 24; - public function collectData() { - $limit = $this->getInput('limit'); - $limit = min(self::LIMIT_MAX, max(self::LIMIT_MIN, $limit)); - switch($this->queriedContext) { - case 'Character': - $id = $this->getInput('cid'); - $url = self::API_CHARACTER; - break; - case 'Cosplayer': - $id = $this->getInput('uid'); - $url = self::API_COSPLAYER; - break; - case 'Series': - $id = $this->getInput('sid'); - $url = self::API_SERIES; - break; - case 'Tag': - $id = $this->getInput('tid'); - $url = self::API_TAG; - break; - default: - returnClientError(self::ERR_CONTEXT); - } - $url = self::URI . sprintf($url, $id, $limit); + const PARAMETERS = [ + 'Character' => [ + 'cid' => [ + 'name' => 'Character ID', + 'type' => 'number', + 'required' => true, + 'title' => 'WorldCosplay character ID', + 'exampleValue' => 18204 + ] + ], + 'Cosplayer' => [ + 'uid' => [ + 'name' => 'Cosplayer ID', + 'type' => 'number', + 'required' => true, + 'title' => 'Cosplayer\'s WorldCosplay profile ID', + 'exampleValue' => 406782 + ] + ], + 'Series' => [ + 'sid' => [ + 'name' => 'Series ID', + 'type' => 'number', + 'required' => true, + 'title' => 'WorldCosplay series ID', + 'exampleValue' => 3139 + ] + ], + 'Tag' => [ + 'tid' => [ + 'name' => 'Tag ID', + 'type' => 'number', + 'required' => true, + 'title' => 'WorldCosplay tag ID', + 'exampleValue' => 33643 + ] + ], + 'global' => [ + 'limit' => [ + 'name' => 'Limit', + 'type' => 'number', + 'required' => false, + 'title' => 'Maximum number of photos to return', + 'exampleValue' => 5, + 'defaultValue' => 5 + ] + ] + ]; - $json = json_decode(getContents($url)); - if($json->has_error) { - returnServerError($json->message); - } - $list = $json->list; + public function collectData() + { + $limit = $this->getInput('limit'); + $limit = min(self::LIMIT_MAX, max(self::LIMIT_MIN, $limit)); + switch ($this->queriedContext) { + case 'Character': + $id = $this->getInput('cid'); + $url = self::API_CHARACTER; + break; + case 'Cosplayer': + $id = $this->getInput('uid'); + $url = self::API_COSPLAYER; + break; + case 'Series': + $id = $this->getInput('sid'); + $url = self::API_SERIES; + break; + case 'Tag': + $id = $this->getInput('tid'); + $url = self::API_TAG; + break; + default: + returnClientError(self::ERR_CONTEXT); + } + $url = self::URI . sprintf($url, $id, $limit); - foreach($list as $img) { - $image = isset($img->photo) ? $img->photo : $img; - $item = array( - 'uri' => self::URI . substr($image->url, 1), - 'title' => $image->subject, - 'timestamp' => $image->created_at, - 'author' => $img->member->global_name, - 'enclosures' => array($image->large_url), - 'uid' => $image->id, - ); - $item['content'] = sprintf( - self::CONTENT_HTML, - $item['uri'], - $item['enclosures'][0], - $item['title'], - $item['title'] - ); - $this->items[] = $item; - } - } + $json = json_decode(getContents($url)); + if ($json->has_error) { + returnServerError($json->message); + } + $list = $json->list; - public function getName() { - switch($this->queriedContext) { - case 'Character': - $id = $this->getInput('cid'); - break; - case 'Cosplayer': - $id = $this->getInput('uid'); - break; - case 'Series': - $id = $this->getInput('sid'); - break; - case 'Tag': - $id = $this->getInput('tid'); - break; - default: - return parent::getName(); - } - return sprintf('%s %u - ', $this->queriedContext, $id) . self::NAME; - } + foreach ($list as $img) { + $image = isset($img->photo) ? $img->photo : $img; + $item = [ + 'uri' => self::URI . substr($image->url, 1), + 'title' => $image->subject, + 'timestamp' => $image->created_at, + 'author' => $img->member->global_name, + 'enclosures' => [$image->large_url], + 'uid' => $image->id, + ]; + $item['content'] = sprintf( + self::CONTENT_HTML, + $item['uri'], + $item['enclosures'][0], + $item['title'], + $item['title'] + ); + $this->items[] = $item; + } + } + + public function getName() + { + switch ($this->queriedContext) { + case 'Character': + $id = $this->getInput('cid'); + break; + case 'Cosplayer': + $id = $this->getInput('uid'); + break; + case 'Series': + $id = $this->getInput('sid'); + break; + case 'Tag': + $id = $this->getInput('tid'); + break; + default: + return parent::getName(); + } + return sprintf('%s %u - ', $this->queriedContext, $id) . self::NAME; + } } diff --git a/bridges/WorldOfTanksBridge.php b/bridges/WorldOfTanksBridge.php index d48b2d6c..6e7a594b 100644 --- a/bridges/WorldOfTanksBridge.php +++ b/bridges/WorldOfTanksBridge.php @@ -1,58 +1,62 @@ array( - 'name' => 'Langue', - 'type' => 'list', - 'values' => array( - 'Français' => 'fr', - 'English' => 'en', - 'Español' => 'es', - 'Deutsch' => 'de', - 'Čeština' => 'cs', - 'Polski' => 'pl', - 'Türkçe' => 'tr' - ) - ) - )); + const PARAMETERS = [ [ + 'lang' => [ + 'name' => 'Langue', + 'type' => 'list', + 'values' => [ + 'Français' => 'fr', + 'English' => 'en', + 'Español' => 'es', + 'Deutsch' => 'de', + 'Čeština' => 'cs', + 'Polski' => 'pl', + 'Türkçe' => 'tr' + ] + ] + ]]; - const POSSIBLE_ARTICLES = array('article', 'rich-article'); + const POSSIBLE_ARTICLES = ['article', 'rich-article']; - public function collectData() { - $this->collectExpandableDatas(sprintf('https://worldoftanks.eu/%s/rss/news/', $this->getInput('lang'))); - } + public function collectData() + { + $this->collectExpandableDatas(sprintf('https://worldoftanks.eu/%s/rss/news/', $this->getInput('lang'))); + } - protected function parseItem($newsItem){ - $item = parent::parseItem($newsItem); - $item['content'] = $this->loadFullArticle($item['uri']); - return $item; - } + protected function parseItem($newsItem) + { + $item = parent::parseItem($newsItem); + $item['content'] = $this->loadFullArticle($item['uri']); + return $item; + } - /** - * Loads the full article and returns the contents - * @param $uri The article URI - * @return The article content - */ - private function loadFullArticle($uri){ - $html = getSimpleHTMLDOMCached($uri); + /** + * Loads the full article and returns the contents + * @param $uri The article URI + * @return The article content + */ + private function loadFullArticle($uri) + { + $html = getSimpleHTMLDOMCached($uri); - foreach(self::POSSIBLE_ARTICLES as $article_class) { - $content = $html->find('article', 0); + foreach (self::POSSIBLE_ARTICLES as $article_class) { + $content = $html->find('article', 0); - if($content !== null) { - // Remove the scripts, please - foreach($content->find('script') as $script) { - $script->outertext = ''; - } - return $content->innertext; - } - } - return null; - } + if ($content !== null) { + // Remove the scripts, please + foreach ($content->find('script') as $script) { + $script->outertext = ''; + } + return $content->innertext; + } + } + return null; + } } diff --git a/bridges/XPathBridge.php b/bridges/XPathBridge.php index 5aa280e0..98defddc 100644 --- a/bridges/XPathBridge.php +++ b/bridges/XPathBridge.php @@ -1,127 +1,128 @@ XPath expressions'; - const MAINTAINER = 'Niehztog'; - const PARAMETERS = array( - '' => array( +class XPathBridge extends XPathAbstract +{ + const NAME = 'XPathBridge'; + const URI = 'https://github.com/rss-bridge/rss-bridge'; + const DESCRIPTION + = 'Parse any webpage using XPath expressions'; + const MAINTAINER = 'Niehztog'; + const PARAMETERS = [ + '' => [ - 'url' => array( - 'name' => 'Enter web page URL', - 'title' => <<<"EOL" + 'url' => [ + 'name' => 'Enter web page URL', + 'title' => <<<"EOL" You can specify any website URL which serves data suited for display in RSS feeds (for example a news blog). EOL - , 'type' => 'text', - 'exampleValue' => 'https://news.blizzard.com/en-en', - 'defaultValue' => 'https://news.blizzard.com/en-en', - 'required' => true - ), + , 'type' => 'text', + 'exampleValue' => 'https://news.blizzard.com/en-en', + 'defaultValue' => 'https://news.blizzard.com/en-en', + 'required' => true + ], - 'item' => array( - 'name' => 'Item selector', - 'title' => <<<"EOL" + 'item' => [ + 'name' => 'Item selector', + 'title' => <<<"EOL" Enter an XPath expression matching a list of dom nodes, each node containing one feed article item in total (usually a surrounding <div> or <span> tag). This will be the context nodes for all of the following expressions. This expression usually starts with a single forward slash. EOL - , 'type' => 'text', - 'exampleValue' => '/html/body/div/div[4]/div[2]/div[2]/div/div/section/ol/li/article', - 'defaultValue' => '/html/body/div/div[4]/div[2]/div[2]/div/div/section/ol/li/article', - 'required' => true - ), + , 'type' => 'text', + 'exampleValue' => '/html/body/div/div[4]/div[2]/div[2]/div/div/section/ol/li/article', + 'defaultValue' => '/html/body/div/div[4]/div[2]/div[2]/div/div/section/ol/li/article', + 'required' => true + ], - 'title' => array( - 'name' => 'Item title selector', - 'title' => <<<"EOL" + 'title' => [ + 'name' => 'Item title selector', + 'title' => <<<"EOL" This expression should match a node contained within each article item node containing the article headline. It should start with a dot followed by two forward slashes, referring to any descendant nodes of the article item node. EOL - , 'type' => 'text', - 'exampleValue' => './/div/div[2]/h2', - 'defaultValue' => './/div/div[2]/h2', - 'required' => true - ), + , 'type' => 'text', + 'exampleValue' => './/div/div[2]/h2', + 'defaultValue' => './/div/div[2]/h2', + 'required' => true + ], - 'content' => array( - 'name' => 'Item description selector', - 'title' => <<<"EOL" + 'content' => [ + 'name' => 'Item description selector', + 'title' => <<<"EOL" This expression should match a node contained within each article item node containing the article content or description. It should start with a dot followed by two forward slashes, referring to any descendant nodes of the article item node. EOL - , 'type' => 'text', - 'exampleValue' => './/div[@class="ArticleListItem-description"]/div[@class="h6"]', - 'defaultValue' => './/div[@class="ArticleListItem-description"]/div[@class="h6"]', - 'required' => false - ), + , 'type' => 'text', + 'exampleValue' => './/div[@class="ArticleListItem-description"]/div[@class="h6"]', + 'defaultValue' => './/div[@class="ArticleListItem-description"]/div[@class="h6"]', + 'required' => false + ], - 'uri' => array( - 'name' => 'Item URL selector', - 'title' => <<<"EOL" + 'uri' => [ + 'name' => 'Item URL selector', + 'title' => <<<"EOL" This expression should match a node's attribute containing the article URL (usually the href attribute of an <a> tag). It should start with a dot followed by two forward slashes, referring to any descendant nodes of the article item node. Attributes can be selected by prepending an @ char before the attributes name. EOL - , 'type' => 'text', - 'exampleValue' => './/a[@class="ArticleLink ArticleLink"]/@href', - 'defaultValue' => './/a[@class="ArticleLink ArticleLink"]/@href', - 'required' => false - ), + , 'type' => 'text', + 'exampleValue' => './/a[@class="ArticleLink ArticleLink"]/@href', + 'defaultValue' => './/a[@class="ArticleLink ArticleLink"]/@href', + 'required' => false + ], - 'author' => array( - 'name' => 'Item author selector', - 'title' => <<<"EOL" + 'author' => [ + 'name' => 'Item author selector', + 'title' => <<<"EOL" This expression should match a node contained within each article item node containing the article author's name. It should start with a dot followed by two forward slashes, referring to any descendant nodes of the article item node. EOL - , 'type' => 'text', - 'required' => false - ), + , 'type' => 'text', + 'required' => false + ], - 'timestamp' => array( - 'name' => 'Item date selector', - 'title' => <<<"EOL" + 'timestamp' => [ + 'name' => 'Item date selector', + 'title' => <<<"EOL" This expression should match a node or node's attribute containing the article timestamp or date (parsable by PHP's strtotime function). It should start with a dot followed by two forward slashes, referring to any descendant nodes of the article item node. Attributes can be selected by prepending an @ char before the attributes name. EOL - , 'type' => 'text', - 'exampleValue' => './/time[@class="ArticleListItem-footerTimestamp"]/@timestamp', - 'defaultValue' => './/time[@class="ArticleListItem-footerTimestamp"]/@timestamp', - 'required' => false - ), + , 'type' => 'text', + 'exampleValue' => './/time[@class="ArticleListItem-footerTimestamp"]/@timestamp', + 'defaultValue' => './/time[@class="ArticleListItem-footerTimestamp"]/@timestamp', + 'required' => false + ], - 'enclosures' => array( - 'name' => 'Item image selector', - 'title' => <<<"EOL" + 'enclosures' => [ + 'name' => 'Item image selector', + 'title' => <<<"EOL" This expression should match a node's attribute containing an article image URL (usually the src attribute of an <img> tag or a style attribute). It should start with a dot followed by two forward slashes, referring to any descendant nodes of the article item node. Attributes can be selected by prepending an @ char before the attributes name. EOL - , 'type' => 'text', - 'exampleValue' => './/div[@class="ArticleListItem-image"]/@style', - 'defaultValue' => './/div[@class="ArticleListItem-image"]/@style', - 'required' => false - ), + , 'type' => 'text', + 'exampleValue' => './/div[@class="ArticleListItem-image"]/@style', + 'defaultValue' => './/div[@class="ArticleListItem-image"]/@style', + 'required' => false + ], - 'categories' => array( - 'name' => 'Item category selector', - 'title' => <<<"EOL" + 'categories' => [ + 'name' => 'Item category selector', + 'title' => <<<"EOL" This expression should match a node or node's attribute contained within each article item node containing the article category. This could be inside <div> or <span> tags or sometimes be hidden @@ -130,122 +131,134 @@ forward slashes, referring to any descendant nodes of the article item node. Attributes can be selected by prepending an @ char before the attributes name. EOL - , 'type' => 'text', - 'exampleValue' => './/div[@class="ArticleListItem-label"]', - 'defaultValue' => './/div[@class="ArticleListItem-label"]', - 'required' => false - ), + , 'type' => 'text', + 'exampleValue' => './/div[@class="ArticleListItem-label"]', + 'defaultValue' => './/div[@class="ArticleListItem-label"]', + 'required' => false + ], - 'fix_encoding' => array( - 'name' => 'Fix encoding', - 'title' => <<<"EOL" + 'fix_encoding' => [ + 'name' => 'Fix encoding', + 'title' => <<<"EOL" Check this to fix feed encoding by invoking PHP's utf8_decode function on all extracted texts. Try this in case you see "broken" or "weird" characters in your feed where you'd normally expect umlauts or any other non-ascii characters. EOL - , 'type' => 'checkbox', - 'required' => false - ), + , 'type' => 'checkbox', + 'required' => false + ], - ) - ); + ] + ]; - /** - * Source Web page URL (should provide either HTML or XML content) - * @return string - */ - protected function getSourceUrl(){ - return $this->encodeUri($this->getInput('url')); - } + /** + * Source Web page URL (should provide either HTML or XML content) + * @return string + */ + protected function getSourceUrl() + { + return $this->encodeUri($this->getInput('url')); + } - /** - * XPath expression for extracting the feed items from the source page - * @return string - */ - protected function getExpressionItem(){ - return urldecode($this->getInput('item')); - } + /** + * XPath expression for extracting the feed items from the source page + * @return string + */ + protected function getExpressionItem() + { + return urldecode($this->getInput('item')); + } - /** - * XPath expression for extracting an item title from the item context - * @return string - */ - protected function getExpressionItemTitle(){ - return urldecode($this->getInput('title')); - } + /** + * XPath expression for extracting an item title from the item context + * @return string + */ + protected function getExpressionItemTitle() + { + return urldecode($this->getInput('title')); + } - /** - * XPath expression for extracting an item's content from the item context - * @return string - */ - protected function getExpressionItemContent(){ - return urldecode($this->getInput('content')); - } + /** + * XPath expression for extracting an item's content from the item context + * @return string + */ + protected function getExpressionItemContent() + { + return urldecode($this->getInput('content')); + } - /** - * XPath expression for extracting an item link from the item context - * @return string - */ - protected function getExpressionItemUri(){ - return urldecode($this->getInput('uri')); - } + /** + * XPath expression for extracting an item link from the item context + * @return string + */ + protected function getExpressionItemUri() + { + return urldecode($this->getInput('uri')); + } - /** - * XPath expression for extracting an item author from the item context - * @return string - */ - protected function getExpressionItemAuthor(){ - return urldecode($this->getInput('author')); - } + /** + * XPath expression for extracting an item author from the item context + * @return string + */ + protected function getExpressionItemAuthor() + { + return urldecode($this->getInput('author')); + } - /** - * XPath expression for extracting an item timestamp from the item context - * @return string - */ - protected function getExpressionItemTimestamp(){ - return urldecode($this->getInput('timestamp')); - } + /** + * XPath expression for extracting an item timestamp from the item context + * @return string + */ + protected function getExpressionItemTimestamp() + { + return urldecode($this->getInput('timestamp')); + } - /** - * XPath expression for extracting item enclosures (media content like - * images or movies) from the item context - * @return string - */ - protected function getExpressionItemEnclosures(){ - return urldecode($this->getInput('enclosures')); - } + /** + * XPath expression for extracting item enclosures (media content like + * images or movies) from the item context + * @return string + */ + protected function getExpressionItemEnclosures() + { + return urldecode($this->getInput('enclosures')); + } - /** - * XPath expression for extracting an item category from the item context - * @return string - */ - protected function getExpressionItemCategories(){ - return urldecode($this->getInput('categories')); - } + /** + * XPath expression for extracting an item category from the item context + * @return string + */ + protected function getExpressionItemCategories() + { + return urldecode($this->getInput('categories')); + } - /** - * Fix encoding - * @return string - */ - protected function getSettingFixEncoding(){ - return $this->getInput('fix_encoding'); - } + /** + * Fix encoding + * @return string + */ + protected function getSettingFixEncoding() + { + return $this->getInput('fix_encoding'); + } - /** - * Fixes URL encoding issues in input URL's - * @param $uri - * @return string|string[] - */ - private function encodeUri($uri) - { - if (strpos($uri, 'https%3A%2F%2F') === 0 - || strpos($uri, 'http%3A%2F%2F') === 0) { - $uri = urldecode($uri); - } + /** + * Fixes URL encoding issues in input URL's + * @param $uri + * @return string|string[] + */ + private function encodeUri($uri) + { + if ( + strpos($uri, 'https%3A%2F%2F') === 0 + || strpos($uri, 'http%3A%2F%2F') === 0 + ) { + $uri = urldecode($uri); + } - $uri = str_replace('|', '%7C', $uri); + $uri = str_replace('|', '%7C', $uri); - return $uri; - } + return $uri; + } } diff --git a/bridges/XbooruBridge.php b/bridges/XbooruBridge.php index 2df4f4e1..d4a132e2 100644 --- a/bridges/XbooruBridge.php +++ b/bridges/XbooruBridge.php @@ -1,14 +1,15 @@ getURI() . 'thumbnails/' . $element->directory - . '/thumbnail_' . $element->hash . '.jpg'; - } + protected function buildThumbnailURI($element) + { + return $this->getURI() . 'thumbnails/' . $element->directory + . '/thumbnail_' . $element->hash . '.jpg'; + } } diff --git a/bridges/XenForoBridge.php b/bridges/XenForoBridge.php index 4904f6cf..1ecb1d74 100644 --- a/bridges/XenForoBridge.php +++ b/bridges/XenForoBridge.php @@ -1,4 +1,5 @@ [ + 'url' => [ + 'name' => 'Thread URL', + 'type' => 'text', + 'required' => true, + 'title' => 'Insert URL to the thread for which the feed should be generated', + 'exampleValue' => 'https://xenforo.com/community/threads/guide-to-suggestions.2285/' + ] + ], + 'global' => [ + 'limit' => [ + 'name' => 'Limit', + 'type' => 'number', + 'required' => false, + 'title' => 'Specify maximum number of elements to return in the feed', + 'defaultValue' => 10 + ] + ] + ]; + const CACHE_TIMEOUT = 7200; // 10 minutes - // RSS-Bridge constants - const NAME = 'XenForo Bridge'; - const URI = 'https://xenforo.com/'; - const DESCRIPTION = 'Generates feeds for threads in forums powered by XenForo'; - const MAINTAINER = 'logmanoriginal'; - const PARAMETERS = array( - self::CONTEXT_THREAD => array( - 'url' => array( - 'name' => 'Thread URL', - 'type' => 'text', - 'required' => true, - 'title' => 'Insert URL to the thread for which the feed should be generated', - 'exampleValue' => 'https://xenforo.com/community/threads/guide-to-suggestions.2285/' - ) - ), - 'global' => array( - 'limit' => array( - 'name' => 'Limit', - 'type' => 'number', - 'required' => false, - 'title' => 'Specify maximum number of elements to return in the feed', - 'defaultValue' => 10 - ) - ) - ); - const CACHE_TIMEOUT = 7200; // 10 minutes + private $title = ''; + private $threadurl = ''; + private $version; // Holds the XenForo version - private $title = ''; - private $threadurl = ''; - private $version; // Holds the XenForo version + public function getName() + { + switch ($this->queriedContext) { + case self::CONTEXT_THREAD: + return $this->title . ' - ' . static::NAME; + } - public function getName() { + return parent::getName(); + } - switch($this->queriedContext) { - case self::CONTEXT_THREAD: return $this->title . ' - ' . static::NAME; - } + public function getURI() + { + switch ($this->queriedContext) { + case self::CONTEXT_THREAD: + return $this->threadurl; + } - return parent::getName(); + return parent::getURI(); + } - } + public function collectData() + { + $this->threadurl = filter_var( + $this->getInput('url'), + FILTER_VALIDATE_URL, + FILTER_FLAG_PATH_REQUIRED + ); - public function getURI() { + if ($this->threadurl === false) { + returnClientError('The URL you provided is invalid!'); + } - switch($this->queriedContext) { - case self::CONTEXT_THREAD: return $this->threadurl; - } + $urlparts = parse_url($this->threadurl, PHP_URL_SCHEME); - return parent::getURI(); + // Scheme must be "http" or "https" + if (preg_match('/http[s]{0,1}/', parse_url($this->threadurl, PHP_URL_SCHEME)) == false) { + returnClientError('The URL you provided doesn\'t specify a valid scheme (http or https)!'); + } - } + // Path cannot be root (../) + if (parse_url($this->threadurl, PHP_URL_PATH) === '/') { + returnClientError('The URL you provided doesn\'t link to a valid thread (root path)!'); + } - public function collectData() { + // XenForo adds a thread ID to the URL, like "...-thread.454934283". It must be present + if (preg_match('/.+\.\d+[\/]{0,1}/', parse_URL($this->threadurl, PHP_URL_PATH)) == false) { + returnClientError('The URL you provided doesn\'t link to a valid thread (ID missing)!'); + } - $this->threadurl = filter_var( - $this->getInput('url'), - FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED); + // We want to start at the first page in the thread. XenForo uses "../page-n" syntax + // to identify pages (except for the first page). + // Notice: XenForo uses the concept of "sentinels" to find and replace parts in the + // URL. Technically forum hosts can change the syntax! + if (preg_match('/.+\/(page-\d+.*)$/', $this->threadurl, $matches) != false) { + // before: https://xenforo.com/community/threads/guide-to-suggestions.2285/page-5 + // after : https://xenforo.com/community/threads/guide-to-suggestions.2285/ + $this->threadurl = str_replace($matches[1], '', $this->threadurl); + } - if($this->threadurl === false) { - returnClientError('The URL you provided is invalid!'); - } + $html = getSimpleHTMLDOMCached($this->threadurl); - $urlparts = parse_url($this->threadurl, PHP_URL_SCHEME); + $html = defaultLinkTo($html, $this->threadurl); - // Scheme must be "http" or "https" - if(preg_match('/http[s]{0,1}/', parse_url($this->threadurl, PHP_URL_SCHEME)) == false) { - returnClientError('The URL you provided doesn\'t specify a valid scheme (http or https)!'); - } + // Notice: The DOM structure changes depending on the XenForo version used + if ($mainContent = $html->find('div.mainContent', 0)) { + $this->version = self::XENFORO_VERSION_1; + } elseif ($mainContent = $html->find('div[class~="p-body"]', 0)) { + $this->version = self::XENFORO_VERSION_2; + } else { + returnServerError('This forum is currently not supported!'); + } - // Path cannot be root (../) - if(parse_url($this->threadurl, PHP_URL_PATH) === '/') { - returnClientError('The URL you provided doesn\'t link to a valid thread (root path)!'); - } + switch ($this->version) { + case self::XENFORO_VERSION_1: + $titleBar = $mainContent->find('div.titleBar > h1', 0) + or returnServerError('Error finding title bar!'); - // XenForo adds a thread ID to the URL, like "...-thread.454934283". It must be present - if(preg_match('/.+\.\d+[\/]{0,1}/', parse_URL($this->threadurl, PHP_URL_PATH)) == false) { - returnClientError('The URL you provided doesn\'t link to a valid thread (ID missing)!'); - } + $this->title = $titleBar->plaintext; - // We want to start at the first page in the thread. XenForo uses "../page-n" syntax - // to identify pages (except for the first page). - // Notice: XenForo uses the concept of "sentinels" to find and replace parts in the - // URL. Technically forum hosts can change the syntax! - if(preg_match('/.+\/(page-\d+.*)$/', $this->threadurl, $matches) != false) { + // Store items from current page (we'll use $this->items as LIFO buffer) + $this->extractThreadPostsV1($html, $this->threadurl); + $this->extractPagesV1($html); - // before: https://xenforo.com/community/threads/guide-to-suggestions.2285/page-5 - // after : https://xenforo.com/community/threads/guide-to-suggestions.2285/ - $this->threadurl = str_replace($matches[1], '', $this->threadurl); + break; - } + case self::XENFORO_VERSION_2: + $titleBar = $mainContent->find('div[class~="p-title"] h1', 0) + or returnServerError('Error finding title bar!'); - $html = getSimpleHTMLDOMCached($this->threadurl); + $this->title = $titleBar->plaintext; + $this->extractThreadPostsV2($html, $this->threadurl); + $this->extractPagesV2($html); - $html = defaultLinkTo($html, $this->threadurl); + break; + } - // Notice: The DOM structure changes depending on the XenForo version used - if($mainContent = $html->find('div.mainContent', 0)) { - $this->version = self::XENFORO_VERSION_1; - } elseif ($mainContent = $html->find('div[class~="p-body"]', 0)) { - $this->version = self::XENFORO_VERSION_2; - } else { - returnServerError('This forum is currently not supported!'); - } + usort($this->items, function ($a, $b) { + return $b['timestamp'] <=> $a['timestamp']; + }); - switch($this->version) { - case self::XENFORO_VERSION_1: + $this->items = array_slice($this->items, 0, $this->getInput('limit')); + } - $titleBar = $mainContent->find('div.titleBar > h1', 0) - or returnServerError('Error finding title bar!'); + /** + * Extracts thread posts + * @param $html A simplehtmldom object + * @param $url The url from which $html was loaded + */ + private function extractThreadPostsV1($html, $url) + { + $lang = $html->find('html', 0)->lang; - $this->title = $titleBar->plaintext; + // Posts are contained in an "ol" + $messageList = $html->find('#messageList > li') + or returnServerError('Error finding message list!'); - // Store items from current page (we'll use $this->items as LIFO buffer) - $this->extractThreadPostsV1($html, $this->threadurl); - $this->extractPagesV1($html); + foreach ($messageList as $post) { + if (!isset($post->attr['id'])) { // Skip ads + continue; + } - break; + $item = []; - case self::XENFORO_VERSION_2: + $item['uri'] = $url . '#' . $post->getAttribute('id'); - $titleBar = $mainContent->find('div[class~="p-title"] h1', 0) - or returnServerError('Error finding title bar!'); + $content = $post->find('.messageContent > article', 0); - $this->title = $titleBar->plaintext; - $this->extractThreadPostsV2($html, $this->threadurl); - $this->extractPagesV2($html); - - break; - } - - usort($this->items, function($a, $b) { - return $b['timestamp'] <=> $a['timestamp']; - }); - - $this->items = array_slice($this->items, 0, $this->getInput('limit')); - } - - /** - * Extracts thread posts - * @param $html A simplehtmldom object - * @param $url The url from which $html was loaded - */ - private function extractThreadPostsV1($html, $url) { - - $lang = $html->find('html', 0)->lang; - - // Posts are contained in an "ol" - $messageList = $html->find('#messageList > li') - or returnServerError('Error finding message list!'); - - foreach($messageList as $post) { - - if(!isset($post->attr['id'])) { // Skip ads - continue; - } - - $item = array(); - - $item['uri'] = $url . '#' . $post->getAttribute('id'); - - $content = $post->find('.messageContent > article', 0); - - // Add some style to quotes - foreach($content->find('.bbCodeQuote') as $quote) { - $quote->style = ' + // Add some style to quotes + foreach ($content->find('.bbCodeQuote') as $quote) { + $quote->style = ' color: #495566; background-color: rgb(248,251,253); border: 1px solid rgb(111, 140, 180); border-color: rgb(111, 140, 180); font-style: italic;'; - } - - // Remove script tags - foreach($content->find('script') as $script) { - $script->outertext = ''; - } - - $item['content'] = $content->innertext; - - // Remove quotes (for the title) - foreach($content->find('.bbCodeQuote') as $quote) { - $quote->innertext = ''; - } - - $title = trim($content->plaintext); - - if(strlen($title) > 70) { - $item['title'] = substr($title, 0, strpos($title, ' ', 70)) . '...'; - } else { - $item['title'] = $title; - } - - /** - * Timestamps are presented in two forms: - * - * 1) short version (for older posts?) - * 22 Oct. 2018 - * - * This form has to be interpreted depending on the current language. - * - * 2) long version (for newer posts?) - * Wednesday at 18:59 - * - * This form has the timestamp embedded (data-time) - */ - if($timestamp = $post->find('abbr.DateTime', 0)) { // long version (preffered) - $item['timestamp'] = $timestamp->{'data-time'}; - } elseif($timestamp = $post->find('span.DateTime', 0)) { // short version - $item['timestamp'] = $this->fixDate($timestamp->title, $lang); - } - - $item['author'] = $post->getAttribute('data-author'); - - // Bridge specific properties - $item['id'] = $post->getAttribute('id'); - - $this->items[] = $item; - - } - - } - - private function extractThreadPostsV2($html, $url) { - - $lang = $html->find('html', 0)->lang; - - $messageList = $html->find('div[class~="block-body"] article') - or returnServerError('Error finding message list!'); - - foreach($messageList as $post) { - - if(!isset($post->attr['id'])) { // Skip ads - continue; - } - - $item = array(); - - $item['uri'] = $url . '#' . $post->getAttribute('id'); - - $title = $post->find('div[class~="message-content"] article', 0)->plaintext; - $end = strpos($title, ' ', min(70, strlen($title))); - $item['title'] = substr($title, 0, $end); - - if ($post->find('time[datetime]', 0)) { - $item['timestamp'] = $post->find('time[datetime]', 0)->datetime; - } else { - $item['timestamp'] = $this->fixDate($post->find('time', 0)->title, $lang); - } - $item['author'] = $post->getAttribute('data-author'); - $item['content'] = $post->find('div[class~="message-content"] article', 0); - - // Bridge specific properties - $item['id'] = $post->getAttribute('id'); - - $this->items[] = $item; - - } - - } - - private function extractPagesV1($html) { - - // A navigation bar becomes available if the number of posts grows too - // high. When this happens we need to load further pages (from last backwards) - if(($pageNav = $html->find('div.PageNav', 0))) { - - $lastpage = $pageNav->{'data-last'}; - $baseurl = $pageNav->{'data-baseurl'}; - $sentinel = $pageNav->{'data-sentinel'}; - - $hosturl = parse_url($this->threadurl, PHP_URL_SCHEME) - . '://' - . parse_url($this->threadurl, PHP_URL_HOST) - . '/'; - - $page = $lastpage; - - // Load at least the last page - do { - - $pageurl = str_replace($sentinel, $lastpage, $baseurl); - - // We can optimize performance by caching all but the last page - if($page != $lastpage) { - $html = getSimpleHTMLDOMCached($pageurl) - or returnServerError('Error loading contents from ' . $pageurl . '!'); - } else { - $html = getSimpleHTMLDOM($pageurl) - or returnServerError('Error loading contents from ' . $pageurl . '!'); - } - - $html = defaultLinkTo($html, $hosturl); - - $this->extractThreadPostsV1($html, $pageurl); - - $page--; - - } while (count($this->items) < $this->getInput('limit') && $page != 1); - - } - - } - - private function extractPagesV2($html) { - - // A navigation bar becomes available if the number of posts grows too - // high. When this happens we need to load further pages (from last backwards) - if(($pageNav = $html->find('div.pageNav', 0))) { - - foreach($pageNav->find('li') as $nav) { - $lastpage = $nav->plaintext; - } - - // Manually extract baseurl and inject sentinel - $baseurl = $pageNav->find('li > a', -1)->href; - $baseurl = str_replace('page-' . $lastpage, 'page-{{sentinel}}', $baseurl); - - $sentinel = '{{sentinel}}'; - - $hosturl = parse_url($this->threadurl, PHP_URL_SCHEME) - . '://' - . parse_url($this->threadurl, PHP_URL_HOST); - - $page = $lastpage; - - // Load at least the last page - do { - - $pageurl = str_replace($sentinel, $lastpage, $baseurl); - - // We can optimize performance by caching all but the last page - if($page != $lastpage) { - $html = getSimpleHTMLDOMCached($pageurl) - or returnServerError('Error loading contents from ' . $pageurl . '!'); - } else { - $html = getSimpleHTMLDOM($pageurl) - or returnServerError('Error loading contents from ' . $pageurl . '!'); - } - - $html = defaultLinkTo($html, $hosturl); - - $this->extractThreadPostsV2($html, $pageurl); - - $page--; - - } while (count($this->items) < $this->getInput('limit') && $page != 1); - - } - - } - - /** - * Fixes dates depending on the choosen language: - * - * de : dd.mm.yy - * en : dd.mm.yy - * it : dd/mm/yy - * - * Basically strtotime doesn't convert dates correctly due to formats - * being hard to interpret. So we use the DateTime object. - * - * We don't know the timezone, so just assume +00:00 (or whatever - * DateTime chooses) - */ - private function fixDate($date, $lang = 'en-US') { - - $mnamesen = array( - 'January', - 'Feburary', - 'March', - 'April', - 'May', - 'June', - 'July', - 'August', - 'September', - 'October', - 'November', - 'December' - ); - - switch($lang) { - case 'en-US': // example: Jun 9, 2018 at 11:46 PM - - $df = date_create_from_format('M d, Y \a\t H:i A', $date); - break; - - case 'de-DE': // example: 19 Juli 2018 um 19:27 Uhr - - $mnamesde = array( - 'Januar', - 'Februar', - 'März', - 'April', - 'Mai', - 'Juni', - 'Juli', - 'August', - 'September', - 'Oktober', - 'November', - 'Dezember' - ); - - $mnamesdeshort = array( - 'Jan.', - 'Feb.', - 'Mär.', - 'Apr.', - 'Mai', - 'Juni', - 'Juli', - 'Aug.', - 'Sep.', - 'Okt.', - 'Nov.', - 'Dez.' - ); - - $date = str_ireplace($mnamesde, $mnamesen, $date); - $date = str_ireplace($mnamesdeshort, $mnamesen, $date); - - $df = date_create_from_format('d M Y \u\m H:i \U\h\r', $date); - break; - - } - - // Debug::log(date_format($df, 'U')); - - return date_format($df, 'U'); - - } + } + + // Remove script tags + foreach ($content->find('script') as $script) { + $script->outertext = ''; + } + + $item['content'] = $content->innertext; + + // Remove quotes (for the title) + foreach ($content->find('.bbCodeQuote') as $quote) { + $quote->innertext = ''; + } + + $title = trim($content->plaintext); + + if (strlen($title) > 70) { + $item['title'] = substr($title, 0, strpos($title, ' ', 70)) . '...'; + } else { + $item['title'] = $title; + } + + /** + * Timestamps are presented in two forms: + * + * 1) short version (for older posts?) + * 22 Oct. 2018 + * + * This form has to be interpreted depending on the current language. + * + * 2) long version (for newer posts?) + * Wednesday at 18:59 + * + * This form has the timestamp embedded (data-time) + */ + if ($timestamp = $post->find('abbr.DateTime', 0)) { // long version (preffered) + $item['timestamp'] = $timestamp->{'data-time'}; + } elseif ($timestamp = $post->find('span.DateTime', 0)) { // short version + $item['timestamp'] = $this->fixDate($timestamp->title, $lang); + } + + $item['author'] = $post->getAttribute('data-author'); + + // Bridge specific properties + $item['id'] = $post->getAttribute('id'); + + $this->items[] = $item; + } + } + + private function extractThreadPostsV2($html, $url) + { + $lang = $html->find('html', 0)->lang; + + $messageList = $html->find('div[class~="block-body"] article') + or returnServerError('Error finding message list!'); + + foreach ($messageList as $post) { + if (!isset($post->attr['id'])) { // Skip ads + continue; + } + + $item = []; + + $item['uri'] = $url . '#' . $post->getAttribute('id'); + + $title = $post->find('div[class~="message-content"] article', 0)->plaintext; + $end = strpos($title, ' ', min(70, strlen($title))); + $item['title'] = substr($title, 0, $end); + + if ($post->find('time[datetime]', 0)) { + $item['timestamp'] = $post->find('time[datetime]', 0)->datetime; + } else { + $item['timestamp'] = $this->fixDate($post->find('time', 0)->title, $lang); + } + $item['author'] = $post->getAttribute('data-author'); + $item['content'] = $post->find('div[class~="message-content"] article', 0); + + // Bridge specific properties + $item['id'] = $post->getAttribute('id'); + + $this->items[] = $item; + } + } + + private function extractPagesV1($html) + { + // A navigation bar becomes available if the number of posts grows too + // high. When this happens we need to load further pages (from last backwards) + if (($pageNav = $html->find('div.PageNav', 0))) { + $lastpage = $pageNav->{'data-last'}; + $baseurl = $pageNav->{'data-baseurl'}; + $sentinel = $pageNav->{'data-sentinel'}; + + $hosturl = parse_url($this->threadurl, PHP_URL_SCHEME) + . '://' + . parse_url($this->threadurl, PHP_URL_HOST) + . '/'; + + $page = $lastpage; + + // Load at least the last page + do { + $pageurl = str_replace($sentinel, $lastpage, $baseurl); + + // We can optimize performance by caching all but the last page + if ($page != $lastpage) { + $html = getSimpleHTMLDOMCached($pageurl) + or returnServerError('Error loading contents from ' . $pageurl . '!'); + } else { + $html = getSimpleHTMLDOM($pageurl) + or returnServerError('Error loading contents from ' . $pageurl . '!'); + } + + $html = defaultLinkTo($html, $hosturl); + + $this->extractThreadPostsV1($html, $pageurl); + + $page--; + } while (count($this->items) < $this->getInput('limit') && $page != 1); + } + } + + private function extractPagesV2($html) + { + // A navigation bar becomes available if the number of posts grows too + // high. When this happens we need to load further pages (from last backwards) + if (($pageNav = $html->find('div.pageNav', 0))) { + foreach ($pageNav->find('li') as $nav) { + $lastpage = $nav->plaintext; + } + + // Manually extract baseurl and inject sentinel + $baseurl = $pageNav->find('li > a', -1)->href; + $baseurl = str_replace('page-' . $lastpage, 'page-{{sentinel}}', $baseurl); + + $sentinel = '{{sentinel}}'; + + $hosturl = parse_url($this->threadurl, PHP_URL_SCHEME) + . '://' + . parse_url($this->threadurl, PHP_URL_HOST); + + $page = $lastpage; + + // Load at least the last page + do { + $pageurl = str_replace($sentinel, $lastpage, $baseurl); + + // We can optimize performance by caching all but the last page + if ($page != $lastpage) { + $html = getSimpleHTMLDOMCached($pageurl) + or returnServerError('Error loading contents from ' . $pageurl . '!'); + } else { + $html = getSimpleHTMLDOM($pageurl) + or returnServerError('Error loading contents from ' . $pageurl . '!'); + } + + $html = defaultLinkTo($html, $hosturl); + + $this->extractThreadPostsV2($html, $pageurl); + + $page--; + } while (count($this->items) < $this->getInput('limit') && $page != 1); + } + } + + /** + * Fixes dates depending on the choosen language: + * + * de : dd.mm.yy + * en : dd.mm.yy + * it : dd/mm/yy + * + * Basically strtotime doesn't convert dates correctly due to formats + * being hard to interpret. So we use the DateTime object. + * + * We don't know the timezone, so just assume +00:00 (or whatever + * DateTime chooses) + */ + private function fixDate($date, $lang = 'en-US') + { + $mnamesen = [ + 'January', + 'Feburary', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December' + ]; + + switch ($lang) { + case 'en-US': // example: Jun 9, 2018 at 11:46 PM + $df = date_create_from_format('M d, Y \a\t H:i A', $date); + break; + + case 'de-DE': // example: 19 Juli 2018 um 19:27 Uhr + $mnamesde = [ + 'Januar', + 'Februar', + 'März', + 'April', + 'Mai', + 'Juni', + 'Juli', + 'August', + 'September', + 'Oktober', + 'November', + 'Dezember' + ]; + + $mnamesdeshort = [ + 'Jan.', + 'Feb.', + 'Mär.', + 'Apr.', + 'Mai', + 'Juni', + 'Juli', + 'Aug.', + 'Sep.', + 'Okt.', + 'Nov.', + 'Dez.' + ]; + + $date = str_ireplace($mnamesde, $mnamesen, $date); + $date = str_ireplace($mnamesdeshort, $mnamesen, $date); + + $df = date_create_from_format('d M Y \u\m H:i \U\h\r', $date); + break; + } + + // Debug::log(date_format($df, 'U')); + + return date_format($df, 'U'); + } } diff --git a/bridges/YGGTorrentBridge.php b/bridges/YGGTorrentBridge.php index 30b5ca7a..f0c31f11 100644 --- a/bridges/YGGTorrentBridge.php +++ b/bridges/YGGTorrentBridge.php @@ -3,148 +3,156 @@ /* This is a mashup of FlickrExploreBridge by sebsauvage and FlickrTagBridge * by erwang.providing the functionality of both in one. */ -class YGGTorrentBridge extends BridgeAbstract { +class YGGTorrentBridge extends BridgeAbstract +{ + const MAINTAINER = 'teromene'; + const NAME = 'Yggtorrent Bridge'; + const URI = 'https://www5.yggtorrent.fi'; + const DESCRIPTION = 'Returns torrent search from Yggtorrent'; - const MAINTAINER = 'teromene'; - const NAME = 'Yggtorrent Bridge'; - const URI = 'https://www5.yggtorrent.fi'; - const DESCRIPTION = 'Returns torrent search from Yggtorrent'; + const PARAMETERS = [ + [ + 'cat' => [ + 'name' => 'category', + 'type' => 'list', + 'values' => [ + 'Toutes les catégories' => 'all.all', + 'Film/Vidéo - Toutes les sous-catégories' => '2145.all', + 'Film/Vidéo - Animation' => '2145.2178', + 'Film/Vidéo - Animation Série' => '2145.2179', + 'Film/Vidéo - Concert' => '2145.2180', + 'Film/Vidéo - Documentaire' => '2145.2181', + 'Film/Vidéo - Émission TV' => '2145.2182', + 'Film/Vidéo - Film' => '2145.2183', + 'Film/Vidéo - Série TV' => '2145.2184', + 'Film/Vidéo - Spectacle' => '2145.2185', + 'Film/Vidéo - Sport' => '2145.2186', + 'Film/Vidéo - Vidéo-clips' => '2145.2186', + 'Audio - Toutes les sous-catégories' => '2139.all', + 'Audio - Karaoké' => '2139.2147', + 'Audio - Musique' => '2139.2148', + 'Audio - Podcast Radio' => '2139.2150', + 'Audio - Samples' => '2139.2149', + 'Jeu vidéo - Toutes les sous-catégories' => '2142.all', + 'Jeu vidéo - Autre' => '2142.2167', + 'Jeu vidéo - Linux' => '2142.2159', + 'Jeu vidéo - MacOS' => '2142.2160', + 'Jeu vidéo - Microsoft' => '2142.2162', + 'Jeu vidéo - Nintendo' => '2142.2163', + 'Jeu vidéo - Smartphone' => '2142.2165', + 'Jeu vidéo - Sony' => '2142.2164', + 'Jeu vidéo - Tablette' => '2142.2166', + 'Jeu vidéo - Windows' => '2142.2161', + 'eBook - Toutes les sous-catégories' => '2140.all', + 'eBook - Audio' => '2140.2151', + 'eBook - Bds' => '2140.2152', + 'eBook - Comics' => '2140.2153', + 'eBook - Livres' => '2140.2154', + 'eBook - Mangas' => '2140.2155', + 'eBook - Presse' => '2140.2156', + 'Emulation - Toutes les sous-catégories' => '2141.all', + 'Emulation - Emulateurs' => '2141.2157', + 'Emulation - Roms' => '2141.2158', + 'GPS - Toutes les sous-catégories' => '2141.all', + 'GPS - Applications' => '2141.2168', + 'GPS - Cartes' => '2141.2169', + 'GPS - Divers' => '2141.2170' + ] + ], + 'nom' => [ + 'name' => 'Nom', + 'description' => 'Nom du torrent', + 'type' => 'text', + 'exampleValue' => 'matrix' + ], + 'description' => [ + 'name' => 'Description', + 'description' => 'Description du torrent', + 'type' => 'text' + ], + 'fichier' => [ + 'name' => 'Fichier', + 'description' => 'Fichier du torrent', + 'type' => 'text' + ], + 'uploader' => [ + 'name' => 'Uploader', + 'description' => 'Uploader du torrent', + 'type' => 'text' + ], - const PARAMETERS = array( - array( - 'cat' => array( - 'name' => 'category', - 'type' => 'list', - 'values' => array( - 'Toutes les catégories' => 'all.all', - 'Film/Vidéo - Toutes les sous-catégories' => '2145.all', - 'Film/Vidéo - Animation' => '2145.2178', - 'Film/Vidéo - Animation Série' => '2145.2179', - 'Film/Vidéo - Concert' => '2145.2180', - 'Film/Vidéo - Documentaire' => '2145.2181', - 'Film/Vidéo - Émission TV' => '2145.2182', - 'Film/Vidéo - Film' => '2145.2183', - 'Film/Vidéo - Série TV' => '2145.2184', - 'Film/Vidéo - Spectacle' => '2145.2185', - 'Film/Vidéo - Sport' => '2145.2186', - 'Film/Vidéo - Vidéo-clips' => '2145.2186', - 'Audio - Toutes les sous-catégories' => '2139.all', - 'Audio - Karaoké' => '2139.2147', - 'Audio - Musique' => '2139.2148', - 'Audio - Podcast Radio' => '2139.2150', - 'Audio - Samples' => '2139.2149', - 'Jeu vidéo - Toutes les sous-catégories' => '2142.all', - 'Jeu vidéo - Autre' => '2142.2167', - 'Jeu vidéo - Linux' => '2142.2159', - 'Jeu vidéo - MacOS' => '2142.2160', - 'Jeu vidéo - Microsoft' => '2142.2162', - 'Jeu vidéo - Nintendo' => '2142.2163', - 'Jeu vidéo - Smartphone' => '2142.2165', - 'Jeu vidéo - Sony' => '2142.2164', - 'Jeu vidéo - Tablette' => '2142.2166', - 'Jeu vidéo - Windows' => '2142.2161', - 'eBook - Toutes les sous-catégories' => '2140.all', - 'eBook - Audio' => '2140.2151', - 'eBook - Bds' => '2140.2152', - 'eBook - Comics' => '2140.2153', - 'eBook - Livres' => '2140.2154', - 'eBook - Mangas' => '2140.2155', - 'eBook - Presse' => '2140.2156', - 'Emulation - Toutes les sous-catégories' => '2141.all', - 'Emulation - Emulateurs' => '2141.2157', - 'Emulation - Roms' => '2141.2158', - 'GPS - Toutes les sous-catégories' => '2141.all', - 'GPS - Applications' => '2141.2168', - 'GPS - Cartes' => '2141.2169', - 'GPS - Divers' => '2141.2170' - ) - ), - 'nom' => array( - 'name' => 'Nom', - 'description' => 'Nom du torrent', - 'type' => 'text', - 'exampleValue' => 'matrix' - ), - 'description' => array( - 'name' => 'Description', - 'description' => 'Description du torrent', - 'type' => 'text' - ), - 'fichier' => array( - 'name' => 'Fichier', - 'description' => 'Fichier du torrent', - 'type' => 'text' - ), - 'uploader' => array( - 'name' => 'Uploader', - 'description' => 'Uploader du torrent', - 'type' => 'text' - ), + ] + ]; - ) - ); + public function collectData() + { + $catInfo = explode('.', $this->getInput('cat')); + $category = $catInfo[0]; + $subcategory = $catInfo[1]; - public function collectData() { - $catInfo = explode('.', $this->getInput('cat')); - $category = $catInfo[0]; - $subcategory = $catInfo[1]; + $html = getSimpleHTMLDOM(self::URI . '/engine/search?name=' + . $this->getInput('nom') + . '&description=' + . $this->getInput('description') + . '&file=' + . $this->getInput('fichier') + . '&uploader=' + . $this->getInput('uploader') + . '&category=' + . $category + . '&sub_category=' + . $subcategory + . '&do=search&order=desc&sort=publish_date'); - $html = getSimpleHTMLDOM(self::URI . '/engine/search?name=' - . $this->getInput('nom') - . '&description=' - . $this->getInput('description') - . '&file=' - . $this->getInput('fichier') - . '&uploader=' - . $this->getInput('uploader') - . '&category=' - . $category - . '&sub_category=' - . $subcategory - . '&do=search&order=desc&sort=publish_date'); + $count = 0; + $results = $html->find('.results', 0); + if (!$results) { + return; + } - $count = 0; - $results = $html->find('.results', 0); - if(!$results) return; + foreach ($results->find('tr') as $row) { + $count++; + if ($count == 1) { + continue; // Skip table header + } + if ($count == 22) { + break; // Stop processing after 21 items (20 + 1 table header) + } + $item = []; + $item['timestamp'] = $row->find('.hidden', 1)->plaintext; + $item['title'] = $row->find('a#torrent_name', 0)->plaintext; + $item['uri'] = $this->processLink($row->find('a#torrent_name', 0)->href); + $item['seeders'] = $row->find('td', 7)->plaintext; + $item['leechers'] = $row->find('td', 8)->plaintext; + $item['size'] = $row->find('td', 5)->plaintext; + $item = array_merge($item, $this->collectTorrentData($item['uri'])); - foreach($results->find('tr') as $row) { - $count++; - if($count == 1) continue; // Skip table header - if($count == 22) break; // Stop processing after 21 items (20 + 1 table header) - $item = array(); - $item['timestamp'] = $row->find('.hidden', 1)->plaintext; - $item['title'] = $row->find('a#torrent_name', 0)->plaintext; - $item['uri'] = $this->processLink($row->find('a#torrent_name', 0)->href); - $item['seeders'] = $row->find('td', 7)->plaintext; - $item['leechers'] = $row->find('td', 8)->plaintext; - $item['size'] = $row->find('td', 5)->plaintext; - $item = array_merge($item, $this->collectTorrentData($item['uri'])); + $this->items[] = $item; + } + } - $this->items[] = $item; - } + /** + * Convert special characters like é to %C3%A9 in the url + */ + private function processLink($url) + { + $url = explode('/', $url); + foreach ($url as $index => $value) { + // Skip https://{self::URI}/ + if ($index < 3) { + continue; + } + // Decode first so that characters like + are not encoded + $url[$index] = urlencode(urldecode($value)); + } + return implode('/', $url); + } - } - - /** - * Convert special characters like é to %C3%A9 in the url - */ - private function processLink($url) { - $url = explode('/', $url); - foreach($url as $index => $value) { - // Skip https://{self::URI}/ - if ($index < 3) { - continue; - } - // Decode first so that characters like + are not encoded - $url[$index] = urlencode(urldecode($value)); - } - return implode('/', $url); - } - - private function collectTorrentData($url) { - $page = defaultLinkTo(getSimpleHTMLDOMCached($url), self::URI); - $author = $page->find('.informations tr', 5)->find('td', 1)->plaintext; - $content = $page->find('.default', 1); - return array('author' => $author, 'content' => $content); - } + private function collectTorrentData($url) + { + $page = defaultLinkTo(getSimpleHTMLDOMCached($url), self::URI); + $author = $page->find('.informations tr', 5)->find('td', 1)->plaintext; + $content = $page->find('.default', 1); + return ['author' => $author, 'content' => $content]; + } } diff --git a/bridges/YandereBridge.php b/bridges/YandereBridge.php index 9a93ef6c..0dc11022 100644 --- a/bridges/YandereBridge.php +++ b/bridges/YandereBridge.php @@ -1,10 +1,9 @@ array( - 'name' => 'Search query', - 'type' => 'text', - 'required' => true, - 'title' => 'Insert your search term here', - 'exampleValue' => 'vase' - ), - 'sortby' => array( - 'name' => 'Sort by', - 'type' => 'list', - 'required' => false, - 'values' => array( - 'Best match' => '0', - 'Popular' => '1', - 'Latest' => '2', - ), - 'defaultValue' => 'newest' - ), - 'show' => array( - 'name' => 'Show', - 'type' => 'list', - 'required' => false, - 'values' => array( - 'All' => '0', - 'Free' => '1', - 'For sale' => '2', - ), - 'defaultValue' => 'all' - ), - 'showimage' => array( - 'name' => 'Show image in content', - 'type' => 'checkbox', - 'required' => false, - 'title' => 'Activate to show the image in the content', - 'defaultValue' => 'checked' - ) - ) - ); +class YeggiBridge extends BridgeAbstract +{ + const NAME = 'Yeggi Search'; + const URI = 'https://www.yeggi.com'; + const DESCRIPTION = 'Returns 3D Models from Thingiverse, MyMiniFactory, Cults3D, and more'; + const MAINTAINER = 'AntoineTurmel'; + const PARAMETERS = [ + [ + 'query' => [ + 'name' => 'Search query', + 'type' => 'text', + 'required' => true, + 'title' => 'Insert your search term here', + 'exampleValue' => 'vase' + ], + 'sortby' => [ + 'name' => 'Sort by', + 'type' => 'list', + 'required' => false, + 'values' => [ + 'Best match' => '0', + 'Popular' => '1', + 'Latest' => '2', + ], + 'defaultValue' => 'newest' + ], + 'show' => [ + 'name' => 'Show', + 'type' => 'list', + 'required' => false, + 'values' => [ + 'All' => '0', + 'Free' => '1', + 'For sale' => '2', + ], + 'defaultValue' => 'all' + ], + 'showimage' => [ + 'name' => 'Show image in content', + 'type' => 'checkbox', + 'required' => false, + 'title' => 'Activate to show the image in the content', + 'defaultValue' => 'checked' + ] + ] + ]; - public function collectData(){ - $html = getSimpleHTMLDOM($this->getURI()); + public function collectData() + { + $html = getSimpleHTMLDOM($this->getURI()); - $results = $html->find('div.item_1_A'); + $results = $html->find('div.item_1_A'); - foreach($results as $result) { + foreach ($results as $result) { + $item = []; + $title = $result->find('.item_3_B_2', 0)->plaintext; + $explodeTitle = explode('  ', $title); + if (count($explodeTitle) == 2) { + $item['title'] = $explodeTitle[1]; + } else { + $item['title'] = $explodeTitle[0]; + } + $item['uri'] = self::URI . $result->find('a', 0)->href; + $item['author'] = 'Yeggi'; - $item = array(); - $title = $result->find('.item_3_B_2', 0)->plaintext; - $explodeTitle = explode('  ', $title); - if(count($explodeTitle) == 2) { - $item['title'] = $explodeTitle[1]; - } else { - $item['title'] = $explodeTitle[0]; - } - $item['uri'] = self::URI . $result->find('a', 0)->href; - $item['author'] = 'Yeggi'; + $text = $result->find('i'); + $item['content'] = $text[0]->plaintext . ' on ' . $text[1]->plaintext; + $item['uid'] = hash('md5', $item['title']); - $text = $result->find('i'); - $item['content'] = $text[0]->plaintext . ' on ' . $text[1]->plaintext; - $item['uid'] = hash('md5', $item['title']); + foreach ($result->find('.item_3_B_2 > a[href^=/q/]') as $tag) { + $item['tags'][] = $tag->plaintext; + } - foreach($result->find('.item_3_B_2 > a[href^=/q/]') as $tag) { - $item['tags'][] = $tag->plaintext; - } + $image = $result->find('img', 0)->src; - $image = $result->find('img', 0)->src; + if ($this->getInput('showimage')) { + $item['content'] .= '
'; + } - if($this->getInput('showimage')) { - $item['content'] .= '
'; - } + $item['enclosures'] = [$image]; - $item['enclosures'] = array($image); + $this->items[] = $item; + } + } - $this->items[] = $item; - } - } + public function getURI() + { + if (!is_null($this->getInput('query'))) { + $uri = self::URI . '/q/' . urlencode($this->getInput('query')) . '/'; + $uri .= '?o_f=' . $this->getInput('show'); + $uri .= '&o_s=' . $this->getInput('sortby'); - public function getURI(){ - if(!is_null($this->getInput('query'))) { - $uri = self::URI . '/q/' . urlencode($this->getInput('query')) . '/'; - $uri .= '?o_f=' . $this->getInput('show'); - $uri .= '&o_s=' . $this->getInput('sortby'); + return $uri; + } - return $uri; - } - - return parent::getURI(); - } + return parent::getURI(); + } } diff --git a/bridges/YouTubeCommunityTabBridge.php b/bridges/YouTubeCommunityTabBridge.php index 842e7489..c44e9557 100644 --- a/bridges/YouTubeCommunityTabBridge.php +++ b/bridges/YouTubeCommunityTabBridge.php @@ -1,271 +1,280 @@ array( - 'channel' => array( - 'name' => 'Channel ID', - 'type' => 'text', - 'required' => true, - 'exampleValue' => 'UCULkRHBdLC5ZcEQBaL0oYHQ' - ) - ), - 'By username' => array( - 'username' => array( - 'name' => 'Username', - 'type' => 'text', - 'required' => true, - 'exampleValue' => 'YouTubeUK' - ), - ) - ); - const CACHE_TIMEOUT = 3600; // 1 hour +class YouTubeCommunityTabBridge extends BridgeAbstract +{ + const NAME = 'YouTube Community Tab Bridge'; + const URI = 'https://www.youtube.com'; + const DESCRIPTION = 'Returns posts from a channel\'s community tab'; + const MAINTAINER = 'VerifiedJoseph'; + const PARAMETERS = [ + 'By channel ID' => [ + 'channel' => [ + 'name' => 'Channel ID', + 'type' => 'text', + 'required' => true, + 'exampleValue' => 'UCULkRHBdLC5ZcEQBaL0oYHQ' + ] + ], + 'By username' => [ + 'username' => [ + 'name' => 'Username', + 'type' => 'text', + 'required' => true, + 'exampleValue' => 'YouTubeUK' + ], + ] + ]; - private $feedUrl = ''; - private $feedName = ''; - private $itemTitle = ''; + const CACHE_TIMEOUT = 3600; // 1 hour - private $urlRegex = '/youtube\.com\/(channel|user|c)\/([\w]+)\/community/'; - private $jsonRegex = '/var ytInitialData = (.*);<\/script>/'; + private $feedUrl = ''; + private $feedName = ''; + private $itemTitle = ''; - public function detectParameters($url) { - $params = array(); + private $urlRegex = '/youtube\.com\/(channel|user|c)\/([\w]+)\/community/'; + private $jsonRegex = '/var ytInitialData = (.*);<\/script>/'; - if(preg_match($this->urlRegex, $url, $matches)) { - if ($matches[1] === 'channel') { - $params['context'] = 'By channel ID'; - $params['channel'] = $matches[2]; - } + public function detectParameters($url) + { + $params = []; - if ($matches[1] === 'user') { - $params['context'] = 'By username'; - $params['username'] = $matches[2]; - } + if (preg_match($this->urlRegex, $url, $matches)) { + if ($matches[1] === 'channel') { + $params['context'] = 'By channel ID'; + $params['channel'] = $matches[2]; + } - return $params; - } + if ($matches[1] === 'user') { + $params['context'] = 'By username'; + $params['username'] = $matches[2]; + } - return null; - } + return $params; + } - public function collectData() { + return null; + } - if (is_null($this->getInput('username')) === false) { - try { - $this->feedUrl = $this->buildCommunityUri($this->getInput('username'), 'c'); - $html = getSimpleHTMLDOM($this->feedUrl); + public function collectData() + { + if (is_null($this->getInput('username')) === false) { + try { + $this->feedUrl = $this->buildCommunityUri($this->getInput('username'), 'c'); + $html = getSimpleHTMLDOM($this->feedUrl); + } catch (Exception $e) { + $this->feedUrl = $this->buildCommunityUri($this->getInput('username'), 'user'); + $html = getSimpleHTMLDOM($this->feedUrl); + } + } else { + $this->feedUrl = $this->buildCommunityUri($this->getInput('channel'), 'channel'); + $html = getSimpleHTMLDOM($this->feedUrl); + } - } catch (Exception $e) { - $this->feedUrl = $this->buildCommunityUri($this->getInput('username'), 'user'); - $html = getSimpleHTMLDOM($this->feedUrl); - } - } else { - $this->feedUrl = $this->buildCommunityUri($this->getInput('channel'), 'channel'); - $html = getSimpleHTMLDOM($this->feedUrl); - } + $json = $this->extractJson($html->find('body', 0)->innertext); - $json = $this->extractJson($html->find('body', 0)->innertext); + $this->feedName = $json->header->c4TabbedHeaderRenderer->title; - $this->feedName = $json->header->c4TabbedHeaderRenderer->title; + if ($this->hasCommunityTab($json) === false) { + returnServerError('Channel does not have a community tab'); + } - if ($this->hasCommunityTab($json) === false) { - returnServerError('Channel does not have a community tab'); - } + foreach ($this->getCommunityPosts($json) as $post) { + $this->itemTitle = ''; - foreach ($this->getCommunityPosts($json) as $post) { - $this->itemTitle = ''; + if (!isset($post->backstagePostThreadRenderer)) { + continue; + } - if (!isset($post->backstagePostThreadRenderer)) { - continue; - } + $details = $post->backstagePostThreadRenderer->post->backstagePostRenderer; - $details = $post->backstagePostThreadRenderer->post->backstagePostRenderer; + $item = []; + $item['uri'] = self::URI . '/post/' . $details->postId; + $item['author'] = $details->authorText->runs[0]->text; + $item['content'] = ''; - $item = array(); - $item['uri'] = self::URI . '/post/' . $details->postId; - $item['author'] = $details->authorText->runs[0]->text; - $item['content'] = ''; + if (isset($details->contentText)) { + $text = $this->getText($details->contentText->runs); - if (isset($details->contentText)) { - $text = $this->getText($details->contentText->runs); + $this->itemTitle = $this->ellipsisTitle($text); + $item['content'] = $text; + } - $this->itemTitle = $this->ellipsisTitle($text); - $item['content'] = $text; - } + $item['content'] .= $this->getAttachments($details); + $item['title'] = $this->itemTitle; - $item['content'] .= $this->getAttachments($details); - $item['title'] = $this->itemTitle; + $this->items[] = $item; + } + } - $this->items[] = $item; - } - } + public function getURI() + { + if (!empty($this->feedUri)) { + return $this->feedUri; + } - public function getURI() { + return parent::getURI(); + } - if (!empty($this->feedUri)) { - return $this->feedUri; - } + public function getName() + { + if (!empty($this->feedName)) { + return $this->feedName . ' - YouTube Community Tab'; + } - return parent::getURI(); - } + return parent::getName(); + } - public function getName() { + /** + * Build Community URI + */ + private function buildCommunityUri($value, $type) + { + return self::URI . '/' . $type . '/' . $value . '/community'; + } - if (!empty($this->feedName)) { - return $this->feedName . ' - YouTube Community Tab'; - } + /** + * Extract JSON from page + */ + private function extractJson($html) + { + if (!preg_match($this->jsonRegex, $html, $parts)) { + returnServerError('Failed to extract data from page'); + } - return parent::getName(); - } + $data = json_decode($parts[1]); - /** - * Build Community URI - */ - private function buildCommunityUri($value, $type) { - return self::URI . '/' . $type . '/' . $value . '/community'; - } + if ($data === false) { + returnServerError('Failed to decode extracted data'); + } - /** - * Extract JSON from page - */ - private function extractJson($html) { + return $data; + } - if (!preg_match($this->jsonRegex, $html, $parts)) { - returnServerError('Failed to extract data from page'); - } + /** + * Check if channel has a community tab + */ + private function hasCommunityTab($json) + { + foreach ($json->contents->twoColumnBrowseResultsRenderer->tabs as $tab) { + if ( + isset($tab->tabRenderer) + && str_ends_with($tab->tabRenderer->endpoint->commandMetadata->webCommandMetadata->url, 'community') + ) { + return true; + } + } - $data = json_decode($parts[1]); + return false; + } - if ($data === false) { - returnServerError('Failed to decode extracted data'); - } + /** + * Get community tab posts + */ + private function getCommunityPosts($json) + { + foreach ($json->contents->twoColumnBrowseResultsRenderer->tabs as $tab) { + if ( + isset($tab->tabRenderer) + && str_ends_with($tab->tabRenderer->endpoint->commandMetadata->webCommandMetadata->url, 'community') + ) { + return $tab->tabRenderer->content->sectionListRenderer->contents[0]->itemSectionRenderer->contents; + } + } + } - return $data; - } + /** + * Get text content for a post + */ + private function getText($runs) + { + $text = ''; - /** - * Check if channel has a community tab - */ - private function hasCommunityTab($json) { + foreach ($runs as $part) { + $text .= $this->formatUrls($part->text); + } - foreach ($json->contents->twoColumnBrowseResultsRenderer->tabs as $tab) { - if (isset($tab->tabRenderer) - && str_ends_with($tab->tabRenderer->endpoint->commandMetadata->webCommandMetadata->url, 'community')) { + return nl2br($text); + } - return true; - } - } + /** + * Get attachments for posts + */ + private function getAttachments($details) + { + $content = ''; - return false; - } + if (isset($details->backstageAttachment)) { + $attachments = $details->backstageAttachment; - /** - * Get community tab posts - */ - private function getCommunityPosts($json) { + // Video + if (isset($attachments->videoRenderer) && isset($attachments->videoRenderer->videoId)) { + if (empty($this->itemTitle)) { + $this->itemTitle = $this->feedName . ' posted a video'; + } - foreach ($json->contents->twoColumnBrowseResultsRenderer->tabs as $tab) { - if (isset($tab->tabRenderer) - && str_ends_with($tab->tabRenderer->endpoint->commandMetadata->webCommandMetadata->url, 'community')) { - - return $tab->tabRenderer->content->sectionListRenderer->contents[0]->itemSectionRenderer->contents; - } - } - } - - /** - * Get text content for a post - */ - private function getText($runs) { - $text = ''; - - foreach ($runs as $part) { - $text .= $this->formatUrls($part->text); - } - - return nl2br($text); - } - - /** - * Get attachments for posts - */ - private function getAttachments($details) { - $content = ''; - - if (isset($details->backstageAttachment)) { - $attachments = $details->backstageAttachment; - - // Video - if (isset($attachments->videoRenderer) && isset($attachments->videoRenderer->videoId)) { - if (empty($this->itemTitle)) { - $this->itemTitle = $this->feedName . ' posted a video'; - } - - $content = << EOD; - } + } - // Image - if (isset($attachments->backstageImageRenderer)) { - if (empty($this->itemTitle)) { - $this->itemTitle = $this->feedName . ' posted an image'; - } + // Image + if (isset($attachments->backstageImageRenderer)) { + if (empty($this->itemTitle)) { + $this->itemTitle = $this->feedName . ' posted an image'; + } - $lastThumb = end($attachments->backstageImageRenderer->image->thumbnails); + $lastThumb = end($attachments->backstageImageRenderer->image->thumbnails); - $content = <<

EOD; - } + } - // Poll - if (isset($attachments->pollRenderer)) { - if (empty($this->itemTitle)) { - $this->itemTitle = $this->feedName . ' posted a poll'; - } + // Poll + if (isset($attachments->pollRenderer)) { + if (empty($this->itemTitle)) { + $this->itemTitle = $this->feedName . ' posted a poll'; + } - $pollChoices = ''; + $pollChoices = ''; - foreach ($attachments->pollRenderer->choices as $choice) { - $pollChoices .= <<pollRenderer->choices as $choice) { + $pollChoices .= <<{$choice->text->runs[0]->text} EOD; - } + } - $content = <<

Poll ({$attachments->pollRenderer->totalVotes->simpleText})

    {$pollChoices}

EOD; - } - } + } + } - return $content; - } + return $content; + } - /* - Ellipsis text for title - */ - private function ellipsisTitle($text) { - $length = 100; + /* + Ellipsis text for title + */ + private function ellipsisTitle($text) + { + $length = 100; - if (strlen($text) > $length) { - $text = explode('
', wordwrap($text, $length, '
')); - return $text[0] . '...'; - } + if (strlen($text) > $length) { + $text = explode('
', wordwrap($text, $length, '
')); + return $text[0] . '...'; + } - return $text; - } + return $text; + } - private function formatUrls($content) { - return preg_replace( - '/(http[s]{0,1}\:\/\/[a-zA-Z0-9.\/\?\&=\-_]{4,})/ims', - '$1 ', - $content - ); - } + private function formatUrls($content) + { + return preg_replace( + '/(http[s]{0,1}\:\/\/[a-zA-Z0-9.\/\?\&=\-_]{4,})/ims', + '$1 ', + $content + ); + } } diff --git a/bridges/YoutubeBridge.php b/bridges/YoutubeBridge.php index 536d6c29..31414472 100644 --- a/bridges/YoutubeBridge.php +++ b/bridges/YoutubeBridge.php @@ -1,4 +1,5 @@ [ + 'u' => [ + 'name' => 'username', + 'exampleValue' => 'LinusTechTips', + 'required' => true + ] + ], + 'By channel id' => [ + 'c' => [ + 'name' => 'channel id', + 'exampleValue' => 'UCw38-8_Ibv_L6hlKChHO9dQ', + 'required' => true + ] + ], + 'By custom name' => [ + 'custom' => [ + 'name' => 'custom name', + 'exampleValue' => 'LinusTechTips', + 'required' => true + ] + ], + 'By playlist Id' => [ + 'p' => [ + 'name' => 'playlist id', + 'exampleValue' => 'PL8mG-RkN2uTzJc8N0EoyhdC54prvBBLpj', + 'required' => true + ] + ], + 'Search result' => [ + 's' => [ + 'name' => 'search keyword', + 'exampleValue' => 'LinusTechTips', + 'required' => true + ], + 'pa' => [ + 'name' => 'page', + 'type' => 'number', + 'title' => 'This option is not work anymore, as YouTube will always return the same page', + 'exampleValue' => 1 + ] + ], + 'global' => [ + 'duration_min' => [ + 'name' => 'min. duration (minutes)', + 'type' => 'number', + 'title' => 'Minimum duration for the video in minutes', + 'exampleValue' => 5 + ], + 'duration_max' => [ + 'name' => 'max. duration (minutes)', + 'type' => 'number', + 'title' => 'Maximum duration for the video in minutes', + 'exampleValue' => 10 + ] + ] + ]; - const PARAMETERS = array( - 'By username' => array( - 'u' => array( - 'name' => 'username', - 'exampleValue' => 'LinusTechTips', - 'required' => true - ) - ), - 'By channel id' => array( - 'c' => array( - 'name' => 'channel id', - 'exampleValue' => 'UCw38-8_Ibv_L6hlKChHO9dQ', - 'required' => true - ) - ), - 'By custom name' => array( - 'custom' => array( - 'name' => 'custom name', - 'exampleValue' => 'LinusTechTips', - 'required' => true - ) - ), - 'By playlist Id' => array( - 'p' => array( - 'name' => 'playlist id', - 'exampleValue' => 'PL8mG-RkN2uTzJc8N0EoyhdC54prvBBLpj', - 'required' => true - ) - ), - 'Search result' => array( - 's' => array( - 'name' => 'search keyword', - 'exampleValue' => 'LinusTechTips', - 'required' => true - ), - 'pa' => array( - 'name' => 'page', - 'type' => 'number', - 'title' => 'This option is not work anymore, as YouTube will always return the same page', - 'exampleValue' => 1 - ) - ), - 'global' => array( - 'duration_min' => array( - 'name' => 'min. duration (minutes)', - 'type' => 'number', - 'title' => 'Minimum duration for the video in minutes', - 'exampleValue' => 5 - ), - 'duration_max' => array( - 'name' => 'max. duration (minutes)', - 'type' => 'number', - 'title' => 'Maximum duration for the video in minutes', - 'exampleValue' => 10 - ) - ) - ); - - private $feedName = ''; - private $feeduri = ''; - private $channel_name = ''; - // This took from repo BetterVideoRss of VerifiedJoseph. + private $feedName = ''; + private $feeduri = ''; + private $channel_name = ''; + // This took from repo BetterVideoRss of VerifiedJoseph. const URI_REGEX = '/(https?:\/\/(?:www\.)?(?:[a-zA-Z0-9-.]{2,256}\.[a-z]{2,20})(\:[0-9]{2 ,4})?(?:\/[a-zA-Z0-9@:%_\+.,~#"\'!?&\/\/=\-*]+|\/)?)/ims'; //phpcs:ignore - private function ytBridgeQueryVideoInfo($vid, &$author, &$desc, &$time){ - $html = $this->ytGetSimpleHTMLDOM(self::URI . "watch?v=$vid", true); + private function ytBridgeQueryVideoInfo($vid, &$author, &$desc, &$time) + { + $html = $this->ytGetSimpleHTMLDOM(self::URI . "watch?v=$vid", true); - // Skip unavailable videos - if(strpos($html->innertext, 'IS_UNAVAILABLE_PAGE') !== false) { - return; - } + // Skip unavailable videos + if (strpos($html->innertext, 'IS_UNAVAILABLE_PAGE') !== false) { + return; + } - $elAuthor = $html->find('span[itemprop=author] > link[itemprop=name]', 0); - if (!is_null($elAuthor)) { - $author = $elAuthor->getAttribute('content'); - } + $elAuthor = $html->find('span[itemprop=author] > link[itemprop=name]', 0); + if (!is_null($elAuthor)) { + $author = $elAuthor->getAttribute('content'); + } - $elDatePublished = $html->find('meta[itemprop=datePublished]', 0); - if(!is_null($elDatePublished)) - $time = strtotime($elDatePublished->getAttribute('content')); + $elDatePublished = $html->find('meta[itemprop=datePublished]', 0); + if (!is_null($elDatePublished)) { + $time = strtotime($elDatePublished->getAttribute('content')); + } - $jsonData = $this->getJSONData($html); - $jsonData = $jsonData->contents->twoColumnWatchNextResults->results->results->contents; + $jsonData = $this->getJSONData($html); + $jsonData = $jsonData->contents->twoColumnWatchNextResults->results->results->contents; - $videoSecondaryInfo = null; - foreach($jsonData as $item) { - if (isset($item->videoSecondaryInfoRenderer)) { - $videoSecondaryInfo = $item->videoSecondaryInfoRenderer; - break; - } - } - if (!$videoSecondaryInfo) { - returnServerError('Could not find videoSecondaryInfoRenderer. Error at: ' . $vid); - } + $videoSecondaryInfo = null; + foreach ($jsonData as $item) { + if (isset($item->videoSecondaryInfoRenderer)) { + $videoSecondaryInfo = $item->videoSecondaryInfoRenderer; + break; + } + } + if (!$videoSecondaryInfo) { + returnServerError('Could not find videoSecondaryInfoRenderer. Error at: ' . $vid); + } - if(isset($videoSecondaryInfo->description)) { - foreach($videoSecondaryInfo->description->runs as $description) { - if(isset($description->navigationEndpoint)) { - $metadata = $description->navigationEndpoint->commandMetadata->webCommandMetadata; - $web_type = $metadata->webPageType; - $url = $metadata->url; - $text = ''; - switch ($web_type) { - case 'WEB_PAGE_TYPE_UNKNOWN': - $url_components = parse_url($url); - if(isset($url_components['query']) && strpos($url_components['query'], '&q=') !== false) { - parse_str($url_components['query'], $params); - $url = urldecode($params['q']); - } - $text = $url; - break; - case 'WEB_PAGE_TYPE_WATCH': - case 'WEB_PAGE_TYPE_BROWSE': - $url = 'https://www.youtube.com' . $url; - $text = $description->text; - break; - } - $desc .= "$text"; - } else { - $desc .= nl2br($description->text); - } - } - } - } + if (isset($videoSecondaryInfo->description)) { + foreach ($videoSecondaryInfo->description->runs as $description) { + if (isset($description->navigationEndpoint)) { + $metadata = $description->navigationEndpoint->commandMetadata->webCommandMetadata; + $web_type = $metadata->webPageType; + $url = $metadata->url; + $text = ''; + switch ($web_type) { + case 'WEB_PAGE_TYPE_UNKNOWN': + $url_components = parse_url($url); + if (isset($url_components['query']) && strpos($url_components['query'], '&q=') !== false) { + parse_str($url_components['query'], $params); + $url = urldecode($params['q']); + } + $text = $url; + break; + case 'WEB_PAGE_TYPE_WATCH': + case 'WEB_PAGE_TYPE_BROWSE': + $url = 'https://www.youtube.com' . $url; + $text = $description->text; + break; + } + $desc .= "$text"; + } else { + $desc .= nl2br($description->text); + } + } + } + } - private function ytBridgeAddItem($vid, $title, $author, $desc, $time, $thumbnail = ''){ - $item = array(); - $item['id'] = $vid; - $item['title'] = $title; - $item['author'] = $author; - $item['timestamp'] = $time; - $item['uri'] = self::URI . 'watch?v=' . $vid; - if(!$thumbnail) { - $thumbnail = '0'; // Fallback to default thumbnail if there aren't any provided. - } - $thumbnailUri = str_replace('/www.', '/img.', self::URI) . 'vi/' . $vid . '/' . $thumbnail . '.jpg'; - $item['content'] = '
' . $desc; - $this->items[] = $item; - } + private function ytBridgeAddItem($vid, $title, $author, $desc, $time, $thumbnail = '') + { + $item = []; + $item['id'] = $vid; + $item['title'] = $title; + $item['author'] = $author; + $item['timestamp'] = $time; + $item['uri'] = self::URI . 'watch?v=' . $vid; + if (!$thumbnail) { + $thumbnail = '0'; // Fallback to default thumbnail if there aren't any provided. + } + $thumbnailUri = str_replace('/www.', '/img.', self::URI) . 'vi/' . $vid . '/' . $thumbnail . '.jpg'; + $item['content'] = '
' . $desc; + $this->items[] = $item; + } - private function ytBridgeParseXmlFeed($xml) { - foreach($xml->find('entry') as $element) { - $title = $this->ytBridgeFixTitle($element->find('title', 0)->plaintext); - $author = $element->find('name', 0)->plaintext; - $desc = $element->find('media:description', 0)->innertext; + private function ytBridgeParseXmlFeed($xml) + { + foreach ($xml->find('entry') as $element) { + $title = $this->ytBridgeFixTitle($element->find('title', 0)->plaintext); + $author = $element->find('name', 0)->plaintext; + $desc = $element->find('media:description', 0)->innertext; - // Make sure the description is easy on the eye :) - $desc = htmlspecialchars($desc); - $desc = nl2br($desc); - $desc = preg_replace(self::URI_REGEX, - '$1 ', - $desc); + // Make sure the description is easy on the eye :) + $desc = htmlspecialchars($desc); + $desc = nl2br($desc); + $desc = preg_replace( + self::URI_REGEX, + '$1 ', + $desc + ); - $vid = str_replace('yt:video:', '', $element->find('id', 0)->plaintext); - $time = strtotime($element->find('published', 0)->plaintext); - if(strpos($vid, 'googleads') === false) - $this->ytBridgeAddItem($vid, $title, $author, $desc, $time); - } - $this->feedName = $this->ytBridgeFixTitle($xml->find('feed > title', 0)->plaintext); // feedName will be used by getName() - } + $vid = str_replace('yt:video:', '', $element->find('id', 0)->plaintext); + $time = strtotime($element->find('published', 0)->plaintext); + if (strpos($vid, 'googleads') === false) { + $this->ytBridgeAddItem($vid, $title, $author, $desc, $time); + } + } + $this->feedName = $this->ytBridgeFixTitle($xml->find('feed > title', 0)->plaintext); // feedName will be used by getName() + } - private function ytBridgeFixTitle($title) { - // convert both Ӓ and " to UTF-8 - return html_entity_decode($title, ENT_QUOTES, 'UTF-8'); - } + private function ytBridgeFixTitle($title) + { + // convert both Ӓ and " to UTF-8 + return html_entity_decode($title, ENT_QUOTES, 'UTF-8'); + } - private function ytGetSimpleHTMLDOM($url, $cached = false){ - $header = array( - 'Accept-Language: en-US' - ); - $opts = array(); - $lowercase = true; - $forceTagsClosed = true; - $target_charset = DEFAULT_TARGET_CHARSET; - $stripRN = false; - $defaultBRText = DEFAULT_BR_TEXT; - $defaultSpanText = DEFAULT_SPAN_TEXT; - if ($cached) { - return getSimpleHTMLDOMCached($url, - 86400, - $header, - $opts, - $lowercase, - $forceTagsClosed, - $target_charset, - $stripRN, - $defaultBRText, - $defaultSpanText); - } - return getSimpleHTMLDOM($url, - $header, - $opts, - $lowercase, - $forceTagsClosed, - $target_charset, - $stripRN, - $defaultBRText, - $defaultSpanText); - } + private function ytGetSimpleHTMLDOM($url, $cached = false) + { + $header = [ + 'Accept-Language: en-US' + ]; + $opts = []; + $lowercase = true; + $forceTagsClosed = true; + $target_charset = DEFAULT_TARGET_CHARSET; + $stripRN = false; + $defaultBRText = DEFAULT_BR_TEXT; + $defaultSpanText = DEFAULT_SPAN_TEXT; + if ($cached) { + return getSimpleHTMLDOMCached( + $url, + 86400, + $header, + $opts, + $lowercase, + $forceTagsClosed, + $target_charset, + $stripRN, + $defaultBRText, + $defaultSpanText + ); + } + return getSimpleHTMLDOM( + $url, + $header, + $opts, + $lowercase, + $forceTagsClosed, + $target_charset, + $stripRN, + $defaultBRText, + $defaultSpanText + ); + } - private function getJSONData($html) { - $scriptRegex = '/var ytInitialData = (.*?);<\/script>/'; - preg_match($scriptRegex, $html, $matches) or returnServerError('Could not find ytInitialData'); - return json_decode($matches[1]); - } + private function getJSONData($html) + { + $scriptRegex = '/var ytInitialData = (.*?);<\/script>/'; + preg_match($scriptRegex, $html, $matches) or returnServerError('Could not find ytInitialData'); + return json_decode($matches[1]); + } - private function parseJSONListing($jsonData) { - $duration_min = $this->getInput('duration_min') ?: -1; - $duration_min = $duration_min * 60; + private function parseJSONListing($jsonData) + { + $duration_min = $this->getInput('duration_min') ?: -1; + $duration_min = $duration_min * 60; - $duration_max = $this->getInput('duration_max') ?: INF; - $duration_max = $duration_max * 60; + $duration_max = $this->getInput('duration_max') ?: INF; + $duration_max = $duration_max * 60; - if($duration_max < $duration_min) { - returnClientError('Max duration must be greater than min duration!'); - } + if ($duration_max < $duration_min) { + returnClientError('Max duration must be greater than min duration!'); + } - // $vid_list = ''; + // $vid_list = ''; - foreach($jsonData as $item) { - $wrapper = null; - if(isset($item->gridVideoRenderer)) { - $wrapper = $item->gridVideoRenderer; - } elseif(isset($item->videoRenderer)) { - $wrapper = $item->videoRenderer; - } elseif(isset($item->playlistVideoRenderer)) { - $wrapper = $item->playlistVideoRenderer; - } else - continue; + foreach ($jsonData as $item) { + $wrapper = null; + if (isset($item->gridVideoRenderer)) { + $wrapper = $item->gridVideoRenderer; + } elseif (isset($item->videoRenderer)) { + $wrapper = $item->videoRenderer; + } elseif (isset($item->playlistVideoRenderer)) { + $wrapper = $item->playlistVideoRenderer; + } else { + continue; + } - $vid = $wrapper->videoId; - $title = $wrapper->title->runs[0]->text; - if(isset($wrapper->ownerText)) { - $this->channel_name = $wrapper->ownerText->runs[0]->text; - } elseif(isset($wrapper->shortBylineText)) { - $this->channel_name = $wrapper->shortBylineText->runs[0]->text; - } + $vid = $wrapper->videoId; + $title = $wrapper->title->runs[0]->text; + if (isset($wrapper->ownerText)) { + $this->channel_name = $wrapper->ownerText->runs[0]->text; + } elseif (isset($wrapper->shortBylineText)) { + $this->channel_name = $wrapper->shortBylineText->runs[0]->text; + } - $author = ''; - $desc = ''; - $time = ''; + $author = ''; + $desc = ''; + $time = ''; - // The duration comes in one of the formats: - // hh:mm:ss / mm:ss / m:ss - // 01:03:30 / 15:06 / 1:24 - $durationText = 0; - if(isset($wrapper->lengthText)) { - $durationText = $wrapper->lengthText; - } else { - foreach($wrapper->thumbnailOverlays as $overlay) { - if(isset($overlay->thumbnailOverlayTimeStatusRenderer)) { - $durationText = $overlay->thumbnailOverlayTimeStatusRenderer->text; - break; - } - } - } + // The duration comes in one of the formats: + // hh:mm:ss / mm:ss / m:ss + // 01:03:30 / 15:06 / 1:24 + $durationText = 0; + if (isset($wrapper->lengthText)) { + $durationText = $wrapper->lengthText; + } else { + foreach ($wrapper->thumbnailOverlays as $overlay) { + if (isset($overlay->thumbnailOverlayTimeStatusRenderer)) { + $durationText = $overlay->thumbnailOverlayTimeStatusRenderer->text; + break; + } + } + } - if(isset($durationText->simpleText)) { - $durationText = trim($durationText->simpleText); - } else { - $durationText = 0; - } + if (isset($durationText->simpleText)) { + $durationText = trim($durationText->simpleText); + } else { + $durationText = 0; + } - if(preg_match('/([\d]{1,2}):([\d]{1,2})\:([\d]{2})/', $durationText)) { - $durationText = preg_replace('/([\d]{1,2}):([\d]{1,2})\:([\d]{2})/', '$1:$2:$3', $durationText); - } else { - $durationText = preg_replace('/([\d]{1,2})\:([\d]{2})/', '00:$1:$2', $durationText); - } - sscanf($durationText, '%d:%d:%d', $hours, $minutes, $seconds); - $duration = $hours * 3600 + $minutes * 60 + $seconds; - if($duration < $duration_min || $duration > $duration_max) { - continue; - } + if (preg_match('/([\d]{1,2}):([\d]{1,2})\:([\d]{2})/', $durationText)) { + $durationText = preg_replace('/([\d]{1,2}):([\d]{1,2})\:([\d]{2})/', '$1:$2:$3', $durationText); + } else { + $durationText = preg_replace('/([\d]{1,2})\:([\d]{2})/', '00:$1:$2', $durationText); + } + sscanf($durationText, '%d:%d:%d', $hours, $minutes, $seconds); + $duration = $hours * 3600 + $minutes * 60 + $seconds; + if ($duration < $duration_min || $duration > $duration_max) { + continue; + } - // $vid_list .= $vid . ','; - $this->ytBridgeQueryVideoInfo($vid, $author, $desc, $time); - $this->ytBridgeAddItem($vid, $title, $author, $desc, $time); - } - } + // $vid_list .= $vid . ','; + $this->ytBridgeQueryVideoInfo($vid, $author, $desc, $time); + $this->ytBridgeAddItem($vid, $title, $author, $desc, $time); + } + } - public function collectData(){ + public function collectData() + { + $xml = ''; + $html = ''; + $url_feed = ''; + $url_listing = ''; - $xml = ''; - $html = ''; - $url_feed = ''; - $url_listing = ''; + if ($this->getInput('u')) { /* User and Channel modes */ + $this->request = $this->getInput('u'); + $url_feed = self::URI . 'feeds/videos.xml?user=' . urlencode($this->request); + $url_listing = self::URI . 'user/' . urlencode($this->request) . '/videos'; + } elseif ($this->getInput('c')) { + $this->request = $this->getInput('c'); + $url_feed = self::URI . 'feeds/videos.xml?channel_id=' . urlencode($this->request); + $url_listing = self::URI . 'channel/' . urlencode($this->request) . '/videos'; + } elseif ($this->getInput('custom')) { + $this->request = $this->getInput('custom'); + $url_listing = self::URI . urlencode($this->request) . '/videos'; + } - if($this->getInput('u')) { /* User and Channel modes */ - $this->request = $this->getInput('u'); - $url_feed = self::URI . 'feeds/videos.xml?user=' . urlencode($this->request); - $url_listing = self::URI . 'user/' . urlencode($this->request) . '/videos'; - } elseif($this->getInput('c')) { - $this->request = $this->getInput('c'); - $url_feed = self::URI . 'feeds/videos.xml?channel_id=' . urlencode($this->request); - $url_listing = self::URI . 'channel/' . urlencode($this->request) . '/videos'; - } elseif($this->getInput('custom')) { - $this->request = $this->getInput('custom'); - $url_listing = self::URI . urlencode($this->request) . '/videos'; - } + if (!empty($url_feed) || !empty($url_listing)) { + $this->feeduri = $url_listing; + if (!empty($this->getInput('custom'))) { + $html = $this->ytGetSimpleHTMLDOM($url_listing); + $jsonData = $this->getJSONData($html); + $url_feed = $jsonData->metadata->channelMetadataRenderer->rssUrl; + } + if (!$this->skipFeeds()) { + $html = $this->ytGetSimpleHTMLDOM($url_feed); + $this->ytBridgeParseXmlFeed($html); + } else { + if (empty($this->getInput('custom'))) { + $html = $this->ytGetSimpleHTMLDOM($url_listing); + $jsonData = $this->getJSONData($html); + } + $channel_id = ''; + if (isset($jsonData->contents)) { + $channel_id = $jsonData->metadata->channelMetadataRenderer->externalId; + $jsonData = $jsonData->contents->twoColumnBrowseResultsRenderer->tabs[1]; + $jsonData = $jsonData->tabRenderer->content->sectionListRenderer->contents[0]; + $jsonData = $jsonData->itemSectionRenderer->contents[0]->gridRenderer->items; + $this->parseJSONListing($jsonData); + } else { + returnServerError('Unable to get data from YouTube. Username/Channel: ' . $this->request); + } + } + $this->feedName = str_replace(' - YouTube', '', $html->find('title', 0)->plaintext); + } elseif ($this->getInput('p')) { /* playlist mode */ + // TODO: this mode makes a lot of excess video query requests. + // To make less requests, we need to cache following dictionary "videoId -> datePublished, duration" + // This cache will be used to find out, which videos to fetch + // to make feed of 15 items or more, if there a lot of videos published on that date. + $this->request = $this->getInput('p'); + $url_feed = self::URI . 'feeds/videos.xml?playlist_id=' . urlencode($this->request); + $url_listing = self::URI . 'playlist?list=' . urlencode($this->request); + $html = $this->ytGetSimpleHTMLDOM($url_listing); + $jsonData = $this->getJSONData($html); + // TODO: this method returns only first 100 video items + // if it has more videos, playlistVideoListRenderer will have continuationItemRenderer as last element + $jsonData = $jsonData->contents->twoColumnBrowseResultsRenderer->tabs[0]; + $jsonData = $jsonData->tabRenderer->content->sectionListRenderer->contents[0]->itemSectionRenderer; + $jsonData = $jsonData->contents[0]->playlistVideoListRenderer->contents; + $item_count = count($jsonData); - if(!empty($url_feed) || !empty($url_listing)) { - $this->feeduri = $url_listing; - if(!empty($this->getInput('custom'))) { - $html = $this->ytGetSimpleHTMLDOM($url_listing); - $jsonData = $this->getJSONData($html); - $url_feed = $jsonData->metadata->channelMetadataRenderer->rssUrl; - } - if(!$this->skipFeeds()) { - $html = $this->ytGetSimpleHTMLDOM($url_feed); - $this->ytBridgeParseXmlFeed($html); - } else { - if(empty($this->getInput('custom'))) { - $html = $this->ytGetSimpleHTMLDOM($url_listing); - $jsonData = $this->getJSONData($html); - } - $channel_id = ''; - if(isset($jsonData->contents)) { - $channel_id = $jsonData->metadata->channelMetadataRenderer->externalId; - $jsonData = $jsonData->contents->twoColumnBrowseResultsRenderer->tabs[1]; - $jsonData = $jsonData->tabRenderer->content->sectionListRenderer->contents[0]; - $jsonData = $jsonData->itemSectionRenderer->contents[0]->gridRenderer->items; - $this->parseJSONListing($jsonData); - } else { - returnServerError('Unable to get data from YouTube. Username/Channel: ' . $this->request); - } - } - $this->feedName = str_replace(' - YouTube', '', $html->find('title', 0)->plaintext); - } elseif($this->getInput('p')) { /* playlist mode */ - // TODO: this mode makes a lot of excess video query requests. - // To make less requests, we need to cache following dictionary "videoId -> datePublished, duration" - // This cache will be used to find out, which videos to fetch - // to make feed of 15 items or more, if there a lot of videos published on that date. - $this->request = $this->getInput('p'); - $url_feed = self::URI . 'feeds/videos.xml?playlist_id=' . urlencode($this->request); - $url_listing = self::URI . 'playlist?list=' . urlencode($this->request); - $html = $this->ytGetSimpleHTMLDOM($url_listing); - $jsonData = $this->getJSONData($html); - // TODO: this method returns only first 100 video items - // if it has more videos, playlistVideoListRenderer will have continuationItemRenderer as last element - $jsonData = $jsonData->contents->twoColumnBrowseResultsRenderer->tabs[0]; - $jsonData = $jsonData->tabRenderer->content->sectionListRenderer->contents[0]->itemSectionRenderer; - $jsonData = $jsonData->contents[0]->playlistVideoListRenderer->contents; - $item_count = count($jsonData); + if ($item_count <= 15 && !$this->skipFeeds() && ($xml = $this->ytGetSimpleHTMLDOM($url_feed))) { + $this->ytBridgeParseXmlFeed($xml); + } else { + $this->parseJSONListing($jsonData); + } + $this->feedName = 'Playlist: ' . str_replace(' - YouTube', '', $html->find('title', 0)->plaintext); // feedName will be used by getName() + usort($this->items, function ($item1, $item2) { + if (!is_int($item1['timestamp']) && !is_int($item2['timestamp'])) { + $item1['timestamp'] = strtotime($item1['timestamp']); + $item2['timestamp'] = strtotime($item2['timestamp']); + } + return $item2['timestamp'] - $item1['timestamp']; + }); + } elseif ($this->getInput('s')) { /* search mode */ + $this->request = $this->getInput('s'); + $url_listing = self::URI + . 'results?search_query=' + . urlencode($this->request) + . '&sp=CAI%253D'; - if ($item_count <= 15 && !$this->skipFeeds() && ($xml = $this->ytGetSimpleHTMLDOM($url_feed))) { - $this->ytBridgeParseXmlFeed($xml); - } else { - $this->parseJSONListing($jsonData); - } - $this->feedName = 'Playlist: ' . str_replace(' - YouTube', '', $html->find('title', 0)->plaintext); // feedName will be used by getName() - usort($this->items, function ($item1, $item2) { - if(!is_int($item1['timestamp']) && !is_int($item2['timestamp'])) { - $item1['timestamp'] = strtotime($item1['timestamp']); - $item2['timestamp'] = strtotime($item2['timestamp']); - } - return $item2['timestamp'] - $item1['timestamp']; - }); - } elseif($this->getInput('s')) { /* search mode */ - $this->request = $this->getInput('s'); - $url_listing = self::URI - . 'results?search_query=' - . urlencode($this->request) - . '&sp=CAI%253D'; + $html = $this->ytGetSimpleHTMLDOM($url_listing); - $html = $this->ytGetSimpleHTMLDOM($url_listing); - - $jsonData = $this->getJSONData($html); - $jsonData = $jsonData->contents->twoColumnSearchResultsRenderer->primaryContents; - $jsonData = $jsonData->sectionListRenderer->contents; - foreach($jsonData as $data) { // Search result includes some ads, have to filter them - if(isset($data->itemSectionRenderer->contents[0]->videoRenderer)) { - $jsonData = $data->itemSectionRenderer->contents; - break; - } - } - $this->parseJSONListing($jsonData); - $this->feeduri = $url_listing; - $this->feedName = 'Search: ' . $this->request; // feedName will be used by getName() - } else { /* no valid mode */ - returnClientError("You must either specify either:\n - YouTube + $jsonData = $this->getJSONData($html); + $jsonData = $jsonData->contents->twoColumnSearchResultsRenderer->primaryContents; + $jsonData = $jsonData->sectionListRenderer->contents; + foreach ($jsonData as $data) { // Search result includes some ads, have to filter them + if (isset($data->itemSectionRenderer->contents[0]->videoRenderer)) { + $jsonData = $data->itemSectionRenderer->contents; + break; + } + } + $this->parseJSONListing($jsonData); + $this->feeduri = $url_listing; + $this->feedName = 'Search: ' . $this->request; // feedName will be used by getName() + } else { /* no valid mode */ + returnClientError("You must either specify either:\n - YouTube username (?u=...)\n - Channel id (?c=...)\n - Playlist id (?p=...)\n - Search (?s=...)"); - } - } + } + } - private function skipFeeds() { - return ($this->getInput('duration_min') || $this->getInput('duration_max')); - } + private function skipFeeds() + { + return ($this->getInput('duration_min') || $this->getInput('duration_max')); + } - public function getURI() - { - if (!is_null($this->getInput('p'))) { - return static::URI . 'playlist?list=' . $this->getInput('p'); - } elseif($this->feeduri) { - return $this->feeduri; - } + public function getURI() + { + if (!is_null($this->getInput('p'))) { + return static::URI . 'playlist?list=' . $this->getInput('p'); + } elseif ($this->feeduri) { + return $this->feeduri; + } - return parent::getURI(); - } + return parent::getURI(); + } - public function getName(){ - // Name depends on queriedContext: - switch($this->queriedContext) { - case 'By username': - case 'By channel id': - case 'By custom name': - case 'By playlist Id': - case 'Search result': - return htmlspecialchars_decode($this->feedName) . ' - YouTube'; // We already know it's a bridge, right? - default: - return parent::getName(); - } - } + public function getName() + { + // Name depends on queriedContext: + switch ($this->queriedContext) { + case 'By username': + case 'By channel id': + case 'By custom name': + case 'By playlist Id': + case 'Search result': + return htmlspecialchars_decode($this->feedName) . ' - YouTube'; // We already know it's a bridge, right? + default: + return parent::getName(); + } + } } diff --git a/bridges/ZDNetBridge.php b/bridges/ZDNetBridge.php index 927e37ae..09bde8e3 100644 --- a/bridges/ZDNetBridge.php +++ b/bridges/ZDNetBridge.php @@ -1,204 +1,209 @@ array( - 'name' => 'Feed', - 'type' => 'list', - 'values' => array( - 'Subscribe to ZDNet RSS Feeds' => array( - 'All Blogs' => 'blog', - 'Just News' => 'news', - 'All Reviews' => 'topic/reviews', - 'Latest Downloads' => 'downloads!recent', - 'Latest Articles' => '/', - 'Latest Australia Articles' => 'au', - 'Latest UK Articles' => 'uk', - 'Latest US Articles' => 'us', - 'Latest Asia Articles' => 'as' - ), - 'Keep up with ZDNet Blogs RSS:' => array( - 'Transforming the Datacenter' => 'blog/transforming-datacenter', - 'SMB India' => 'blog/smb-india', - 'Indonesia BizTech' => 'blog/indonesia-biztech', - 'Hong Kong Techie' => 'blog/hong-kong-techie', - 'Tech Taiwan' => 'blog/tech-taiwan', - 'Startup India' => 'blog/startup-india', - 'Starting Up Asia' => 'blog/starting-up-asia', - 'Next-Gen Partner' => 'blog/partner', - 'Post-PC Developments' => 'blog/post-pc', - 'Benelux' => 'blog/benelux', - 'Heat Sink' => 'blog/heat-sink', - 'Italy\'s got tech' => 'blog/italy', - 'African Enterprise' => 'blog/african-enterprise', - 'New Tech for Old India' => 'blog/new-india', - 'Estonia Uncovered' => 'blog/estonia', - 'IT Iberia' => 'blog/iberia', - 'Brazil Tech' => 'blog/brazil', - '500 words into the future' => 'blog/500-words-into-the-future', - 'ÜberTech' => 'blog/ubertech', - 'All About Microsoft' => 'blog/microsoft', - 'Back office' => 'blog/back-office', - 'Barker Bites Back' => 'blog/barker-bites-back', - 'Between the Lines' => 'blog/btl', - 'Big on Data' => 'blog/big-data', - 'bootstrappr' => 'blog/bootstrappr', - 'By The Way' => 'blog/by-the-way', - 'Central European Processing' => 'blog/central-europe', - 'Cloud Builders' => 'blog/cloud-builders', - 'Communication Breakdown' => 'blog/communication-breakdown', - 'Collaboration 2.0' => 'blog/collaboration', - 'Constellation Research' => 'blog/constellation', - 'Consumerization: BYOD' => 'blog/consumerization', - 'DIY-IT' => 'blog/diy-it', - 'Enterprise Web 2.0' => 'blog/hinchcliffe', - 'Five Nines: The Next Gen Datacenter' => 'blog/datacenter', - 'Forrester Research' => 'blog/forrester', - 'Full Duplex' => 'blog/full-duplex', - 'Gen Why?' => 'blog/gen-why', - 'Hardware 2.0' => 'blog/hardware', - 'Identity Matters' => 'blog/identity', - 'iGeneration' => 'blog/igeneration', - 'Internet of Everything' => 'blog/cisco', - 'Beyond IT Failure' => 'blog/projectfailures', - 'Jamie\'s Mostly Linux Stuff' => 'blog/jamies-mostly-linux-stuff', - 'Jack\'s Blog' => 'blog/jacks-blog', - 'Laptops & Desktops' => 'blog/computers', - 'Linux and Open Source' => 'blog/open-source', - 'London Calling' => 'blog/london', - 'Mapping Babel' => 'blog/mapping-babel', - 'Mixed Signals' => 'blog/mixed-signals', - 'Mobile India' => 'blog/mobile-india', - 'Mobile News' => 'blog/mobile-news', - 'Networking' => 'blog/networking', - 'Norse Code' => 'blog/norse-code', - 'Null Pointer' => 'blog/null-pointer', - 'The Full Tilt' => 'blog/the-full-tilt', - 'Pinoy Post' => 'blog/pinoy-post', - 'Practically Tech' => 'blog/practically-tech', - 'Product Central' => 'blog/product-central', - 'Pulp Tech' => 'blog/violetblue', - 'Qubits and Pieces' => 'blog/qubits-and-pieces', - 'Securify This!' => 'blog/securify-this', - 'Service Oriented' => 'blog/service-oriented', - 'Small Talk' => 'blog/small-talk', - 'Small Business Matters' => 'blog/small-business-matters', - 'Smartphones and Cell Phones' => 'blog/cell-phones', - 'Social Business' => 'blog/feeds', - 'Social CRM: The Conversation' => 'blog/crm', - 'Software & Services Safari' => 'blog/sommer', - 'Storage Bits' => 'blog/storage', - 'Stacking up Open Clouds' => 'blog/apac-redhat', - 'Techie Isles' => 'blog/techie-isles', - 'Technolatte' => 'blog/technolatte', - 'Tech Podium' => 'blog/tech-podium', - 'Tel Aviv Tech' => 'blog/tel-aviv', - 'Tech Broiler' => 'blog/perlow', - 'The SANMAN' => 'blog/the-sanman', - 'The open source revolution' => 'blog/the-open-source-revolution', - 'The German View' => 'blog/german', - 'The Ed Bott Report' => 'blog/bott', - 'The Mobile Gadgeteer' => 'blog/mobile-gadgeteer', - 'The Apple Core' => 'blog/apple', - 'Tom Foremski: IMHO' => 'blog/foremski', - 'Twisted Wire' => 'blog/twisted-wire', - 'Vive la tech' => 'blog/france', - 'Virtually Speaking' => 'blog/virtualization', - 'View from China' => 'blog/china', - 'Web design & Free Software' => 'blog/web-design-and-free-software', - 'ZDNet Government' => 'blog/government', - 'ZDNet UK Book Reviews' => 'blog/zdnet-uk-book-reviews', - 'ZDNet UK First Take' => 'blog/zdnet-uk-first-take', - 'Zero Day' => 'blog/security' - ), - 'ZDNet Hot Topics RSS:' => array( - 'Apple' => 'topic/apple', - 'Collaboration' => 'topic/collaboration', - 'Enterprise Software' => 'topic/enterprise-software', - 'Google' => 'topic/google', - 'Great debate' => 'topic/great-debate', - 'Hardware' => 'topic/hardware', - 'IBM' => 'topic/ibm', - 'iOS' => 'topic/ios', - 'iPhone' => 'topic/iphone', - 'iPad' => 'topic/ipad', - 'IT Priorities' => 'topic/it-priorities', - 'Laptops' => 'topic/laptops', - 'Legal' => 'topic/legal', - 'Linux' => 'topic/linux', - 'Microsoft' => 'topic/microsoft', - 'Mobile OS' => 'topic/mobile-os', - 'Mobility' => 'topic/mobility', - 'Networking' => 'topic/networking', - 'Oracle' => 'topic/oracle', - 'Processors' => 'topic/processors', - 'Samsung' => 'topic/samsung', - 'Security' => 'topic/security', - 'Small business: going big on mobility' => 'topic/small-business-going-big-on-mobility' - ), - 'Product Blogs:' => array( - 'Digital Cameras & Camcorders' => 'blog/digitalcameras', - 'Home Theater' => 'blog/home-theater', - 'Laptops and Desktops' => 'blog/computers', - 'The Mobile Gadgeteer' => 'blog/mobile-gadgeteer', - 'Smartphones and Cell Phones' => 'blog/cell-phones', - 'The ToyBox' => 'blog/gadgetreviews' - ), - 'Vertical Blogs:' => array( - 'ZDNet Education' => 'blog/education', - 'ZDNet Healthcare' => 'blog/healthcare', - 'ZDNet Government' => 'blog/government' - ) - ) - ), - 'limit' => self::LIMIT, - )); + //http://www.zdnet.com/zdnet.opml + const PARAMETERS = [ [ + 'feed' => [ + 'name' => 'Feed', + 'type' => 'list', + 'values' => [ + 'Subscribe to ZDNet RSS Feeds' => [ + 'All Blogs' => 'blog', + 'Just News' => 'news', + 'All Reviews' => 'topic/reviews', + 'Latest Downloads' => 'downloads!recent', + 'Latest Articles' => '/', + 'Latest Australia Articles' => 'au', + 'Latest UK Articles' => 'uk', + 'Latest US Articles' => 'us', + 'Latest Asia Articles' => 'as' + ], + 'Keep up with ZDNet Blogs RSS:' => [ + 'Transforming the Datacenter' => 'blog/transforming-datacenter', + 'SMB India' => 'blog/smb-india', + 'Indonesia BizTech' => 'blog/indonesia-biztech', + 'Hong Kong Techie' => 'blog/hong-kong-techie', + 'Tech Taiwan' => 'blog/tech-taiwan', + 'Startup India' => 'blog/startup-india', + 'Starting Up Asia' => 'blog/starting-up-asia', + 'Next-Gen Partner' => 'blog/partner', + 'Post-PC Developments' => 'blog/post-pc', + 'Benelux' => 'blog/benelux', + 'Heat Sink' => 'blog/heat-sink', + 'Italy\'s got tech' => 'blog/italy', + 'African Enterprise' => 'blog/african-enterprise', + 'New Tech for Old India' => 'blog/new-india', + 'Estonia Uncovered' => 'blog/estonia', + 'IT Iberia' => 'blog/iberia', + 'Brazil Tech' => 'blog/brazil', + '500 words into the future' => 'blog/500-words-into-the-future', + 'ÜberTech' => 'blog/ubertech', + 'All About Microsoft' => 'blog/microsoft', + 'Back office' => 'blog/back-office', + 'Barker Bites Back' => 'blog/barker-bites-back', + 'Between the Lines' => 'blog/btl', + 'Big on Data' => 'blog/big-data', + 'bootstrappr' => 'blog/bootstrappr', + 'By The Way' => 'blog/by-the-way', + 'Central European Processing' => 'blog/central-europe', + 'Cloud Builders' => 'blog/cloud-builders', + 'Communication Breakdown' => 'blog/communication-breakdown', + 'Collaboration 2.0' => 'blog/collaboration', + 'Constellation Research' => 'blog/constellation', + 'Consumerization: BYOD' => 'blog/consumerization', + 'DIY-IT' => 'blog/diy-it', + 'Enterprise Web 2.0' => 'blog/hinchcliffe', + 'Five Nines: The Next Gen Datacenter' => 'blog/datacenter', + 'Forrester Research' => 'blog/forrester', + 'Full Duplex' => 'blog/full-duplex', + 'Gen Why?' => 'blog/gen-why', + 'Hardware 2.0' => 'blog/hardware', + 'Identity Matters' => 'blog/identity', + 'iGeneration' => 'blog/igeneration', + 'Internet of Everything' => 'blog/cisco', + 'Beyond IT Failure' => 'blog/projectfailures', + 'Jamie\'s Mostly Linux Stuff' => 'blog/jamies-mostly-linux-stuff', + 'Jack\'s Blog' => 'blog/jacks-blog', + 'Laptops & Desktops' => 'blog/computers', + 'Linux and Open Source' => 'blog/open-source', + 'London Calling' => 'blog/london', + 'Mapping Babel' => 'blog/mapping-babel', + 'Mixed Signals' => 'blog/mixed-signals', + 'Mobile India' => 'blog/mobile-india', + 'Mobile News' => 'blog/mobile-news', + 'Networking' => 'blog/networking', + 'Norse Code' => 'blog/norse-code', + 'Null Pointer' => 'blog/null-pointer', + 'The Full Tilt' => 'blog/the-full-tilt', + 'Pinoy Post' => 'blog/pinoy-post', + 'Practically Tech' => 'blog/practically-tech', + 'Product Central' => 'blog/product-central', + 'Pulp Tech' => 'blog/violetblue', + 'Qubits and Pieces' => 'blog/qubits-and-pieces', + 'Securify This!' => 'blog/securify-this', + 'Service Oriented' => 'blog/service-oriented', + 'Small Talk' => 'blog/small-talk', + 'Small Business Matters' => 'blog/small-business-matters', + 'Smartphones and Cell Phones' => 'blog/cell-phones', + 'Social Business' => 'blog/feeds', + 'Social CRM: The Conversation' => 'blog/crm', + 'Software & Services Safari' => 'blog/sommer', + 'Storage Bits' => 'blog/storage', + 'Stacking up Open Clouds' => 'blog/apac-redhat', + 'Techie Isles' => 'blog/techie-isles', + 'Technolatte' => 'blog/technolatte', + 'Tech Podium' => 'blog/tech-podium', + 'Tel Aviv Tech' => 'blog/tel-aviv', + 'Tech Broiler' => 'blog/perlow', + 'The SANMAN' => 'blog/the-sanman', + 'The open source revolution' => 'blog/the-open-source-revolution', + 'The German View' => 'blog/german', + 'The Ed Bott Report' => 'blog/bott', + 'The Mobile Gadgeteer' => 'blog/mobile-gadgeteer', + 'The Apple Core' => 'blog/apple', + 'Tom Foremski: IMHO' => 'blog/foremski', + 'Twisted Wire' => 'blog/twisted-wire', + 'Vive la tech' => 'blog/france', + 'Virtually Speaking' => 'blog/virtualization', + 'View from China' => 'blog/china', + 'Web design & Free Software' => 'blog/web-design-and-free-software', + 'ZDNet Government' => 'blog/government', + 'ZDNet UK Book Reviews' => 'blog/zdnet-uk-book-reviews', + 'ZDNet UK First Take' => 'blog/zdnet-uk-first-take', + 'Zero Day' => 'blog/security' + ], + 'ZDNet Hot Topics RSS:' => [ + 'Apple' => 'topic/apple', + 'Collaboration' => 'topic/collaboration', + 'Enterprise Software' => 'topic/enterprise-software', + 'Google' => 'topic/google', + 'Great debate' => 'topic/great-debate', + 'Hardware' => 'topic/hardware', + 'IBM' => 'topic/ibm', + 'iOS' => 'topic/ios', + 'iPhone' => 'topic/iphone', + 'iPad' => 'topic/ipad', + 'IT Priorities' => 'topic/it-priorities', + 'Laptops' => 'topic/laptops', + 'Legal' => 'topic/legal', + 'Linux' => 'topic/linux', + 'Microsoft' => 'topic/microsoft', + 'Mobile OS' => 'topic/mobile-os', + 'Mobility' => 'topic/mobility', + 'Networking' => 'topic/networking', + 'Oracle' => 'topic/oracle', + 'Processors' => 'topic/processors', + 'Samsung' => 'topic/samsung', + 'Security' => 'topic/security', + 'Small business: going big on mobility' => 'topic/small-business-going-big-on-mobility' + ], + 'Product Blogs:' => [ + 'Digital Cameras & Camcorders' => 'blog/digitalcameras', + 'Home Theater' => 'blog/home-theater', + 'Laptops and Desktops' => 'blog/computers', + 'The Mobile Gadgeteer' => 'blog/mobile-gadgeteer', + 'Smartphones and Cell Phones' => 'blog/cell-phones', + 'The ToyBox' => 'blog/gadgetreviews' + ], + 'Vertical Blogs:' => [ + 'ZDNet Education' => 'blog/education', + 'ZDNet Healthcare' => 'blog/healthcare', + 'ZDNet Government' => 'blog/government' + ] + ] + ], + 'limit' => self::LIMIT, + ]]; - public function collectData(){ - $baseUri = static::URI; - $feed = $this->getInput('feed'); - if(strpos($feed, 'downloads!') !== false) { - $feed = str_replace('downloads!', '', $feed); - $baseUri = str_replace('www.', 'downloads.', $baseUri); - } - $url = $baseUri . trim($feed, '/') . '/rss.xml'; - $limit = $this->getInput('limit') ?? 10; - $this->collectExpandableDatas($url, $limit); - } + public function collectData() + { + $baseUri = static::URI; + $feed = $this->getInput('feed'); + if (strpos($feed, 'downloads!') !== false) { + $feed = str_replace('downloads!', '', $feed); + $baseUri = str_replace('www.', 'downloads.', $baseUri); + } + $url = $baseUri . trim($feed, '/') . '/rss.xml'; + $limit = $this->getInput('limit') ?? 10; + $this->collectExpandableDatas($url, $limit); + } - protected function parseItem($item){ - $item = parent::parseItem($item); + protected function parseItem($item) + { + $item = parent::parseItem($item); - $article = getSimpleHTMLDOMCached($item['uri']); - if(!$article) - returnServerError('Could not request ZDNet: ' . $url); + $article = getSimpleHTMLDOMCached($item['uri']); + if (!$article) { + returnServerError('Could not request ZDNet: ' . $url); + } - $contents = $article->find('article', 0)->innertext; - foreach(array( - '

'); - $contents = stripWithDelimiters($contents, ''); - $contents = stripWithDelimiters($contents, '')); - $item['content'] = $contents; + $contents = $article->find('article', 0)->innertext; + foreach ( + [ + '
'); + $contents = stripWithDelimiters($contents, ''); + $contents = stripWithDelimiters($contents, '')); + $item['content'] = $contents; - return $item; - - } + return $item; + } } diff --git a/bridges/ZenodoBridge.php b/bridges/ZenodoBridge.php index 6d0c134b..1144c90c 100644 --- a/bridges/ZenodoBridge.php +++ b/bridges/ZenodoBridge.php @@ -1,54 +1,56 @@ getURI()); + public function collectData() + { + $html = getSimpleHTMLDOM($this->getURI()); - foreach($html->find('div.record-elem.row') as $element) { - $item = array(); - $item['uri'] = self::URI . $element->find('h4 > a', 0)->href; - $item['title'] = trim(htmlspecialchars_decode($element->find('h4 > a', 0)->innertext, ENT_QUOTES)); + foreach ($html->find('div.record-elem.row') as $element) { + $item = []; + $item['uri'] = self::URI . $element->find('h4 > a', 0)->href; + $item['title'] = trim(htmlspecialchars_decode($element->find('h4 > a', 0)->innertext, ENT_QUOTES)); - $authors = $element->find('p', 0); - if ($authors) { - $item['author'] = $authors->plaintext; - } + $authors = $element->find('p', 0); + if ($authors) { + $item['author'] = $authors->plaintext; + } - $summary = $element->find('p.hidden-xs > a', 0); - if ($summary) { - $content = $summary->innertext . '
'; - } else { - $content = 'No content'; - } + $summary = $element->find('p.hidden-xs > a', 0); + if ($summary) { + $content = $summary->innertext . '
'; + } else { + $content = 'No content'; + } - $type = '
Type: ' . $element->find('span.label-default', 0)->innertext; - $item['categories'] = array($element->find('span.label-default', 0)->innertext); + $type = '
Type: ' . $element->find('span.label-default', 0)->innertext; + $item['categories'] = [$element->find('span.label-default', 0)->innertext]; - $raw_date = $element->find('small.text-muted', 0)->innertext; - $clean_date = str_replace('Uploaded on ', '', $raw_date); + $raw_date = $element->find('small.text-muted', 0)->innertext; + $clean_date = str_replace('Uploaded on ', '', $raw_date); - $content = $content . $raw_date; + $content = $content . $raw_date; - $item['timestamp'] = $clean_date; + $item['timestamp'] = $clean_date; - $access = ''; - if ($element->find('span.label-success', 0)) { - $access = 'Open Access'; - } elseif ($element->find('span.label-warning', 0)) { - $access = 'Embargoed Access'; - } else { - $access = $element->find('span.label-error', 0)->innertext; - } - $access = '
Access: ' . $access; - $publication = '
Publication Date: ' . $element->find('span.label-info', 0)->innertext; - $item['content'] = $content . $type . $access . $publication; - $this->items[] = $item; - } - } + $access = ''; + if ($element->find('span.label-success', 0)) { + $access = 'Open Access'; + } elseif ($element->find('span.label-warning', 0)) { + $access = 'Embargoed Access'; + } else { + $access = $element->find('span.label-error', 0)->innertext; + } + $access = '
Access: ' . $access; + $publication = '
Publication Date: ' . $element->find('span.label-info', 0)->innertext; + $item['content'] = $content . $type . $access . $publication; + $this->items[] = $item; + } + } } diff --git a/caches/FileCache.php b/caches/FileCache.php index 1b8ae6cd..29f4d78b 100644 --- a/caches/FileCache.php +++ b/caches/FileCache.php @@ -1,137 +1,150 @@ getCacheFile())) { - return unserialize(file_get_contents($this->getCacheFile())); - } + public function loadData() + { + if (file_exists($this->getCacheFile())) { + return unserialize(file_get_contents($this->getCacheFile())); + } - return null; - } + return null; + } - public function saveData($data){ - // Notice: We use plain serialize() here to reduce memory footprint on - // large input data. - $writeStream = file_put_contents($this->getCacheFile(), serialize($data)); + public function saveData($data) + { + // Notice: We use plain serialize() here to reduce memory footprint on + // large input data. + $writeStream = file_put_contents($this->getCacheFile(), serialize($data)); - if($writeStream === false) { - throw new \Exception('Cannot write the cache... Do you have the right permissions ?'); - } + if ($writeStream === false) { + throw new \Exception('Cannot write the cache... Do you have the right permissions ?'); + } - return $this; - } + return $this; + } - public function getTime(){ - $cacheFile = $this->getCacheFile(); - clearstatcache(false, $cacheFile); - if(file_exists($cacheFile)) { - $time = filemtime($cacheFile); - return ($time !== false) ? $time : null; - } + public function getTime() + { + $cacheFile = $this->getCacheFile(); + clearstatcache(false, $cacheFile); + if (file_exists($cacheFile)) { + $time = filemtime($cacheFile); + return ($time !== false) ? $time : null; + } - return null; - } + return null; + } - public function purgeCache($seconds){ - $cachePath = $this->getPath(); - if(file_exists($cachePath)) { - $cacheIterator = new RecursiveIteratorIterator( - new RecursiveDirectoryIterator($cachePath), - RecursiveIteratorIterator::CHILD_FIRST - ); + public function purgeCache($seconds) + { + $cachePath = $this->getPath(); + if (file_exists($cachePath)) { + $cacheIterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($cachePath), + RecursiveIteratorIterator::CHILD_FIRST + ); - foreach($cacheIterator as $cacheFile) { - if(in_array($cacheFile->getBasename(), array('.', '..', '.gitkeep'))) - continue; - elseif($cacheFile->isFile()) { - if(filemtime($cacheFile->getPathname()) < time() - $seconds) - unlink($cacheFile->getPathname()); - } - } - } - } + foreach ($cacheIterator as $cacheFile) { + if (in_array($cacheFile->getBasename(), ['.', '..', '.gitkeep'])) { + continue; + } elseif ($cacheFile->isFile()) { + if (filemtime($cacheFile->getPathname()) < time() - $seconds) { + unlink($cacheFile->getPathname()); + } + } + } + } + } - /** - * Set scope - * @return self - */ - public function setScope($scope){ - if(is_null($scope) || !is_string($scope)) { - throw new \Exception('The given scope is invalid!'); - } + /** + * Set scope + * @return self + */ + public function setScope($scope) + { + if (is_null($scope) || !is_string($scope)) { + throw new \Exception('The given scope is invalid!'); + } - $this->path = PATH_CACHE . trim($scope, " \t\n\r\0\x0B\\\/") . '/'; + $this->path = PATH_CACHE . trim($scope, " \t\n\r\0\x0B\\\/") . '/'; - return $this; - } + return $this; + } - /** - * Set key - * @return self - */ - public function setKey($key){ - if (!empty($key) && is_array($key)) { - $key = array_map('strtolower', $key); - } - $key = json_encode($key); + /** + * Set key + * @return self + */ + public function setKey($key) + { + if (!empty($key) && is_array($key)) { + $key = array_map('strtolower', $key); + } + $key = json_encode($key); - if (!is_string($key)) { - throw new \Exception('The given key is invalid!'); - } + if (!is_string($key)) { + throw new \Exception('The given key is invalid!'); + } - $this->key = $key; - return $this; - } + $this->key = $key; + return $this; + } - /** - * Return cache path (and create if not exist) - * @return string Cache path - */ - private function getPath(){ - if(is_null($this->path)) { - throw new \Exception('Call "setScope" first!'); - } + /** + * Return cache path (and create if not exist) + * @return string Cache path + */ + private function getPath() + { + if (is_null($this->path)) { + throw new \Exception('Call "setScope" first!'); + } - if(!is_dir($this->path)) { - if (mkdir($this->path, 0755, true) !== true) { - throw new \Exception('Unable to create ' . $this->path); - } - } + if (!is_dir($this->path)) { + if (mkdir($this->path, 0755, true) !== true) { + throw new \Exception('Unable to create ' . $this->path); + } + } - return $this->path; - } + return $this->path; + } - /** - * Get the file name use for cache store - * @return string Path to the file cache - */ - private function getCacheFile(){ - return $this->getPath() . $this->getCacheName(); - } + /** + * Get the file name use for cache store + * @return string Path to the file cache + */ + private function getCacheFile() + { + return $this->getPath() . $this->getCacheName(); + } - /** - * Determines file name for store the cache - * return string - */ - private function getCacheName(){ - if(is_null($this->key)) { - throw new \Exception('Call "setKey" first!'); - } + /** + * Determines file name for store the cache + * return string + */ + private function getCacheName() + { + if (is_null($this->key)) { + throw new \Exception('Call "setKey" first!'); + } - return hash('md5', $this->key) . '.cache'; - } + return hash('md5', $this->key) . '.cache'; + } } diff --git a/caches/MemcachedCache.php b/caches/MemcachedCache.php index b431279a..8619c255 100644 --- a/caches/MemcachedCache.php +++ b/caches/MemcachedCache.php @@ -1,115 +1,126 @@ 65535) { - returnServerError('"port" param is invalid for ' . get_called_class() . '. Please check your ' . FILE_CONFIG); - } + if ($port < 1 || $port > 65535) { + returnServerError('"port" param is invalid for ' . get_called_class() . '. Please check your ' . FILE_CONFIG); + } - $conn = new Memcached(); - $conn->addServer($host, $port) or returnServerError('Could not connect to memcached server'); - $this->conn = $conn; - } + $conn = new Memcached(); + $conn->addServer($host, $port) or returnServerError('Could not connect to memcached server'); + $this->conn = $conn; + } - public function loadData(){ - if ($this->data) return $this->data; - $result = $this->conn->get($this->getCacheKey()); - if ($result === false) { - return null; - } + public function loadData() + { + if ($this->data) { + return $this->data; + } + $result = $this->conn->get($this->getCacheKey()); + if ($result === false) { + return null; + } - $this->time = $result['time']; - $this->data = $result['data']; - return $result['data']; - } + $this->time = $result['time']; + $this->data = $result['data']; + return $result['data']; + } - public function saveData($datas){ - $time = time(); - $object_to_save = array( - 'data' => $datas, - 'time' => $time, - ); - $result = $this->conn->set($this->getCacheKey(), $object_to_save, $this->expiration); + public function saveData($datas) + { + $time = time(); + $object_to_save = [ + 'data' => $datas, + 'time' => $time, + ]; + $result = $this->conn->set($this->getCacheKey(), $object_to_save, $this->expiration); - if($result === false) { - returnServerError('Cannot write the cache to memcached server'); - } + if ($result === false) { + returnServerError('Cannot write the cache to memcached server'); + } - $this->time = $time; + $this->time = $time; - return $this; - } + return $this; + } - public function getTime(){ - if ($this->time === false) { - $this->loadData(); - } - return $this->time; - } + public function getTime() + { + if ($this->time === false) { + $this->loadData(); + } + return $this->time; + } - public function purgeCache($duration){ - // Note: does not purges cache right now - // Just sets cache expiration and leave cache purging for memcached itself - $this->expiration = $duration; - } + public function purgeCache($duration) + { + // Note: does not purges cache right now + // Just sets cache expiration and leave cache purging for memcached itself + $this->expiration = $duration; + } - /** - * Set scope - * @return self - */ - public function setScope($scope){ - $this->scope = $scope; - return $this; - } + /** + * Set scope + * @return self + */ + public function setScope($scope) + { + $this->scope = $scope; + return $this; + } - /** - * Set key - * @return self - */ - public function setKey($key){ - if (!empty($key) && is_array($key)) { - $key = array_map('strtolower', $key); - } - $key = json_encode($key); + /** + * Set key + * @return self + */ + public function setKey($key) + { + if (!empty($key) && is_array($key)) { + $key = array_map('strtolower', $key); + } + $key = json_encode($key); - if (!is_string($key)) { - throw new \Exception('The given key is invalid!'); - } + if (!is_string($key)) { + throw new \Exception('The given key is invalid!'); + } - $this->key = $key; - return $this; - } + $this->key = $key; + return $this; + } - private function getCacheKey(){ - if(is_null($this->key)) { - returnServerError('Call "setKey" first!'); - } + private function getCacheKey() + { + if (is_null($this->key)) { + returnServerError('Call "setKey" first!'); + } - return 'rss_bridge_cache_' . hash('md5', $this->scope . $this->key . 'A'); - } + return 'rss_bridge_cache_' . hash('md5', $this->scope . $this->key . 'A'); + } } diff --git a/caches/SQLiteCache.php b/caches/SQLiteCache.php index 5ec69417..e8d020a5 100644 --- a/caches/SQLiteCache.php +++ b/caches/SQLiteCache.php @@ -1,128 +1,138 @@ */ -class SQLiteCache implements CacheInterface { - protected $scope; - protected $key; +class SQLiteCache implements CacheInterface +{ + protected $scope; + protected $key; - private $db = null; + private $db = null; - public function __construct() { - if (!extension_loaded('sqlite3')) { - die('"sqlite3" extension not loaded. Please check "php.ini"'); - } + public function __construct() + { + if (!extension_loaded('sqlite3')) { + die('"sqlite3" extension not loaded. Please check "php.ini"'); + } - if (!is_writable(PATH_CACHE)) { - returnServerError( - 'RSS-Bridge does not have write permissions for ' - . PATH_CACHE . '!' - ); - } + if (!is_writable(PATH_CACHE)) { + returnServerError( + 'RSS-Bridge does not have write permissions for ' + . PATH_CACHE . '!' + ); + } - $file = Configuration::getConfig(get_called_class(), 'file'); - if (empty($file)) { - die('Configuration for ' . get_called_class() . ' missing. Please check your ' . FILE_CONFIG); - } - if (dirname($file) == '.') { - $file = PATH_CACHE . $file; - } elseif (!is_dir(dirname($file))) { - die('Invalid configuration for ' . get_called_class() . '. Please check your ' . FILE_CONFIG); - } + $file = Configuration::getConfig(get_called_class(), 'file'); + if (empty($file)) { + die('Configuration for ' . get_called_class() . ' missing. Please check your ' . FILE_CONFIG); + } + if (dirname($file) == '.') { + $file = PATH_CACHE . $file; + } elseif (!is_dir(dirname($file))) { + die('Invalid configuration for ' . get_called_class() . '. Please check your ' . FILE_CONFIG); + } - if (!is_file($file)) { - $this->db = new SQLite3($file); - $this->db->enableExceptions(true); - $this->db->exec("CREATE TABLE storage ('key' BLOB PRIMARY KEY, 'value' BLOB, 'updated' INTEGER)"); - } else { - $this->db = new SQLite3($file); - $this->db->enableExceptions(true); - } - $this->db->busyTimeout(5000); - } + if (!is_file($file)) { + $this->db = new SQLite3($file); + $this->db->enableExceptions(true); + $this->db->exec("CREATE TABLE storage ('key' BLOB PRIMARY KEY, 'value' BLOB, 'updated' INTEGER)"); + } else { + $this->db = new SQLite3($file); + $this->db->enableExceptions(true); + } + $this->db->busyTimeout(5000); + } - public function loadData(){ - $Qselect = $this->db->prepare('SELECT value FROM storage WHERE key = :key'); - $Qselect->bindValue(':key', $this->getCacheKey()); - $result = $Qselect->execute(); - if ($result instanceof SQLite3Result) { - $data = $result->fetchArray(SQLITE3_ASSOC); - if (isset($data['value'])) { - return unserialize($data['value']); - } - } + public function loadData() + { + $Qselect = $this->db->prepare('SELECT value FROM storage WHERE key = :key'); + $Qselect->bindValue(':key', $this->getCacheKey()); + $result = $Qselect->execute(); + if ($result instanceof SQLite3Result) { + $data = $result->fetchArray(SQLITE3_ASSOC); + if (isset($data['value'])) { + return unserialize($data['value']); + } + } - return null; - } + return null; + } - public function saveData($data){ - $Qupdate = $this->db->prepare('INSERT OR REPLACE INTO storage (key, value, updated) VALUES (:key, :value, :updated)'); - $Qupdate->bindValue(':key', $this->getCacheKey()); - $Qupdate->bindValue(':value', serialize($data)); - $Qupdate->bindValue(':updated', time()); - $Qupdate->execute(); + public function saveData($data) + { + $Qupdate = $this->db->prepare('INSERT OR REPLACE INTO storage (key, value, updated) VALUES (:key, :value, :updated)'); + $Qupdate->bindValue(':key', $this->getCacheKey()); + $Qupdate->bindValue(':value', serialize($data)); + $Qupdate->bindValue(':updated', time()); + $Qupdate->execute(); - return $this; - } + return $this; + } - public function getTime(){ - $Qselect = $this->db->prepare('SELECT updated FROM storage WHERE key = :key'); - $Qselect->bindValue(':key', $this->getCacheKey()); - $result = $Qselect->execute(); - if ($result instanceof SQLite3Result) { - $data = $result->fetchArray(SQLITE3_ASSOC); - if (isset($data['updated'])) { - return $data['updated']; - } - } + public function getTime() + { + $Qselect = $this->db->prepare('SELECT updated FROM storage WHERE key = :key'); + $Qselect->bindValue(':key', $this->getCacheKey()); + $result = $Qselect->execute(); + if ($result instanceof SQLite3Result) { + $data = $result->fetchArray(SQLITE3_ASSOC); + if (isset($data['updated'])) { + return $data['updated']; + } + } - return null; - } + return null; + } - public function purgeCache($seconds){ - $Qdelete = $this->db->prepare('DELETE FROM storage WHERE updated < :expired'); - $Qdelete->bindValue(':expired', time() - $seconds); - $Qdelete->execute(); - } + public function purgeCache($seconds) + { + $Qdelete = $this->db->prepare('DELETE FROM storage WHERE updated < :expired'); + $Qdelete->bindValue(':expired', time() - $seconds); + $Qdelete->execute(); + } - /** - * Set scope - * @return self - */ - public function setScope($scope){ - if(is_null($scope) || !is_string($scope)) { - throw new \Exception('The given scope is invalid!'); - } + /** + * Set scope + * @return self + */ + public function setScope($scope) + { + if (is_null($scope) || !is_string($scope)) { + throw new \Exception('The given scope is invalid!'); + } - $this->scope = $scope; - return $this; - } + $this->scope = $scope; + return $this; + } - /** - * Set key - * @return self - */ - public function setKey($key){ - if (!empty($key) && is_array($key)) { - $key = array_map('strtolower', $key); - } - $key = json_encode($key); + /** + * Set key + * @return self + */ + public function setKey($key) + { + if (!empty($key) && is_array($key)) { + $key = array_map('strtolower', $key); + } + $key = json_encode($key); - if (!is_string($key)) { - throw new \Exception('The given key is invalid!'); - } + if (!is_string($key)) { + throw new \Exception('The given key is invalid!'); + } - $this->key = $key; - return $this; - } + $this->key = $key; + return $this; + } - //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// - private function getCacheKey(){ - if(is_null($this->key)) { - throw new \Exception('Call "setKey" first!'); - } + private function getCacheKey() + { + if (is_null($this->key)) { + throw new \Exception('Call "setKey" first!'); + } - return hash('sha1', $this->scope . $this->key, true); - } + return hash('sha1', $this->scope . $this->key, true); + } } diff --git a/contrib/prepare_release/fetch_contributors.php b/contrib/prepare_release/fetch_contributors.php index 9659b800..76cef24f 100644 --- a/contrib/prepare_release/fetch_contributors.php +++ b/contrib/prepare_release/fetch_contributors.php @@ -1,49 +1,49 @@ $headers]); - $headers = [ - 'Accept: application/json', - 'Content-Type: application/json', - 'User-Agent: RSS-Bridge' - ]; - $result = _http_request($url, ['headers' => $headers]); + foreach (json_decode($result['body']) as $contributor) { + $contributors[] = $contributor; + } - foreach(json_decode($result['body']) as $contributor) - $contributors[] = $contributor; + // Extract links to "next", "last", etc... + $links = explode(',', $result['headers']['link'][0]); + $next = false; - // Extract links to "next", "last", etc... - $links = explode(',', $result['headers']['link'][0]); - $next = false; - - // Check if there is a link with 'rel="next"' - foreach($links as $link) { - list($url, $type) = explode(';', $link, 2); - - if(trim($type) === 'rel="next"') { - $url = trim(preg_replace('/([<>])/', '', $url)); - $next = true; - break; - } - } + // Check if there is a link with 'rel="next"' + foreach ($links as $link) { + list($url, $type) = explode(';', $link, 2); + if (trim($type) === 'rel="next"') { + $url = trim(preg_replace('/([<>])/', '', $url)); + $next = true; + break; + } + } } /* Example JSON data: https://api.github.com/repos/rss-bridge/rss-bridge/contributors */ // We want contributors sorted by name -usort($contributors, function($a, $b){ - return strcasecmp($a->login, $b->login); +usort($contributors, function ($a, $b) { + return strcasecmp($a->login, $b->login); }); // Export as Markdown list -foreach($contributors as $contributor) { - echo " * [{$contributor->login}]({$contributor->html_url})\n"; +foreach ($contributors as $contributor) { + echo " * [{$contributor->login}]({$contributor->html_url})\n"; } diff --git a/formats/AtomFormat.php b/formats/AtomFormat.php index 3d9b7c93..5f564266 100644 --- a/formats/AtomFormat.php +++ b/formats/AtomFormat.php @@ -1,4 +1,5 @@ getExtraInfos(); - $uri = !empty($extraInfos['uri']) ? $extraInfos['uri'] : REPOSITORY; + $extraInfos = $this->getExtraInfos(); + $uri = !empty($extraInfos['uri']) ? $extraInfos['uri'] : REPOSITORY; - $document = new DomDocument('1.0', $this->getCharset()); - $document->formatOutput = true; - $feed = $document->createElementNS(self::ATOM_NS, 'feed'); - $document->appendChild($feed); - $feed->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:media', self::MRSS_NS); + $document = new DomDocument('1.0', $this->getCharset()); + $document->formatOutput = true; + $feed = $document->createElementNS(self::ATOM_NS, 'feed'); + $document->appendChild($feed); + $feed->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:media', self::MRSS_NS); - $title = $document->createElement('title'); - $feed->appendChild($title); - $title->setAttribute('type', 'text'); - $title->appendChild($document->createTextNode($extraInfos['name'])); + $title = $document->createElement('title'); + $feed->appendChild($title); + $title->setAttribute('type', 'text'); + $title->appendChild($document->createTextNode($extraInfos['name'])); - $id = $document->createElement('id'); - $feed->appendChild($id); - $id->appendChild($document->createTextNode($feedUrl)); + $id = $document->createElement('id'); + $feed->appendChild($id); + $id->appendChild($document->createTextNode($feedUrl)); - $uriparts = parse_url($uri); - if(!empty($extraInfos['icon'])) { - $iconUrl = $extraInfos['icon']; - } else { - $iconUrl = $uriparts['scheme'] . '://' . $uriparts['host'] . '/favicon.ico'; - } - $icon = $document->createElement('icon'); - $feed->appendChild($icon); - $icon->appendChild($document->createTextNode($iconUrl)); + $uriparts = parse_url($uri); + if (!empty($extraInfos['icon'])) { + $iconUrl = $extraInfos['icon']; + } else { + $iconUrl = $uriparts['scheme'] . '://' . $uriparts['host'] . '/favicon.ico'; + } + $icon = $document->createElement('icon'); + $feed->appendChild($icon); + $icon->appendChild($document->createTextNode($iconUrl)); - $logo = $document->createElement('logo'); - $feed->appendChild($logo); - $logo->appendChild($document->createTextNode($iconUrl)); + $logo = $document->createElement('logo'); + $feed->appendChild($logo); + $logo->appendChild($document->createTextNode($iconUrl)); - $feedTimestamp = gmdate(DATE_ATOM, $this->lastModified); - $updated = $document->createElement('updated'); - $feed->appendChild($updated); - $updated->appendChild($document->createTextNode($feedTimestamp)); + $feedTimestamp = gmdate(DATE_ATOM, $this->lastModified); + $updated = $document->createElement('updated'); + $feed->appendChild($updated); + $updated->appendChild($document->createTextNode($feedTimestamp)); - // since we can't guarantee that all items have an author, - // a global feed author is mandatory - $feedAuthor = 'RSS-Bridge'; - $author = $document->createElement('author'); - $feed->appendChild($author); - $authorName = $document->createElement('name'); - $author->appendChild($authorName); - $authorName->appendChild($document->createTextNode($feedAuthor)); + // since we can't guarantee that all items have an author, + // a global feed author is mandatory + $feedAuthor = 'RSS-Bridge'; + $author = $document->createElement('author'); + $feed->appendChild($author); + $authorName = $document->createElement('name'); + $author->appendChild($authorName); + $authorName->appendChild($document->createTextNode($feedAuthor)); - $linkAlternate = $document->createElement('link'); - $feed->appendChild($linkAlternate); - $linkAlternate->setAttribute('rel', 'alternate'); - $linkAlternate->setAttribute('type', 'text/html'); - $linkAlternate->setAttribute('href', $uri); + $linkAlternate = $document->createElement('link'); + $feed->appendChild($linkAlternate); + $linkAlternate->setAttribute('rel', 'alternate'); + $linkAlternate->setAttribute('type', 'text/html'); + $linkAlternate->setAttribute('href', $uri); - $linkSelf = $document->createElement('link'); - $feed->appendChild($linkSelf); - $linkSelf->setAttribute('rel', 'self'); - $linkSelf->setAttribute('type', 'application/atom+xml'); - $linkSelf->setAttribute('href', $feedUrl); + $linkSelf = $document->createElement('link'); + $feed->appendChild($linkSelf); + $linkSelf->setAttribute('rel', 'self'); + $linkSelf->setAttribute('type', 'application/atom+xml'); + $linkSelf->setAttribute('href', $feedUrl); - foreach($this->getItems() as $item) { - $entryTimestamp = $item->getTimestamp(); - $entryTitle = $item->getTitle(); - $entryContent = $item->getContent(); - $entryUri = $item->getURI(); - $entryID = ''; + foreach ($this->getItems() as $item) { + $entryTimestamp = $item->getTimestamp(); + $entryTitle = $item->getTitle(); + $entryContent = $item->getContent(); + $entryUri = $item->getURI(); + $entryID = ''; - if (!empty($item->getUid())) - $entryID = 'urn:sha1:' . $item->getUid(); + if (!empty($item->getUid())) { + $entryID = 'urn:sha1:' . $item->getUid(); + } - if (empty($entryID)) // Fallback to provided URI - $entryID = $entryUri; + if (empty($entryID)) { // Fallback to provided URI + $entryID = $entryUri; + } - if (empty($entryID)) // Fallback to title and content - $entryID = 'urn:sha1:' . hash('sha1', $entryTitle . $entryContent); + if (empty($entryID)) { // Fallback to title and content + $entryID = 'urn:sha1:' . hash('sha1', $entryTitle . $entryContent); + } - if (empty($entryTimestamp)) - $entryTimestamp = $this->lastModified; + if (empty($entryTimestamp)) { + $entryTimestamp = $this->lastModified; + } - if (empty($entryTitle)) { - $entryTitle = str_replace("\n", ' ', strip_tags($entryContent)); - if (strlen($entryTitle) > self::LIMIT_TITLE) { - $wrapPos = strpos(wordwrap($entryTitle, self::LIMIT_TITLE), "\n"); - $entryTitle = substr($entryTitle, 0, $wrapPos) . '...'; - } - } + if (empty($entryTitle)) { + $entryTitle = str_replace("\n", ' ', strip_tags($entryContent)); + if (strlen($entryTitle) > self::LIMIT_TITLE) { + $wrapPos = strpos(wordwrap($entryTitle, self::LIMIT_TITLE), "\n"); + $entryTitle = substr($entryTitle, 0, $wrapPos) . '...'; + } + } - if (empty($entryContent)) - $entryContent = ' '; + if (empty($entryContent)) { + $entryContent = ' '; + } - $entry = $document->createElement('entry'); - $feed->appendChild($entry); + $entry = $document->createElement('entry'); + $feed->appendChild($entry); - $title = $document->createElement('title'); - $entry->appendChild($title); - $title->setAttribute('type', 'html'); - $title->appendChild($document->createTextNode($entryTitle)); + $title = $document->createElement('title'); + $entry->appendChild($title); + $title->setAttribute('type', 'html'); + $title->appendChild($document->createTextNode($entryTitle)); - $entryTimestamp = gmdate(DATE_ATOM, $entryTimestamp); - $published = $document->createElement('published'); - $entry->appendChild($published); - $published->appendChild($document->createTextNode($entryTimestamp)); + $entryTimestamp = gmdate(DATE_ATOM, $entryTimestamp); + $published = $document->createElement('published'); + $entry->appendChild($published); + $published->appendChild($document->createTextNode($entryTimestamp)); - $updated = $document->createElement('updated'); - $entry->appendChild($updated); - $updated->appendChild($document->createTextNode($entryTimestamp)); + $updated = $document->createElement('updated'); + $entry->appendChild($updated); + $updated->appendChild($document->createTextNode($entryTimestamp)); - $id = $document->createElement('id'); - $entry->appendChild($id); - $id->appendChild($document->createTextNode($entryID)); + $id = $document->createElement('id'); + $entry->appendChild($id); + $id->appendChild($document->createTextNode($entryID)); - if (!empty($entryUri)) { - $entryLinkAlternate = $document->createElement('link'); - $entry->appendChild($entryLinkAlternate); - $entryLinkAlternate->setAttribute('rel', 'alternate'); - $entryLinkAlternate->setAttribute('type', 'text/html'); - $entryLinkAlternate->setAttribute('href', $entryUri); - } + if (!empty($entryUri)) { + $entryLinkAlternate = $document->createElement('link'); + $entry->appendChild($entryLinkAlternate); + $entryLinkAlternate->setAttribute('rel', 'alternate'); + $entryLinkAlternate->setAttribute('type', 'text/html'); + $entryLinkAlternate->setAttribute('href', $entryUri); + } - if (!empty($item->getAuthor())) { - $author = $document->createElement('author'); - $entry->appendChild($author); - $authorName = $document->createElement('name'); - $author->appendChild($authorName); - $authorName->appendChild($document->createTextNode($item->getAuthor())); - } + if (!empty($item->getAuthor())) { + $author = $document->createElement('author'); + $entry->appendChild($author); + $authorName = $document->createElement('name'); + $author->appendChild($authorName); + $authorName->appendChild($document->createTextNode($item->getAuthor())); + } - $content = $document->createElement('content'); - $content->setAttribute('type', 'html'); - $content->appendChild($document->createTextNode($this->sanitizeHtml($entryContent))); - $entry->appendChild($content); + $content = $document->createElement('content'); + $content->setAttribute('type', 'html'); + $content->appendChild($document->createTextNode($this->sanitizeHtml($entryContent))); + $entry->appendChild($content); - foreach($item->getEnclosures() as $enclosure) { - $entryEnclosure = $document->createElement('link'); - $entry->appendChild($entryEnclosure); - $entryEnclosure->setAttribute('rel', 'enclosure'); - $entryEnclosure->setAttribute('type', getMimeType($enclosure)); - $entryEnclosure->setAttribute('href', $enclosure); - } + foreach ($item->getEnclosures() as $enclosure) { + $entryEnclosure = $document->createElement('link'); + $entry->appendChild($entryEnclosure); + $entryEnclosure->setAttribute('rel', 'enclosure'); + $entryEnclosure->setAttribute('type', getMimeType($enclosure)); + $entryEnclosure->setAttribute('href', $enclosure); + } - foreach($item->getCategories() as $category) { - $entryCategory = $document->createElement('category'); - $entry->appendChild($entryCategory); - $entryCategory->setAttribute('term', $category); - } + foreach ($item->getCategories() as $category) { + $entryCategory = $document->createElement('category'); + $entry->appendChild($entryCategory); + $entryCategory->setAttribute('term', $category); + } - if (!empty($item->thumbnail)) { - $thumbnail = $document->createElementNS(self::MRSS_NS, 'thumbnail'); - $entry->appendChild($thumbnail); - $thumbnail->setAttribute('url', $item->thumbnail); - } - } + if (!empty($item->thumbnail)) { + $thumbnail = $document->createElementNS(self::MRSS_NS, 'thumbnail'); + $entry->appendChild($thumbnail); + $thumbnail->setAttribute('url', $item->thumbnail); + } + } - $toReturn = $document->saveXML(); + $toReturn = $document->saveXML(); - // Remove invalid characters - ini_set('mbstring.substitute_character', 'none'); - $toReturn = mb_convert_encoding($toReturn, $this->getCharset(), 'UTF-8'); - return $toReturn; - } + // Remove invalid characters + ini_set('mbstring.substitute_character', 'none'); + $toReturn = mb_convert_encoding($toReturn, $this->getCharset(), 'UTF-8'); + return $toReturn; + } } diff --git a/formats/HtmlFormat.php b/formats/HtmlFormat.php index 12b5fc3a..d60c4d81 100644 --- a/formats/HtmlFormat.php +++ b/formats/HtmlFormat.php @@ -1,96 +1,97 @@ getExtraInfos(); - $title = htmlspecialchars($extraInfos['name']); - $uri = htmlspecialchars($extraInfos['uri']); - $donationUri = htmlspecialchars($extraInfos['donationUri']); - $donationsAllowed = Configuration::getConfig('admin', 'donations'); +class HtmlFormat extends FormatAbstract +{ + const MIME_TYPE = 'text/html'; - // Dynamically build buttons for all formats (except HTML) - $formatFac = new FormatFactory(); + public function stringify() + { + $extraInfos = $this->getExtraInfos(); + $title = htmlspecialchars($extraInfos['name']); + $uri = htmlspecialchars($extraInfos['uri']); + $donationUri = htmlspecialchars($extraInfos['donationUri']); + $donationsAllowed = Configuration::getConfig('admin', 'donations'); - $buttons = ''; - $links = ''; + // Dynamically build buttons for all formats (except HTML) + $formatFac = new FormatFactory(); - foreach($formatFac->getFormatNames() as $format) { - if(strcasecmp($format, 'HTML') === 0) { - continue; - } + $buttons = ''; + $links = ''; - $query = str_ireplace('format=Html', 'format=' . $format, htmlentities($_SERVER['QUERY_STRING'])); - $buttons .= $this->buildButton($format, $query) . PHP_EOL; + foreach ($formatFac->getFormatNames() as $format) { + if (strcasecmp($format, 'HTML') === 0) { + continue; + } - $mime = $formatFac->create($format)->getMimeType(); - $links .= $this->buildLink($format, $query, $mime) . PHP_EOL; - } + $query = str_ireplace('format=Html', 'format=' . $format, htmlentities($_SERVER['QUERY_STRING'])); + $buttons .= $this->buildButton($format, $query) . PHP_EOL; - if($donationUri !== '' && $donationsAllowed) { - $buttons .= '' - . PHP_EOL; - $links .= '' - . PHP_EOL; - } + $mime = $formatFac->create($format)->getMimeType(); + $links .= $this->buildLink($format, $query, $mime) . PHP_EOL; + } - $entries = ''; - foreach($this->getItems() as $item) { - $entryAuthor = $item->getAuthor() ? '

by: ' . $item->getAuthor() . '

' : ''; - $entryTitle = $this->sanitizeHtml(strip_tags($item->getTitle())); - $entryUri = $item->getURI() ?: $uri; + if ($donationUri !== '' && $donationsAllowed) { + $buttons .= '' + . PHP_EOL; + $links .= '' + . PHP_EOL; + } - $entryDate = ''; - if($item->getTimestamp()) { + $entries = ''; + foreach ($this->getItems() as $item) { + $entryAuthor = $item->getAuthor() ? '

by: ' . $item->getAuthor() . '

' : ''; + $entryTitle = $this->sanitizeHtml(strip_tags($item->getTitle())); + $entryUri = $item->getURI() ?: $uri; - $entryDate = sprintf( - '', - date('Y-m-d H:i:s', $item->getTimestamp()), - date('Y-m-d H:i:s', $item->getTimestamp()) - ); - } + $entryDate = ''; + if ($item->getTimestamp()) { + $entryDate = sprintf( + '', + date('Y-m-d H:i:s', $item->getTimestamp()), + date('Y-m-d H:i:s', $item->getTimestamp()) + ); + } - $entryContent = ''; - if($item->getContent()) { - $entryContent = '
' - . $this->sanitizeHtml($item->getContent()) - . '
'; - } + $entryContent = ''; + if ($item->getContent()) { + $entryContent = '
' + . $this->sanitizeHtml($item->getContent()) + . '
'; + } - $entryEnclosures = ''; - if(!empty($item->getEnclosures())) { - $entryEnclosures = '

Attachments:

'; + $entryEnclosures = ''; + if (!empty($item->getEnclosures())) { + $entryEnclosures = '

Attachments:

'; - foreach($item->getEnclosures() as $enclosure) { - $template = '
  • %s
  • '; - $url = $this->sanitizeHtml($enclosure); - $anchorText = substr($url, strrpos($url, '/') + 1); + foreach ($item->getEnclosures() as $enclosure) { + $template = '
  • %s
  • '; + $url = $this->sanitizeHtml($enclosure); + $anchorText = substr($url, strrpos($url, '/') + 1); - $entryEnclosures .= sprintf($template, $url, $anchorText); - } + $entryEnclosures .= sprintf($template, $url, $anchorText); + } - $entryEnclosures .= '
    '; - } + $entryEnclosures .= '
    '; + } - $entryCategories = ''; - if(!empty($item->getCategories())) { - $entryCategories = '

    Categories:

    '; + $entryCategories = ''; + if (!empty($item->getCategories())) { + $entryCategories = '

    Categories:

    '; - foreach($item->getCategories() as $category) { + foreach ($item->getCategories() as $category) { + $entryCategories .= '
  • ' + . $this->sanitizeHtml($category) + . '
  • '; + } - $entryCategories .= '
  • ' - . $this->sanitizeHtml($category) - . '
  • '; - } + $entryCategories .= '
    '; + } - $entryCategories .= '
    '; - } - - $entries .= <<

    {$entryTitle}

    @@ -102,12 +103,12 @@ class HtmlFormat extends FormatAbstract { EOD; - } + } - $charset = $this->getCharset(); + $charset = $this->getCharset(); - /* Data are prepared, now let's begin the "MAGIE !!!" */ - $toReturn = << @@ -130,22 +131,24 @@ EOD; EOD; - // Remove invalid characters - ini_set('mbstring.substitute_character', 'none'); - $toReturn = mb_convert_encoding($toReturn, $this->getCharset(), 'UTF-8'); - return $toReturn; - } + // Remove invalid characters + ini_set('mbstring.substitute_character', 'none'); + $toReturn = mb_convert_encoding($toReturn, $this->getCharset(), 'UTF-8'); + return $toReturn; + } - private function buildButton($format, $query) { - return << EOD; - } + } - private function buildLink($format, $query, $mime) { - return << EOD; - } + } } diff --git a/formats/JsonFormat.php b/formats/JsonFormat.php index 1efc87fe..3b2a29ab 100644 --- a/formats/JsonFormat.php +++ b/formats/JsonFormat.php @@ -1,4 +1,5 @@ getExtraInfos(); + $extraInfos = $this->getExtraInfos(); - $data = array( - 'version' => 'https://jsonfeed.org/version/1', - 'title' => (!empty($extraInfos['name'])) ? $extraInfos['name'] : $urlHost, - 'home_page_url' => (!empty($extraInfos['uri'])) ? $extraInfos['uri'] : REPOSITORY, - 'feed_url' => $urlPrefix . $urlHost . $urlRequest - ); + $data = [ + 'version' => 'https://jsonfeed.org/version/1', + 'title' => (!empty($extraInfos['name'])) ? $extraInfos['name'] : $urlHost, + 'home_page_url' => (!empty($extraInfos['uri'])) ? $extraInfos['uri'] : REPOSITORY, + 'feed_url' => $urlPrefix . $urlHost . $urlRequest + ]; - if (!empty($extraInfos['icon'])) { - $data['icon'] = $extraInfos['icon']; - $data['favicon'] = $extraInfos['icon']; - } + if (!empty($extraInfos['icon'])) { + $data['icon'] = $extraInfos['icon']; + $data['favicon'] = $extraInfos['icon']; + } - $items = array(); - foreach ($this->getItems() as $item) { - $entry = array(); + $items = []; + foreach ($this->getItems() as $item) { + $entry = []; - $entryAuthor = $item->getAuthor(); - $entryTitle = $item->getTitle(); - $entryUri = $item->getURI(); - $entryTimestamp = $item->getTimestamp(); - $entryContent = $item->getContent() ? $this->sanitizeHtml($item->getContent()) : ''; - $entryEnclosures = $item->getEnclosures(); - $entryCategories = $item->getCategories(); + $entryAuthor = $item->getAuthor(); + $entryTitle = $item->getTitle(); + $entryUri = $item->getURI(); + $entryTimestamp = $item->getTimestamp(); + $entryContent = $item->getContent() ? $this->sanitizeHtml($item->getContent()) : ''; + $entryEnclosures = $item->getEnclosures(); + $entryCategories = $item->getCategories(); - $vendorFields = $item->toArray(); - foreach (self::VENDOR_EXCLUDES as $key) { - unset($vendorFields[$key]); - } + $vendorFields = $item->toArray(); + foreach (self::VENDOR_EXCLUDES as $key) { + unset($vendorFields[$key]); + } - $entry['id'] = $item->getUid(); + $entry['id'] = $item->getUid(); - if (empty($entry['id'])) { - $entry['id'] = $entryUri; - } + if (empty($entry['id'])) { + $entry['id'] = $entryUri; + } - if (!empty($entryTitle)) { - $entry['title'] = $entryTitle; - } - if (!empty($entryAuthor)) { - $entry['author'] = array( - 'name' => $entryAuthor - ); - } - if (!empty($entryTimestamp)) { - $entry['date_modified'] = gmdate(DATE_ATOM, $entryTimestamp); - } - if (!empty($entryUri)) { - $entry['url'] = $entryUri; - } - if (!empty($entryContent)) { - if ($this->isHTML($entryContent)) { - $entry['content_html'] = $entryContent; - } else { - $entry['content_text'] = $entryContent; - } - } - if (!empty($entryEnclosures)) { - $entry['attachments'] = array(); - foreach ($entryEnclosures as $enclosure) { - $entry['attachments'][] = array( - 'url' => $enclosure, - 'mime_type' => getMimeType($enclosure) - ); - } - } - if (!empty($entryCategories)) { - $entry['tags'] = array(); - foreach ($entryCategories as $category) { - $entry['tags'][] = $category; - } - } - if (!empty($vendorFields)) { - $entry['_rssbridge'] = $vendorFields; - } + if (!empty($entryTitle)) { + $entry['title'] = $entryTitle; + } + if (!empty($entryAuthor)) { + $entry['author'] = [ + 'name' => $entryAuthor + ]; + } + if (!empty($entryTimestamp)) { + $entry['date_modified'] = gmdate(DATE_ATOM, $entryTimestamp); + } + if (!empty($entryUri)) { + $entry['url'] = $entryUri; + } + if (!empty($entryContent)) { + if ($this->isHTML($entryContent)) { + $entry['content_html'] = $entryContent; + } else { + $entry['content_text'] = $entryContent; + } + } + if (!empty($entryEnclosures)) { + $entry['attachments'] = []; + foreach ($entryEnclosures as $enclosure) { + $entry['attachments'][] = [ + 'url' => $enclosure, + 'mime_type' => getMimeType($enclosure) + ]; + } + } + if (!empty($entryCategories)) { + $entry['tags'] = []; + foreach ($entryCategories as $category) { + $entry['tags'][] = $category; + } + } + if (!empty($vendorFields)) { + $entry['_rssbridge'] = $vendorFields; + } - if (empty($entry['id'])) - $entry['id'] = hash('sha1', $entryTitle . $entryContent); + if (empty($entry['id'])) { + $entry['id'] = hash('sha1', $entryTitle . $entryContent); + } - $items[] = $entry; - } - $data['items'] = $items; + $items[] = $entry; + } + $data['items'] = $items; - /** - * The intention here is to discard non-utf8 byte sequences. - * But the JSON_PARTIAL_OUTPUT_ON_ERROR also discards lots of other errors. - * So consider this a hack. - * Switch to JSON_INVALID_UTF8_IGNORE when PHP 7.2 is the latest platform requirement. - */ - $json = json_encode($data, JSON_PRETTY_PRINT | JSON_PARTIAL_OUTPUT_ON_ERROR); + /** + * The intention here is to discard non-utf8 byte sequences. + * But the JSON_PARTIAL_OUTPUT_ON_ERROR also discards lots of other errors. + * So consider this a hack. + * Switch to JSON_INVALID_UTF8_IGNORE when PHP 7.2 is the latest platform requirement. + */ + $json = json_encode($data, JSON_PRETTY_PRINT | JSON_PARTIAL_OUTPUT_ON_ERROR); - return $json; - } + return $json; + } - private function isHTML($text) { - return (strlen(strip_tags($text)) != strlen($text)); - } + private function isHTML($text) + { + return (strlen(strip_tags($text)) != strlen($text)); + } } diff --git a/formats/MrssFormat.php b/formats/MrssFormat.php index 386b7d37..45c2181f 100644 --- a/formats/MrssFormat.php +++ b/formats/MrssFormat.php @@ -1,4 +1,5 @@ getExtraInfos(); - $uri = !empty($extraInfos['uri']) ? $extraInfos['uri'] : REPOSITORY; + $extraInfos = $this->getExtraInfos(); + $uri = !empty($extraInfos['uri']) ? $extraInfos['uri'] : REPOSITORY; - $document = new DomDocument('1.0', $this->getCharset()); - $document->formatOutput = true; - $feed = $document->createElement('rss'); - $document->appendChild($feed); - $feed->setAttribute('version', '2.0'); - $feed->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:atom', self::ATOM_NS); - $feed->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:media', self::MRSS_NS); + $document = new DomDocument('1.0', $this->getCharset()); + $document->formatOutput = true; + $feed = $document->createElement('rss'); + $document->appendChild($feed); + $feed->setAttribute('version', '2.0'); + $feed->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:atom', self::ATOM_NS); + $feed->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:media', self::MRSS_NS); - $channel = $document->createElement('channel'); - $feed->appendChild($channel); + $channel = $document->createElement('channel'); + $feed->appendChild($channel); - $title = $extraInfos['name']; - $channelTitle = $document->createElement('title'); - $channel->appendChild($channelTitle); - $channelTitle->appendChild($document->createTextNode($title)); + $title = $extraInfos['name']; + $channelTitle = $document->createElement('title'); + $channel->appendChild($channelTitle); + $channelTitle->appendChild($document->createTextNode($title)); - $link = $document->createElement('link'); - $channel->appendChild($link); - $link->appendChild($document->createTextNode($uri)); + $link = $document->createElement('link'); + $channel->appendChild($link); + $link->appendChild($document->createTextNode($uri)); - $description = $document->createElement('description'); - $channel->appendChild($description); - $description->appendChild($document->createTextNode($extraInfos['name'])); + $description = $document->createElement('description'); + $channel->appendChild($description); + $description->appendChild($document->createTextNode($extraInfos['name'])); - $icon = $extraInfos['icon']; - if (!empty($icon) && in_array(substr($icon, -4), self::ALLOWED_IMAGE_EXT)) { - $feedImage = $document->createElement('image'); - $channel->appendChild($feedImage); - $iconUrl = $document->createElement('url'); - $iconUrl->appendChild($document->createTextNode($icon)); - $feedImage->appendChild($iconUrl); - $iconTitle = $document->createElement('title'); - $iconTitle->appendChild($document->createTextNode($title)); - $feedImage->appendChild($iconTitle); - $iconLink = $document->createElement('link'); - $iconLink->appendChild($document->createTextNode($uri)); - $feedImage->appendChild($iconLink); - } + $icon = $extraInfos['icon']; + if (!empty($icon) && in_array(substr($icon, -4), self::ALLOWED_IMAGE_EXT)) { + $feedImage = $document->createElement('image'); + $channel->appendChild($feedImage); + $iconUrl = $document->createElement('url'); + $iconUrl->appendChild($document->createTextNode($icon)); + $feedImage->appendChild($iconUrl); + $iconTitle = $document->createElement('title'); + $iconTitle->appendChild($document->createTextNode($title)); + $feedImage->appendChild($iconTitle); + $iconLink = $document->createElement('link'); + $iconLink->appendChild($document->createTextNode($uri)); + $feedImage->appendChild($iconLink); + } - $linkAlternate = $document->createElementNS(self::ATOM_NS, 'link'); - $channel->appendChild($linkAlternate); - $linkAlternate->setAttribute('rel', 'alternate'); - $linkAlternate->setAttribute('type', 'text/html'); - $linkAlternate->setAttribute('href', $uri); + $linkAlternate = $document->createElementNS(self::ATOM_NS, 'link'); + $channel->appendChild($linkAlternate); + $linkAlternate->setAttribute('rel', 'alternate'); + $linkAlternate->setAttribute('type', 'text/html'); + $linkAlternate->setAttribute('href', $uri); - $linkSelf = $document->createElementNS(self::ATOM_NS, 'link'); - $channel->appendChild($linkSelf); - $linkSelf->setAttribute('rel', 'self'); - $linkSelf->setAttribute('type', 'application/atom+xml'); - $linkSelf->setAttribute('href', $feedUrl); + $linkSelf = $document->createElementNS(self::ATOM_NS, 'link'); + $channel->appendChild($linkSelf); + $linkSelf->setAttribute('rel', 'self'); + $linkSelf->setAttribute('type', 'application/atom+xml'); + $linkSelf->setAttribute('href', $feedUrl); - foreach($this->getItems() as $item) { - $itemTimestamp = $item->getTimestamp(); - $itemTitle = $item->getTitle(); - $itemUri = $item->getURI(); - $itemContent = $item->getContent() ? $this->sanitizeHtml($item->getContent()) : ''; - $entryID = $item->getUid(); - $isPermaLink = 'false'; + foreach ($this->getItems() as $item) { + $itemTimestamp = $item->getTimestamp(); + $itemTitle = $item->getTitle(); + $itemUri = $item->getURI(); + $itemContent = $item->getContent() ? $this->sanitizeHtml($item->getContent()) : ''; + $entryID = $item->getUid(); + $isPermaLink = 'false'; - if (empty($entryID) && !empty($itemUri)) { // Fallback to provided URI - $entryID = $itemUri; - $isPermaLink = 'true'; - } + if (empty($entryID) && !empty($itemUri)) { // Fallback to provided URI + $entryID = $itemUri; + $isPermaLink = 'true'; + } - if (empty($entryID)) // Fallback to title and content - $entryID = hash('sha1', $itemTitle . $itemContent); + if (empty($entryID)) { // Fallback to title and content + $entryID = hash('sha1', $itemTitle . $itemContent); + } - $entry = $document->createElement('item'); - $channel->appendChild($entry); + $entry = $document->createElement('item'); + $channel->appendChild($entry); - if (!empty($itemTitle)) { - $entryTitle = $document->createElement('title'); - $entry->appendChild($entryTitle); - $entryTitle->appendChild($document->createTextNode($itemTitle)); - } + if (!empty($itemTitle)) { + $entryTitle = $document->createElement('title'); + $entry->appendChild($entryTitle); + $entryTitle->appendChild($document->createTextNode($itemTitle)); + } - if (!empty($itemUri)) { - $entryLink = $document->createElement('link'); - $entry->appendChild($entryLink); - $entryLink->appendChild($document->createTextNode($itemUri)); - } + if (!empty($itemUri)) { + $entryLink = $document->createElement('link'); + $entry->appendChild($entryLink); + $entryLink->appendChild($document->createTextNode($itemUri)); + } - $entryGuid = $document->createElement('guid'); - $entryGuid->setAttribute('isPermaLink', $isPermaLink); - $entry->appendChild($entryGuid); - $entryGuid->appendChild($document->createTextNode($entryID)); + $entryGuid = $document->createElement('guid'); + $entryGuid->setAttribute('isPermaLink', $isPermaLink); + $entry->appendChild($entryGuid); + $entryGuid->appendChild($document->createTextNode($entryID)); - if (!empty($itemTimestamp)) { - $entryPublished = $document->createElement('pubDate'); - $entry->appendChild($entryPublished); - $entryPublished->appendChild($document->createTextNode(gmdate(DATE_RFC2822, $itemTimestamp))); - } + if (!empty($itemTimestamp)) { + $entryPublished = $document->createElement('pubDate'); + $entry->appendChild($entryPublished); + $entryPublished->appendChild($document->createTextNode(gmdate(DATE_RFC2822, $itemTimestamp))); + } - if (!empty($itemContent)) { - $entryDescription = $document->createElement('description'); - $entry->appendChild($entryDescription); - $entryDescription->appendChild($document->createTextNode($itemContent)); - } + if (!empty($itemContent)) { + $entryDescription = $document->createElement('description'); + $entry->appendChild($entryDescription); + $entryDescription->appendChild($document->createTextNode($itemContent)); + } - foreach($item->getEnclosures() as $enclosure) { - $entryEnclosure = $document->createElementNS(self::MRSS_NS, 'content'); - $entry->appendChild($entryEnclosure); - $entryEnclosure->setAttribute('url', $enclosure); - $entryEnclosure->setAttribute('type', getMimeType($enclosure)); - } + foreach ($item->getEnclosures() as $enclosure) { + $entryEnclosure = $document->createElementNS(self::MRSS_NS, 'content'); + $entry->appendChild($entryEnclosure); + $entryEnclosure->setAttribute('url', $enclosure); + $entryEnclosure->setAttribute('type', getMimeType($enclosure)); + } - $entryCategories = ''; - foreach($item->getCategories() as $category) { - $entryCategory = $document->createElement('category'); - $entry->appendChild($entryCategory); - $entryCategory->appendChild($document->createTextNode($category)); - } - } + $entryCategories = ''; + foreach ($item->getCategories() as $category) { + $entryCategory = $document->createElement('category'); + $entry->appendChild($entryCategory); + $entryCategory->appendChild($document->createTextNode($category)); + } + } - $toReturn = $document->saveXML(); + $toReturn = $document->saveXML(); - // Remove invalid non-UTF8 characters - ini_set('mbstring.substitute_character', 'none'); - $toReturn = mb_convert_encoding($toReturn, $this->getCharset(), 'UTF-8'); - return $toReturn; - } + // Remove invalid non-UTF8 characters + ini_set('mbstring.substitute_character', 'none'); + $toReturn = mb_convert_encoding($toReturn, $this->getCharset(), 'UTF-8'); + return $toReturn; + } } diff --git a/formats/PlaintextFormat.php b/formats/PlaintextFormat.php index a1ef9e7f..a1e125c7 100644 --- a/formats/PlaintextFormat.php +++ b/formats/PlaintextFormat.php @@ -1,24 +1,27 @@ items as raw php data. */ -class PlaintextFormat extends FormatAbstract { - const MIME_TYPE = 'text/plain'; +class PlaintextFormat extends FormatAbstract +{ + const MIME_TYPE = 'text/plain'; - public function stringify(){ - $items = $this->getItems(); - $data = array(); + public function stringify() + { + $items = $this->getItems(); + $data = []; - foreach($items as $item) { - $data[] = $item->toArray(); - } + foreach ($items as $item) { + $data[] = $item->toArray(); + } - $toReturn = print_r($data, true); + $toReturn = print_r($data, true); - // Remove invalid non-UTF8 characters - ini_set('mbstring.substitute_character', 'none'); - $toReturn = mb_convert_encoding($toReturn, $this->getCharset(), 'UTF-8'); - return $toReturn; - } + // Remove invalid non-UTF8 characters + ini_set('mbstring.substitute_character', 'none'); + $toReturn = mb_convert_encoding($toReturn, $this->getCharset(), 'UTF-8'); + return $toReturn; + } } diff --git a/index.php b/index.php index f118d500..8ca1990c 100644 --- a/index.php +++ b/index.php @@ -7,32 +7,32 @@ Move the CLI arguments to the $_GET array, in order to be able to use rss-bridge from the command line */ if (isset($argv)) { - parse_str(implode('&', array_slice($argv, 1)), $cliArgs); - $params = array_merge($_GET, $cliArgs); + parse_str(implode('&', array_slice($argv, 1)), $cliArgs); + $params = array_merge($_GET, $cliArgs); } else { - $params = $_GET; + $params = $_GET; } try { - $actionFac = new ActionFactory(); + $actionFac = new ActionFactory(); - if (array_key_exists('action', $params)) { - $action = $actionFac->create($params['action']); - $action->userData = $params; - $action->execute(); - } else { - $showInactive = filter_input(INPUT_GET, 'show_inactive', FILTER_VALIDATE_BOOLEAN); - echo BridgeList::create($showInactive); - } + if (array_key_exists('action', $params)) { + $action = $actionFac->create($params['action']); + $action->userData = $params; + $action->execute(); + } else { + $showInactive = filter_input(INPUT_GET, 'show_inactive', FILTER_VALIDATE_BOOLEAN); + echo BridgeList::create($showInactive); + } } catch (\Throwable $e) { - error_log($e); + error_log($e); - $code = $e->getCode(); - if ($code !== -1) { - header('Content-Type: text/plain', true, $code); - } + $code = $e->getCode(); + if ($code !== -1) { + header('Content-Type: text/plain', true, $code); + } - $message = sprintf("Uncaught Exception %s: '%s'\n", get_class($e), $e->getMessage()); + $message = sprintf("Uncaught Exception %s: '%s'\n", get_class($e), $e->getMessage()); - print $message; + print $message; } diff --git a/lib/ActionFactory.php b/lib/ActionFactory.php index bd1297b4..5a413767 100644 --- a/lib/ActionFactory.php +++ b/lib/ActionFactory.php @@ -1,4 +1,5 @@ folder = $folder; - } + public function __construct(string $folder = PATH_LIB_ACTIONS) + { + $this->folder = $folder; + } - /** - * @param string $name The name of the action e.g. "Display", "List", or "Connectivity" - */ - public function create(string $name): ActionInterface - { - $name = ucfirst(strtolower($name)) . 'Action'; - $filePath = $this->folder . $name . '.php'; - if(!file_exists($filePath)) { - throw new \Exception('Invalid action'); - } - $className = '\\' . $name; - return new $className(); - } + /** + * @param string $name The name of the action e.g. "Display", "List", or "Connectivity" + */ + public function create(string $name): ActionInterface + { + $name = ucfirst(strtolower($name)) . 'Action'; + $filePath = $this->folder . $name . '.php'; + if (!file_exists($filePath)) { + throw new \Exception('Invalid action'); + } + $className = '\\' . $name; + return new $className(); + } } diff --git a/lib/ActionInterface.php b/lib/ActionInterface.php index c8684d52..78284ab4 100644 --- a/lib/ActionInterface.php +++ b/lib/ActionInterface.php @@ -1,4 +1,5 @@ 'Limit', + 'type' => 'number', + 'title' => 'Maximum number of items to return', + ]; - /** - * This is a convenient const for the limit option in bridge contexts. - * Can be inlined and modified if necessary. - */ - protected const LIMIT = [ - 'name' => 'Limit', - 'type' => 'number', - 'title' => 'Maximum number of items to return', - ]; + /** + * Holds the list of items collected by the bridge + * + * Items must be collected by {@see BridgeInterface::collectData()} + * + * Use {@see BridgeAbstract::getItems()} to access items. + * + * @var array + */ + protected $items = []; - /** - * Holds the list of items collected by the bridge - * - * Items must be collected by {@see BridgeInterface::collectData()} - * - * Use {@see BridgeAbstract::getItems()} to access items. - * - * @var array - */ - protected $items = array(); + /** + * Holds the list of input parameters used by the bridge + * + * Do not access this parameter directly! + * Use {@see BridgeAbstract::setInputs()} and {@see BridgeAbstract::getInput()} instead! + * + * @var array + */ + protected $inputs = []; - /** - * Holds the list of input parameters used by the bridge - * - * Do not access this parameter directly! - * Use {@see BridgeAbstract::setInputs()} and {@see BridgeAbstract::getInput()} instead! - * - * @var array - */ - protected $inputs = array(); + /** + * Holds the name of the queried context + * + * @var string + */ + protected $queriedContext = ''; - /** - * Holds the name of the queried context - * - * @var string - */ - protected $queriedContext = ''; + /** {@inheritdoc} */ + public function getItems() + { + return $this->items; + } - /** {@inheritdoc} */ - public function getItems(){ - return $this->items; - } + /** + * Sets the input values for a given context. + * + * @param array $inputs Associative array of inputs + * @param string $queriedContext The context name + * @return void + */ + protected function setInputs(array $inputs, $queriedContext) + { + // Import and assign all inputs to their context + foreach ($inputs as $name => $value) { + foreach (static::PARAMETERS as $context => $set) { + if (array_key_exists($name, static::PARAMETERS[$context])) { + $this->inputs[$context][$name]['value'] = $value; + } + } + } - /** - * Sets the input values for a given context. - * - * @param array $inputs Associative array of inputs - * @param string $queriedContext The context name - * @return void - */ - protected function setInputs(array $inputs, $queriedContext){ - // Import and assign all inputs to their context - foreach($inputs as $name => $value) { - foreach(static::PARAMETERS as $context => $set) { - if(array_key_exists($name, static::PARAMETERS[$context])) { - $this->inputs[$context][$name]['value'] = $value; - } - } - } + // Apply default values to missing data + $contexts = [$queriedContext]; + if (array_key_exists('global', static::PARAMETERS)) { + $contexts[] = 'global'; + } - // Apply default values to missing data - $contexts = array($queriedContext); - if(array_key_exists('global', static::PARAMETERS)) { - $contexts[] = 'global'; - } + foreach ($contexts as $context) { + foreach (static::PARAMETERS[$context] as $name => $properties) { + if (isset($this->inputs[$context][$name]['value'])) { + continue; + } - foreach($contexts as $context) { - foreach(static::PARAMETERS[$context] as $name => $properties) { - if(isset($this->inputs[$context][$name]['value'])) { - continue; - } + $type = isset($properties['type']) ? $properties['type'] : 'text'; - $type = isset($properties['type']) ? $properties['type'] : 'text'; + switch ($type) { + case 'checkbox': + if (!isset($properties['defaultValue'])) { + $this->inputs[$context][$name]['value'] = false; + } else { + $this->inputs[$context][$name]['value'] = $properties['defaultValue']; + } + break; + case 'list': + if (!isset($properties['defaultValue'])) { + $firstItem = reset($properties['values']); + if (is_array($firstItem)) { + $firstItem = reset($firstItem); + } + $this->inputs[$context][$name]['value'] = $firstItem; + } else { + $this->inputs[$context][$name]['value'] = $properties['defaultValue']; + } + break; + default: + if (isset($properties['defaultValue'])) { + $this->inputs[$context][$name]['value'] = $properties['defaultValue']; + } + break; + } + } + } - switch($type) { - case 'checkbox': - if(!isset($properties['defaultValue'])) { - $this->inputs[$context][$name]['value'] = false; - } else { - $this->inputs[$context][$name]['value'] = $properties['defaultValue']; - } - break; - case 'list': - if(!isset($properties['defaultValue'])) { - $firstItem = reset($properties['values']); - if(is_array($firstItem)) { - $firstItem = reset($firstItem); - } - $this->inputs[$context][$name]['value'] = $firstItem; - } else { - $this->inputs[$context][$name]['value'] = $properties['defaultValue']; - } - break; - default: - if(isset($properties['defaultValue'])) { - $this->inputs[$context][$name]['value'] = $properties['defaultValue']; - } - break; - } - } - } + // Copy global parameter values to the guessed context + if (array_key_exists('global', static::PARAMETERS)) { + foreach (static::PARAMETERS['global'] as $name => $properties) { + if (isset($inputs[$name])) { + $value = $inputs[$name]; + } elseif (isset($properties['defaultValue'])) { + $value = $properties['defaultValue']; + } else { + continue; + } + $this->inputs[$queriedContext][$name]['value'] = $value; + } + } - // Copy global parameter values to the guessed context - if(array_key_exists('global', static::PARAMETERS)) { - foreach(static::PARAMETERS['global'] as $name => $properties) { - if(isset($inputs[$name])) { - $value = $inputs[$name]; - } elseif(isset($properties['defaultValue'])) { - $value = $properties['defaultValue']; - } else { - continue; - } - $this->inputs[$queriedContext][$name]['value'] = $value; - } - } + // Only keep guessed context parameters values + if (isset($this->inputs[$queriedContext])) { + $this->inputs = [$queriedContext => $this->inputs[$queriedContext]]; + } else { + $this->inputs = []; + } + } - // Only keep guessed context parameters values - if(isset($this->inputs[$queriedContext])) { - $this->inputs = array($queriedContext => $this->inputs[$queriedContext]); - } else { - $this->inputs = array(); - } - } + /** + * Set inputs for the bridge + * + * Returns errors and aborts execution if the provided input parameters are + * invalid. + * + * @param array List of input parameters. Each element in this list must + * relate to an item in {@see BridgeAbstract::PARAMETERS} + * @return void + */ + public function setDatas(array $inputs) + { + if (isset($inputs['context'])) { // Context hinting (optional) + $this->queriedContext = $inputs['context']; + unset($inputs['context']); + } - /** - * Set inputs for the bridge - * - * Returns errors and aborts execution if the provided input parameters are - * invalid. - * - * @param array List of input parameters. Each element in this list must - * relate to an item in {@see BridgeAbstract::PARAMETERS} - * @return void - */ - public function setDatas(array $inputs){ + if (empty(static::PARAMETERS)) { + if (!empty($inputs)) { + returnClientError('Invalid parameters value(s)'); + } - if(isset($inputs['context'])) { // Context hinting (optional) - $this->queriedContext = $inputs['context']; - unset($inputs['context']); - } + return; + } - if(empty(static::PARAMETERS)) { + $validator = new ParameterValidator(); - if(!empty($inputs)) { - returnClientError('Invalid parameters value(s)'); - } + if (!$validator->validateData($inputs, static::PARAMETERS)) { + $parameters = array_map( + function ($i) { + return $i['name']; + }, // Just display parameter names + $validator->getInvalidParameters() + ); - return; + returnClientError( + 'Invalid parameters value(s): ' + . implode(', ', $parameters) + ); + } - } + // Guess the context from input data + if (empty($this->queriedContext)) { + $this->queriedContext = $validator->getQueriedContext($inputs, static::PARAMETERS); + } - $validator = new ParameterValidator(); + if (is_null($this->queriedContext)) { + returnClientError('Required parameter(s) missing'); + } elseif ($this->queriedContext === false) { + returnClientError('Mixed context parameters'); + } - if(!$validator->validateData($inputs, static::PARAMETERS)) { - $parameters = array_map( - function($i){ return $i['name']; }, // Just display parameter names - $validator->getInvalidParameters() - ); + $this->setInputs($inputs, $this->queriedContext); + } - returnClientError( - 'Invalid parameters value(s): ' - . implode(', ', $parameters) - ); - } + /** + * Loads configuration for the bridge + * + * Returns errors and aborts execution if the provided configuration is + * invalid. + * + * @return void + */ + public function loadConfiguration() + { + foreach (static::CONFIGURATION as $optionName => $optionValue) { + $configurationOption = Configuration::getConfig(get_class($this), $optionName); - // Guess the context from input data - if(empty($this->queriedContext)) { - $this->queriedContext = $validator->getQueriedContext($inputs, static::PARAMETERS); - } + if ($configurationOption !== null) { + $this->configuration[$optionName] = $configurationOption; + continue; + } - if(is_null($this->queriedContext)) { - returnClientError('Required parameter(s) missing'); - } elseif($this->queriedContext === false) { - returnClientError('Mixed context parameters'); - } + if (isset($optionValue['required']) && $optionValue['required'] === true) { + returnServerError( + 'Missing configuration option: ' + . $optionName + ); + } elseif (isset($optionValue['defaultValue'])) { + $this->configuration[$optionName] = $optionValue['defaultValue']; + } + } + } - $this->setInputs($inputs, $this->queriedContext); + /** + * Returns the value for the provided input + * + * @param string $input The input name + * @return mixed|null The input value or null if the input is not defined + */ + protected function getInput($input) + { + if (!isset($this->inputs[$this->queriedContext][$input]['value'])) { + return null; + } + return $this->inputs[$this->queriedContext][$input]['value']; + } - } + /** + * Returns the value for the selected configuration + * + * @param string $input The option name + * @return mixed|null The option value or null if the input is not defined + */ + public function getOption($name) + { + if (!isset($this->configuration[$name])) { + return null; + } + return $this->configuration[$name]; + } - /** - * Loads configuration for the bridge - * - * Returns errors and aborts execution if the provided configuration is - * invalid. - * - * @return void - */ - public function loadConfiguration() { - foreach(static::CONFIGURATION as $optionName => $optionValue) { + /** {@inheritdoc} */ + public function getDescription() + { + return static::DESCRIPTION; + } - $configurationOption = Configuration::getConfig(get_class($this), $optionName); + /** {@inheritdoc} */ + public function getMaintainer() + { + return static::MAINTAINER; + } - if($configurationOption !== null) { - $this->configuration[$optionName] = $configurationOption; - continue; - } + /** {@inheritdoc} */ + public function getName() + { + return static::NAME; + } - if(isset($optionValue['required']) && $optionValue['required'] === true) { - returnServerError( - 'Missing configuration option: ' - . $optionName - ); - } elseif(isset($optionValue['defaultValue'])) { - $this->configuration[$optionName] = $optionValue['defaultValue']; - } + /** {@inheritdoc} */ + public function getIcon() + { + return static::URI . '/favicon.ico'; + } - } - } + /** {@inheritdoc} */ + public function getConfiguration() + { + return static::CONFIGURATION; + } - /** - * Returns the value for the provided input - * - * @param string $input The input name - * @return mixed|null The input value or null if the input is not defined - */ - protected function getInput($input){ - if(!isset($this->inputs[$this->queriedContext][$input]['value'])) { - return null; - } - return $this->inputs[$this->queriedContext][$input]['value']; - } + /** {@inheritdoc} */ + public function getParameters() + { + return static::PARAMETERS; + } - /** - * Returns the value for the selected configuration - * - * @param string $input The option name - * @return mixed|null The option value or null if the input is not defined - */ - public function getOption($name){ - if(!isset($this->configuration[$name])) { - return null; - } - return $this->configuration[$name]; - } + /** {@inheritdoc} */ + public function getURI() + { + return static::URI; + } - /** {@inheritdoc} */ - public function getDescription(){ - return static::DESCRIPTION; - } + /** {@inheritdoc} */ + public function getDonationURI() + { + return static::DONATION_URI; + } - /** {@inheritdoc} */ - public function getMaintainer(){ - return static::MAINTAINER; - } + /** {@inheritdoc} */ + public function getCacheTimeout() + { + return static::CACHE_TIMEOUT; + } - /** {@inheritdoc} */ - public function getName(){ - return static::NAME; - } + /** {@inheritdoc} */ + public function detectParameters($url) + { + $regex = '/^(https?:\/\/)?(www\.)?(.+?)(\/)?$/'; + if ( + empty(static::PARAMETERS) + && preg_match($regex, $url, $urlMatches) > 0 + && preg_match($regex, static::URI, $bridgeUriMatches) > 0 + && $urlMatches[3] === $bridgeUriMatches[3] + ) { + return []; + } else { + return null; + } + } - /** {@inheritdoc} */ - public function getIcon(){ - return static::URI . '/favicon.ico'; - } + /** + * Loads a cached value for the specified key + * + * @param string $key Key name + * @param int $duration Cache duration (optional, default: 24 hours) + * @return mixed Cached value or null if the key doesn't exist or has expired + */ + protected function loadCacheValue($key, $duration = 86400) + { + $cacheFac = new CacheFactory(); - /** {@inheritdoc} */ - public function getConfiguration(){ - return static::CONFIGURATION; - } + $cache = $cacheFac->create(Configuration::getConfig('cache', 'type')); + $cache->setScope(get_called_class()); + $cache->setKey($key); + if ($cache->getTime() < time() - $duration) { + return null; + } + return $cache->loadData(); + } - /** {@inheritdoc} */ - public function getParameters(){ - return static::PARAMETERS; - } + /** + * Stores a value to cache with the specified key + * + * @param string $key Key name + * @param mixed $value Value to cache + */ + protected function saveCacheValue($key, $value) + { + $cacheFac = new CacheFactory(); - /** {@inheritdoc} */ - public function getURI(){ - return static::URI; - } - - /** {@inheritdoc} */ - public function getDonationURI(){ - return static::DONATION_URI; - } - - /** {@inheritdoc} */ - public function getCacheTimeout(){ - return static::CACHE_TIMEOUT; - } - - /** {@inheritdoc} */ - public function detectParameters($url){ - $regex = '/^(https?:\/\/)?(www\.)?(.+?)(\/)?$/'; - if(empty(static::PARAMETERS) - && preg_match($regex, $url, $urlMatches) > 0 - && preg_match($regex, static::URI, $bridgeUriMatches) > 0 - && $urlMatches[3] === $bridgeUriMatches[3]) { - return array(); - } else { - return null; - } - } - - /** - * Loads a cached value for the specified key - * - * @param string $key Key name - * @param int $duration Cache duration (optional, default: 24 hours) - * @return mixed Cached value or null if the key doesn't exist or has expired - */ - protected function loadCacheValue($key, $duration = 86400){ - $cacheFac = new CacheFactory(); - - $cache = $cacheFac->create(Configuration::getConfig('cache', 'type')); - $cache->setScope(get_called_class()); - $cache->setKey($key); - if($cache->getTime() < time() - $duration) - return null; - return $cache->loadData(); - } - - /** - * Stores a value to cache with the specified key - * - * @param string $key Key name - * @param mixed $value Value to cache - */ - protected function saveCacheValue($key, $value){ - $cacheFac = new CacheFactory(); - - $cache = $cacheFac->create(Configuration::getConfig('cache', 'type')); - $cache->setScope(get_called_class()); - $cache->setKey($key); - $cache->saveData($value); - } + $cache = $cacheFac->create(Configuration::getConfig('cache', 'type')); + $cache->setScope(get_called_class()); + $cache->setKey($key); + $cache->saveData($value); + } } diff --git a/lib/BridgeCard.php b/lib/BridgeCard.php index 22520170..78132776 100644 --- a/lib/BridgeCard.php +++ b/lib/BridgeCard.php @@ -1,4 +1,5 @@ EOD; - if(!empty($parameterName)) { - $form .= << EOD; - } + } - if(!$isHttps) { - $form .= '
    Warning : + if (!$isHttps) { + $form .= '
    Warning : This bridge is not fetching its content through a secure connection
    '; - } + } - return $form; - } + return $form; + } - /** - * Get the form body for a bridge - * - * @param string $bridgeName The bridge name - * @param array $formats A list of supported formats - * @param bool $isActive Indicates if a bridge is enabled or not - * @param bool $isHttps Indicates if a bridge uses HTTPS or not - * @param string $parameterName Sets the bridge context for the current form - * @param array $parameters The bridge parameters - * @return string The form body - */ - private static function getForm($bridgeName, - $formats, - $isActive = false, - $isHttps = false, - $parameterName = '', - $parameters = array()) { - $form = self::getFormHeader($bridgeName, $isHttps, $parameterName); + /** + * Get the form body for a bridge + * + * @param string $bridgeName The bridge name + * @param array $formats A list of supported formats + * @param bool $isActive Indicates if a bridge is enabled or not + * @param bool $isHttps Indicates if a bridge uses HTTPS or not + * @param string $parameterName Sets the bridge context for the current form + * @param array $parameters The bridge parameters + * @return string The form body + */ + private static function getForm( + $bridgeName, + $formats, + $isActive = false, + $isHttps = false, + $parameterName = '', + $parameters = [] + ) { + $form = self::getFormHeader($bridgeName, $isHttps, $parameterName); - if(count($parameters) > 0) { + if (count($parameters) > 0) { + $form .= '
    '; - $form .= '
    '; + foreach ($parameters as $id => $inputEntry) { + if (!isset($inputEntry['exampleValue'])) { + $inputEntry['exampleValue'] = ''; + } - foreach($parameters as $id => $inputEntry) { - if(!isset($inputEntry['exampleValue'])) - $inputEntry['exampleValue'] = ''; + if (!isset($inputEntry['defaultValue'])) { + $inputEntry['defaultValue'] = ''; + } - if(!isset($inputEntry['defaultValue'])) - $inputEntry['defaultValue'] = ''; + $idArg = 'arg-' + . urlencode($bridgeName) + . '-' + . urlencode($parameterName) + . '-' + . urlencode($id); - $idArg = 'arg-' - . urlencode($bridgeName) - . '-' - . urlencode($parameterName) - . '-' - . urlencode($id); + $form .= '' + . PHP_EOL; - $form .= '' - . PHP_EOL; + if (!isset($inputEntry['type']) || $inputEntry['type'] === 'text') { + $form .= self::getTextInput($inputEntry, $idArg, $id); + } elseif ($inputEntry['type'] === 'number') { + $form .= self::getNumberInput($inputEntry, $idArg, $id); + } elseif ($inputEntry['type'] === 'list') { + $form .= self::getListInput($inputEntry, $idArg, $id); + } elseif ($inputEntry['type'] === 'checkbox') { + $form .= self::getCheckboxInput($inputEntry, $idArg, $id); + } - if(!isset($inputEntry['type']) || $inputEntry['type'] === 'text') { - $form .= self::getTextInput($inputEntry, $idArg, $id); - } elseif($inputEntry['type'] === 'number') { - $form .= self::getNumberInput($inputEntry, $idArg, $id); - } else if($inputEntry['type'] === 'list') { - $form .= self::getListInput($inputEntry, $idArg, $id); - } elseif($inputEntry['type'] === 'checkbox') { - $form .= self::getCheckboxInput($inputEntry, $idArg, $id); - } + if (isset($inputEntry['title'])) { + $title_filtered = filter_var($inputEntry['title'], FILTER_SANITIZE_FULL_SPECIAL_CHARS); + $form .= 'i'; + } else { + $form .= ''; + } + } - if(isset($inputEntry['title'])) { - $title_filtered = filter_var($inputEntry['title'], FILTER_SANITIZE_FULL_SPECIAL_CHARS); - $form .= 'i'; - } else { - $form .= ''; - } - } + $form .= '
    '; + } - $form .= '
    '; + if ($isActive) { + $form .= ''; + } else { + $form .= 'Inactive'; + } - } + return $form . '' . PHP_EOL; + } - if($isActive) { - $form .= ''; - } else { - $form .= 'Inactive'; - } + /** + * Get input field attributes + * + * @param array $entry The current entry + * @return string The input field attributes + */ + private static function getInputAttributes($entry) + { + $retVal = ''; - return $form . '' . PHP_EOL; - } + if (isset($entry['required']) && $entry['required'] === true) { + $retVal .= ' required'; + } - /** - * Get input field attributes - * - * @param array $entry The current entry - * @return string The input field attributes - */ - private static function getInputAttributes($entry) { - $retVal = ''; + if (isset($entry['pattern'])) { + $retVal .= ' pattern="' . $entry['pattern'] . '"'; + } - if(isset($entry['required']) && $entry['required'] === true) - $retVal .= ' required'; + return $retVal; + } - if(isset($entry['pattern'])) - $retVal .= ' pattern="' . $entry['pattern'] . '"'; + /** + * Get text input + * + * @param array $entry The current entry + * @param string $id The field ID + * @param string $name The field name + * @return string The text input field + */ + private static function getTextInput($entry, $id, $name) + { + return '' + . PHP_EOL; + } - return $retVal; - } + /** + * Get number input + * + * @param array $entry The current entry + * @param string $id The field ID + * @param string $name The field name + * @return string The number input field + */ + private static function getNumberInput($entry, $id, $name) + { + return '' + . PHP_EOL; + } - /** - * Get text input - * - * @param array $entry The current entry - * @param string $id The field ID - * @param string $name The field name - * @return string The text input field - */ - private static function getTextInput($entry, $id, $name) { - return '' - . PHP_EOL; - } + /** + * Get list input + * + * @param array $entry The current entry + * @param string $id The field ID + * @param string $name The field name + * @return string The list input field + */ + private static function getListInput($entry, $id, $name) + { + if (isset($entry['required']) && $entry['required'] === true) { + Debug::log('The "required" attribute is not supported for lists.'); + unset($entry['required']); + } - /** - * Get number input - * - * @param array $entry The current entry - * @param string $id The field ID - * @param string $name The field name - * @return string The number input field - */ - private static function getNumberInput($entry, $id, $name) { - return '' - . PHP_EOL; - } + $list = ''; + $list .= ''; - foreach($entry['values'] as $name => $value) { - if(is_array($value)) { - $list .= ''; - foreach($value as $subname => $subvalue) { - if($entry['defaultValue'] === $subname - || $entry['defaultValue'] === $subvalue) { - $list .= ''; - } else { - $list .= ''; - } - } - $list .= ''; - } else { - if($entry['defaultValue'] === $name - || $entry['defaultValue'] === $value) { - $list .= ''; - } else { - $list .= ''; - } - } - } + return $list; + } - $list .= ''; + /** + * Get checkbox input + * + * @param array $entry The current entry + * @param string $id The field ID + * @param string $name The field name + * @return string The checkbox input field + */ + private static function getCheckboxInput($entry, $id, $name) + { + if (isset($entry['required']) && $entry['required'] === true) { + Debug::log('The "required" attribute is not supported for checkboxes.'); + unset($entry['required']); + } - return $list; - } + return '' + . PHP_EOL; + } - /** - * Get checkbox input - * - * @param array $entry The current entry - * @param string $id The field ID - * @param string $name The field name - * @return string The checkbox input field - */ - private static function getCheckboxInput($entry, $id, $name) { - if(isset($entry['required']) && $entry['required'] === true) { - Debug::log('The "required" attribute is not supported for checkboxes.'); - unset($entry['required']); - } + /** + * Gets a single bridge card + * + * @param string $bridgeName The bridge name + * @param array $formats A list of formats + * @param bool $isActive Indicates if the bridge is active or not + * @return string The bridge card + */ + public static function displayBridgeCard($bridgeName, $formats, $isActive = true) + { + $bridgeFac = new \BridgeFactory(); - return '' - . PHP_EOL; - } + $bridge = $bridgeFac->create($bridgeName); - /** - * Gets a single bridge card - * - * @param string $bridgeName The bridge name - * @param array $formats A list of formats - * @param bool $isActive Indicates if the bridge is active or not - * @return string The bridge card - */ - public static function displayBridgeCard($bridgeName, $formats, $isActive = true){ + if ($bridge == false) { + return ''; + } - $bridgeFac = new \BridgeFactory(); + $isHttps = strpos($bridge->getURI(), 'https') === 0; - $bridge = $bridgeFac->create($bridgeName); + $uri = $bridge->getURI(); + $name = $bridge->getName(); + $icon = $bridge->getIcon(); + $description = $bridge->getDescription(); + $parameters = $bridge->getParameters(); + $donationUri = $bridge->getDonationURI(); + $maintainer = $bridge->getMaintainer(); - if($bridge == false) - return ''; + $donationsAllowed = Configuration::getConfig('admin', 'donations'); - $isHttps = strpos($bridge->getURI(), 'https') === 0; + if (defined('PROXY_URL') && PROXY_BYBRIDGE) { + $parameters['global']['_noproxy'] = [ + 'name' => 'Disable proxy (' . ((defined('PROXY_NAME') && PROXY_NAME) ? PROXY_NAME : PROXY_URL) . ')', + 'type' => 'checkbox' + ]; + } - $uri = $bridge->getURI(); - $name = $bridge->getName(); - $icon = $bridge->getIcon(); - $description = $bridge->getDescription(); - $parameters = $bridge->getParameters(); - $donationUri = $bridge->getDonationURI(); - $maintainer = $bridge->getMaintainer(); + if (CUSTOM_CACHE_TIMEOUT) { + $parameters['global']['_cache_timeout'] = [ + 'name' => 'Cache timeout in seconds', + 'type' => 'number', + 'defaultValue' => $bridge->getCacheTimeout() + ]; + } - $donationsAllowed = Configuration::getConfig('admin', 'donations'); - - if(defined('PROXY_URL') && PROXY_BYBRIDGE) { - $parameters['global']['_noproxy'] = array( - 'name' => 'Disable proxy (' . ((defined('PROXY_NAME') && PROXY_NAME) ? PROXY_NAME : PROXY_URL) . ')', - 'type' => 'checkbox' - ); - } - - if(CUSTOM_CACHE_TIMEOUT) { - $parameters['global']['_cache_timeout'] = array( - 'name' => 'Cache timeout in seconds', - 'type' => 'number', - 'defaultValue' => $bridge->getCacheTimeout() - ); - } - - $card = <<

    {$name}

    {$description}

    @@ -330,38 +347,39 @@ This bridge is not fetching its content through a secure connection
    '; CARD; - // If we don't have any parameter for the bridge, we print a generic form to load it. - if (count($parameters) === 0) { - $card .= self::getForm($bridgeName, $formats, $isActive, $isHttps); + // If we don't have any parameter for the bridge, we print a generic form to load it. + if (count($parameters) === 0) { + $card .= self::getForm($bridgeName, $formats, $isActive, $isHttps); - // Display form with cache timeout and/or noproxy options (if enabled) when bridge has no parameters - } else if (count($parameters) === 1 && array_key_exists('global', $parameters)) { - $card .= self::getForm($bridgeName, $formats, $isActive, $isHttps, '', $parameters['global']); - } else { + // Display form with cache timeout and/or noproxy options (if enabled) when bridge has no parameters + } elseif (count($parameters) === 1 && array_key_exists('global', $parameters)) { + $card .= self::getForm($bridgeName, $formats, $isActive, $isHttps, '', $parameters['global']); + } else { + foreach ($parameters as $parameterName => $parameter) { + if (!is_numeric($parameterName) && $parameterName === 'global') { + continue; + } - foreach($parameters as $parameterName => $parameter) { - if(!is_numeric($parameterName) && $parameterName === 'global') - continue; + if (array_key_exists('global', $parameters)) { + $parameter = array_merge($parameter, $parameters['global']); + } - if(array_key_exists('global', $parameters)) - $parameter = array_merge($parameter, $parameters['global']); + if (!is_numeric($parameterName)) { + $card .= '
    ' . $parameterName . '
    ' . PHP_EOL; + } - if(!is_numeric($parameterName)) - $card .= '
    ' . $parameterName . '
    ' . PHP_EOL; + $card .= self::getForm($bridgeName, $formats, $isActive, $isHttps, $parameterName, $parameter); + } + } - $card .= self::getForm($bridgeName, $formats, $isActive, $isHttps, $parameterName, $parameter); - } + $card .= ''; + if ($donationUri !== '' && $donationsAllowed) { + $card .= '

    ' . $maintainer . ' ~ Donate

    '; + } else { + $card .= '

    ' . $maintainer . '

    '; + } + $card .= ''; - } - - $card .= ''; - if($donationUri !== '' && $donationsAllowed) { - $card .= '

    ' . $maintainer . ' ~ Donate

    '; - } else { - $card .= '

    ' . $maintainer . '

    '; - } - $card .= ''; - - return $card; - } + return $card; + } } diff --git a/lib/BridgeFactory.php b/lib/BridgeFactory.php index f435261c..3e355b7a 100644 --- a/lib/BridgeFactory.php +++ b/lib/BridgeFactory.php @@ -1,87 +1,87 @@ folder = $folder; - public function __construct(string $folder = PATH_LIB_BRIDGES) - { - $this->folder = $folder; + // create names + foreach (scandir($this->folder) as $file) { + if (preg_match('/^([^.]+)Bridge\.php$/U', $file, $m)) { + $this->bridgeNames[] = $m[1]; + } + } - // create names - foreach(scandir($this->folder) as $file) { - if(preg_match('/^([^.]+)Bridge\.php$/U', $file, $m)) { - $this->bridgeNames[] = $m[1]; - } - } + // create whitelist + if (file_exists(WHITELIST)) { + $contents = trim(file_get_contents(WHITELIST)); + } elseif (file_exists(WHITELIST_DEFAULT)) { + $contents = trim(file_get_contents(WHITELIST_DEFAULT)); + } else { + $contents = ''; + } + if ($contents === '*') { // Whitelist all bridges + $this->whitelist = $this->getBridgeNames(); + } else { + foreach (explode("\n", $contents) as $bridgeName) { + $this->whitelist[] = $this->sanitizeBridgeName($bridgeName); + } + } + } - // create whitelist - if (file_exists(WHITELIST)) { - $contents = trim(file_get_contents(WHITELIST)); - } elseif (file_exists(WHITELIST_DEFAULT)) { - $contents = trim(file_get_contents(WHITELIST_DEFAULT)); - } else { - $contents = ''; - } - if ($contents === '*') { // Whitelist all bridges - $this->whitelist = $this->getBridgeNames(); - } else { - foreach (explode("\n", $contents) as $bridgeName) { - $this->whitelist[] = $this->sanitizeBridgeName($bridgeName); - } - } - } + public function create(string $name): BridgeInterface + { + if (preg_match('/^[A-Z][a-zA-Z0-9-]*$/', $name)) { + $className = sprintf('%sBridge', $this->sanitizeBridgeName($name)); + return new $className(); + } + throw new \InvalidArgumentException('Bridge name invalid!'); + } - public function create(string $name): BridgeInterface - { - if(preg_match('/^[A-Z][a-zA-Z0-9-]*$/', $name)) { - $className = sprintf('%sBridge', $this->sanitizeBridgeName($name)); - return new $className(); - } - throw new \InvalidArgumentException('Bridge name invalid!'); - } + public function getBridgeNames(): array + { + return $this->bridgeNames; + } - public function getBridgeNames(): array - { - return $this->bridgeNames; - } + public function isWhitelisted($name): bool + { + return in_array($this->sanitizeBridgeName($name), $this->whitelist); + } - public function isWhitelisted($name): bool - { - return in_array($this->sanitizeBridgeName($name), $this->whitelist); - } + private function sanitizeBridgeName($name) + { + if (!is_string($name)) { + return null; + } - private function sanitizeBridgeName($name) { + // Trim trailing '.php' if exists + if (preg_match('/(.+)(?:\.php)/', $name, $matches)) { + $name = $matches[1]; + } - if(!is_string($name)) { - return null; - } + // Trim trailing 'Bridge' if exists + if (preg_match('/(.+)(?:Bridge)/i', $name, $matches)) { + $name = $matches[1]; + } - // Trim trailing '.php' if exists - if (preg_match('/(.+)(?:\.php)/', $name, $matches)) { - $name = $matches[1]; - } + // Improve performance for correctly written bridge names + if (in_array($name, $this->getBridgeNames())) { + $index = array_search($name, $this->getBridgeNames()); + return $this->getBridgeNames()[$index]; + } - // Trim trailing 'Bridge' if exists - if (preg_match('/(.+)(?:Bridge)/i', $name, $matches)) { - $name = $matches[1]; - } + // The name is valid if a corresponding bridge file is found on disk + if (in_array(strtolower($name), array_map('strtolower', $this->getBridgeNames()))) { + $index = array_search(strtolower($name), array_map('strtolower', $this->getBridgeNames())); + return $this->getBridgeNames()[$index]; + } - // Improve performance for correctly written bridge names - if (in_array($name, $this->getBridgeNames())) { - $index = array_search($name, $this->getBridgeNames()); - return $this->getBridgeNames()[$index]; - } - - // The name is valid if a corresponding bridge file is found on disk - if (in_array(strtolower($name), array_map('strtolower', $this->getBridgeNames()))) { - $index = array_search(strtolower($name), array_map('strtolower', $this->getBridgeNames())); - return $this->getBridgeNames()[$index]; - } - - Debug::log('Invalid bridge name specified: "' . $name . '"!'); - return null; - } + Debug::log('Invalid bridge name specified: "' . $name . '"!'); + return null; + } } diff --git a/lib/BridgeInterface.php b/lib/BridgeInterface.php index 70625125..6cf949c8 100644 --- a/lib/BridgeInterface.php +++ b/lib/BridgeInterface.php @@ -1,4 +1,5 @@ @@ -45,91 +48,87 @@ final class BridgeList { EOD; - } + } - /** - * Get the document body for all bridge cards - * - * @param bool $showInactive Inactive bridges are visible on the home page if - * enabled. - * @param int $totalBridges (ref) Returns the total number of bridges. - * @param int $totalActiveBridges (ref) Returns the number of active bridges. - * @return string The document body for all bridge cards. - */ - private static function getBridges($showInactive, &$totalBridges, &$totalActiveBridges) { + /** + * Get the document body for all bridge cards + * + * @param bool $showInactive Inactive bridges are visible on the home page if + * enabled. + * @param int $totalBridges (ref) Returns the total number of bridges. + * @param int $totalActiveBridges (ref) Returns the number of active bridges. + * @return string The document body for all bridge cards. + */ + private static function getBridges($showInactive, &$totalBridges, &$totalActiveBridges) + { + $body = ''; + $totalActiveBridges = 0; + $inactiveBridges = ''; - $body = ''; - $totalActiveBridges = 0; - $inactiveBridges = ''; + $bridgeFac = new \BridgeFactory(); + $bridgeList = $bridgeFac->getBridgeNames(); - $bridgeFac = new \BridgeFactory(); - $bridgeList = $bridgeFac->getBridgeNames(); + $formatFac = new FormatFactory(); + $formats = $formatFac->getFormatNames(); - $formatFac = new FormatFactory(); - $formats = $formatFac->getFormatNames(); + $totalBridges = count($bridgeList); - $totalBridges = count($bridgeList); + foreach ($bridgeList as $bridgeName) { + if ($bridgeFac->isWhitelisted($bridgeName)) { + $body .= BridgeCard::displayBridgeCard($bridgeName, $formats); + $totalActiveBridges++; + } elseif ($showInactive) { + // inactive bridges + $inactiveBridges .= BridgeCard::displayBridgeCard($bridgeName, $formats, false) . PHP_EOL; + } + } - foreach($bridgeList as $bridgeName) { + $body .= $inactiveBridges; - if($bridgeFac->isWhitelisted($bridgeName)) { + return $body; + } - $body .= BridgeCard::displayBridgeCard($bridgeName, $formats); - $totalActiveBridges++; + /** + * Get the document header + * + * @return string The document header + */ + private static function getHeader() + { + $warning = ''; - } elseif($showInactive) { - - // inactive bridges - $inactiveBridges .= BridgeCard::displayBridgeCard($bridgeName, $formats, false) . PHP_EOL; - - } - - } - - $body .= $inactiveBridges; - - return $body; - } - - /** - * Get the document header - * - * @return string The document header - */ - private static function getHeader() { - $warning = ''; - - if(Debug::isEnabled()) { - if(!Debug::isSecure()) { - $warning .= <<Warning : Debug mode is active from any location, make sure only you can access RSS-Bridge. EOD; - } else { - $warning .= <<Warning : Debug mode is active from your IP address, your requests will bypass the cache. EOD; - } - } + } + } - return << {$warning} EOD; - } + } - /** - * Get the searchbar - * - * @return string The searchbar - */ - private static function getSearchbar() { - $query = filter_input(INPUT_GET, 'q', FILTER_SANITIZE_SPECIAL_CHARS); + /** + * Get the searchbar + * + * @return string The searchbar + */ + private static function getSearchbar() + { + $query = filter_input(INPUT_GET, 'q', FILTER_SANITIZE_SPECIAL_CHARS); - return <<

    Search

    EOD; - } + } - /** - * Get the document footer - * - * @param int $totalBridges The total number of bridges, shown in the footer - * @param int $totalActiveBridges The total number of active bridges, shown - * in the footer. - * @param bool $showInactive Sets the 'Show active'/'Show inactive' text in - * the footer. - * @return string The document footer - */ - private static function getFooter($totalBridges, $totalActiveBridges, $showInactive) { - $version = Configuration::getVersion(); + /** + * Get the document footer + * + * @param int $totalBridges The total number of bridges, shown in the footer + * @param int $totalActiveBridges The total number of active bridges, shown + * in the footer. + * @param bool $showInactive Sets the 'Show active'/'Show inactive' text in + * the footer. + * @return string The document footer + */ + private static function getFooter($totalBridges, $totalActiveBridges, $showInactive) + { + $version = Configuration::getVersion(); - $email = Configuration::getConfig('admin', 'email'); - $admininfo = ''; - if (!empty($email)) { - $admininfo = << You may email the administrator of this RSS-Bridge instance at {$email} EOD; - } + } - $inactive = ''; + $inactive = ''; - if($totalActiveBridges !== $totalBridges) { + if ($totalActiveBridges !== $totalBridges) { + if (!$showInactive) { + $inactive = '
    '; + } else { + $inactive = '
    '; + } + } - if(!$showInactive) { - $inactive = '
    '; - } else { - $inactive = '
    '; - } - - } - - return << RSS-Bridge ~ Public Domain

    {$version}

    @@ -185,28 +183,27 @@ EOD; {$admininfo} EOD; - } + } - /** - * Create the entire home page - * - * @param bool $showInactive Inactive bridges are displayed on the home page, - * if enabled. - * @return string The home page - */ - public static function create($showInactive = true) { + /** + * Create the entire home page + * + * @param bool $showInactive Inactive bridges are displayed on the home page, + * if enabled. + * @return string The home page + */ + public static function create($showInactive = true) + { + $totalBridges = 0; + $totalActiveBridges = 0; - $totalBridges = 0; - $totalActiveBridges = 0; - - return '' - . BridgeList::getHead() - . '' - . BridgeList::getHeader() - . BridgeList::getSearchbar() - . BridgeList::getBridges($showInactive, $totalBridges, $totalActiveBridges) - . BridgeList::getFooter($totalBridges, $totalActiveBridges, $showInactive) - . ''; - - } + return '' + . BridgeList::getHead() + . '' + . BridgeList::getHeader() + . BridgeList::getSearchbar() + . BridgeList::getBridges($showInactive, $totalBridges, $totalActiveBridges) + . BridgeList::getFooter($totalBridges, $totalActiveBridges, $showInactive) + . ''; + } } diff --git a/lib/CacheFactory.php b/lib/CacheFactory.php index 451f625f..ba1c3cb9 100644 --- a/lib/CacheFactory.php +++ b/lib/CacheFactory.php @@ -1,4 +1,5 @@ folder = $folder; - // create cache names - foreach(scandir($this->folder) as $file) { - if(preg_match('/^([^.]+)Cache\.php$/U', $file, $m)) { - $this->cacheNames[] = $m[1]; - } - } - } + public function __construct(string $folder = PATH_LIB_CACHES) + { + $this->folder = $folder; + // create cache names + foreach (scandir($this->folder) as $file) { + if (preg_match('/^([^.]+)Cache\.php$/U', $file, $m)) { + $this->cacheNames[] = $m[1]; + } + } + } - /** - * @param string $name The name of the cache e.g. "File", "Memcached" or "SQLite" - */ - public function create(string $name): CacheInterface - { - $name = $this->sanitizeCacheName($name) . 'Cache'; + /** + * @param string $name The name of the cache e.g. "File", "Memcached" or "SQLite" + */ + public function create(string $name): CacheInterface + { + $name = $this->sanitizeCacheName($name) . 'Cache'; - if(! preg_match('/^[A-Z][a-zA-Z0-9-]*$/', $name)) { - throw new \InvalidArgumentException('Cache name invalid!'); - } + if (! preg_match('/^[A-Z][a-zA-Z0-9-]*$/', $name)) { + throw new \InvalidArgumentException('Cache name invalid!'); + } - $filePath = $this->folder . $name . '.php'; - if(!file_exists($filePath)) { - throw new \Exception('Invalid cache'); - } - $className = '\\' . $name; - return new $className(); - } + $filePath = $this->folder . $name . '.php'; + if (!file_exists($filePath)) { + throw new \Exception('Invalid cache'); + } + $className = '\\' . $name; + return new $className(); + } - protected function sanitizeCacheName(string $name) - { - // Trim trailing '.php' if exists - if (preg_match('/(.+)(?:\.php)/', $name, $matches)) { - $name = $matches[1]; - } + protected function sanitizeCacheName(string $name) + { + // Trim trailing '.php' if exists + if (preg_match('/(.+)(?:\.php)/', $name, $matches)) { + $name = $matches[1]; + } - // Trim trailing 'Cache' if exists - if (preg_match('/(.+)(?:Cache)$/i', $name, $matches)) { - $name = $matches[1]; - } + // Trim trailing 'Cache' if exists + if (preg_match('/(.+)(?:Cache)$/i', $name, $matches)) { + $name = $matches[1]; + } - if(in_array(strtolower($name), array_map('strtolower', $this->cacheNames))) { - $index = array_search(strtolower($name), array_map('strtolower', $this->cacheNames)); - return $this->cacheNames[$index]; - } - return null; - } + if (in_array(strtolower($name), array_map('strtolower', $this->cacheNames))) { + $index = array_search(strtolower($name), array_map('strtolower', $this->cacheNames)); + return $this->cacheNames[$index]; + } + return null; + } } diff --git a/lib/CacheInterface.php b/lib/CacheInterface.php index 091c5f02..67cee681 100644 --- a/lib/CacheInterface.php +++ b/lib/CacheInterface.php @@ -1,4 +1,5 @@ $section) { + foreach ($section as $key => $value) { + Configuration::$config[$header][$key] = $value; + } + } + } - /** - * Loads the configuration from disk and checks if the parameters are valid. - * - * Returns an error message and aborts execution if the configuration is invalid. - * - * The RSS-Bridge configuration is split into two files: - * - {@see FILE_CONFIG_DEFAULT} The default configuration file that ships - * with every release of RSS-Bridge (do not modify this file!). - * - {@see FILE_CONFIG} The local configuration file that can be modified - * by server administrators. - * - * RSS-Bridge will first load {@see FILE_CONFIG_DEFAULT} into memory and then - * replace parameters with the contents of {@see FILE_CONFIG}. That way new - * parameters are automatically initialized with default values and custom - * configurations can be reduced to the minimum set of parametes necessary - * (only the ones that changed). - * - * The configuration files must be placed in the root folder of RSS-Bridge - * (next to `index.php`). - * - * _Notice_: The configuration is stored in {@see Configuration::$config}. - * - * @return void - */ - public static function loadConfiguration() { + foreach (getenv() as $envkey => $value) { + // Replace all settings with their respective environment variable if available + $keyArray = explode('_', $envkey); + if ($keyArray[0] === 'RSSBRIDGE') { + $header = strtolower($keyArray[1]); + $key = strtolower($keyArray[2]); + if ($value === 'true' || $value === 'false') { + $value = filter_var($value, FILTER_VALIDATE_BOOLEAN); + } + Configuration::$config[$header][$key] = $value; + } + } - if(!file_exists(FILE_CONFIG_DEFAULT)) - self::reportError('The default configuration file is missing at ' . FILE_CONFIG_DEFAULT); + if ( + !is_string(self::getConfig('system', 'timezone')) + || !in_array(self::getConfig('system', 'timezone'), timezone_identifiers_list(DateTimeZone::ALL_WITH_BC)) + ) { + self::reportConfigurationError('system', 'timezone'); + } - Configuration::$config = parse_ini_file(FILE_CONFIG_DEFAULT, true, INI_SCANNER_TYPED); - if(!Configuration::$config) - self::reportError('Error parsing ' . FILE_CONFIG_DEFAULT); + date_default_timezone_set(self::getConfig('system', 'timezone')); - if(file_exists(FILE_CONFIG)) { - // Replace default configuration with custom settings - foreach(parse_ini_file(FILE_CONFIG, true, INI_SCANNER_TYPED) as $header => $section) { - foreach($section as $key => $value) { - Configuration::$config[$header][$key] = $value; - } - } - } + if (!is_string(self::getConfig('proxy', 'url'))) { + self::reportConfigurationError('proxy', 'url', 'Is not a valid string'); + } - foreach (getenv() as $envkey => $value) { - // Replace all settings with their respective environment variable if available - $keyArray = explode('_', $envkey); - if($keyArray[0] === 'RSSBRIDGE') { - $header = strtolower($keyArray[1]); - $key = strtolower($keyArray[2]); - if($value === 'true' || $value === 'false') { - $value = filter_var($value, FILTER_VALIDATE_BOOLEAN); - } - Configuration::$config[$header][$key] = $value; - } - } + if (!empty(self::getConfig('proxy', 'url'))) { + /** URL of the proxy server */ + define('PROXY_URL', self::getConfig('proxy', 'url')); + } - if(!is_string(self::getConfig('system', 'timezone')) - || !in_array(self::getConfig('system', 'timezone'), timezone_identifiers_list(DateTimeZone::ALL_WITH_BC))) - self::reportConfigurationError('system', 'timezone'); + if (!is_bool(self::getConfig('proxy', 'by_bridge'))) { + self::reportConfigurationError('proxy', 'by_bridge', 'Is not a valid Boolean'); + } - date_default_timezone_set(self::getConfig('system', 'timezone')); + /** True if proxy usage can be enabled selectively for each bridge */ + define('PROXY_BYBRIDGE', self::getConfig('proxy', 'by_bridge')); - if(!is_string(self::getConfig('proxy', 'url'))) - self::reportConfigurationError('proxy', 'url', 'Is not a valid string'); + if (!is_string(self::getConfig('proxy', 'name'))) { + self::reportConfigurationError('proxy', 'name', 'Is not a valid string'); + } - if(!empty(self::getConfig('proxy', 'url'))) { - /** URL of the proxy server */ - define('PROXY_URL', self::getConfig('proxy', 'url')); - } + /** Name of the proxy server */ + define('PROXY_NAME', self::getConfig('proxy', 'name')); - if(!is_bool(self::getConfig('proxy', 'by_bridge'))) - self::reportConfigurationError('proxy', 'by_bridge', 'Is not a valid Boolean'); + if (!is_string(self::getConfig('cache', 'type'))) { + self::reportConfigurationError('cache', 'type', 'Is not a valid string'); + } - /** True if proxy usage can be enabled selectively for each bridge */ - define('PROXY_BYBRIDGE', self::getConfig('proxy', 'by_bridge')); + if (!is_bool(self::getConfig('cache', 'custom_timeout'))) { + self::reportConfigurationError('cache', 'custom_timeout', 'Is not a valid Boolean'); + } - if(!is_string(self::getConfig('proxy', 'name'))) - self::reportConfigurationError('proxy', 'name', 'Is not a valid string'); + /** True if the cache timeout can be specified by the user */ + define('CUSTOM_CACHE_TIMEOUT', self::getConfig('cache', 'custom_timeout')); - /** Name of the proxy server */ - define('PROXY_NAME', self::getConfig('proxy', 'name')); + if (!is_bool(self::getConfig('authentication', 'enable'))) { + self::reportConfigurationError('authentication', 'enable', 'Is not a valid Boolean'); + } - if(!is_string(self::getConfig('cache', 'type'))) - self::reportConfigurationError('cache', 'type', 'Is not a valid string'); + if (!is_string(self::getConfig('authentication', 'username'))) { + self::reportConfigurationError('authentication', 'username', 'Is not a valid string'); + } - if(!is_bool(self::getConfig('cache', 'custom_timeout'))) - self::reportConfigurationError('cache', 'custom_timeout', 'Is not a valid Boolean'); + if (!is_string(self::getConfig('authentication', 'password'))) { + self::reportConfigurationError('authentication', 'password', 'Is not a valid string'); + } - /** True if the cache timeout can be specified by the user */ - define('CUSTOM_CACHE_TIMEOUT', self::getConfig('cache', 'custom_timeout')); + if ( + !empty(self::getConfig('admin', 'email')) + && !filter_var(self::getConfig('admin', 'email'), FILTER_VALIDATE_EMAIL) + ) { + self::reportConfigurationError('admin', 'email', 'Is not a valid email address'); + } - if(!is_bool(self::getConfig('authentication', 'enable'))) - self::reportConfigurationError('authentication', 'enable', 'Is not a valid Boolean'); + if (!is_bool(self::getConfig('admin', 'donations'))) { + self::reportConfigurationError('admin', 'donations', 'Is not a valid Boolean'); + } - if(!is_string(self::getConfig('authentication', 'username'))) - self::reportConfigurationError('authentication', 'username', 'Is not a valid string'); + if (!is_string(self::getConfig('error', 'output'))) { + self::reportConfigurationError('error', 'output', 'Is not a valid String'); + } - if(!is_string(self::getConfig('authentication', 'password'))) - self::reportConfigurationError('authentication', 'password', 'Is not a valid string'); + if ( + !is_numeric(self::getConfig('error', 'report_limit')) + || self::getConfig('error', 'report_limit') < 1 + ) { + self::reportConfigurationError('admin', 'report_limit', 'Value is invalid'); + } + } - if(!empty(self::getConfig('admin', 'email')) - && !filter_var(self::getConfig('admin', 'email'), FILTER_VALIDATE_EMAIL)) - self::reportConfigurationError('admin', 'email', 'Is not a valid email address'); + /** + * Returns the value of a parameter identified by section and key. + * + * @param string $section The section name. + * @param string $key The property name (key). + * @return mixed|null The parameter value. + */ + public static function getConfig($section, $key) + { + if (array_key_exists($section, self::$config) && array_key_exists($key, self::$config[$section])) { + return self::$config[$section][$key]; + } - if(!is_bool(self::getConfig('admin', 'donations'))) - self::reportConfigurationError('admin', 'donations', 'Is not a valid Boolean'); + return null; + } - if(!is_string(self::getConfig('error', 'output'))) - self::reportConfigurationError('error', 'output', 'Is not a valid String'); + /** + * Returns the current version string of RSS-Bridge. + * + * This function returns the contents of {@see Configuration::$VERSION} for + * regular installations and the git branch name and commit id for instances + * running in a git environment. + * + * @return string The version string. + */ + public static function getVersion() + { + $headFile = PATH_ROOT . '.git/HEAD'; - if(!is_numeric(self::getConfig('error', 'report_limit')) - || self::getConfig('error', 'report_limit') < 1) - self::reportConfigurationError('admin', 'report_limit', 'Value is invalid'); + // '@' is used to mute open_basedir warning + if (@is_readable($headFile)) { + $revisionHashFile = '.git/' . substr(file_get_contents($headFile), 5, -1); + $parts = explode('/', $revisionHashFile); - } + if (isset($parts[3])) { + $branchName = $parts[3]; + if (file_exists($revisionHashFile)) { + return 'git.' . $branchName . '.' . substr(file_get_contents($revisionHashFile), 0, 7); + } + } + } - /** - * Returns the value of a parameter identified by section and key. - * - * @param string $section The section name. - * @param string $key The property name (key). - * @return mixed|null The parameter value. - */ - public static function getConfig($section, $key) { - if(array_key_exists($section, self::$config) && array_key_exists($key, self::$config[$section])) { - return self::$config[$section][$key]; - } + return Configuration::$VERSION; + } - return null; - } + /** + * Reports an configuration error for the specified section and key to the + * user and ends execution + * + * @param string $section The section name + * @param string $key The configuration key + * @param string $message An optional message to the user + * + * @return void + */ + private static function reportConfigurationError($section, $key, $message = '') + { + $report = "Parameter [{$section}] => \"{$key}\" is invalid!" . PHP_EOL; - /** - * Returns the current version string of RSS-Bridge. - * - * This function returns the contents of {@see Configuration::$VERSION} for - * regular installations and the git branch name and commit id for instances - * running in a git environment. - * - * @return string The version string. - */ - public static function getVersion() { + if (file_exists(FILE_CONFIG)) { + $report .= 'Please check your configuration file at ' . FILE_CONFIG . PHP_EOL; + } elseif (!file_exists(FILE_CONFIG_DEFAULT)) { + $report .= 'The default configuration file is missing at ' . FILE_CONFIG_DEFAULT . PHP_EOL; + } else { + $report .= 'The default configuration file is broken.' . PHP_EOL + . 'Restore the original file from ' . REPOSITORY . PHP_EOL; + } - $headFile = PATH_ROOT . '.git/HEAD'; + $report .= $message; + self::reportError($report); + } - // '@' is used to mute open_basedir warning - if(@is_readable($headFile)) { - - $revisionHashFile = '.git/' . substr(file_get_contents($headFile), 5, -1); - $parts = explode('/', $revisionHashFile); - - if(isset($parts[3])) { - $branchName = $parts[3]; - if(file_exists($revisionHashFile)) { - return 'git.' . $branchName . '.' . substr(file_get_contents($revisionHashFile), 0, 7); - } - } - } - - return Configuration::$VERSION; - - } - - /** - * Reports an configuration error for the specified section and key to the - * user and ends execution - * - * @param string $section The section name - * @param string $key The configuration key - * @param string $message An optional message to the user - * - * @return void - */ - private static function reportConfigurationError($section, $key, $message = '') { - - $report = "Parameter [{$section}] => \"{$key}\" is invalid!" . PHP_EOL; - - if(file_exists(FILE_CONFIG)) { - $report .= 'Please check your configuration file at ' . FILE_CONFIG . PHP_EOL; - } elseif(!file_exists(FILE_CONFIG_DEFAULT)) { - $report .= 'The default configuration file is missing at ' . FILE_CONFIG_DEFAULT . PHP_EOL; - } else { - $report .= 'The default configuration file is broken.' . PHP_EOL - . 'Restore the original file from ' . REPOSITORY . PHP_EOL; - } - - $report .= $message; - self::reportError($report); - - } - - /** - * Reports an error message to the user and ends execution - * - * @param string $message The error message - * - * @return void - */ - private static function reportError($message) { - - header('Content-Type: text/plain', true, 500); - die('Configuration error' . PHP_EOL . $message); - - } + /** + * Reports an error message to the user and ends execution + * + * @param string $message The error message + * + * @return void + */ + private static function reportError($message) + { + header('Content-Type: text/plain', true, 500); + die('Configuration error' . PHP_EOL . $message); + } } diff --git a/lib/Debug.php b/lib/Debug.php index f912fb3b..75bf5f33 100644 --- a/lib/Debug.php +++ b/lib/Debug.php @@ -1,4 +1,5 @@ ') . '->' + . $calling['function'] . ' - ' + . $text; - /** - * Returns true if debug mode is enabled only for specific IP addresses. - * - * Notice: The security flag is set by {@see Debug::isEnabled()}. If this - * function is called before {@see Debug::isEnabled()}, the default value is - * false! - * - * @return bool True if debug mode is secure - */ - public static function isSecure() { - return self::$secure; - } - - /** - * Adds a debug message to error_log if debug mode is enabled - * - * @param string $text The message to add to error_log - */ - public static function log($text) { - if(!self::isEnabled()) { - return; - } - - $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3); - $calling = end($backtrace); - $message = $calling['file'] . ':' - . $calling['line'] . ' class ' - . (isset($calling['class']) ? $calling['class'] : '') . '->' - . $calling['function'] . ' - ' - . $text; - - error_log($message); - } + error_log($message); + } } diff --git a/lib/Exceptions.php b/lib/Exceptions.php index a9d2365b..8cd42de5 100644 --- a/lib/Exceptions.php +++ b/lib/Exceptions.php @@ -1,4 +1,5 @@ 0) { - if(count($lables) === 1) { - $uri .= '&labels=' . urlencode($labels[0]); - } else { - foreach($labels as $label) { - $uri .= '&labels[]=' . urlencode($label); - } - } - } elseif(!is_null($labels) && is_string($labels)) { - $uri .= '&labels=' . urlencode($labels); - } + // Add labels + if (!is_null($labels) && is_array($labels) && count($labels) > 0) { + if (count($lables) === 1) { + $uri .= '&labels=' . urlencode($labels[0]); + } else { + foreach ($labels as $label) { + $uri .= '&labels[]=' . urlencode($label); + } + } + } elseif (!is_null($labels) && is_string($labels)) { + $uri .= '&labels=' . urlencode($labels); + } - // Add maintainer - if(!empty($maintainer)) { - $uri .= '&assignee=' . urlencode($maintainer); - } + // Add maintainer + if (!empty($maintainer)) { + $uri .= '&assignee=' . urlencode($maintainer); + } - return $uri; + return $uri; } function buildBridgeException(\Throwable $e, BridgeInterface $bridge): string { - $title = $bridge->getName() . ' failed with error ' . $e->getCode(); + $title = $bridge->getName() . ' failed with error ' . $e->getCode(); - // Build a GitHub compatible message - $body = 'Error message: `' - . $e->getMessage() - . "`\nQuery string: `" - . (isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '') - . "`\nVersion: `" - . Configuration::getVersion() - . '`'; + // Build a GitHub compatible message + $body = 'Error message: `' + . $e->getMessage() + . "`\nQuery string: `" + . (isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '') + . "`\nVersion: `" + . Configuration::getVersion() + . '`'; - $body_html = nl2br($body); - $link = buildGitHubIssueQuery($title, $body, 'Bridge-Broken', $bridge->getMaintainer()); - $searchQuery = buildGitHubSearchQuery($bridge::NAME); + $body_html = nl2br($body); + $link = buildGitHubIssueQuery($title, $body, 'Bridge-Broken', $bridge->getMaintainer()); + $searchQuery = buildGitHubSearchQuery($bridge::NAME); - $header = buildHeader($e, $bridge); - $message = <<{$bridge->getName()} was unable to receive or process the remote website's content!
    {$body_html} EOD; - $section = buildSection($e, $bridge, $message, $link, $searchQuery); + $section = buildSection($e, $bridge, $message, $link, $searchQuery); - return $section; + return $section; } function buildTransformException(\Throwable $e, BridgeInterface $bridge): string { - $title = $bridge->getName() . ' failed with error ' . $e->getCode(); + $title = $bridge->getName() . ' failed with error ' . $e->getCode(); - // Build a GitHub compatible message - $body = 'Error message: `' - . $e->getMessage() - . "`\nQuery string: `" - . (isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '') - . '`'; + // Build a GitHub compatible message + $body = 'Error message: `' + . $e->getMessage() + . "`\nQuery string: `" + . (isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '') + . '`'; - $link = buildGitHubIssueQuery($title, $body, 'Bridge-Broken', $bridge->getMaintainer()); - $searchQuery = buildGitHubSearchQuery($bridge::NAME); - $header = buildHeader($e, $bridge); - $message = "RSS-Bridge was unable to transform the contents returned by + $link = buildGitHubIssueQuery($title, $body, 'Bridge-Broken', $bridge->getMaintainer()); + $searchQuery = buildGitHubSearchQuery($bridge::NAME); + $header = buildHeader($e, $bridge); + $message = "RSS-Bridge was unable to transform the contents returned by {$bridge->getName()}!"; - $section = buildSection($e, $bridge, $message, $link, $searchQuery); + $section = buildSection($e, $bridge, $message, $link, $searchQuery); - return buildPage($title, $header, $section); + return buildPage($title, $header, $section); } /** @@ -124,8 +127,9 @@ function buildTransformException(\Throwable $e, BridgeInterface $bridge): string * * @todo This function belongs inside a class */ -function buildHeader($e, $bridge){ - return <<

    Error {$e->getCode()}

    {$e->getMessage()}

    @@ -146,8 +150,9 @@ EOD; * * @todo This function belongs inside a class */ -function buildSection($e, $bridge, $message, $link, $searchQuery){ - return <<

    {$message}

    @@ -178,8 +183,9 @@ EOD; * * @todo This function belongs inside a class */ -function buildPage($title, $header, $section){ - return << diff --git a/lib/FactoryAbstract.php b/lib/FactoryAbstract.php index c91ae2e0..53ffb839 100644 --- a/lib/FactoryAbstract.php +++ b/lib/FactoryAbstract.php @@ -1,4 +1,5 @@ workingDir = null; - /** - * Set the working directory. - * - * @param string $dir The working directory. - * @return void - */ - public function setWorkingDir($dir) { - $this->workingDir = null; + if (!is_string($dir)) { + throw new \InvalidArgumentException('Working directory must be a string!'); + } - if(!is_string($dir)) { - throw new \InvalidArgumentException('Working directory must be a string!'); - } + if (!file_exists($dir)) { + throw new \Exception('Working directory does not exist!'); + } - if(!file_exists($dir)) { - throw new \Exception('Working directory does not exist!'); - } + if (!is_dir($dir)) { + throw new \InvalidArgumentException($dir . ' is not a directory!'); + } - if(!is_dir($dir)) { - throw new \InvalidArgumentException($dir . ' is not a directory!'); - } + $this->workingDir = realpath($dir) . '/'; + } - $this->workingDir = realpath($dir) . '/'; - } + /** + * Get the working directory + * + * @return string The working directory. + */ + public function getWorkingDir() + { + if (is_null($this->workingDir)) { + throw new \LogicException('Working directory is not set!'); + } - /** - * Get the working directory - * - * @return string The working directory. - */ - public function getWorkingDir() { - if(is_null($this->workingDir)) { - throw new \LogicException('Working directory is not set!'); - } + return $this->workingDir; + } - return $this->workingDir; - } - - /** - * Creates a new instance for the object specified by name. - * - * @param string $name The name of the object to create. - * @return object The object instance - */ - abstract public function create($name); + /** + * Creates a new instance for the object specified by name. + * + * @param string $name The name of the object to create. + * @return object The object instance + */ + abstract public function create($name); } diff --git a/lib/FeedExpander.php b/lib/FeedExpander.php index b84c608a..b79bf3a8 100644 --- a/lib/FeedExpander.php +++ b/lib/FeedExpander.php @@ -1,4 +1,5 @@ item[0]): + Debug::log('Detected RSS 1.0 format'); + $this->feedType = self::FEED_TYPE_RSS_1_0; + $this->collectRss1($rssContent, $maxItems); + break; + case isset($rssContent->channel[0]): + Debug::log('Detected RSS 0.9x or 2.0 format'); + $this->feedType = self::FEED_TYPE_RSS_2_0; + $this->collectRss2($rssContent, $maxItems); + break; + case isset($rssContent->entry[0]): + Debug::log('Detected ATOM format'); + $this->feedType = self::FEED_TYPE_ATOM_1_0; + $this->collectAtom1($rssContent, $maxItems); + break; + default: + Debug::log('Unknown feed format/version'); + returnServerError('The feed format is unknown!'); + break; + } - Debug::log('Detecting feed format/version'); - switch(true) { - case isset($rssContent->item[0]): - Debug::log('Detected RSS 1.0 format'); - $this->feedType = self::FEED_TYPE_RSS_1_0; - $this->collectRss1($rssContent, $maxItems); - break; - case isset($rssContent->channel[0]): - Debug::log('Detected RSS 0.9x or 2.0 format'); - $this->feedType = self::FEED_TYPE_RSS_2_0; - $this->collectRss2($rssContent, $maxItems); - break; - case isset($rssContent->entry[0]): - Debug::log('Detected ATOM format'); - $this->feedType = self::FEED_TYPE_ATOM_1_0; - $this->collectAtom1($rssContent, $maxItems); - break; - default: - Debug::log('Unknown feed format/version'); - returnServerError('The feed format is unknown!'); - break; - } + return $this; + } - return $this; - } + /** + * Collect data from a RSS 1.0 compatible feed + * + * @link http://web.resource.org/rss/1.0/spec RDF Site Summary (RSS) 1.0 + * + * @param string $rssContent The RSS content + * @param int $maxItems Maximum number of items to collect from the feed + * (`-1`: no limit). + * @return void + * + * @todo Instead of passing $maxItems to all functions, just add all items + * and remove excessive items later. + */ + protected function collectRss1($rssContent, $maxItems) + { + $this->loadRss2Data($rssContent->channel[0]); + foreach ($rssContent->item as $item) { + Debug::log('parsing item ' . var_export($item, true)); + $tmp_item = $this->parseItem($item); + if (!empty($tmp_item)) { + $this->items[] = $tmp_item; + } + if ($maxItems !== -1 && count($this->items) >= $maxItems) { + break; + } + } + } - /** - * Collect data from a RSS 1.0 compatible feed - * - * @link http://web.resource.org/rss/1.0/spec RDF Site Summary (RSS) 1.0 - * - * @param string $rssContent The RSS content - * @param int $maxItems Maximum number of items to collect from the feed - * (`-1`: no limit). - * @return void - * - * @todo Instead of passing $maxItems to all functions, just add all items - * and remove excessive items later. - */ - protected function collectRss1($rssContent, $maxItems){ - $this->loadRss2Data($rssContent->channel[0]); - foreach($rssContent->item as $item) { - Debug::log('parsing item ' . var_export($item, true)); - $tmp_item = $this->parseItem($item); - if (!empty($tmp_item)) { - $this->items[] = $tmp_item; - } - if($maxItems !== -1 && count($this->items) >= $maxItems) break; - } - } + /** + * Collect data from a RSS 2.0 compatible feed + * + * @link http://www.rssboard.org/rss-specification RSS 2.0 Specification + * + * @param object $rssContent The RSS content + * @param int $maxItems Maximum number of items to collect from the feed + * (`-1`: no limit). + * @return void + * + * @todo Instead of passing $maxItems to all functions, just add all items + * and remove excessive items later. + */ + protected function collectRss2($rssContent, $maxItems) + { + $rssContent = $rssContent->channel[0]; + Debug::log('RSS content is ===========\n' + . var_export($rssContent, true) + . '==========='); - /** - * Collect data from a RSS 2.0 compatible feed - * - * @link http://www.rssboard.org/rss-specification RSS 2.0 Specification - * - * @param object $rssContent The RSS content - * @param int $maxItems Maximum number of items to collect from the feed - * (`-1`: no limit). - * @return void - * - * @todo Instead of passing $maxItems to all functions, just add all items - * and remove excessive items later. - */ - protected function collectRss2($rssContent, $maxItems){ - $rssContent = $rssContent->channel[0]; - Debug::log('RSS content is ===========\n' - . var_export($rssContent, true) - . '==========='); + $this->loadRss2Data($rssContent); + foreach ($rssContent->item as $item) { + Debug::log('parsing item ' . var_export($item, true)); + $tmp_item = $this->parseItem($item); + if (!empty($tmp_item)) { + $this->items[] = $tmp_item; + } + if ($maxItems !== -1 && count($this->items) >= $maxItems) { + break; + } + } + } - $this->loadRss2Data($rssContent); - foreach($rssContent->item as $item) { - Debug::log('parsing item ' . var_export($item, true)); - $tmp_item = $this->parseItem($item); - if (!empty($tmp_item)) { - $this->items[] = $tmp_item; - } - if($maxItems !== -1 && count($this->items) >= $maxItems) break; - } - } + /** + * Collect data from a Atom 1.0 compatible feed + * + * @link https://tools.ietf.org/html/rfc4287 The Atom Syndication Format + * + * @param object $content The Atom content + * @param int $maxItems Maximum number of items to collect from the feed + * (`-1`: no limit). + * @return void + * + * @todo Instead of passing $maxItems to all functions, just add all items + * and remove excessive items later. + */ + protected function collectAtom1($content, $maxItems) + { + $this->loadAtomData($content); + foreach ($content->entry as $item) { + Debug::log('parsing item ' . var_export($item, true)); + $tmp_item = $this->parseItem($item); + if (!empty($tmp_item)) { + $this->items[] = $tmp_item; + } + if ($maxItems !== -1 && count($this->items) >= $maxItems) { + break; + } + } + } - /** - * Collect data from a Atom 1.0 compatible feed - * - * @link https://tools.ietf.org/html/rfc4287 The Atom Syndication Format - * - * @param object $content The Atom content - * @param int $maxItems Maximum number of items to collect from the feed - * (`-1`: no limit). - * @return void - * - * @todo Instead of passing $maxItems to all functions, just add all items - * and remove excessive items later. - */ - protected function collectAtom1($content, $maxItems){ - $this->loadAtomData($content); - foreach($content->entry as $item) { - Debug::log('parsing item ' . var_export($item, true)); - $tmp_item = $this->parseItem($item); - if (!empty($tmp_item)) { - $this->items[] = $tmp_item; - } - if($maxItems !== -1 && count($this->items) >= $maxItems) break; - } - } + /** + * Load RSS 2.0 feed data into RSS-Bridge + * + * @param object $rssContent The RSS content + * @return void + * + * @todo set title, link, description, language, and so on + */ + protected function loadRss2Data($rssContent) + { + $this->title = trim((string)$rssContent->title); + $this->uri = trim((string)$rssContent->link); - /** - * Load RSS 2.0 feed data into RSS-Bridge - * - * @param object $rssContent The RSS content - * @return void - * - * @todo set title, link, description, language, and so on - */ - protected function loadRss2Data($rssContent){ - $this->title = trim((string)$rssContent->title); - $this->uri = trim((string)$rssContent->link); + if (!empty($rssContent->image)) { + $this->icon = trim((string)$rssContent->image->url); + } + } - if (!empty($rssContent->image)) { - $this->icon = trim((string)$rssContent->image->url); - } - } + /** + * Load Atom feed data into RSS-Bridge + * + * @param object $content The Atom content + * @return void + */ + protected function loadAtomData($content) + { + $this->title = (string)$content->title; - /** - * Load Atom feed data into RSS-Bridge - * - * @param object $content The Atom content - * @return void - */ - protected function loadAtomData($content){ - $this->title = (string)$content->title; + // Find best link (only one, or first of 'alternate') + if (!isset($content->link)) { + $this->uri = ''; + } elseif (count($content->link) === 1) { + $this->uri = (string)$content->link[0]['href']; + } else { + $this->uri = ''; + foreach ($content->link as $link) { + if (strtolower($link['rel']) === 'alternate') { + $this->uri = (string)$link['href']; + break; + } + } + } - // Find best link (only one, or first of 'alternate') - if(!isset($content->link)) { - $this->uri = ''; - } elseif (count($content->link) === 1) { - $this->uri = (string)$content->link[0]['href']; - } else { - $this->uri = ''; - foreach($content->link as $link) { - if(strtolower($link['rel']) === 'alternate') { - $this->uri = (string)$link['href']; - break; - } - } - } + if (!empty($content->icon)) { + $this->icon = (string)$content->icon; + } elseif (!empty($content->logo)) { + $this->icon = (string)$content->logo; + } + } - if(!empty($content->icon)) { - $this->icon = (string)$content->icon; - } elseif(!empty($content->logo)) { - $this->icon = (string)$content->logo; - } - } + /** + * Parse the contents of a single Atom feed item into a RSS-Bridge item for + * further transformation. + * + * @param object $feedItem A single feed item + * @return object The RSS-Bridge item + * + * @todo To reduce confusion, the RSS-Bridge item should maybe have a class + * of its own? + */ + protected function parseATOMItem($feedItem) + { + // Some ATOM entries also contain RSS 2.0 fields + $item = $this->parseRss2Item($feedItem); - /** - * Parse the contents of a single Atom feed item into a RSS-Bridge item for - * further transformation. - * - * @param object $feedItem A single feed item - * @return object The RSS-Bridge item - * - * @todo To reduce confusion, the RSS-Bridge item should maybe have a class - * of its own? - */ - protected function parseATOMItem($feedItem){ - // Some ATOM entries also contain RSS 2.0 fields - $item = $this->parseRss2Item($feedItem); + if (isset($feedItem->id)) { + $item['uri'] = (string)$feedItem->id; + } + if (isset($feedItem->title)) { + $item['title'] = (string)$feedItem->title; + } + if (isset($feedItem->updated)) { + $item['timestamp'] = strtotime((string)$feedItem->updated); + } + if (isset($feedItem->author)) { + $item['author'] = (string)$feedItem->author->name; + } + if (isset($feedItem->content)) { + $item['content'] = (string)$feedItem->content; + } - if(isset($feedItem->id)) $item['uri'] = (string)$feedItem->id; - if(isset($feedItem->title)) $item['title'] = (string)$feedItem->title; - if(isset($feedItem->updated)) $item['timestamp'] = strtotime((string)$feedItem->updated); - if(isset($feedItem->author)) $item['author'] = (string)$feedItem->author->name; - if(isset($feedItem->content)) $item['content'] = (string)$feedItem->content; + //When "link" field is present, URL is more reliable than "id" field + if (count($feedItem->link) === 1) { + $item['uri'] = (string)$feedItem->link[0]['href']; + } else { + foreach ($feedItem->link as $link) { + if (strtolower($link['rel']) === 'alternate') { + $item['uri'] = (string)$link['href']; + } + if (strtolower($link['rel']) === 'enclosure') { + $item['enclosures'][] = (string)$link['href']; + } + } + } - //When "link" field is present, URL is more reliable than "id" field - if (count($feedItem->link) === 1) { - $item['uri'] = (string)$feedItem->link[0]['href']; - } else { - foreach($feedItem->link as $link) { - if(strtolower($link['rel']) === 'alternate') { - $item['uri'] = (string)$link['href']; - } - if(strtolower($link['rel']) === 'enclosure') { - $item['enclosures'][] = (string)$link['href']; - } - } - } + return $item; + } - return $item; - } + /** + * Parse the contents of a single RSS 0.91 feed item into a RSS-Bridge item + * for further transformation. + * + * @param object $feedItem A single feed item + * @return object The RSS-Bridge item + * + * @todo To reduce confusion, the RSS-Bridge item should maybe have a class + * of its own? + */ + protected function parseRss091Item($feedItem) + { + $item = []; + if (isset($feedItem->link)) { + $item['uri'] = (string)$feedItem->link; + } + if (isset($feedItem->title)) { + $item['title'] = (string)$feedItem->title; + } + // rss 0.91 doesn't support timestamps + // rss 0.91 doesn't support authors + // rss 0.91 doesn't support enclosures + if (isset($feedItem->description)) { + $item['content'] = (string)$feedItem->description; + } + return $item; + } - /** - * Parse the contents of a single RSS 0.91 feed item into a RSS-Bridge item - * for further transformation. - * - * @param object $feedItem A single feed item - * @return object The RSS-Bridge item - * - * @todo To reduce confusion, the RSS-Bridge item should maybe have a class - * of its own? - */ - protected function parseRss091Item($feedItem){ - $item = array(); - if(isset($feedItem->link)) $item['uri'] = (string)$feedItem->link; - if(isset($feedItem->title)) $item['title'] = (string)$feedItem->title; - // rss 0.91 doesn't support timestamps - // rss 0.91 doesn't support authors - // rss 0.91 doesn't support enclosures - if(isset($feedItem->description)) $item['content'] = (string)$feedItem->description; - return $item; - } + /** + * Parse the contents of a single RSS 1.0 feed item into a RSS-Bridge item + * for further transformation. + * + * @param object $feedItem A single feed item + * @return object The RSS-Bridge item + * + * @todo To reduce confusion, the RSS-Bridge item should maybe have a class + * of its own? + */ + protected function parseRss1Item($feedItem) + { + // 1.0 adds optional elements around the 0.91 standard + $item = $this->parseRss091Item($feedItem); - /** - * Parse the contents of a single RSS 1.0 feed item into a RSS-Bridge item - * for further transformation. - * - * @param object $feedItem A single feed item - * @return object The RSS-Bridge item - * - * @todo To reduce confusion, the RSS-Bridge item should maybe have a class - * of its own? - */ - protected function parseRss1Item($feedItem){ - // 1.0 adds optional elements around the 0.91 standard - $item = $this->parseRss091Item($feedItem); + $namespaces = $feedItem->getNamespaces(true); + if (isset($namespaces['dc'])) { + $dc = $feedItem->children($namespaces['dc']); + if (isset($dc->date)) { + $item['timestamp'] = strtotime((string)$dc->date); + } + if (isset($dc->creator)) { + $item['author'] = (string)$dc->creator; + } + } - $namespaces = $feedItem->getNamespaces(true); - if(isset($namespaces['dc'])) { - $dc = $feedItem->children($namespaces['dc']); - if(isset($dc->date)) $item['timestamp'] = strtotime((string)$dc->date); - if(isset($dc->creator)) $item['author'] = (string)$dc->creator; - } + return $item; + } - return $item; - } + /** + * Parse the contents of a single RSS 2.0 feed item into a RSS-Bridge item + * for further transformation. + * + * @param object $feedItem A single feed item + * @return object The RSS-Bridge item + * + * @todo To reduce confusion, the RSS-Bridge item should maybe have a class + * of its own? + */ + protected function parseRss2Item($feedItem) + { + // Primary data is compatible to 0.91 with some additional data + $item = $this->parseRss091Item($feedItem); - /** - * Parse the contents of a single RSS 2.0 feed item into a RSS-Bridge item - * for further transformation. - * - * @param object $feedItem A single feed item - * @return object The RSS-Bridge item - * - * @todo To reduce confusion, the RSS-Bridge item should maybe have a class - * of its own? - */ - protected function parseRss2Item($feedItem){ - // Primary data is compatible to 0.91 with some additional data - $item = $this->parseRss091Item($feedItem); + $namespaces = $feedItem->getNamespaces(true); + if (isset($namespaces['dc'])) { + $dc = $feedItem->children($namespaces['dc']); + } + if (isset($namespaces['media'])) { + $media = $feedItem->children($namespaces['media']); + } - $namespaces = $feedItem->getNamespaces(true); - if(isset($namespaces['dc'])) $dc = $feedItem->children($namespaces['dc']); - if(isset($namespaces['media'])) $media = $feedItem->children($namespaces['media']); + if (isset($feedItem->guid)) { + foreach ($feedItem->guid->attributes() as $attribute => $value) { + if ( + $attribute === 'isPermaLink' + && ($value === 'true' || ( + filter_var($feedItem->guid, FILTER_VALIDATE_URL) + && (empty($item['uri']) || !filter_var($item['uri'], FILTER_VALIDATE_URL)) + ) + ) + ) { + $item['uri'] = (string)$feedItem->guid; + break; + } + } + } - if(isset($feedItem->guid)) { - foreach($feedItem->guid->attributes() as $attribute => $value) { - if($attribute === 'isPermaLink' - && ($value === 'true' || ( - filter_var($feedItem->guid, FILTER_VALIDATE_URL) - && (empty($item['uri']) || !filter_var($item['uri'], FILTER_VALIDATE_URL)) - ) - ) - ) { - $item['uri'] = (string)$feedItem->guid; - break; - } - } - } + if (isset($feedItem->pubDate)) { + $item['timestamp'] = strtotime((string)$feedItem->pubDate); + } elseif (isset($dc->date)) { + $item['timestamp'] = strtotime((string)$dc->date); + } - if(isset($feedItem->pubDate)) { - $item['timestamp'] = strtotime((string)$feedItem->pubDate); - } elseif(isset($dc->date)) { - $item['timestamp'] = strtotime((string)$dc->date); - } + if (isset($feedItem->author)) { + $item['author'] = (string)$feedItem->author; + } elseif (isset($feedItem->creator)) { + $item['author'] = (string)$feedItem->creator; + } elseif (isset($dc->creator)) { + $item['author'] = (string)$dc->creator; + } elseif (isset($media->credit)) { + $item['author'] = (string)$media->credit; + } - if(isset($feedItem->author)) { - $item['author'] = (string)$feedItem->author; - } elseif (isset($feedItem->creator)) { - $item['author'] = (string)$feedItem->creator; - } elseif(isset($dc->creator)) { - $item['author'] = (string)$dc->creator; - } elseif(isset($media->credit)) { - $item['author'] = (string)$media->credit; - } + if (isset($feedItem->enclosure) && !empty($feedItem->enclosure['url'])) { + $item['enclosures'] = [(string)$feedItem->enclosure['url']]; + } - if(isset($feedItem->enclosure) && !empty($feedItem->enclosure['url'])) { - $item['enclosures'] = array((string)$feedItem->enclosure['url']); - } + return $item; + } - return $item; - } + /** + * Parse the contents of a single feed item, depending on the current feed + * type, into a RSS-Bridge item. + * + * @param object $item The current feed item + * @return object A RSS-Bridge item, with (hopefully) the whole content + */ + protected function parseItem($item) + { + switch ($this->feedType) { + case self::FEED_TYPE_RSS_1_0: + return $this->parseRss1Item($item); + break; + case self::FEED_TYPE_RSS_2_0: + return $this->parseRss2Item($item); + break; + case self::FEED_TYPE_ATOM_1_0: + return $this->parseATOMItem($item); + break; + default: + returnClientError('Unknown version ' . $this->getInput('version') . '!'); + } + } - /** - * Parse the contents of a single feed item, depending on the current feed - * type, into a RSS-Bridge item. - * - * @param object $item The current feed item - * @return object A RSS-Bridge item, with (hopefully) the whole content - */ - protected function parseItem($item){ - switch($this->feedType) { - case self::FEED_TYPE_RSS_1_0: - return $this->parseRss1Item($item); - break; - case self::FEED_TYPE_RSS_2_0: - return $this->parseRss2Item($item); - break; - case self::FEED_TYPE_ATOM_1_0: - return $this->parseATOMItem($item); - break; - default: returnClientError('Unknown version ' . $this->getInput('version') . '!'); - } - } + /** {@inheritdoc} */ + public function getURI() + { + return !empty($this->uri) ? $this->uri : parent::getURI(); + } - /** {@inheritdoc} */ - public function getURI(){ - return !empty($this->uri) ? $this->uri : parent::getURI(); - } + /** {@inheritdoc} */ + public function getName() + { + return !empty($this->title) ? $this->title : parent::getName(); + } - /** {@inheritdoc} */ - public function getName(){ - return !empty($this->title) ? $this->title : parent::getName(); - } - - /** {@inheritdoc} */ - public function getIcon(){ - return !empty($this->icon) ? $this->icon : parent::getIcon(); - } + /** {@inheritdoc} */ + public function getIcon() + { + return !empty($this->icon) ? $this->icon : parent::getIcon(); + } } diff --git a/lib/FeedItem.php b/lib/FeedItem.php index 8690eb95..2d3872f2 100644 --- a/lib/FeedItem.php +++ b/lib/FeedItem.php @@ -1,4 +1,5 @@ uri = 'https://www.github.com/rss-bridge/rss-bridge/'; - * $feedItem->title = 'Title'; - * $feedItem->timestamp = strtotime('now'); - * $feedItem->autor = 'Unknown author'; - * $feedItem->content = 'Hello World!'; - * $feedItem->enclosures = array('https://github.com/favicon.ico'); - * $feedItem->categories = array('php', 'rss-bridge', 'awesome'); - * ``` - * - * @param array $item (optional) A legacy item (empty: no legacy support). - * @return object A new object of this class - */ - public function __construct($item = array()) { - if(!is_array($item)) - Debug::log('Item must be an array!'); + /** + * Create object from legacy item. + * + * The provided array must be an associative array of key-value-pairs, where + * keys may correspond to any of the properties of this class. + * + * Example use: + * + * ```PHP + * uri = 'https://www.github.com/rss-bridge/rss-bridge/'; + * $feedItem->title = 'Title'; + * $feedItem->timestamp = strtotime('now'); + * $feedItem->autor = 'Unknown author'; + * $feedItem->content = 'Hello World!'; + * $feedItem->enclosures = array('https://github.com/favicon.ico'); + * $feedItem->categories = array('php', 'rss-bridge', 'awesome'); + * ``` + * + * @param array $item (optional) A legacy item (empty: no legacy support). + * @return object A new object of this class + */ + public function __construct($item = []) + { + if (!is_array($item)) { + Debug::log('Item must be an array!'); + } - foreach($item as $key => $value) { - $this->__set($key, $value); - } - } + foreach ($item as $key => $value) { + $this->__set($key, $value); + } + } - /** - * Get current URI. - * - * Use {@see FeedItem::setURI()} to set the URI. - * - * @return string|null The URI or null if it hasn't been set. - */ - public function getURI() { - return $this->uri; - } + /** + * Get current URI. + * + * Use {@see FeedItem::setURI()} to set the URI. + * + * @return string|null The URI or null if it hasn't been set. + */ + public function getURI() + { + return $this->uri; + } - /** - * Set URI to the full article. - * - * Use {@see FeedItem::getURI()} to get the URI. - * - * _Note_: Removes whitespace from the beginning and end of the URI. - * - * _Remarks_: Uses the attribute "href" or "src" if the provided URI is an - * object of simple_html_dom_node. - * - * @param object|string $uri URI to the full article. - * @return self - */ - public function setURI($uri) { - $this->uri = null; // Clear previous data + /** + * Set URI to the full article. + * + * Use {@see FeedItem::getURI()} to get the URI. + * + * _Note_: Removes whitespace from the beginning and end of the URI. + * + * _Remarks_: Uses the attribute "href" or "src" if the provided URI is an + * object of simple_html_dom_node. + * + * @param object|string $uri URI to the full article. + * @return self + */ + public function setURI($uri) + { + $this->uri = null; // Clear previous data - if($uri instanceof simple_html_dom_node) { - if($uri->hasAttribute('href')) { // Anchor - $uri = $uri->href; - } elseif($uri->hasAttribute('src')) { // Image - $uri = $uri->src; - } else { - Debug::log('The item provided as URI is unknown!'); - } - } + if ($uri instanceof simple_html_dom_node) { + if ($uri->hasAttribute('href')) { // Anchor + $uri = $uri->href; + } elseif ($uri->hasAttribute('src')) { // Image + $uri = $uri->src; + } else { + Debug::log('The item provided as URI is unknown!'); + } + } - if(!is_string($uri)) { - Debug::log('URI must be a string!'); - } elseif(!filter_var( - $uri, - FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED)) { - Debug::log('URI must include a scheme, host and path!'); - } else { - $scheme = parse_url($uri, PHP_URL_SCHEME); + if (!is_string($uri)) { + Debug::log('URI must be a string!'); + } elseif ( + !filter_var( + $uri, + FILTER_VALIDATE_URL, + FILTER_FLAG_PATH_REQUIRED + ) + ) { + Debug::log('URI must include a scheme, host and path!'); + } else { + $scheme = parse_url($uri, PHP_URL_SCHEME); - if($scheme !== 'http' && $scheme !== 'https') { - Debug::log('URI scheme must be "http" or "https"!'); - } else { - $this->uri = trim($uri); - } - } + if ($scheme !== 'http' && $scheme !== 'https') { + Debug::log('URI scheme must be "http" or "https"!'); + } else { + $this->uri = trim($uri); + } + } - return $this; - } + return $this; + } - /** - * Get current title. - * - * Use {@see FeedItem::setTitle()} to set the title. - * - * @return string|null The current title or null if it hasn't been set. - */ - public function getTitle() { - return $this->title; - } + /** + * Get current title. + * + * Use {@see FeedItem::setTitle()} to set the title. + * + * @return string|null The current title or null if it hasn't been set. + */ + public function getTitle() + { + return $this->title; + } - /** - * Set title. - * - * Use {@see FeedItem::getTitle()} to get the title. - * - * _Note_: Removes whitespace from beginning and end of the title. - * - * @param string $title The title - * @return self - */ - public function setTitle($title) { - $this->title = null; // Clear previous data + /** + * Set title. + * + * Use {@see FeedItem::getTitle()} to get the title. + * + * _Note_: Removes whitespace from beginning and end of the title. + * + * @param string $title The title + * @return self + */ + public function setTitle($title) + { + $this->title = null; // Clear previous data - if(!is_string($title)) { - Debug::log('Title must be a string!'); - } else { - $this->title = trim($title); - } + if (!is_string($title)) { + Debug::log('Title must be a string!'); + } else { + $this->title = trim($title); + } - return $this; - } + return $this; + } - /** - * Get current timestamp. - * - * Use {@see FeedItem::setTimestamp()} to set the timestamp. - * - * @return int|null The current timestamp or null if it hasn't been set. - */ - public function getTimestamp() { - return $this->timestamp; - } + /** + * Get current timestamp. + * + * Use {@see FeedItem::setTimestamp()} to set the timestamp. + * + * @return int|null The current timestamp or null if it hasn't been set. + */ + public function getTimestamp() + { + return $this->timestamp; + } - /** - * Set timestamp of first release. - * - * _Note_: The timestamp should represent the number of seconds since - * January 1 1970 00:00:00 GMT (Unix time). - * - * _Remarks_: If the provided timestamp is a string (not numeric), this - * function automatically attempts to parse the string using - * [strtotime](http://php.net/manual/en/function.strtotime.php) - * - * @link http://php.net/manual/en/function.strtotime.php strtotime (PHP) - * @link https://en.wikipedia.org/wiki/Unix_time Unix time (Wikipedia) - * - * @param string|int $timestamp A timestamp of when the item was first released - * @return self - */ - public function setTimestamp($timestamp) { - $this->timestamp = null; // Clear previous data + /** + * Set timestamp of first release. + * + * _Note_: The timestamp should represent the number of seconds since + * January 1 1970 00:00:00 GMT (Unix time). + * + * _Remarks_: If the provided timestamp is a string (not numeric), this + * function automatically attempts to parse the string using + * [strtotime](http://php.net/manual/en/function.strtotime.php) + * + * @link http://php.net/manual/en/function.strtotime.php strtotime (PHP) + * @link https://en.wikipedia.org/wiki/Unix_time Unix time (Wikipedia) + * + * @param string|int $timestamp A timestamp of when the item was first released + * @return self + */ + public function setTimestamp($timestamp) + { + $this->timestamp = null; // Clear previous data - if(!is_numeric($timestamp) - && !$timestamp = strtotime($timestamp)) { - Debug::log('Unable to parse timestamp!'); - } + if ( + !is_numeric($timestamp) + && !$timestamp = strtotime($timestamp) + ) { + Debug::log('Unable to parse timestamp!'); + } - if($timestamp <= 0) { - Debug::log('Timestamp must be greater than zero!'); - } else { - $this->timestamp = $timestamp; - } + if ($timestamp <= 0) { + Debug::log('Timestamp must be greater than zero!'); + } else { + $this->timestamp = $timestamp; + } - return $this; - } + return $this; + } - /** - * Get the current author name. - * - * Use {@see FeedItem::setAuthor()} to set the author. - * - * @return string|null The author or null if it hasn't been set. - */ - public function getAuthor() { - return $this->author; - } + /** + * Get the current author name. + * + * Use {@see FeedItem::setAuthor()} to set the author. + * + * @return string|null The author or null if it hasn't been set. + */ + public function getAuthor() + { + return $this->author; + } - /** - * Set the author name. - * - * Use {@see FeedItem::getAuthor()} to get the author. - * - * @param string $author The author name. - * @return self - */ - public function setAuthor($author) { - $this->author = null; // Clear previous data + /** + * Set the author name. + * + * Use {@see FeedItem::getAuthor()} to get the author. + * + * @param string $author The author name. + * @return self + */ + public function setAuthor($author) + { + $this->author = null; // Clear previous data - if(!is_string($author)) { - Debug::log('Author must be a string!'); - } else { - $this->author = $author; - } + if (!is_string($author)) { + Debug::log('Author must be a string!'); + } else { + $this->author = $author; + } - return $this; - } + return $this; + } - /** - * Get item content. - * - * Use {@see FeedItem::setContent()} to set the item content. - * - * @return string|null The item content or null if it hasn't been set. - */ - public function getContent() { - return $this->content; - } + /** + * Get item content. + * + * Use {@see FeedItem::setContent()} to set the item content. + * + * @return string|null The item content or null if it hasn't been set. + */ + public function getContent() + { + return $this->content; + } - /** - * Set item content. - * - * Note: This function casts objects of type simple_html_dom and - * simple_html_dom_node to string. - * - * Use {@see FeedItem::getContent()} to get the current item content. - * - * @param string|object $content The item content as text or simple_html_dom - * object. - * @return self - */ - public function setContent($content) { - $this->content = null; // Clear previous data + /** + * Set item content. + * + * Note: This function casts objects of type simple_html_dom and + * simple_html_dom_node to string. + * + * Use {@see FeedItem::getContent()} to get the current item content. + * + * @param string|object $content The item content as text or simple_html_dom + * object. + * @return self + */ + public function setContent($content) + { + $this->content = null; // Clear previous data - if($content instanceof simple_html_dom - || $content instanceof simple_html_dom_node) { - $content = (string)$content; - } + if ( + $content instanceof simple_html_dom + || $content instanceof simple_html_dom_node + ) { + $content = (string)$content; + } - if(!is_string($content)) { - Debug::log('Content must be a string!'); - } else { - $this->content = $content; - } + if (!is_string($content)) { + Debug::log('Content must be a string!'); + } else { + $this->content = $content; + } - return $this; - } + return $this; + } - /** - * Get item enclosures. - * - * Use {@see FeedItem::setEnclosures()} to set feed enclosures. - * - * @return array Enclosures as array of enclosure URIs. - */ - public function getEnclosures() { - return $this->enclosures; - } + /** + * Get item enclosures. + * + * Use {@see FeedItem::setEnclosures()} to set feed enclosures. + * + * @return array Enclosures as array of enclosure URIs. + */ + public function getEnclosures() + { + return $this->enclosures; + } - /** - * Set item enclosures. - * - * Use {@see FeedItem::getEnclosures()} to get the current item enclosures. - * - * @param array $enclosures Array of enclosures, where each element links to - * one enclosure. - * @return self - */ - public function setEnclosures($enclosures) { - $this->enclosures = array(); // Clear previous data + /** + * Set item enclosures. + * + * Use {@see FeedItem::getEnclosures()} to get the current item enclosures. + * + * @param array $enclosures Array of enclosures, where each element links to + * one enclosure. + * @return self + */ + public function setEnclosures($enclosures) + { + $this->enclosures = []; // Clear previous data - if(!is_array($enclosures)) { - Debug::log('Enclosures must be an array!'); - } else { - foreach($enclosures as $enclosure) { - if(!filter_var( - $enclosure, - FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED)) { - Debug::log('Each enclosure must contain a scheme, host and path!'); - } elseif(!in_array($enclosure, $this->enclosures)) { - $this->enclosures[] = $enclosure; - } - } - } + if (!is_array($enclosures)) { + Debug::log('Enclosures must be an array!'); + } else { + foreach ($enclosures as $enclosure) { + if ( + !filter_var( + $enclosure, + FILTER_VALIDATE_URL, + FILTER_FLAG_PATH_REQUIRED + ) + ) { + Debug::log('Each enclosure must contain a scheme, host and path!'); + } elseif (!in_array($enclosure, $this->enclosures)) { + $this->enclosures[] = $enclosure; + } + } + } - return $this; - } + return $this; + } - /** - * Get item categories. - * - * Use {@see FeedItem::setCategories()} to set item categories. - * - * @param array The item categories. - */ - public function getCategories() { - return $this->categories; - } + /** + * Get item categories. + * + * Use {@see FeedItem::setCategories()} to set item categories. + * + * @param array The item categories. + */ + public function getCategories() + { + return $this->categories; + } - /** - * Set item categories. - * - * Use {@see FeedItem::getCategories()} to get the current item categories. - * - * @param array $categories Array of categories, where each element defines - * a single category name. - * @return self - */ - public function setCategories($categories) { - $this->categories = array(); // Clear previous data + /** + * Set item categories. + * + * Use {@see FeedItem::getCategories()} to get the current item categories. + * + * @param array $categories Array of categories, where each element defines + * a single category name. + * @return self + */ + public function setCategories($categories) + { + $this->categories = []; // Clear previous data - if(!is_array($categories)) { - Debug::log('Categories must be an array!'); - } else { - foreach($categories as $category) { - if(!is_string($category)) { - Debug::log('Category must be a string!'); - } else { - $this->categories[] = $category; - } - } - } + if (!is_array($categories)) { + Debug::log('Categories must be an array!'); + } else { + foreach ($categories as $category) { + if (!is_string($category)) { + Debug::log('Category must be a string!'); + } else { + $this->categories[] = $category; + } + } + } - return $this; - } + return $this; + } - /** - * Get unique id - * - * Use {@see FeedItem::setUid()} to set the unique id. - * - * @param string The unique id. - */ - public function getUid() { - return $this->uid; - } + /** + * Get unique id + * + * Use {@see FeedItem::setUid()} to set the unique id. + * + * @param string The unique id. + */ + public function getUid() + { + return $this->uid; + } - /** - * Set unique id. - * - * Use {@see FeedItem::getUid()} to get the unique id. - * - * @param string $uid A string that uniquely identifies the current item - * @return self - */ - public function setUid($uid) { - $this->uid = null; // Clear previous data + /** + * Set unique id. + * + * Use {@see FeedItem::getUid()} to get the unique id. + * + * @param string $uid A string that uniquely identifies the current item + * @return self + */ + public function setUid($uid) + { + $this->uid = null; // Clear previous data - if(!is_string($uid)) { - Debug::log('Unique id must be a string!'); - } elseif (preg_match('/^[a-f0-9]{40}$/', $uid)) { - // keep id if it already is a SHA-1 hash - $this->uid = $uid; - } else { - $this->uid = sha1($uid); - } + if (!is_string($uid)) { + Debug::log('Unique id must be a string!'); + } elseif (preg_match('/^[a-f0-9]{40}$/', $uid)) { + // keep id if it already is a SHA-1 hash + $this->uid = $uid; + } else { + $this->uid = sha1($uid); + } - return $this; - } + return $this; + } - /** - * Add miscellaneous elements to the item. - * - * @param string $key Name of the element. - * @param mixed $value Value of the element. - * @return self - */ - public function addMisc($key, $value) { + /** + * Add miscellaneous elements to the item. + * + * @param string $key Name of the element. + * @param mixed $value Value of the element. + * @return self + */ + public function addMisc($key, $value) + { + if (!is_string($key)) { + Debug::log('Key must be a string!'); + } elseif (in_array($key, get_object_vars($this))) { + Debug::log('Key must be unique!'); + } else { + $this->misc[$key] = $value; + } - if(!is_string($key)) { - Debug::log('Key must be a string!'); - } elseif(in_array($key, get_object_vars($this))) { - Debug::log('Key must be unique!'); - } else { - $this->misc[$key] = $value; - } + return $this; + } - return $this; - } + /** + * Transform current object to array + * + * @return array + */ + public function toArray() + { + return array_merge( + [ + 'uri' => $this->uri, + 'title' => $this->title, + 'timestamp' => $this->timestamp, + 'author' => $this->author, + 'content' => $this->content, + 'enclosures' => $this->enclosures, + 'categories' => $this->categories, + 'uid' => $this->uid, + ], + $this->misc + ); + } - /** - * Transform current object to array - * - * @return array - */ - public function toArray() { - return array_merge( - array( - 'uri' => $this->uri, - 'title' => $this->title, - 'timestamp' => $this->timestamp, - 'author' => $this->author, - 'content' => $this->content, - 'enclosures' => $this->enclosures, - 'categories' => $this->categories, - 'uid' => $this->uid, - ), $this->misc - ); - } + /** + * Set item property + * + * Allows simple assignment to parameters. This method is slower, but easier + * to implement in some cases: + * + * ```PHP + * $item = new \FeedItem(); + * $item->content = 'Hello World!'; + * $item->my_id = 42; + * ``` + * + * @param string $name Property name + * @param mixed $value Property value + */ + public function __set($name, $value) + { + switch ($name) { + case 'uri': + $this->setURI($value); + break; + case 'title': + $this->setTitle($value); + break; + case 'timestamp': + $this->setTimestamp($value); + break; + case 'author': + $this->setAuthor($value); + break; + case 'content': + $this->setContent($value); + break; + case 'enclosures': + $this->setEnclosures($value); + break; + case 'categories': + $this->setCategories($value); + break; + case 'uid': + $this->setUid($value); + break; + default: + $this->addMisc($name, $value); + } + } - /** - * Set item property - * - * Allows simple assignment to parameters. This method is slower, but easier - * to implement in some cases: - * - * ```PHP - * $item = new \FeedItem(); - * $item->content = 'Hello World!'; - * $item->my_id = 42; - * ``` - * - * @param string $name Property name - * @param mixed $value Property value - */ - public function __set($name, $value) { - switch($name) { - case 'uri': $this->setURI($value); break; - case 'title': $this->setTitle($value); break; - case 'timestamp': $this->setTimestamp($value); break; - case 'author': $this->setAuthor($value); break; - case 'content': $this->setContent($value); break; - case 'enclosures': $this->setEnclosures($value); break; - case 'categories': $this->setCategories($value); break; - case 'uid': $this->setUid($value); break; - default: $this->addMisc($name, $value); - } - } - - /** - * Get item property - * - * Allows simple assignment to parameters. This method is slower, but easier - * to implement in some cases. - * - * @param string $name Property name - * @return mixed Property value - */ - public function __get($name) { - switch($name) { - case 'uri': return $this->getURI(); - case 'title': return $this->getTitle(); - case 'timestamp': return $this->getTimestamp(); - case 'author': return $this->getAuthor(); - case 'content': return $this->getContent(); - case 'enclosures': return $this->getEnclosures(); - case 'categories': return $this->getCategories(); - case 'uid': return $this->getUid(); - default: - if(array_key_exists($name, $this->misc)) - return $this->misc[$name]; - return null; - } - } + /** + * Get item property + * + * Allows simple assignment to parameters. This method is slower, but easier + * to implement in some cases. + * + * @param string $name Property name + * @return mixed Property value + */ + public function __get($name) + { + switch ($name) { + case 'uri': + return $this->getURI(); + case 'title': + return $this->getTitle(); + case 'timestamp': + return $this->getTimestamp(); + case 'author': + return $this->getAuthor(); + case 'content': + return $this->getContent(); + case 'enclosures': + return $this->getEnclosures(); + case 'categories': + return $this->getCategories(); + case 'uid': + return $this->getUid(); + default: + if (array_key_exists($name, $this->misc)) { + return $this->misc[$name]; + } + return null; + } + } } diff --git a/lib/FormatAbstract.php b/lib/FormatAbstract.php index 768b0157..7a4c6c92 100644 --- a/lib/FormatAbstract.php +++ b/lib/FormatAbstract.php @@ -1,4 +1,5 @@ charset = $charset; - /** - * {@inheritdoc} - * - * @param string $charset {@inheritdoc} - */ - public function setCharset($charset){ - $this->charset = $charset; + return $this; + } - return $this; - } + /** {@inheritdoc} */ + public function getCharset() + { + $charset = $this->charset; - /** {@inheritdoc} */ - public function getCharset(){ - $charset = $this->charset; + return is_null($charset) ? static::DEFAULT_CHARSET : $charset; + } - return is_null($charset) ? static::DEFAULT_CHARSET : $charset; - } + /** + * Set the last modified time + * + * @param int $lastModified The last modified time + * @return void + */ + public function setLastModified($lastModified) + { + $this->lastModified = $lastModified; + } - /** - * Set the last modified time - * - * @param int $lastModified The last modified time - * @return void - */ - public function setLastModified($lastModified){ - $this->lastModified = $lastModified; - } + /** + * {@inheritdoc} + * + * @param array $items {@inheritdoc} + */ + public function setItems(array $items) + { + $this->items = $items; - /** - * {@inheritdoc} - * - * @param array $items {@inheritdoc} - */ - public function setItems(array $items){ - $this->items = $items; + return $this; + } - return $this; - } + /** {@inheritdoc} */ + public function getItems() + { + if (!is_array($this->items)) { + throw new \LogicException('Feed the ' . get_class($this) . ' with "setItems" method before !'); + } - /** {@inheritdoc} */ - public function getItems(){ - if(!is_array($this->items)) - throw new \LogicException('Feed the ' . get_class($this) . ' with "setItems" method before !'); + return $this->items; + } - return $this->items; - } + /** + * {@inheritdoc} + * + * @param array $extraInfos {@inheritdoc} + */ + public function setExtraInfos(array $extraInfos = []) + { + foreach (['name', 'uri', 'icon', 'donationUri'] as $infoName) { + if (!isset($extraInfos[$infoName])) { + $extraInfos[$infoName] = ''; + } + } - /** - * {@inheritdoc} - * - * @param array $extraInfos {@inheritdoc} - */ - public function setExtraInfos(array $extraInfos = array()){ - foreach(array('name', 'uri', 'icon', 'donationUri') as $infoName) { - if(!isset($extraInfos[$infoName])) { - $extraInfos[$infoName] = ''; - } - } + $this->extraInfos = $extraInfos; - $this->extraInfos = $extraInfos; + return $this; + } - return $this; - } + /** {@inheritdoc} */ + public function getExtraInfos() + { + if (is_null($this->extraInfos)) { // No extra info ? + $this->setExtraInfos(); // Define with default value + } - /** {@inheritdoc} */ - public function getExtraInfos(){ - if(is_null($this->extraInfos)) { // No extra info ? - $this->setExtraInfos(); // Define with default value - } + return $this->extraInfos; + } - return $this->extraInfos; - } - - /** - * Sanitize HTML while leaving it functional. - * - * Keeps HTML as-is (with clickable hyperlinks) while reducing annoying and - * potentially dangerous things. - * - * @param string $html The HTML content - * @return string The sanitized HTML content - * - * @todo This belongs into `html.php` - * @todo Maybe switch to http://htmlpurifier.org/ - * @todo Maybe switch to http://www.bioinformatics.org/phplabware/internal_utilities/htmLawed/index.php - */ - protected function sanitizeHtml(string $html): string - { - $html = str_replace('folder = $folder; + public function __construct(string $folder = PATH_LIB_FORMATS) + { + $this->folder = $folder; - // create format names - foreach(scandir($this->folder) as $file) { - if(preg_match('/^([^.]+)Format\.php$/U', $file, $m)) { - $this->formatNames[] = $m[1]; - } - } - } + // create format names + foreach (scandir($this->folder) as $file) { + if (preg_match('/^([^.]+)Format\.php$/U', $file, $m)) { + $this->formatNames[] = $m[1]; + } + } + } - /** - * @throws \InvalidArgumentException - * @param string $name The name of the format e.g. "Atom", "Mrss" or "Json" - */ - public function create(string $name): FormatInterface - { - if (! preg_match('/^[a-zA-Z0-9-]*$/', $name)) { - throw new \InvalidArgumentException('Format name invalid!'); - } - $name = $this->sanitizeFormatName($name); - if ($name === null) { - throw new \InvalidArgumentException('Unknown format given!'); - } - $className = '\\' . $name . 'Format'; - return new $className; - } + /** + * @throws \InvalidArgumentException + * @param string $name The name of the format e.g. "Atom", "Mrss" or "Json" + */ + public function create(string $name): FormatInterface + { + if (! preg_match('/^[a-zA-Z0-9-]*$/', $name)) { + throw new \InvalidArgumentException('Format name invalid!'); + } + $name = $this->sanitizeFormatName($name); + if ($name === null) { + throw new \InvalidArgumentException('Unknown format given!'); + } + $className = '\\' . $name . 'Format'; + return new $className(); + } - public function getFormatNames(): array - { - return $this->formatNames; - } + public function getFormatNames(): array + { + return $this->formatNames; + } - protected function sanitizeFormatName(string $name) { - $name = ucfirst(strtolower($name)); + protected function sanitizeFormatName(string $name) + { + $name = ucfirst(strtolower($name)); - // Trim trailing '.php' if exists - if (preg_match('/(.+)(?:\.php)/', $name, $matches)) { - $name = $matches[1]; - } + // Trim trailing '.php' if exists + if (preg_match('/(.+)(?:\.php)/', $name, $matches)) { + $name = $matches[1]; + } - // Trim trailing 'Format' if exists - if (preg_match('/(.+)(?:Format)/i', $name, $matches)) { - $name = $matches[1]; - } - if (in_array($name, $this->formatNames)) { - return $name; - } - return null; - } + // Trim trailing 'Format' if exists + if (preg_match('/(.+)(?:Format)/i', $name, $matches)) { + $name = $matches[1]; + } + if (in_array($name, $this->formatNames)) { + return $name; + } + return null; + } } diff --git a/lib/FormatInterface.php b/lib/FormatInterface.php index 5fd46ef9..8f98d6e4 100644 --- a/lib/FormatInterface.php +++ b/lib/FormatInterface.php @@ -1,4 +1,5 @@ invalid[] = [ + 'name' => $name, + 'reason' => $reason + ]; + } - /** - * Add item to list of invalid parameters - * - * @param string $name The name of the parameter - * @param string $reason The reason for that parameter being invalid - * @return void - */ - private function addInvalidParameter($name, $reason){ - $this->invalid[] = array( - 'name' => $name, - 'reason' => $reason - ); - } + /** + * Return list of invalid parameters. + * + * Each element is an array of 'name' and 'reason'. + * + * @return array List of invalid parameters + */ + public function getInvalidParameters() + { + return $this->invalid; + } - /** - * Return list of invalid parameters. - * - * Each element is an array of 'name' and 'reason'. - * - * @return array List of invalid parameters - */ - public function getInvalidParameters() { - return $this->invalid; - } + /** + * Validate value for a text input + * + * @param string $value The value of a text input + * @param string|null $pattern (optional) A regex pattern + * @return string|null The filtered value or null if the value is invalid + */ + private function validateTextValue($value, $pattern = null) + { + if (!is_null($pattern)) { + $filteredValue = filter_var( + $value, + FILTER_VALIDATE_REGEXP, + ['options' => [ + 'regexp' => '/^' . $pattern . '$/' + ] + ] + ); + } else { + $filteredValue = filter_var($value); + } - /** - * Validate value for a text input - * - * @param string $value The value of a text input - * @param string|null $pattern (optional) A regex pattern - * @return string|null The filtered value or null if the value is invalid - */ - private function validateTextValue($value, $pattern = null){ - if(!is_null($pattern)) { - $filteredValue = filter_var($value, - FILTER_VALIDATE_REGEXP, - array('options' => array( - 'regexp' => '/^' . $pattern . '$/' - ) - )); - } else { - $filteredValue = filter_var($value); - } + if ($filteredValue === false) { + return null; + } - if($filteredValue === false) - return null; + return $filteredValue; + } - return $filteredValue; - } + /** + * Validate value for a number input + * + * @param int $value The value of a number input + * @return int|null The filtered value or null if the value is invalid + */ + private function validateNumberValue($value) + { + $filteredValue = filter_var($value, FILTER_VALIDATE_INT); - /** - * Validate value for a number input - * - * @param int $value The value of a number input - * @return int|null The filtered value or null if the value is invalid - */ - private function validateNumberValue($value){ - $filteredValue = filter_var($value, FILTER_VALIDATE_INT); + if ($filteredValue === false) { + return null; + } - if($filteredValue === false) - return null; + return $filteredValue; + } - return $filteredValue; - } + /** + * Validate value for a checkbox + * + * @param bool $value The value of a checkbox + * @return bool The filtered value + */ + private function validateCheckboxValue($value) + { + return filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); + } - /** - * Validate value for a checkbox - * - * @param bool $value The value of a checkbox - * @return bool The filtered value - */ - private function validateCheckboxValue($value){ - return filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); - } + /** + * Validate value for a list + * + * @param string $value The value of a list + * @param array $expectedValues A list of expected values + * @return string|null The filtered value or null if the value is invalid + */ + private function validateListValue($value, $expectedValues) + { + $filteredValue = filter_var($value); - /** - * Validate value for a list - * - * @param string $value The value of a list - * @param array $expectedValues A list of expected values - * @return string|null The filtered value or null if the value is invalid - */ - private function validateListValue($value, $expectedValues){ - $filteredValue = filter_var($value); + if ($filteredValue === false) { + return null; + } - if($filteredValue === false) - return null; + if (!in_array($filteredValue, $expectedValues)) { // Check sub-values? + foreach ($expectedValues as $subName => $subValue) { + if (is_array($subValue) && in_array($filteredValue, $subValue)) { + return $filteredValue; + } + } + return null; + } - if(!in_array($filteredValue, $expectedValues)) { // Check sub-values? - foreach($expectedValues as $subName => $subValue) { - if(is_array($subValue) && in_array($filteredValue, $subValue)) - return $filteredValue; - } - return null; - } + return $filteredValue; + } - return $filteredValue; - } + /** + * Check if all required parameters are satisfied + * + * @param array $data (ref) A list of input values + * @param array $parameters The bridge parameters + * @return bool True if all parameters are satisfied + */ + public function validateData(&$data, $parameters) + { + if (!is_array($data)) { + return false; + } - /** - * Check if all required parameters are satisfied - * - * @param array $data (ref) A list of input values - * @param array $parameters The bridge parameters - * @return bool True if all parameters are satisfied - */ - public function validateData(&$data, $parameters){ + foreach ($data as $name => $value) { + // Some RSS readers add a cache-busting parameter (_=) to feed URLs, detect and ignore them. + if ($name === '_') { + continue; + } - if(!is_array($data)) - return false; + $registered = false; + foreach ($parameters as $context => $set) { + if (array_key_exists($name, $set)) { + $registered = true; + if (!isset($set[$name]['type'])) { + $set[$name]['type'] = 'text'; + } - foreach($data as $name => $value) { - // Some RSS readers add a cache-busting parameter (_=) to feed URLs, detect and ignore them. - if ($name === '_') continue; + switch ($set[$name]['type']) { + case 'number': + $data[$name] = $this->validateNumberValue($value); + break; + case 'checkbox': + $data[$name] = $this->validateCheckboxValue($value); + break; + case 'list': + $data[$name] = $this->validateListValue($value, $set[$name]['values']); + break; + default: + case 'text': + if (isset($set[$name]['pattern'])) { + $data[$name] = $this->validateTextValue($value, $set[$name]['pattern']); + } else { + $data[$name] = $this->validateTextValue($value); + } + break; + } - $registered = false; - foreach($parameters as $context => $set) { - if(array_key_exists($name, $set)) { - $registered = true; - if(!isset($set[$name]['type'])) { - $set[$name]['type'] = 'text'; - } + if (is_null($data[$name]) && isset($set[$name]['required']) && $set[$name]['required']) { + $this->addInvalidParameter($name, 'Parameter is invalid!'); + } + } + } - switch($set[$name]['type']) { - case 'number': - $data[$name] = $this->validateNumberValue($value); - break; - case 'checkbox': - $data[$name] = $this->validateCheckboxValue($value); - break; - case 'list': - $data[$name] = $this->validateListValue($value, $set[$name]['values']); - break; - default: - case 'text': - if(isset($set[$name]['pattern'])) { - $data[$name] = $this->validateTextValue($value, $set[$name]['pattern']); - } else { - $data[$name] = $this->validateTextValue($value); - } - break; - } + if (!$registered) { + $this->addInvalidParameter($name, 'Parameter is not registered!'); + } + } - if(is_null($data[$name]) && isset($set[$name]['required']) && $set[$name]['required']) { - $this->addInvalidParameter($name, 'Parameter is invalid!'); - } - } - } + return empty($this->invalid); + } - if(!$registered) { - $this->addInvalidParameter($name, 'Parameter is not registered!'); - } - } + /** + * Get the name of the context matching the provided inputs + * + * @param array $data Associative array of user data + * @param array $parameters Array of bridge parameters + * @return string|null Returns the context name or null if no match was found + */ + public function getQueriedContext($data, $parameters) + { + $queriedContexts = []; - return empty($this->invalid); - } + // Detect matching context + foreach ($parameters as $context => $set) { + $queriedContexts[$context] = null; - /** - * Get the name of the context matching the provided inputs - * - * @param array $data Associative array of user data - * @param array $parameters Array of bridge parameters - * @return string|null Returns the context name or null if no match was found - */ - public function getQueriedContext($data, $parameters){ - $queriedContexts = array(); + // Ensure all user data exist in the current context + $notInContext = array_diff_key($data, $set); + if (array_key_exists('global', $parameters)) { + $notInContext = array_diff_key($notInContext, $parameters['global']); + } + if (sizeof($notInContext) > 0) { + continue; + } - // Detect matching context - foreach($parameters as $context => $set) { - $queriedContexts[$context] = null; + // Check if all parameters of the context are satisfied + foreach ($set as $id => $properties) { + if (isset($data[$id]) && !empty($data[$id])) { + $queriedContexts[$context] = true; + } elseif ( + isset($properties['type']) + && ($properties['type'] === 'checkbox' || $properties['type'] === 'list') + ) { + continue; + } elseif (isset($properties['required']) && $properties['required'] === true) { + $queriedContexts[$context] = false; + break; + } + } + } - // Ensure all user data exist in the current context - $notInContext = array_diff_key($data, $set); - if(array_key_exists('global', $parameters)) - $notInContext = array_diff_key($notInContext, $parameters['global']); - if(sizeof($notInContext) > 0) - continue; + // Abort if one of the globally required parameters is not satisfied + if ( + array_key_exists('global', $parameters) + && $queriedContexts['global'] === false + ) { + return null; + } + unset($queriedContexts['global']); - // Check if all parameters of the context are satisfied - foreach($set as $id => $properties) { - if(isset($data[$id]) && !empty($data[$id])) { - $queriedContexts[$context] = true; - } elseif (isset($properties['type']) - && ($properties['type'] === 'checkbox' || $properties['type'] === 'list')) { - continue; - } elseif(isset($properties['required']) && $properties['required'] === true) { - $queriedContexts[$context] = false; - break; - } - } - } - - // Abort if one of the globally required parameters is not satisfied - if(array_key_exists('global', $parameters) - && $queriedContexts['global'] === false) { - return null; - } - unset($queriedContexts['global']); - - switch(array_sum($queriedContexts)) { - case 0: // Found no match, is there a context without parameters? - if(isset($data['context'])) return $data['context']; - foreach($queriedContexts as $context => $queried) { - if(is_null($queried)) { - return $context; - } - } - return null; - case 1: // Found unique match - return array_search(true, $queriedContexts); - default: return false; - } - } + switch (array_sum($queriedContexts)) { + case 0: // Found no match, is there a context without parameters? + if (isset($data['context'])) { + return $data['context']; + } + foreach ($queriedContexts as $context => $queried) { + if (is_null($queried)) { + return $context; + } + } + return null; + case 1: // Found unique match + return array_search(true, $queriedContexts); + default: + return false; + } + } } diff --git a/lib/XPathAbstract.php b/lib/XPathAbstract.php index 0ca1587b..686addf4 100644 --- a/lib/XPathAbstract.php +++ b/lib/XPathAbstract.php @@ -15,572 +15,598 @@ * This class extends {@see BridgeAbstract}, which means it incorporates and * extends all of its functionality. **/ -abstract class XPathAbstract extends BridgeAbstract { +abstract class XPathAbstract extends BridgeAbstract +{ + /** + * Source Web page URL (should provide either HTML or XML content) + * You can specify any website URL which serves data suited for display in RSS feeds + * (for example a news blog). + * + * Use {@see XPathAbstract::getSourceUrl()} to read this parameter + */ + const FEED_SOURCE_URL = ''; - /** - * Source Web page URL (should provide either HTML or XML content) - * You can specify any website URL which serves data suited for display in RSS feeds - * (for example a news blog). - * - * Use {@see XPathAbstract::getSourceUrl()} to read this parameter - */ - const FEED_SOURCE_URL = ''; + /** + * XPath expression for extracting the feed title from the source page. + * If this is left blank or does not provide any data {@see BridgeAbstract::getName()} + * is used instead as the feed's title. + * + * Use {@see XPathAbstract::getExpressionTitle()} to read this parameter + */ + const XPATH_EXPRESSION_FEED_TITLE = './/title'; - /** - * XPath expression for extracting the feed title from the source page. - * If this is left blank or does not provide any data {@see BridgeAbstract::getName()} - * is used instead as the feed's title. - * - * Use {@see XPathAbstract::getExpressionTitle()} to read this parameter - */ - const XPATH_EXPRESSION_FEED_TITLE = './/title'; + /** + * XPath expression for extracting the feed favicon URL from the source page. + * If this is left blank or does not provide any data {@see BridgeAbstract::getIcon()} + * is used instead as the feed's favicon URL. + * + * Use {@see XPathAbstract::getExpressionIcon()} to read this parameter + */ + const XPATH_EXPRESSION_FEED_ICON = './/link[@rel="icon"]/@href'; - /** - * XPath expression for extracting the feed favicon URL from the source page. - * If this is left blank or does not provide any data {@see BridgeAbstract::getIcon()} - * is used instead as the feed's favicon URL. - * - * Use {@see XPathAbstract::getExpressionIcon()} to read this parameter - */ - const XPATH_EXPRESSION_FEED_ICON = './/link[@rel="icon"]/@href'; + /** + * XPath expression for extracting the feed items from the source page + * Enter an XPath expression matching a list of dom nodes, each node containing one + * feed article item in total (usually a surrounding
    or tag). This will + * be the context nodes for all of the following expressions. This expression usually + * starts with a single forward slash. + * + * Use {@see XPathAbstract::getExpressionItem()} to read this parameter + */ + const XPATH_EXPRESSION_ITEM = ''; - /** - * XPath expression for extracting the feed items from the source page - * Enter an XPath expression matching a list of dom nodes, each node containing one - * feed article item in total (usually a surrounding
    or tag). This will - * be the context nodes for all of the following expressions. This expression usually - * starts with a single forward slash. - * - * Use {@see XPathAbstract::getExpressionItem()} to read this parameter - */ - const XPATH_EXPRESSION_ITEM = ''; + /** + * XPath expression for extracting an item title from the item context + * This expression should match a node contained within each article item node + * containing the article headline. It should start with a dot followed by two + * forward slashes, referring to any descendant nodes of the article item node. + * + * Use {@see XPathAbstract::getExpressionItemTitle()} to read this parameter + */ + const XPATH_EXPRESSION_ITEM_TITLE = ''; - /** - * XPath expression for extracting an item title from the item context - * This expression should match a node contained within each article item node - * containing the article headline. It should start with a dot followed by two - * forward slashes, referring to any descendant nodes of the article item node. - * - * Use {@see XPathAbstract::getExpressionItemTitle()} to read this parameter - */ - const XPATH_EXPRESSION_ITEM_TITLE = ''; + /** + * XPath expression for extracting an item's content from the item context + * This expression should match a node contained within each article item node + * containing the article content or description. It should start with a dot + * followed by two forward slashes, referring to any descendant nodes of the + * article item node. + * + * Use {@see XPathAbstract::getExpressionItemContent()} to read this parameter + */ + const XPATH_EXPRESSION_ITEM_CONTENT = ''; - /** - * XPath expression for extracting an item's content from the item context - * This expression should match a node contained within each article item node - * containing the article content or description. It should start with a dot - * followed by two forward slashes, referring to any descendant nodes of the - * article item node. - * - * Use {@see XPathAbstract::getExpressionItemContent()} to read this parameter - */ - const XPATH_EXPRESSION_ITEM_CONTENT = ''; + /** + * XPath expression for extracting an item link from the item context + * This expression should match a node's attribute containing the article URL + * (usually the href attribute of an tag). It should start with a dot + * followed by two forward slashes, referring to any descendant nodes of + * the article item node. Attributes can be selected by prepending an @ char + * before the attributes name. + * + * Use {@see XPathAbstract::getExpressionItemUri()} to read this parameter + */ + const XPATH_EXPRESSION_ITEM_URI = ''; - /** - * XPath expression for extracting an item link from the item context - * This expression should match a node's attribute containing the article URL - * (usually the href attribute of an tag). It should start with a dot - * followed by two forward slashes, referring to any descendant nodes of - * the article item node. Attributes can be selected by prepending an @ char - * before the attributes name. - * - * Use {@see XPathAbstract::getExpressionItemUri()} to read this parameter - */ - const XPATH_EXPRESSION_ITEM_URI = ''; + /** + * XPath expression for extracting an item author from the item context + * This expression should match a node contained within each article item + * node containing the article author's name. It should start with a dot + * followed by two forward slashes, referring to any descendant nodes of + * the article item node. + * + * Use {@see XPathAbstract::getExpressionItemAuthor()} to read this parameter + */ + const XPATH_EXPRESSION_ITEM_AUTHOR = ''; - /** - * XPath expression for extracting an item author from the item context - * This expression should match a node contained within each article item - * node containing the article author's name. It should start with a dot - * followed by two forward slashes, referring to any descendant nodes of - * the article item node. - * - * Use {@see XPathAbstract::getExpressionItemAuthor()} to read this parameter - */ - const XPATH_EXPRESSION_ITEM_AUTHOR = ''; + /** + * XPath expression for extracting an item timestamp from the item context + * This expression should match a node or node's attribute containing the + * article timestamp or date (parsable by PHP's strtotime function). It + * should start with a dot followed by two forward slashes, referring to + * any descendant nodes of the article item node. Attributes can be + * selected by prepending an @ char before the attributes name. + * + * Use {@see XPathAbstract::getExpressionItemTimestamp()} to read this parameter + */ + const XPATH_EXPRESSION_ITEM_TIMESTAMP = ''; - /** - * XPath expression for extracting an item timestamp from the item context - * This expression should match a node or node's attribute containing the - * article timestamp or date (parsable by PHP's strtotime function). It - * should start with a dot followed by two forward slashes, referring to - * any descendant nodes of the article item node. Attributes can be - * selected by prepending an @ char before the attributes name. - * - * Use {@see XPathAbstract::getExpressionItemTimestamp()} to read this parameter - */ - const XPATH_EXPRESSION_ITEM_TIMESTAMP = ''; + /** + * XPath expression for extracting item enclosures (media content like + * images or movies) from the item context + * This expression should match a node's attribute containing an article + * image URL (usually the src attribute of an tag or a style + * attribute). It should start with a dot followed by two forward slashes, + * referring to any descendant nodes of the article item node. Attributes + * can be selected by prepending an @ char before the attributes name. + * + * Use {@see XPathAbstract::getExpressionItemEnclosures()} to read this parameter + */ + const XPATH_EXPRESSION_ITEM_ENCLOSURES = ''; - /** - * XPath expression for extracting item enclosures (media content like - * images or movies) from the item context - * This expression should match a node's attribute containing an article - * image URL (usually the src attribute of an tag or a style - * attribute). It should start with a dot followed by two forward slashes, - * referring to any descendant nodes of the article item node. Attributes - * can be selected by prepending an @ char before the attributes name. - * - * Use {@see XPathAbstract::getExpressionItemEnclosures()} to read this parameter - */ - const XPATH_EXPRESSION_ITEM_ENCLOSURES = ''; + /** + * XPath expression for extracting an item category from the item context + * This expression should match a node or node's attribute contained + * within each article item node containing the article category. This + * could be inside
    or tags or sometimes be hidden + * in a data attribute. It should start with a dot followed by two + * forward slashes, referring to any descendant nodes of the article + * item node. Attributes can be selected by prepending an @ char + * before the attributes name. + * + * Use {@see XPathAbstract::getExpressionItemCategories()} to read this parameter + */ + const XPATH_EXPRESSION_ITEM_CATEGORIES = ''; - /** - * XPath expression for extracting an item category from the item context - * This expression should match a node or node's attribute contained - * within each article item node containing the article category. This - * could be inside
    or tags or sometimes be hidden - * in a data attribute. It should start with a dot followed by two - * forward slashes, referring to any descendant nodes of the article - * item node. Attributes can be selected by prepending an @ char - * before the attributes name. - * - * Use {@see XPathAbstract::getExpressionItemCategories()} to read this parameter - */ - const XPATH_EXPRESSION_ITEM_CATEGORIES = ''; + /** + * Fix encoding + * Set this to true for fixing feed encoding by invoking PHP's utf8_decode + * function on all extracted texts. Try this in case you see "broken" or + * "weird" characters in your feed where you'd normally expect umlauts + * or any other non-ascii characters. + * + * Use {@see XPathAbstract::getSettingFixEncoding()} to read this parameter + */ + const SETTING_FIX_ENCODING = false; - /** - * Fix encoding - * Set this to true for fixing feed encoding by invoking PHP's utf8_decode - * function on all extracted texts. Try this in case you see "broken" or - * "weird" characters in your feed where you'd normally expect umlauts - * or any other non-ascii characters. - * - * Use {@see XPathAbstract::getSettingFixEncoding()} to read this parameter - */ - const SETTING_FIX_ENCODING = false; + /** + * Internal storage for resulting feed name, automatically detected + * @var string + */ + private $feedName; - /** - * Internal storage for resulting feed name, automatically detected - * @var string - */ - private $feedName; + /** + * Internal storage for resulting feed name, automatically detected + * @var string + */ + private $feedUri; - /** - * Internal storage for resulting feed name, automatically detected - * @var string - */ - private $feedUri; + /** + * Internal storage for resulting feed favicon, automatically detected + * @var string + */ + private $feedIcon; - /** - * Internal storage for resulting feed favicon, automatically detected - * @var string - */ - private $feedIcon; + public function getName() + { + return $this->feedName ?: parent::getName(); + } - public function getName(){ - return $this->feedName ?: parent::getName(); - } + public function getURI() + { + return $this->feedUri ?: parent::getURI(); + } - public function getURI() { - return $this->feedUri ?: parent::getURI(); - } + public function getIcon() + { + return $this->feedIcon ?: parent::getIcon(); + } - public function getIcon() { - return $this->feedIcon ?: parent::getIcon(); - } + /** + * Source Web page URL (should provide either HTML or XML content) + * @return string + */ + protected function getSourceUrl() + { + return static::FEED_SOURCE_URL; + } - /** - * Source Web page URL (should provide either HTML or XML content) - * @return string - */ - protected function getSourceUrl(){ - return static::FEED_SOURCE_URL; - } + /** + * XPath expression for extracting the feed title from the source page + * @return string + */ + protected function getExpressionTitle() + { + return static::XPATH_EXPRESSION_FEED_TITLE; + } - /** - * XPath expression for extracting the feed title from the source page - * @return string - */ - protected function getExpressionTitle(){ - return static::XPATH_EXPRESSION_FEED_TITLE; - } + /** + * XPath expression for extracting the feed favicon from the source page + * @return string + */ + protected function getExpressionIcon() + { + return static::XPATH_EXPRESSION_FEED_ICON; + } - /** - * XPath expression for extracting the feed favicon from the source page - * @return string - */ - protected function getExpressionIcon(){ - return static::XPATH_EXPRESSION_FEED_ICON; - } + /** + * XPath expression for extracting the feed items from the source page + * @return string + */ + protected function getExpressionItem() + { + return static::XPATH_EXPRESSION_ITEM; + } - /** - * XPath expression for extracting the feed items from the source page - * @return string - */ - protected function getExpressionItem(){ - return static::XPATH_EXPRESSION_ITEM; - } + /** + * XPath expression for extracting an item title from the item context + * @return string + */ + protected function getExpressionItemTitle() + { + return static::XPATH_EXPRESSION_ITEM_TITLE; + } - /** - * XPath expression for extracting an item title from the item context - * @return string - */ - protected function getExpressionItemTitle(){ - return static::XPATH_EXPRESSION_ITEM_TITLE; - } + /** + * XPath expression for extracting an item's content from the item context + * @return string + */ + protected function getExpressionItemContent() + { + return static::XPATH_EXPRESSION_ITEM_CONTENT; + } - /** - * XPath expression for extracting an item's content from the item context - * @return string - */ - protected function getExpressionItemContent(){ - return static::XPATH_EXPRESSION_ITEM_CONTENT; - } + /** + * XPath expression for extracting an item link from the item context + * @return string + */ + protected function getExpressionItemUri() + { + return static::XPATH_EXPRESSION_ITEM_URI; + } - /** - * XPath expression for extracting an item link from the item context - * @return string - */ - protected function getExpressionItemUri(){ - return static::XPATH_EXPRESSION_ITEM_URI; - } + /** + * XPath expression for extracting an item author from the item context + * @return string + */ + protected function getExpressionItemAuthor() + { + return static::XPATH_EXPRESSION_ITEM_AUTHOR; + } - /** - * XPath expression for extracting an item author from the item context - * @return string - */ - protected function getExpressionItemAuthor(){ - return static::XPATH_EXPRESSION_ITEM_AUTHOR; - } + /** + * XPath expression for extracting an item timestamp from the item context + * @return string + */ + protected function getExpressionItemTimestamp() + { + return static::XPATH_EXPRESSION_ITEM_TIMESTAMP; + } - /** - * XPath expression for extracting an item timestamp from the item context - * @return string - */ - protected function getExpressionItemTimestamp(){ - return static::XPATH_EXPRESSION_ITEM_TIMESTAMP; - } + /** + * XPath expression for extracting item enclosures (media content like + * images or movies) from the item context + * @return string + */ + protected function getExpressionItemEnclosures() + { + return static::XPATH_EXPRESSION_ITEM_ENCLOSURES; + } - /** - * XPath expression for extracting item enclosures (media content like - * images or movies) from the item context - * @return string - */ - protected function getExpressionItemEnclosures(){ - return static::XPATH_EXPRESSION_ITEM_ENCLOSURES; - } + /** + * XPath expression for extracting an item category from the item context + * @return string + */ + protected function getExpressionItemCategories() + { + return static::XPATH_EXPRESSION_ITEM_CATEGORIES; + } - /** - * XPath expression for extracting an item category from the item context - * @return string - */ - protected function getExpressionItemCategories(){ - return static::XPATH_EXPRESSION_ITEM_CATEGORIES; - } + /** + * Fix encoding + * @return string + */ + protected function getSettingFixEncoding() + { + return static::SETTING_FIX_ENCODING; + } - /** - * Fix encoding - * @return string - */ - protected function getSettingFixEncoding(){ - return static::SETTING_FIX_ENCODING; - } + /** + * Internal helper method for quickly accessing all the user defined constants + * in derived classes + * + * @param $name + * @return bool|string + */ + private function getParam($name) + { + switch ($name) { + case 'url': + return $this->getSourceUrl(); + case 'feed_title': + return $this->getExpressionTitle(); + case 'feed_icon': + return $this->getExpressionIcon(); + case 'item': + return $this->getExpressionItem(); + case 'title': + return $this->getExpressionItemTitle(); + case 'content': + return $this->getExpressionItemContent(); + case 'uri': + return $this->getExpressionItemUri(); + case 'author': + return $this->getExpressionItemAuthor(); + case 'timestamp': + return $this->getExpressionItemTimestamp(); + case 'enclosures': + return $this->getExpressionItemEnclosures(); + case 'categories': + return $this->getExpressionItemCategories(); + case 'fix_encoding': + return $this->getSettingFixEncoding(); + } + } - /** - * Internal helper method for quickly accessing all the user defined constants - * in derived classes - * - * @param $name - * @return bool|string - */ - private function getParam($name){ - switch($name) { + /** + * Should provide the source website HTML content + * can be easily overwritten for example if special headers or auth infos are required + * @return string + */ + protected function provideWebsiteContent() + { + return getContents($this->feedUri); + } - case 'url': - return $this->getSourceUrl(); - case 'feed_title': - return $this->getExpressionTitle(); - case 'feed_icon': - return $this->getExpressionIcon(); - case 'item': - return $this->getExpressionItem(); - case 'title': - return $this->getExpressionItemTitle(); - case 'content': - return $this->getExpressionItemContent(); - case 'uri': - return $this->getExpressionItemUri(); - case 'author': - return $this->getExpressionItemAuthor(); - case 'timestamp': - return $this->getExpressionItemTimestamp(); - case 'enclosures': - return $this->getExpressionItemEnclosures(); - case 'categories': - return $this->getExpressionItemCategories(); - case 'fix_encoding': - return $this->getSettingFixEncoding(); - } - } + /** + * Should provide the feeds title + * + * @param DOMXPath $xpath + * @return string + */ + protected function provideFeedTitle(DOMXPath $xpath) + { + $title = $xpath->query($this->getParam('feed_title')); + if (count($title) === 1) { + return $this->getItemValueOrNodeValue($title); + } + } - /** - * Should provide the source website HTML content - * can be easily overwritten for example if special headers or auth infos are required - * @return string - */ - protected function provideWebsiteContent() { - return getContents($this->feedUri); - } + /** + * Should provide the URL of the feed's favicon + * + * @param DOMXPath $xpath + * @return string + */ + protected function provideFeedIcon(DOMXPath $xpath) + { + $icon = $xpath->query($this->getParam('feed_icon')); + if (count($icon) === 1) { + return $this->cleanMediaUrl($this->getItemValueOrNodeValue($icon)); + } + } - /** - * Should provide the feeds title - * - * @param DOMXPath $xpath - * @return string - */ - protected function provideFeedTitle(DOMXPath $xpath) { - $title = $xpath->query($this->getParam('feed_title')); - if(count($title) === 1) { - return $this->getItemValueOrNodeValue($title); - } - } + /** + * Should provide the feed's items. + * + * @param DOMXPath $xpath + * @return DOMNodeList + */ + protected function provideFeedItems(DOMXPath $xpath) + { + return @$xpath->query($this->getParam('item')); + } - /** - * Should provide the URL of the feed's favicon - * - * @param DOMXPath $xpath - * @return string - */ - protected function provideFeedIcon(DOMXPath $xpath) { - $icon = $xpath->query($this->getParam('feed_icon')); - if(count($icon) === 1) { - return $this->cleanMediaUrl($this->getItemValueOrNodeValue($icon)); - } - } + public function collectData() + { + $this->feedUri = $this->getParam('url'); - /** - * Should provide the feed's items. - * - * @param DOMXPath $xpath - * @return DOMNodeList - */ - protected function provideFeedItems(DOMXPath $xpath) { - return @$xpath->query($this->getParam('item')); - } + $webPageHtml = new DOMDocument(); + libxml_use_internal_errors(true); + $webPageHtml->loadHTML($this->provideWebsiteContent()); + libxml_clear_errors(); + libxml_use_internal_errors(false); - public function collectData() { + $xpath = new DOMXPath($webPageHtml); - $this->feedUri = $this->getParam('url'); + $this->feedName = $this->provideFeedTitle($xpath); + $this->feedIcon = $this->provideFeedIcon($xpath); - $webPageHtml = new DOMDocument(); - libxml_use_internal_errors(true); - $webPageHtml->loadHTML($this->provideWebsiteContent()); - libxml_clear_errors(); - libxml_use_internal_errors(false); + $entries = $this->provideFeedItems($xpath); + if ($entries === false) { + return; + } - $xpath = new DOMXPath($webPageHtml); + foreach ($entries as $entry) { + $item = new \FeedItem(); + foreach (['title', 'content', 'uri', 'author', 'timestamp', 'enclosures', 'categories'] as $param) { + $expression = $this->getParam($param); + if ('' === $expression) { + continue; + } - $this->feedName = $this->provideFeedTitle($xpath); - $this->feedIcon = $this->provideFeedIcon($xpath); + //can be a string or DOMNodeList, depending on the expression result + $typedResult = @$xpath->evaluate($expression, $entry); + if ( + $typedResult === false || ($typedResult instanceof DOMNodeList && count($typedResult) === 0) + || (is_string($typedResult) && strlen(trim($typedResult)) === 0) + ) { + continue; + } - $entries = $this->provideFeedItems($xpath); - if($entries === false) { - return; - } + $item->__set($param, $this->formatParamValue($param, $this->getItemValueOrNodeValue($typedResult))); + } - foreach ($entries as $entry) { - $item = new \FeedItem(); - foreach(array('title', 'content', 'uri', 'author', 'timestamp', 'enclosures', 'categories') as $param) { + $itemId = $this->generateItemId($item); + if (null !== $itemId) { + $item->setUid($itemId); + } - $expression = $this->getParam($param); - if('' === $expression) { - continue; - } + $this->items[] = $item; + } + } - //can be a string or DOMNodeList, depending on the expression result - $typedResult = @$xpath->evaluate($expression, $entry); - if ($typedResult === false || ($typedResult instanceof DOMNodeList && count($typedResult) === 0) - || (is_string($typedResult) && strlen(trim($typedResult)) === 0)) { - continue; - } + /** + * @param $param + * @param $value + * @return string|array + */ + protected function formatParamValue($param, $value) + { + $value = $this->fixEncoding($value); + switch ($param) { + case 'title': + return $this->formatItemTitle($value); + case 'content': + return $this->formatItemContent($value); + case 'uri': + return $this->formatItemUri($value); + case 'author': + return $this->formatItemAuthor($value); + case 'timestamp': + return $this->formatItemTimestamp($value); + case 'enclosures': + return $this->formatItemEnclosures($value); + case 'categories': + return $this->formatItemCategories($value); + } + return $value; + } - $item->__set($param, $this->formatParamValue($param, $this->getItemValueOrNodeValue($typedResult))); + /** + * Formats the title of a feed item. Takes extracted raw title and returns it formatted + * as string. + * Can be easily overwritten for in case the value needs to be transformed into something + * else. + * @param string $value + * @return string + */ + protected function formatItemTitle($value) + { + return $value; + } - } + /** + * Formats the timestamp of a feed item. Takes extracted raw timestamp and returns unix + * timestamp as integer. + * Can be easily overwritten for example if a special format has to be expected on the + * source website. + * @param string $value + * @return string + */ + protected function formatItemContent($value) + { + return $value; + } - $itemId = $this->generateItemId($item); - if(null !== $itemId) { - $item->setUid($itemId); - } + /** + * Formats the URI of a feed item. Takes extracted raw URI and returns it formatted + * as string. + * Can be easily overwritten for in case the value needs to be transformed into something + * else. + * @param string $value + * @return string + */ + protected function formatItemUri($value) + { + if (strlen($value) === 0) { + return ''; + } + if (strpos($value, 'http://') === 0 || strpos($value, 'https://') === 0) { + return $value; + } - $this->items[] = $item; - } + return urljoin($this->feedUri, $value); + } - } + /** + * Formats the author of a feed item. Takes extracted raw author and returns it formatted + * as string. + * Can be easily overwritten for in case the value needs to be transformed into something + * else. + * @param string $value + * @return string + */ + protected function formatItemAuthor($value) + { + return $value; + } - /** - * @param $param - * @param $value - * @return string|array - */ - protected function formatParamValue($param, $value) - { - $value = $this->fixEncoding($value); - switch ($param) { - case 'title': - return $this->formatItemTitle($value); - case 'content': - return $this->formatItemContent($value); - case 'uri': - return $this->formatItemUri($value); - case 'author': - return $this->formatItemAuthor($value); - case 'timestamp': - return $this->formatItemTimestamp($value); - case 'enclosures': - return $this->formatItemEnclosures($value); - case 'categories': - return $this->formatItemCategories($value); - } - return $value; - } + /** + * Formats the timestamp of a feed item. Takes extracted raw timestamp and returns unix + * timestamp as integer. + * Can be easily overwritten for example if a special format has to be expected on the + * source website. + * @param string $value + * @return false|int + */ + protected function formatItemTimestamp($value) + { + return strtotime($value); + } - /** - * Formats the title of a feed item. Takes extracted raw title and returns it formatted - * as string. - * Can be easily overwritten for in case the value needs to be transformed into something - * else. - * @param string $value - * @return string - */ - protected function formatItemTitle($value) { - return $value; - } + /** + * Formats the enclosures of a feed item. Takes extracted raw enclosures and returns them + * formatted as array. + * Can be easily overwritten for in case the values need to be transformed into something + * else. + * @param string $value + * @return array + */ + protected function formatItemEnclosures($value) + { + return [$this->cleanMediaUrl($value)]; + } - /** - * Formats the timestamp of a feed item. Takes extracted raw timestamp and returns unix - * timestamp as integer. - * Can be easily overwritten for example if a special format has to be expected on the - * source website. - * @param string $value - * @return string - */ - protected function formatItemContent($value) { - return $value; - } + /** + * Formats the categories of a feed item. Takes extracted raw categories and returns them + * formatted as array. + * Can be easily overwritten for in case the values need to be transformed into something + * else. + * @param string $value + * @return array + */ + protected function formatItemCategories($value) + { + return [$value]; + } - /** - * Formats the URI of a feed item. Takes extracted raw URI and returns it formatted - * as string. - * Can be easily overwritten for in case the value needs to be transformed into something - * else. - * @param string $value - * @return string - */ - protected function formatItemUri($value) { - if(strlen($value) === 0) { - return ''; - } - if(strpos($value, 'http://') === 0 || strpos($value, 'https://') === 0) { - return $value; - } + /** + * @param $mediaUrl + * @return string|void + */ + protected function cleanMediaUrl($mediaUrl) + { + $pattern = '~(?:http(?:s)?:)?[\/a-zA-Z0-9\-=_,\.\%]+\.(?:jpg|gif|png|jpeg|ico|mp3|webp){1}~i'; + $result = preg_match($pattern, $mediaUrl, $matches); + if (1 !== $result) { + return; + } + return urljoin($this->feedUri, $matches[0]); + } - return urljoin($this->feedUri, $value); - } + /** + * @param $typedResult + * @return string + */ + protected function getItemValueOrNodeValue($typedResult) + { + if ($typedResult instanceof DOMNodeList) { + $item = $typedResult->item(0); + if ($item instanceof DOMElement) { + return trim($item->nodeValue); + } elseif ($item instanceof DOMAttr) { + return trim($item->value); + } elseif ($item instanceof DOMText) { + return trim($item->wholeText); + } + } elseif (is_string($typedResult) && strlen($typedResult) > 0) { + return trim($typedResult); + } + returnServerError('Unknown type of XPath expression result.'); + } - /** - * Formats the author of a feed item. Takes extracted raw author and returns it formatted - * as string. - * Can be easily overwritten for in case the value needs to be transformed into something - * else. - * @param string $value - * @return string - */ - protected function formatItemAuthor($value) { - return $value; - } + /** + * Fixes feed encoding by invoking PHP's utf8_decode function on extracted texts. + * Useful in case of "broken" or "weird" characters in the feed where you'd normally + * expect umlauts. + * + * @param $input + * @return string + */ + protected function fixEncoding($input) + { + return $this->getParam('fix_encoding') ? utf8_decode($input) : $input; + } - /** - * Formats the timestamp of a feed item. Takes extracted raw timestamp and returns unix - * timestamp as integer. - * Can be easily overwritten for example if a special format has to be expected on the - * source website. - * @param string $value - * @return false|int - */ - protected function formatItemTimestamp($value) { - return strtotime($value); - } - - /** - * Formats the enclosures of a feed item. Takes extracted raw enclosures and returns them - * formatted as array. - * Can be easily overwritten for in case the values need to be transformed into something - * else. - * @param string $value - * @return array - */ - protected function formatItemEnclosures($value) { - return array($this->cleanMediaUrl($value)); - } - - /** - * Formats the categories of a feed item. Takes extracted raw categories and returns them - * formatted as array. - * Can be easily overwritten for in case the values need to be transformed into something - * else. - * @param string $value - * @return array - */ - protected function formatItemCategories($value) { - return array($value); - } - - /** - * @param $mediaUrl - * @return string|void - */ - protected function cleanMediaUrl($mediaUrl) - { - $pattern = '~(?:http(?:s)?:)?[\/a-zA-Z0-9\-=_,\.\%]+\.(?:jpg|gif|png|jpeg|ico|mp3|webp){1}~i'; - $result = preg_match($pattern, $mediaUrl, $matches); - if(1 !== $result) { - return; - } - return urljoin($this->feedUri, $matches[0]); - } - - /** - * @param $typedResult - * @return string - */ - protected function getItemValueOrNodeValue($typedResult) - { - if($typedResult instanceof DOMNodeList) { - $item = $typedResult->item(0); - if ($item instanceof DOMElement) { - return trim($item->nodeValue); - } elseif ($item instanceof DOMAttr) { - return trim($item->value); - } elseif ($item instanceof DOMText) { - return trim($item->wholeText); - } - } elseif(is_string($typedResult) && strlen($typedResult) > 0) { - return trim($typedResult); - } - returnServerError('Unknown type of XPath expression result.'); - } - - /** - * Fixes feed encoding by invoking PHP's utf8_decode function on extracted texts. - * Useful in case of "broken" or "weird" characters in the feed where you'd normally - * expect umlauts. - * - * @param $input - * @return string - */ - protected function fixEncoding($input) - { - return $this->getParam('fix_encoding') ? utf8_decode($input) : $input; - } - - /** - * Allows overriding default mechanism determining items Uid's - * - * @param FeedItem $item - * @return string|null - */ - protected function generateItemId(\FeedItem $item) { - return null; //auto generation - } + /** + * Allows overriding default mechanism determining items Uid's + * + * @param FeedItem $item + * @return string|null + */ + protected function generateItemId(\FeedItem $item) + { + return null; //auto generation + } } diff --git a/lib/contents.php b/lib/contents.php index cc80248b..a01d81e1 100644 --- a/lib/contents.php +++ b/lib/contents.php @@ -1,48 +1,50 @@ 'Continue', - '101' => 'Switching Protocols', - '200' => 'OK', - '201' => 'Created', - '202' => 'Accepted', - '203' => 'Non-Authoritative Information', - '204' => 'No Content', - '205' => 'Reset Content', - '206' => 'Partial Content', - '300' => 'Multiple Choices', - '302' => 'Found', - '303' => 'See Other', - '304' => 'Not Modified', - '305' => 'Use Proxy', - '400' => 'Bad Request', - '401' => 'Unauthorized', - '402' => 'Payment Required', - '403' => 'Forbidden', - '404' => 'Not Found', - '405' => 'Method Not Allowed', - '406' => 'Not Acceptable', - '407' => 'Proxy Authentication Required', - '408' => 'Request Timeout', - '409' => 'Conflict', - '410' => 'Gone', - '411' => 'Length Required', - '412' => 'Precondition Failed', - '413' => 'Request Entity Too Large', - '414' => 'Request-URI Too Long', - '415' => 'Unsupported Media Type', - '416' => 'Requested Range Not Satisfiable', - '417' => 'Expectation Failed', - '429' => 'Too Many Requests', - '500' => 'Internal Server Error', - '501' => 'Not Implemented', - '502' => 'Bad Gateway', - '503' => 'Service Unavailable', - '504' => 'Gateway Timeout', - '505' => 'HTTP Version Not Supported' + '100' => 'Continue', + '101' => 'Switching Protocols', + '200' => 'OK', + '201' => 'Created', + '202' => 'Accepted', + '203' => 'Non-Authoritative Information', + '204' => 'No Content', + '205' => 'Reset Content', + '206' => 'Partial Content', + '300' => 'Multiple Choices', + '302' => 'Found', + '303' => 'See Other', + '304' => 'Not Modified', + '305' => 'Use Proxy', + '400' => 'Bad Request', + '401' => 'Unauthorized', + '402' => 'Payment Required', + '403' => 'Forbidden', + '404' => 'Not Found', + '405' => 'Method Not Allowed', + '406' => 'Not Acceptable', + '407' => 'Proxy Authentication Required', + '408' => 'Request Timeout', + '409' => 'Conflict', + '410' => 'Gone', + '411' => 'Length Required', + '412' => 'Precondition Failed', + '413' => 'Request Entity Too Large', + '414' => 'Request-URI Too Long', + '415' => 'Unsupported Media Type', + '416' => 'Requested Range Not Satisfiable', + '417' => 'Expectation Failed', + '429' => 'Too Many Requests', + '500' => 'Internal Server Error', + '501' => 'Not Implemented', + '502' => 'Bad Gateway', + '503' => 'Service Unavailable', + '504' => 'Gateway Timeout', + '505' => 'HTTP Version Not Supported' ]; /** @@ -61,70 +63,70 @@ const RSSBRIDGE_HTTP_STATUS_CODES = [ * @return string|array */ function getContents( - string $url, - array $httpHeaders = [], - array $curlOptions = [], - bool $returnFull = false + string $url, + array $httpHeaders = [], + array $curlOptions = [], + bool $returnFull = false ) { - $cacheFactory = new CacheFactory(); + $cacheFactory = new CacheFactory(); - $cache = $cacheFactory->create(Configuration::getConfig('cache', 'type')); - $cache->setScope('server'); - $cache->purgeCache(86400); // 24 hours (forced) - $cache->setKey([$url]); + $cache = $cacheFactory->create(Configuration::getConfig('cache', 'type')); + $cache->setScope('server'); + $cache->purgeCache(86400); // 24 hours (forced) + $cache->setKey([$url]); - $config = [ - 'headers' => $httpHeaders, - 'curl_options' => $curlOptions, - ]; - if (defined('PROXY_URL') && !defined('NOPROXY')) { - $config['proxy'] = PROXY_URL; - } - if(!Debug::isEnabled() && $cache->getTime()) { - $config['if_not_modified_since'] = $cache->getTime(); - } + $config = [ + 'headers' => $httpHeaders, + 'curl_options' => $curlOptions, + ]; + if (defined('PROXY_URL') && !defined('NOPROXY')) { + $config['proxy'] = PROXY_URL; + } + if (!Debug::isEnabled() && $cache->getTime()) { + $config['if_not_modified_since'] = $cache->getTime(); + } - $result = _http_request($url, $config); - $response = [ - 'code' => $result['code'], - 'status_lines' => $result['status_lines'], - 'header' => $result['headers'], - 'content' => $result['body'], - ]; + $result = _http_request($url, $config); + $response = [ + 'code' => $result['code'], + 'status_lines' => $result['status_lines'], + 'header' => $result['headers'], + 'content' => $result['body'], + ]; - switch($result['code']) { - case 200: - case 201: - case 202: - if(isset($result['headers']['cache-control'])) { - $cachecontrol = $result['headers']['cache-control']; - $lastValue = array_pop($cachecontrol); - $directives = explode(',', $lastValue); - $directives = array_map('trim', $directives); - if(in_array('no-cache', $directives) || in_array('no-store', $directives)) { - // Don't cache as instructed by the server - break; - } - } - $cache->saveData($result['body']); - break; - case 304: // Not Modified - $response['content'] = $cache->loadData(); - break; - default: - throw new HttpException( - sprintf( - '%s %s', - $result['code'], - RSSBRIDGE_HTTP_STATUS_CODES[$result['code']] ?? '' - ), - $result['code'] - ); - } - if ($returnFull === true) { - return $response; - } - return $response['content']; + switch ($result['code']) { + case 200: + case 201: + case 202: + if (isset($result['headers']['cache-control'])) { + $cachecontrol = $result['headers']['cache-control']; + $lastValue = array_pop($cachecontrol); + $directives = explode(',', $lastValue); + $directives = array_map('trim', $directives); + if (in_array('no-cache', $directives) || in_array('no-store', $directives)) { + // Don't cache as instructed by the server + break; + } + } + $cache->saveData($result['body']); + break; + case 304: // Not Modified + $response['content'] = $cache->loadData(); + break; + default: + throw new HttpException( + sprintf( + '%s %s', + $result['code'], + RSSBRIDGE_HTTP_STATUS_CODES[$result['code']] ?? '' + ), + $result['code'] + ); + } + if ($returnFull === true) { + return $response; + } + return $response['content']; } /** @@ -136,85 +138,85 @@ function getContents( */ function _http_request(string $url, array $config = []): array { - $defaults = [ - 'useragent' => Configuration::getConfig('http', 'useragent'), - 'timeout' => Configuration::getConfig('http', 'timeout'), - 'headers' => [], - 'proxy' => null, - 'curl_options' => [], - 'if_not_modified_since' => null, - 'retries' => 3, - ]; - $config = array_merge($defaults, $config); + $defaults = [ + 'useragent' => Configuration::getConfig('http', 'useragent'), + 'timeout' => Configuration::getConfig('http', 'timeout'), + 'headers' => [], + 'proxy' => null, + 'curl_options' => [], + 'if_not_modified_since' => null, + 'retries' => 3, + ]; + $config = array_merge($defaults, $config); - $ch = curl_init($url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); - curl_setopt($ch, CURLOPT_MAXREDIRS, 5); - curl_setopt($ch, CURLOPT_HEADER, false); - curl_setopt($ch, CURLOPT_HTTPHEADER, $config['headers']); - curl_setopt($ch, CURLOPT_USERAGENT, $config['useragent']); - curl_setopt($ch, CURLOPT_TIMEOUT, $config['timeout']); - curl_setopt($ch, CURLOPT_ENCODING, ''); - curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); - if($config['proxy']) { - curl_setopt($ch, CURLOPT_PROXY, $config['proxy']); - } - if (curl_setopt_array($ch, $config['curl_options']) === false) { - throw new \Exception('Tried to set an illegal curl option'); - } + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_MAXREDIRS, 5); + curl_setopt($ch, CURLOPT_HEADER, false); + curl_setopt($ch, CURLOPT_HTTPHEADER, $config['headers']); + curl_setopt($ch, CURLOPT_USERAGENT, $config['useragent']); + curl_setopt($ch, CURLOPT_TIMEOUT, $config['timeout']); + curl_setopt($ch, CURLOPT_ENCODING, ''); + curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); + if ($config['proxy']) { + curl_setopt($ch, CURLOPT_PROXY, $config['proxy']); + } + if (curl_setopt_array($ch, $config['curl_options']) === false) { + throw new \Exception('Tried to set an illegal curl option'); + } - if ($config['if_not_modified_since']) { - curl_setopt($ch, CURLOPT_TIMEVALUE, $config['if_not_modified_since']); - curl_setopt($ch, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE); - } + if ($config['if_not_modified_since']) { + curl_setopt($ch, CURLOPT_TIMEVALUE, $config['if_not_modified_since']); + curl_setopt($ch, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE); + } - $responseStatusLines = []; - $responseHeaders = []; - curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($ch, $rawHeader) use (&$responseHeaders, &$responseStatusLines) { - $len = strlen($rawHeader); - if ($rawHeader === "\r\n") { - return $len; - } - if (preg_match('#^HTTP/(2|1.1|1.0)#', $rawHeader)) { - $responseStatusLines[] = $rawHeader; - return $len; - } - $header = explode(':', $rawHeader); - if (count($header) === 1) { - return $len; - } - $name = mb_strtolower(trim($header[0])); - $value = trim(implode(':', array_slice($header, 1))); - if (!isset($responseHeaders[$name])) { - $responseHeaders[$name] = []; - } - $responseHeaders[$name][] = $value; - return $len; - }); + $responseStatusLines = []; + $responseHeaders = []; + curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($ch, $rawHeader) use (&$responseHeaders, &$responseStatusLines) { + $len = strlen($rawHeader); + if ($rawHeader === "\r\n") { + return $len; + } + if (preg_match('#^HTTP/(2|1.1|1.0)#', $rawHeader)) { + $responseStatusLines[] = $rawHeader; + return $len; + } + $header = explode(':', $rawHeader); + if (count($header) === 1) { + return $len; + } + $name = mb_strtolower(trim($header[0])); + $value = trim(implode(':', array_slice($header, 1))); + if (!isset($responseHeaders[$name])) { + $responseHeaders[$name] = []; + } + $responseHeaders[$name][] = $value; + return $len; + }); - $attempts = 0; - while(true) { - $attempts++; - $data = curl_exec($ch); - if ($data !== false) { - // The network call was successful, so break out of the loop - break; - } - if ($attempts > $config['retries']) { - // Finally give up - throw new HttpException(sprintf('%s (%s)', curl_error($ch), curl_errno($ch))); - } - } + $attempts = 0; + while (true) { + $attempts++; + $data = curl_exec($ch); + if ($data !== false) { + // The network call was successful, so break out of the loop + break; + } + if ($attempts > $config['retries']) { + // Finally give up + throw new HttpException(sprintf('%s (%s)', curl_error($ch), curl_errno($ch))); + } + } - $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); - curl_close($ch); - return [ - 'code' => $statusCode, - 'status_lines' => $responseStatusLines, - 'headers' => $responseHeaders, - 'body' => $data, - ]; + $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + return [ + 'code' => $statusCode, + 'status_lines' => $responseStatusLines, + 'headers' => $responseHeaders, + 'body' => $data, + ]; } /** @@ -243,28 +245,31 @@ function _http_request(string $url, array $config = []): array * tags when returning plaintext. * @return false|simple_html_dom Contents as simplehtmldom object. */ -function getSimpleHTMLDOM($url, - $header = array(), - $opts = array(), - $lowercase = true, - $forceTagsClosed = true, - $target_charset = DEFAULT_TARGET_CHARSET, - $stripRN = true, - $defaultBRText = DEFAULT_BR_TEXT, - $defaultSpanText = DEFAULT_SPAN_TEXT){ - - $content = getContents( - $url, - $header ?? [], - $opts ?? [] - ); - return str_get_html($content, - $lowercase, - $forceTagsClosed, - $target_charset, - $stripRN, - $defaultBRText, - $defaultSpanText); +function getSimpleHTMLDOM( + $url, + $header = [], + $opts = [], + $lowercase = true, + $forceTagsClosed = true, + $target_charset = DEFAULT_TARGET_CHARSET, + $stripRN = true, + $defaultBRText = DEFAULT_BR_TEXT, + $defaultSpanText = DEFAULT_SPAN_TEXT +) { + $content = getContents( + $url, + $header ?? [], + $opts ?? [] + ); + return str_get_html( + $content, + $lowercase, + $forceTagsClosed, + $target_charset, + $stripRN, + $defaultBRText, + $defaultSpanText + ); } /** @@ -297,53 +302,58 @@ function getSimpleHTMLDOM($url, * tags when returning plaintext. * @return false|simple_html_dom Contents as simplehtmldom object. */ -function getSimpleHTMLDOMCached($url, - $duration = 86400, - $header = array(), - $opts = array(), - $lowercase = true, - $forceTagsClosed = true, - $target_charset = DEFAULT_TARGET_CHARSET, - $stripRN = true, - $defaultBRText = DEFAULT_BR_TEXT, - $defaultSpanText = DEFAULT_SPAN_TEXT){ +function getSimpleHTMLDOMCached( + $url, + $duration = 86400, + $header = [], + $opts = [], + $lowercase = true, + $forceTagsClosed = true, + $target_charset = DEFAULT_TARGET_CHARSET, + $stripRN = true, + $defaultBRText = DEFAULT_BR_TEXT, + $defaultSpanText = DEFAULT_SPAN_TEXT +) { + Debug::log('Caching url ' . $url . ', duration ' . $duration); - Debug::log('Caching url ' . $url . ', duration ' . $duration); + // Initialize cache + $cacheFac = new CacheFactory(); - // Initialize cache - $cacheFac = new CacheFactory(); + $cache = $cacheFac->create(Configuration::getConfig('cache', 'type')); + $cache->setScope('pages'); + $cache->purgeCache(86400); // 24 hours (forced) - $cache = $cacheFac->create(Configuration::getConfig('cache', 'type')); - $cache->setScope('pages'); - $cache->purgeCache(86400); // 24 hours (forced) + $params = [$url]; + $cache->setKey($params); - $params = array($url); - $cache->setKey($params); + // Determine if cached file is within duration + $time = $cache->getTime(); + if ( + $time !== false + && (time() - $duration < $time) + && !Debug::isEnabled() + ) { // Contents within duration + $content = $cache->loadData(); + } else { // Content not within duration + $content = getContents( + $url, + $header ?? [], + $opts ?? [] + ); + if ($content !== false) { + $cache->saveData($content); + } + } - // Determine if cached file is within duration - $time = $cache->getTime(); - if($time !== false - && (time() - $duration < $time) - && !Debug::isEnabled()) { // Contents within duration - $content = $cache->loadData(); - } else { // Content not within duration - $content = getContents( - $url, - $header ?? [], - $opts ?? [] - ); - if($content !== false) { - $cache->saveData($content); - } - } - - return str_get_html($content, - $lowercase, - $forceTagsClosed, - $target_charset, - $stripRN, - $defaultBRText, - $defaultSpanText); + return str_get_html( + $content, + $lowercase, + $forceTagsClosed, + $target_charset, + $stripRN, + $defaultBRText, + $defaultSpanText + ); } /** @@ -360,49 +370,53 @@ function getSimpleHTMLDOMCached($url, * @param string $url The URL or path to the file. * @return string The MIME type of the file. */ -function getMimeType($url) { - static $mime = null; +function getMimeType($url) +{ + static $mime = null; - if (is_null($mime)) { - // Default values, overriden by /etc/mime.types when present - $mime = array( - 'jpg' => 'image/jpeg', - 'gif' => 'image/gif', - 'png' => 'image/png', - 'image' => 'image/*', - 'mp3' => 'audio/mpeg', - ); - // '@' is used to mute open_basedir warning, see issue #818 - if (@is_readable('/etc/mime.types')) { - $file = fopen('/etc/mime.types', 'r'); - while(($line = fgets($file)) !== false) { - $line = trim(preg_replace('/#.*/', '', $line)); - if(!$line) - continue; - $parts = preg_split('/\s+/', $line); - if(count($parts) == 1) - continue; - $type = array_shift($parts); - foreach($parts as $part) - $mime[$part] = $type; - } - fclose($file); - } - } + if (is_null($mime)) { + // Default values, overriden by /etc/mime.types when present + $mime = [ + 'jpg' => 'image/jpeg', + 'gif' => 'image/gif', + 'png' => 'image/png', + 'image' => 'image/*', + 'mp3' => 'audio/mpeg', + ]; + // '@' is used to mute open_basedir warning, see issue #818 + if (@is_readable('/etc/mime.types')) { + $file = fopen('/etc/mime.types', 'r'); + while (($line = fgets($file)) !== false) { + $line = trim(preg_replace('/#.*/', '', $line)); + if (!$line) { + continue; + } + $parts = preg_split('/\s+/', $line); + if (count($parts) == 1) { + continue; + } + $type = array_shift($parts); + foreach ($parts as $part) { + $mime[$part] = $type; + } + } + fclose($file); + } + } - if (strpos($url, '?') !== false) { - $url_temp = substr($url, 0, strpos($url, '?')); - if (strpos($url, '#') !== false) { - $anchor = substr($url, strpos($url, '#')); - $url_temp .= $anchor; - } - $url = $url_temp; - } + if (strpos($url, '?') !== false) { + $url_temp = substr($url, 0, strpos($url, '?')); + if (strpos($url, '#') !== false) { + $anchor = substr($url, strpos($url, '#')); + $url_temp .= $anchor; + } + $url = $url_temp; + } - $ext = strtolower(pathinfo($url, PATHINFO_EXTENSION)); - if (!empty($mime[$ext])) { - return $mime[$ext]; - } + $ext = strtolower(pathinfo($url, PATHINFO_EXTENSION)); + if (!empty($mime[$ext])) { + return $mime[$ext]; + } - return 'application/octet-stream'; + return 'application/octet-stream'; } diff --git a/lib/error.php b/lib/error.php index c2f26247..f9950cea 100644 --- a/lib/error.php +++ b/lib/error.php @@ -1,4 +1,5 @@ create(Configuration::getConfig('cache', 'type')); - $cache->setScope('error_reporting'); - $cache->setkey($bridgeName . '_' . $code); - $cache->purgeCache(86400); // 24 hours + $cache = $cacheFac->create(Configuration::getConfig('cache', 'type')); + $cache->setScope('error_reporting'); + $cache->setkey($bridgeName . '_' . $code); + $cache->purgeCache(86400); // 24 hours - if($report = $cache->loadData()) { - $report = json_decode($report, true); - $report['time'] = time(); - $report['count']++; - } else { - $report = array( - 'error' => $code, - 'time' => time(), - 'count' => 1, - ); - } + if ($report = $cache->loadData()) { + $report = json_decode($report, true); + $report['time'] = time(); + $report['count']++; + } else { + $report = [ + 'error' => $code, + 'time' => time(), + 'count' => 1, + ]; + } - $cache->saveData(json_encode($report)); + $cache->saveData(json_encode($report)); - return $report['count']; + return $report['count']; } diff --git a/lib/html.php b/lib/html.php index 69bd1424..e82d5e0e 100644 --- a/lib/html.php +++ b/lib/html.php @@ -1,4 +1,5 @@ find('*') as $element) { + if (in_array($element->tag, $text_to_keep)) { + $element->outertext = $element->plaintext; + } elseif (in_array($element->tag, $tags_to_remove)) { + $element->outertext = ''; + } else { + foreach ($element->getAllAttributes() as $attributeName => $attribute) { + if (!in_array($attributeName, $attributes_to_keep)) { + $element->removeAttribute($attributeName); + } + } + } + } - foreach($htmlContent->find('*') as $element) { - if(in_array($element->tag, $text_to_keep)) { - $element->outertext = $element->plaintext; - } elseif(in_array($element->tag, $tags_to_remove)) { - $element->outertext = ''; - } else { - foreach($element->getAllAttributes() as $attributeName => $attribute) { - if(!in_array($attributeName, $attributes_to_keep)) - $element->removeAttribute($attributeName); - } - } - } - - return $htmlContent; + return $htmlContent; } /** @@ -74,23 +77,18 @@ function sanitize($html, * @param string $htmlContent The HTML content * @return string The HTML content with all ocurrences replaced */ -function backgroundToImg($htmlContent) { +function backgroundToImg($htmlContent) +{ + $regex = '/background-image[ ]{0,}:[ ]{0,}url\([\'"]{0,}(.*?)[\'"]{0,}\)/'; + $htmlContent = str_get_html($htmlContent); - $regex = '/background-image[ ]{0,}:[ ]{0,}url\([\'"]{0,}(.*?)[\'"]{0,}\)/'; - $htmlContent = str_get_html($htmlContent); - - foreach($htmlContent->find('*') as $element) { - - if(preg_match($regex, $element->style, $matches) > 0) { - - $element->outertext = ''; - - } - - } - - return $htmlContent; + foreach ($htmlContent->find('*') as $element) { + if (preg_match($regex, $element->style, $matches) > 0) { + $element->outertext = ''; + } + } + return $htmlContent; } /** @@ -104,26 +102,27 @@ function backgroundToImg($htmlContent) { * @param string $server Fully qualified URL to the page containing relative links * @return object Content with fixed URLs. */ -function defaultLinkTo($content, $server){ - $string_convert = false; - if (is_string($content)) { - $string_convert = true; - $content = str_get_html($content); - } +function defaultLinkTo($content, $server) +{ + $string_convert = false; + if (is_string($content)) { + $string_convert = true; + $content = str_get_html($content); + } - foreach($content->find('img') as $image) { - $image->src = urljoin($server, $image->src); - } + foreach ($content->find('img') as $image) { + $image->src = urljoin($server, $image->src); + } - foreach($content->find('a') as $anchor) { - $anchor->href = urljoin($server, $anchor->href); - } + foreach ($content->find('a') as $anchor) { + $anchor->href = urljoin($server, $anchor->href); + } - if ($string_convert) { - $content = $content->outertext; - } + if ($string_convert) { + $content = $content->outertext; + } - return $content; + return $content; } /** @@ -135,12 +134,13 @@ function defaultLinkTo($content, $server){ * @return string|bool Extracted string, e.g. `John Doe`, or false if the * delimiters were not found. */ -function extractFromDelimiters($string, $start, $end) { - if (strpos($string, $start) !== false) { - $section_retrieved = substr($string, strpos($string, $start) + strlen($start)); - $section_retrieved = substr($section_retrieved, 0, strpos($section_retrieved, $end)); - return $section_retrieved; - } return false; +function extractFromDelimiters($string, $start, $end) +{ + if (strpos($string, $start) !== false) { + $section_retrieved = substr($string, strpos($string, $start) + strlen($start)); + $section_retrieved = substr($section_retrieved, 0, strpos($section_retrieved, $end)); + return $section_retrieved; + } return false; } /** @@ -151,13 +151,14 @@ function extractFromDelimiters($string, $start, $end) { * @param string $end End delimiter, e.g. `` * @return string Cleaned string, e.g. `foobar` */ -function stripWithDelimiters($string, $start, $end) { - while(strpos($string, $start) !== false) { - $section_to_remove = substr($string, strpos($string, $start)); - $section_to_remove = substr($section_to_remove, 0, strpos($section_to_remove, $end) + strlen($end)); - $string = str_replace($section_to_remove, '', $string); - } - return $string; +function stripWithDelimiters($string, $start, $end) +{ + while (strpos($string, $start) !== false) { + $section_to_remove = substr($string, strpos($string, $start)); + $section_to_remove = substr($section_to_remove, 0, strpos($section_to_remove, $end) + strlen($end)); + $string = str_replace($section_to_remove, '', $string); + } + return $string; } /** @@ -170,28 +171,29 @@ function stripWithDelimiters($string, $start, $end) { * * @todo This function needs more documentation to make it maintainable. */ -function stripRecursiveHTMLSection($string, $tag_name, $tag_start){ - $open_tag = '<' . $tag_name; - $close_tag = ''; - $close_tag_length = strlen($close_tag); - if(strpos($tag_start, $open_tag) === 0) { - while(strpos($string, $tag_start) !== false) { - $max_recursion = 100; - $section_to_remove = null; - $section_start = strpos($string, $tag_start); - $search_offset = $section_start; - do { - $max_recursion--; - $section_end = strpos($string, $close_tag, $search_offset); - $search_offset = $section_end + $close_tag_length; - $section_to_remove = substr($string, $section_start, $section_end - $section_start + $close_tag_length); - $open_tag_count = substr_count($section_to_remove, $open_tag); - $close_tag_count = substr_count($section_to_remove, $close_tag); - } while ($open_tag_count > $close_tag_count && $max_recursion > 0); - $string = str_replace($section_to_remove, '', $string); - } - } - return $string; +function stripRecursiveHTMLSection($string, $tag_name, $tag_start) +{ + $open_tag = '<' . $tag_name; + $close_tag = ''; + $close_tag_length = strlen($close_tag); + if (strpos($tag_start, $open_tag) === 0) { + while (strpos($string, $tag_start) !== false) { + $max_recursion = 100; + $section_to_remove = null; + $section_start = strpos($string, $tag_start); + $search_offset = $section_start; + do { + $max_recursion--; + $section_end = strpos($string, $close_tag, $search_offset); + $search_offset = $section_end + $close_tag_length; + $section_to_remove = substr($string, $section_start, $section_end - $section_start + $close_tag_length); + $open_tag_count = substr_count($section_to_remove, $open_tag); + $close_tag_count = substr_count($section_to_remove, $close_tag); + } while ($open_tag_count > $close_tag_count && $max_recursion > 0); + $string = str_replace($section_to_remove, '', $string); + } + } + return $string; } /** @@ -202,8 +204,8 @@ function stripRecursiveHTMLSection($string, $tag_name, $tag_start){ * @param string $string Input string in Markdown format * @return string output string in HTML format */ -function markdownToHtml($string) { - - $Parsedown = new Parsedown(); - return $Parsedown->text($string); +function markdownToHtml($string) +{ + $Parsedown = new Parsedown(); + return $Parsedown->text($string); } diff --git a/lib/php8backports.php b/lib/php8backports.php index 3b2bb966..30dfdbd9 100644 --- a/lib/php8backports.php +++ b/lib/php8backports.php @@ -1,4 +1,5 @@ ./vendor ./config.default.ini.php ./config.ini.php + + + + + + + + + + + + + + + + + + + - - + @@ -24,15 +42,7 @@ - - - - - - - - - + @@ -46,37 +56,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -93,13 +73,7 @@ - - - - - - - + diff --git a/tests/Actions/ActionImplementationTest.php b/tests/Actions/ActionImplementationTest.php index 0caf6d80..3f063682 100644 --- a/tests/Actions/ActionImplementationTest.php +++ b/tests/Actions/ActionImplementationTest.php @@ -5,54 +5,60 @@ namespace RssBridge\Tests\Actions; use ActionInterface; use PHPUnit\Framework\TestCase; -class ActionImplementationTest extends TestCase { - private $class; - private $obj; +class ActionImplementationTest extends TestCase +{ + private $class; + private $obj; - /** - * @dataProvider dataActionsProvider - */ - public function testClassName($path) { - $this->setAction($path); - $this->assertTrue($this->class === ucfirst($this->class), 'class name must start with uppercase character'); - $this->assertEquals(0, substr_count($this->class, ' '), 'class name must not contain spaces'); - $this->assertStringEndsWith('Action', $this->class, 'class name must end with "Action"'); - } + /** + * @dataProvider dataActionsProvider + */ + public function testClassName($path) + { + $this->setAction($path); + $this->assertTrue($this->class === ucfirst($this->class), 'class name must start with uppercase character'); + $this->assertEquals(0, substr_count($this->class, ' '), 'class name must not contain spaces'); + $this->assertStringEndsWith('Action', $this->class, 'class name must end with "Action"'); + } - /** - * @dataProvider dataActionsProvider - */ - public function testClassType($path) { - $this->setAction($path); - $this->assertInstanceOf(ActionInterface::class, $this->obj); - } + /** + * @dataProvider dataActionsProvider + */ + public function testClassType($path) + { + $this->setAction($path); + $this->assertInstanceOf(ActionInterface::class, $this->obj); + } - /** - * @dataProvider dataActionsProvider - */ - public function testVisibleMethods($path) { - $allowedMethods = get_class_methods(ActionInterface::class); - sort($allowedMethods); + /** + * @dataProvider dataActionsProvider + */ + public function testVisibleMethods($path) + { + $allowedMethods = get_class_methods(ActionInterface::class); + sort($allowedMethods); - $this->setAction($path); + $this->setAction($path); - $methods = get_class_methods($this->obj); - sort($methods); + $methods = get_class_methods($this->obj); + sort($methods); - $this->assertEquals($allowedMethods, $methods); - } + $this->assertEquals($allowedMethods, $methods); + } - public function dataActionsProvider() { - $actions = array(); - foreach (glob(PATH_LIB_ACTIONS . '*.php') as $path) { - $actions[basename($path, '.php')] = array($path); - } - return $actions; - } + public function dataActionsProvider() + { + $actions = []; + foreach (glob(PATH_LIB_ACTIONS . '*.php') as $path) { + $actions[basename($path, '.php')] = [$path]; + } + return $actions; + } - private function setAction($path) { - $this->class = '\\' . basename($path, '.php'); - $this->assertTrue(class_exists($this->class), 'class ' . $this->class . ' doesn\'t exist'); - $this->obj = new $this->class(); - } + private function setAction($path) + { + $this->class = '\\' . basename($path, '.php'); + $this->assertTrue(class_exists($this->class), 'class ' . $this->class . ' doesn\'t exist'); + $this->obj = new $this->class(); + } } diff --git a/tests/Actions/ListActionTest.php b/tests/Actions/ListActionTest.php index 1ecf50ed..2eb2049d 100644 --- a/tests/Actions/ListActionTest.php +++ b/tests/Actions/ListActionTest.php @@ -6,85 +6,88 @@ use ActionFactory; use BridgeFactory; use PHPUnit\Framework\TestCase; -class ListActionTest extends TestCase { +class ListActionTest extends TestCase +{ + private $data; - private $data; + /** + * @runInSeparateProcess + * @requires function xdebug_get_headers + */ + public function testHeaders() + { + $this->initAction(); - /** - * @runInSeparateProcess - * @requires function xdebug_get_headers - */ - public function testHeaders() { - $this->initAction(); + $this->assertContains( + 'Content-Type: application/json', + xdebug_get_headers() + ); + } - $this->assertContains( - 'Content-Type: application/json', - xdebug_get_headers() - ); - } + /** + * @runInSeparateProcess + */ + public function testOutput() + { + $this->initAction(); - /** - * @runInSeparateProcess - */ - public function testOutput() { - $this->initAction(); + $items = json_decode($this->data, true); - $items = json_decode($this->data, true); + $this->assertNotNull($items, 'invalid JSON output: ' . json_last_error_msg()); - $this->assertNotNull($items, 'invalid JSON output: ' . json_last_error_msg()); + $this->assertArrayHasKey('total', $items, 'Missing "total" parameter'); + $this->assertIsInt($items['total'], 'Invalid type'); - $this->assertArrayHasKey('total', $items, 'Missing "total" parameter'); - $this->assertIsInt($items['total'], 'Invalid type'); + $this->assertArrayHasKey('bridges', $items, 'Missing "bridges" array'); - $this->assertArrayHasKey('bridges', $items, 'Missing "bridges" array'); + $this->assertEquals( + $items['total'], + count($items['bridges']), + 'Item count doesn\'t match' + ); - $this->assertEquals( - $items['total'], - count($items['bridges']), - 'Item count doesn\'t match' - ); + $bridgeFac = new BridgeFactory(); - $bridgeFac = new BridgeFactory(); + $this->assertEquals( + count($bridgeFac->getBridgeNames()), + count($items['bridges']), + 'Number of bridges doesn\'t match' + ); - $this->assertEquals( - count($bridgeFac->getBridgeNames()), - count($items['bridges']), - 'Number of bridges doesn\'t match' - ); + $expectedKeys = [ + 'status', + 'uri', + 'name', + 'icon', + 'parameters', + 'maintainer', + 'description' + ]; - $expectedKeys = array( - 'status', - 'uri', - 'name', - 'icon', - 'parameters', - 'maintainer', - 'description' - ); + $allowedStatus = [ + 'active', + 'inactive' + ]; - $allowedStatus = array( - 'active', - 'inactive' - ); + foreach ($items['bridges'] as $bridge) { + foreach ($expectedKeys as $key) { + $this->assertArrayHasKey($key, $bridge, 'Missing key "' . $key . '"'); + } - foreach($items['bridges'] as $bridge) { - foreach($expectedKeys as $key) { - $this->assertArrayHasKey($key, $bridge, 'Missing key "' . $key . '"'); - } + $this->assertContains($bridge['status'], $allowedStatus, 'Invalid status value'); + } + } - $this->assertContains($bridge['status'], $allowedStatus, 'Invalid status value'); - } - } + private function initAction() + { + $actionFac = new ActionFactory(); - private function initAction() { - $actionFac = new ActionFactory(); + $action = $actionFac->create('list'); - $action = $actionFac->create('list'); - - ob_start(); - $action->execute(); - $this->data = ob_get_contents(); - ob_clean(); - ob_end_flush(); - } + ob_start(); + $action->execute(); + $this->data = ob_get_contents(); + ob_clean(); + ob_end_flush(); + } } diff --git a/tests/Bridges/BridgeImplementationTest.php b/tests/Bridges/BridgeImplementationTest.php index e0e095a6..60f94d4a 100644 --- a/tests/Bridges/BridgeImplementationTest.php +++ b/tests/Bridges/BridgeImplementationTest.php @@ -7,223 +7,236 @@ use BridgeInterface; use FeedExpander; use PHPUnit\Framework\TestCase; -class BridgeImplementationTest extends TestCase { - private $class; - private $obj; +class BridgeImplementationTest extends TestCase +{ + private $class; + private $obj; - /** - * @dataProvider dataBridgesProvider - */ - public function testClassName($path) { - $this->setBridge($path); - $this->assertTrue($this->class === ucfirst($this->class), 'class name must start with uppercase character'); - $this->assertEquals(0, substr_count($this->class, ' '), 'class name must not contain spaces'); - $this->assertStringEndsWith('Bridge', $this->class, 'class name must end with "Bridge"'); - } + /** + * @dataProvider dataBridgesProvider + */ + public function testClassName($path) + { + $this->setBridge($path); + $this->assertTrue($this->class === ucfirst($this->class), 'class name must start with uppercase character'); + $this->assertEquals(0, substr_count($this->class, ' '), 'class name must not contain spaces'); + $this->assertStringEndsWith('Bridge', $this->class, 'class name must end with "Bridge"'); + } - /** - * @dataProvider dataBridgesProvider - */ - public function testClassType($path) { - $this->setBridge($path); - $this->assertInstanceOf(BridgeInterface::class, $this->obj); - } + /** + * @dataProvider dataBridgesProvider + */ + public function testClassType($path) + { + $this->setBridge($path); + $this->assertInstanceOf(BridgeInterface::class, $this->obj); + } - /** - * @dataProvider dataBridgesProvider - */ - public function testConstants($path) { - $this->setBridge($path); + /** + * @dataProvider dataBridgesProvider + */ + public function testConstants($path) + { + $this->setBridge($path); - $this->assertIsString($this->obj::NAME, 'class::NAME'); - $this->assertNotEmpty($this->obj::NAME, 'class::NAME'); - $this->assertIsString($this->obj::URI, 'class::URI'); - $this->assertNotEmpty($this->obj::URI, 'class::URI'); - $this->assertIsString($this->obj::DESCRIPTION, 'class::DESCRIPTION'); - $this->assertNotEmpty($this->obj::DESCRIPTION, 'class::DESCRIPTION'); - $this->assertIsString($this->obj::MAINTAINER, 'class::MAINTAINER'); - $this->assertNotEmpty($this->obj::MAINTAINER, 'class::MAINTAINER'); + $this->assertIsString($this->obj::NAME, 'class::NAME'); + $this->assertNotEmpty($this->obj::NAME, 'class::NAME'); + $this->assertIsString($this->obj::URI, 'class::URI'); + $this->assertNotEmpty($this->obj::URI, 'class::URI'); + $this->assertIsString($this->obj::DESCRIPTION, 'class::DESCRIPTION'); + $this->assertNotEmpty($this->obj::DESCRIPTION, 'class::DESCRIPTION'); + $this->assertIsString($this->obj::MAINTAINER, 'class::MAINTAINER'); + $this->assertNotEmpty($this->obj::MAINTAINER, 'class::MAINTAINER'); - $this->assertIsArray($this->obj::PARAMETERS, 'class::PARAMETERS'); - $this->assertIsInt($this->obj::CACHE_TIMEOUT, 'class::CACHE_TIMEOUT'); - $this->assertGreaterThanOrEqual(0, $this->obj::CACHE_TIMEOUT, 'class::CACHE_TIMEOUT'); - } + $this->assertIsArray($this->obj::PARAMETERS, 'class::PARAMETERS'); + $this->assertIsInt($this->obj::CACHE_TIMEOUT, 'class::CACHE_TIMEOUT'); + $this->assertGreaterThanOrEqual(0, $this->obj::CACHE_TIMEOUT, 'class::CACHE_TIMEOUT'); + } - /** - * @dataProvider dataBridgesProvider - */ - public function testParameters($path) { - $this->setBridge($path); + /** + * @dataProvider dataBridgesProvider + */ + public function testParameters($path) + { + $this->setBridge($path); - $multiMinimum = 2; - if (isset($this->obj::PARAMETERS['global'])) { - ++$multiMinimum; - } - $multiContexts = (count($this->obj::PARAMETERS) >= $multiMinimum); - $paramsSeen = array(); + $multiMinimum = 2; + if (isset($this->obj::PARAMETERS['global'])) { + ++$multiMinimum; + } + $multiContexts = (count($this->obj::PARAMETERS) >= $multiMinimum); + $paramsSeen = []; - $allowedTypes = array( - 'text', - 'number', - 'list', - 'checkbox' - ); + $allowedTypes = [ + 'text', + 'number', + 'list', + 'checkbox' + ]; - foreach($this->obj::PARAMETERS as $context => $params) { - if ($multiContexts) { - $this->assertIsString($context, 'invalid context name'); + foreach ($this->obj::PARAMETERS as $context => $params) { + if ($multiContexts) { + $this->assertIsString($context, 'invalid context name'); - $this->assertNotEmpty($context, 'The context name cannot be empty'); - } + $this->assertNotEmpty($context, 'The context name cannot be empty'); + } - if (empty($params)) { - continue; - } + if (empty($params)) { + continue; + } - foreach ($paramsSeen as $seen) { - $this->assertNotEquals($seen, $params, 'same set of parameters not allowed'); - } - $paramsSeen[] = $params; + foreach ($paramsSeen as $seen) { + $this->assertNotEquals($seen, $params, 'same set of parameters not allowed'); + } + $paramsSeen[] = $params; - foreach ($params as $field => $options) { - $this->assertIsString($field, $field . ': invalid id'); - $this->assertNotEmpty($field, $field . ':empty id'); + foreach ($params as $field => $options) { + $this->assertIsString($field, $field . ': invalid id'); + $this->assertNotEmpty($field, $field . ':empty id'); - $this->assertIsString($options['name'], $field . ': invalid name'); - $this->assertNotEmpty($options['name'], $field . ': empty name'); + $this->assertIsString($options['name'], $field . ': invalid name'); + $this->assertNotEmpty($options['name'], $field . ': empty name'); - if (isset($options['type'])) { - $this->assertIsString($options['type'], $field . ': invalid type'); - $this->assertContains($options['type'], $allowedTypes, $field . ': unknown type'); + if (isset($options['type'])) { + $this->assertIsString($options['type'], $field . ': invalid type'); + $this->assertContains($options['type'], $allowedTypes, $field . ': unknown type'); - if ($options['type'] == 'list') { - $this->assertArrayHasKey('values', $options, $field . ': missing list values'); - $this->assertIsArray($options['values'], $field . ': invalid list values'); - $this->assertNotEmpty($options['values'], $field . ': empty list values'); + if ($options['type'] == 'list') { + $this->assertArrayHasKey('values', $options, $field . ': missing list values'); + $this->assertIsArray($options['values'], $field . ': invalid list values'); + $this->assertNotEmpty($options['values'], $field . ': empty list values'); - foreach ($options['values'] as $valueName => $value) { - $this->assertIsString($valueName, $field . ': invalid value name'); - } - } - } + foreach ($options['values'] as $valueName => $value) { + $this->assertIsString($valueName, $field . ': invalid value name'); + } + } + } - if (isset($options['required'])) { - $this->assertIsBool($options['required'], $field . ': invalid required'); + if (isset($options['required'])) { + $this->assertIsBool($options['required'], $field . ': invalid required'); - if($options['required'] === true && isset($options['type'])) { - switch($options['type']) { - case 'list': - case 'checkbox': - $this->assertArrayNotHasKey( - 'required', - $options, - $field . ': "required" attribute not supported for ' . $options['type'] - ); - break; - } - } - } + if ($options['required'] === true && isset($options['type'])) { + switch ($options['type']) { + case 'list': + case 'checkbox': + $this->assertArrayNotHasKey( + 'required', + $options, + $field . ': "required" attribute not supported for ' . $options['type'] + ); + break; + } + } + } - if (isset($options['title'])) { - $this->assertIsString($options['title'], $field . ': invalid title'); - $this->assertNotEmpty($options['title'], $field . ': empty title'); - } + if (isset($options['title'])) { + $this->assertIsString($options['title'], $field . ': invalid title'); + $this->assertNotEmpty($options['title'], $field . ': empty title'); + } - if (isset($options['pattern'])) { - $this->assertIsString($options['pattern'], $field . ': invalid pattern'); - $this->assertNotEquals('', $options['pattern'], $field . ': empty pattern'); - } + if (isset($options['pattern'])) { + $this->assertIsString($options['pattern'], $field . ': invalid pattern'); + $this->assertNotEquals('', $options['pattern'], $field . ': empty pattern'); + } - if (isset($options['exampleValue'])) { - if (is_string($options['exampleValue'])) - $this->assertNotEquals('', $options['exampleValue'], $field . ': empty exampleValue'); - } + if (isset($options['exampleValue'])) { + if (is_string($options['exampleValue'])) { + $this->assertNotEquals('', $options['exampleValue'], $field . ': empty exampleValue'); + } + } - if (isset($options['defaultValue'])) { - if (is_string($options['defaultValue'])) - $this->assertNotEquals('', $options['defaultValue'], $field . ': empty defaultValue'); - } - } - } + if (isset($options['defaultValue'])) { + if (is_string($options['defaultValue'])) { + $this->assertNotEquals('', $options['defaultValue'], $field . ': empty defaultValue'); + } + } + } + } - foreach($this->obj::TEST_DETECT_PARAMETERS as $url => $params) { - $this->assertEquals($this->obj->detectParameters($url), $params); - } + foreach ($this->obj::TEST_DETECT_PARAMETERS as $url => $params) { + $this->assertEquals($this->obj->detectParameters($url), $params); + } - $this->assertTrue(true); - } + $this->assertTrue(true); + } - /** - * @dataProvider dataBridgesProvider - */ - public function testVisibleMethods($path) { - $allowedBridgeAbstract = get_class_methods(BridgeAbstract::class); - sort($allowedBridgeAbstract); - $allowedFeedExpander = get_class_methods(FeedExpander::class); - sort($allowedFeedExpander); + /** + * @dataProvider dataBridgesProvider + */ + public function testVisibleMethods($path) + { + $allowedBridgeAbstract = get_class_methods(BridgeAbstract::class); + sort($allowedBridgeAbstract); + $allowedFeedExpander = get_class_methods(FeedExpander::class); + sort($allowedFeedExpander); - $this->setBridge($path); + $this->setBridge($path); - $methods = get_class_methods($this->obj); - sort($methods); - if ($this->obj instanceof FeedExpander) { - $this->assertEquals($allowedFeedExpander, $methods); - } else { - $this->assertEquals($allowedBridgeAbstract, $methods); - } - } + $methods = get_class_methods($this->obj); + sort($methods); + if ($this->obj instanceof FeedExpander) { + $this->assertEquals($allowedFeedExpander, $methods); + } else { + $this->assertEquals($allowedBridgeAbstract, $methods); + } + } - /** - * @dataProvider dataBridgesProvider - */ - public function testMethodValues($path) { - $this->setBridge($path); + /** + * @dataProvider dataBridgesProvider + */ + public function testMethodValues($path) + { + $this->setBridge($path); - $value = $this->obj->getDescription(); - $this->assertIsString($value, '$class->getDescription()'); - $this->assertNotEmpty($value, '$class->getDescription()'); + $value = $this->obj->getDescription(); + $this->assertIsString($value, '$class->getDescription()'); + $this->assertNotEmpty($value, '$class->getDescription()'); - $value = $this->obj->getMaintainer(); - $this->assertIsString($value, '$class->getMaintainer()'); - $this->assertNotEmpty($value, '$class->getMaintainer()'); + $value = $this->obj->getMaintainer(); + $this->assertIsString($value, '$class->getMaintainer()'); + $this->assertNotEmpty($value, '$class->getMaintainer()'); - $value = $this->obj->getName(); - $this->assertIsString($value, '$class->getName()'); - $this->assertNotEmpty($value, '$class->getName()'); + $value = $this->obj->getName(); + $this->assertIsString($value, '$class->getName()'); + $this->assertNotEmpty($value, '$class->getName()'); - $value = $this->obj->getURI(); - $this->assertIsString($value, '$class->getURI()'); - $this->assertNotEmpty($value, '$class->getURI()'); + $value = $this->obj->getURI(); + $this->assertIsString($value, '$class->getURI()'); + $this->assertNotEmpty($value, '$class->getURI()'); - $value = $this->obj->getIcon(); - $this->assertIsString($value, '$class->getIcon()'); - } + $value = $this->obj->getIcon(); + $this->assertIsString($value, '$class->getIcon()'); + } - /** - * @dataProvider dataBridgesProvider - */ - public function testUri($path) { - $this->setBridge($path); + /** + * @dataProvider dataBridgesProvider + */ + public function testUri($path) + { + $this->setBridge($path); - $this->checkUrl($this->obj::URI); - $this->checkUrl($this->obj->getURI()); - } + $this->checkUrl($this->obj::URI); + $this->checkUrl($this->obj->getURI()); + } - public function dataBridgesProvider() { - $bridges = array(); - foreach (glob(PATH_LIB_BRIDGES . '*Bridge.php') as $path) { - $bridges[basename($path, '.php')] = array($path); - } - return $bridges; - } + public function dataBridgesProvider() + { + $bridges = []; + foreach (glob(PATH_LIB_BRIDGES . '*Bridge.php') as $path) { + $bridges[basename($path, '.php')] = [$path]; + } + return $bridges; + } - private function setBridge($path) { - $this->class = '\\' . basename($path, '.php'); - $this->assertTrue(class_exists($this->class), 'class ' . $this->class . ' doesn\'t exist'); - $this->obj = new $this->class(); - } + private function setBridge($path) + { + $this->class = '\\' . basename($path, '.php'); + $this->assertTrue(class_exists($this->class), 'class ' . $this->class . ' doesn\'t exist'); + $this->obj = new $this->class(); + } - private function checkUrl($url) { - $this->assertNotFalse(filter_var($url, FILTER_VALIDATE_URL), 'no valid URL: ' . $url); - } + private function checkUrl($url) + { + $this->assertNotFalse(filter_var($url, FILTER_VALIDATE_URL), 'no valid URL: ' . $url); + } } diff --git a/tests/Caches/CacheImplementationTest.php b/tests/Caches/CacheImplementationTest.php index 12018685..a3ad5f79 100644 --- a/tests/Caches/CacheImplementationTest.php +++ b/tests/Caches/CacheImplementationTest.php @@ -5,39 +5,44 @@ namespace RssBridge\Tests\Caches; use CacheInterface; use PHPUnit\Framework\TestCase; -class CacheImplementationTest extends TestCase { - private $class; +class CacheImplementationTest extends TestCase +{ + private $class; - /** - * @dataProvider dataCachesProvider - */ - public function testClassName($path) { - $this->setCache($path); - $this->assertTrue($this->class === ucfirst($this->class), 'class name must start with uppercase character'); - $this->assertEquals(0, substr_count($this->class, ' '), 'class name must not contain spaces'); - $this->assertStringEndsWith('Cache', $this->class, 'class name must end with "Cache"'); - } + /** + * @dataProvider dataCachesProvider + */ + public function testClassName($path) + { + $this->setCache($path); + $this->assertTrue($this->class === ucfirst($this->class), 'class name must start with uppercase character'); + $this->assertEquals(0, substr_count($this->class, ' '), 'class name must not contain spaces'); + $this->assertStringEndsWith('Cache', $this->class, 'class name must end with "Cache"'); + } - /** - * @dataProvider dataCachesProvider - */ - public function testClassType($path) { - $this->setCache($path); - $this->assertTrue(is_subclass_of($this->class, CacheInterface::class), 'class must be subclass of CacheInterface'); - } + /** + * @dataProvider dataCachesProvider + */ + public function testClassType($path) + { + $this->setCache($path); + $this->assertTrue(is_subclass_of($this->class, CacheInterface::class), 'class must be subclass of CacheInterface'); + } - //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// - public function dataCachesProvider() { - $caches = array(); - foreach (glob(PATH_LIB_CACHES . '*.php') as $path) { - $caches[basename($path, '.php')] = array($path); - } - return $caches; - } + public function dataCachesProvider() + { + $caches = []; + foreach (glob(PATH_LIB_CACHES . '*.php') as $path) { + $caches[basename($path, '.php')] = [$path]; + } + return $caches; + } - private function setCache($path) { - $this->class = '\\' . basename($path, '.php'); - $this->assertTrue(class_exists($this->class), 'class ' . $this->class . ' doesn\'t exist'); - } + private function setCache($path) + { + $this->class = '\\' . basename($path, '.php'); + $this->assertTrue(class_exists($this->class), 'class ' . $this->class . ' doesn\'t exist'); + } } diff --git a/tests/Formats/AtomFormatTest.php b/tests/Formats/AtomFormatTest.php index a871ea86..77bb9cbc 100644 --- a/tests/Formats/AtomFormatTest.php +++ b/tests/Formats/AtomFormatTest.php @@ -1,4 +1,5 @@ formatData('Atom', $this->loadSample($path)); - $this->assertNotFalse(simplexml_load_string($data)); + /** + * @dataProvider sampleProvider + * @runInSeparateProcess + */ + public function testOutput(string $name, string $path) + { + $data = $this->formatData('Atom', $this->loadSample($path)); + $this->assertNotFalse(simplexml_load_string($data)); - $expected = self::PATH_EXPECTED . $name . '.xml'; - $this->assertXmlStringEqualsXmlFile($expected, $data); - } + $expected = self::PATH_EXPECTED . $name . '.xml'; + $this->assertXmlStringEqualsXmlFile($expected, $data); + } } diff --git a/tests/Formats/BaseFormatTest.php b/tests/Formats/BaseFormatTest.php index 94da7b04..ace4d3ea 100644 --- a/tests/Formats/BaseFormatTest.php +++ b/tests/Formats/BaseFormatTest.php @@ -5,59 +5,65 @@ namespace RssBridge\Tests\Formats; use PHPUnit\Framework\TestCase; use FormatFactory; -abstract class BaseFormatTest extends TestCase { - protected const PATH_SAMPLES = __DIR__ . '/samples/'; +abstract class BaseFormatTest extends TestCase +{ + protected const PATH_SAMPLES = __DIR__ . '/samples/'; - /** - * @return array - */ - public function sampleProvider() { - $samples = []; - foreach (glob(self::PATH_SAMPLES . '*.json') as $path) { - $name = basename($path, '.json'); - $samples[$name] = [ - $name, - $path, - ]; - } - return $samples; - } + /** + * @return array + */ + public function sampleProvider() + { + $samples = []; + foreach (glob(self::PATH_SAMPLES . '*.json') as $path) { + $name = basename($path, '.json'); + $samples[$name] = [ + $name, + $path, + ]; + } + return $samples; + } - /** - * Cannot be part of the sample returned by sampleProvider since this modifies $_SERVER - * and thus needs to be run in a separate process to avoid side effects. - */ - protected function loadSample(string $path): \stdClass { - $data = json_decode(file_get_contents($path), true); - if (isset($data['meta']) && isset($data['items'])) { - if (!empty($data['server'])) - $this->setServerVars($data['server']); + /** + * Cannot be part of the sample returned by sampleProvider since this modifies $_SERVER + * and thus needs to be run in a separate process to avoid side effects. + */ + protected function loadSample(string $path): \stdClass + { + $data = json_decode(file_get_contents($path), true); + if (isset($data['meta']) && isset($data['items'])) { + if (!empty($data['server'])) { + $this->setServerVars($data['server']); + } - $items = array(); - foreach($data['items'] as $item) { - $items[] = new \FeedItem($item); - } + $items = []; + foreach ($data['items'] as $item) { + $items[] = new \FeedItem($item); + } - return (object)array( - 'meta' => $data['meta'], - 'items' => $items, - ); - } else { - $this->fail('invalid test sample: ' . basename($path, '.json')); - } - } + return (object)[ + 'meta' => $data['meta'], + 'items' => $items, + ]; + } else { + $this->fail('invalid test sample: ' . basename($path, '.json')); + } + } - private function setServerVars(array $list): void { - $_SERVER = array_merge($_SERVER, $list); - } + private function setServerVars(array $list): void + { + $_SERVER = array_merge($_SERVER, $list); + } - protected function formatData(string $formatName, \stdClass $sample): string { - $formatFac = new FormatFactory(); - $format = $formatFac->create($formatName); - $format->setItems($sample->items); - $format->setExtraInfos($sample->meta); - $format->setLastModified(strtotime('2000-01-01 12:00:00 UTC')); + protected function formatData(string $formatName, \stdClass $sample): string + { + $formatFac = new FormatFactory(); + $format = $formatFac->create($formatName); + $format->setItems($sample->items); + $format->setExtraInfos($sample->meta); + $format->setLastModified(strtotime('2000-01-01 12:00:00 UTC')); - return $format->stringify(); - } + return $format->stringify(); + } } diff --git a/tests/Formats/FormatImplementationTest.php b/tests/Formats/FormatImplementationTest.php index e4501d68..55c6335f 100644 --- a/tests/Formats/FormatImplementationTest.php +++ b/tests/Formats/FormatImplementationTest.php @@ -2,39 +2,44 @@ use PHPUnit\Framework\TestCase; -class FormatImplementationTest extends TestCase { - private $class; - private $obj; +class FormatImplementationTest extends TestCase +{ + private $class; + private $obj; - /** - * @dataProvider dataFormatsProvider - */ - public function testClassName($path) { - $this->setFormat($path); - $this->assertTrue($this->class === ucfirst($this->class), 'class name must start with uppercase character'); - $this->assertEquals(0, substr_count($this->class, ' '), 'class name must not contain spaces'); - $this->assertStringEndsWith('Format', $this->class, 'class name must end with "Format"'); - } + /** + * @dataProvider dataFormatsProvider + */ + public function testClassName($path) + { + $this->setFormat($path); + $this->assertTrue($this->class === ucfirst($this->class), 'class name must start with uppercase character'); + $this->assertEquals(0, substr_count($this->class, ' '), 'class name must not contain spaces'); + $this->assertStringEndsWith('Format', $this->class, 'class name must end with "Format"'); + } - /** - * @dataProvider dataFormatsProvider - */ - public function testClassType($path) { - $this->setFormat($path); - $this->assertInstanceOf(FormatInterface::class, $this->obj); - } + /** + * @dataProvider dataFormatsProvider + */ + public function testClassType($path) + { + $this->setFormat($path); + $this->assertInstanceOf(FormatInterface::class, $this->obj); + } - public function dataFormatsProvider() { - $formats = array(); - foreach (glob(PATH_LIB_FORMATS . '*.php') as $path) { - $formats[basename($path, '.php')] = array($path); - } - return $formats; - } + public function dataFormatsProvider() + { + $formats = []; + foreach (glob(PATH_LIB_FORMATS . '*.php') as $path) { + $formats[basename($path, '.php')] = [$path]; + } + return $formats; + } - private function setFormat($path) { - $this->class = basename($path, '.php'); - $this->assertTrue(class_exists($this->class), 'class ' . $this->class . ' doesn\'t exist'); - $this->obj = new $this->class(); - } + private function setFormat($path) + { + $this->class = basename($path, '.php'); + $this->assertTrue(class_exists($this->class), 'class ' . $this->class . ' doesn\'t exist'); + $this->obj = new $this->class(); + } } diff --git a/tests/Formats/JsonFormatTest.php b/tests/Formats/JsonFormatTest.php index 3b9f8d47..c21d3f34 100644 --- a/tests/Formats/JsonFormatTest.php +++ b/tests/Formats/JsonFormatTest.php @@ -1,4 +1,5 @@ formatData('Json', $this->loadSample($path)); - $this->assertNotNull(json_decode($data), 'invalid JSON output: ' . json_last_error_msg()); + /** + * @dataProvider sampleProvider + * @runInSeparateProcess + */ + public function testOutput(string $name, string $path) + { + $data = $this->formatData('Json', $this->loadSample($path)); + $this->assertNotNull(json_decode($data), 'invalid JSON output: ' . json_last_error_msg()); - $expected = self::PATH_EXPECTED . $name . '.json'; - $this->assertJsonStringEqualsJsonFile($expected, $data); - } + $expected = self::PATH_EXPECTED . $name . '.json'; + $this->assertJsonStringEqualsJsonFile($expected, $data); + } } diff --git a/tests/Formats/MrssFormatTest.php b/tests/Formats/MrssFormatTest.php index 6def6afb..af74923e 100644 --- a/tests/Formats/MrssFormatTest.php +++ b/tests/Formats/MrssFormatTest.php @@ -1,4 +1,5 @@ formatData('Mrss', $this->loadSample($path)); - $this->assertNotFalse(simplexml_load_string($data)); + /** + * @dataProvider sampleProvider + * @runInSeparateProcess + */ + public function testOutput(string $name, string $path) + { + $data = $this->formatData('Mrss', $this->loadSample($path)); + $this->assertNotFalse(simplexml_load_string($data)); - $expected = self::PATH_EXPECTED . $name . '.xml'; - $this->assertXmlStringEqualsXmlFile($expected, $data); - } + $expected = self::PATH_EXPECTED . $name . '.xml'; + $this->assertXmlStringEqualsXmlFile($expected, $data); + } }