mirror of
				https://github.com/getgrav/grav-plugin-admin.git
				synced 2025-10-31 10:25:50 +01:00 
			
		
		
		
	two factor authentication essental elements
This commit is contained in:
		| @@ -5,6 +5,7 @@ theme: grav | ||||
| logo_text: '' | ||||
| body_classes: '' | ||||
| content_padding: true | ||||
| twofa_enabled: true | ||||
| sidebar: | ||||
|   activate: tab | ||||
|   hover_delay: 100 | ||||
|   | ||||
| @@ -48,6 +48,18 @@ form: | ||||
|       validate: | ||||
|           type: bool | ||||
|  | ||||
|     twofa_enabled: | ||||
|       type: toggle | ||||
|       label: PLUGIN_ADMIN.2FA_TITLE | ||||
|       help: PLUGIN_ADMIN.2FA_ENABLED_HELP | ||||
|       default: 0 | ||||
|       highlight: 0 | ||||
|       options: | ||||
|           1: PLUGIN_ADMIN.YES | ||||
|           0: PLUGIN_ADMIN.NO | ||||
|       validate: | ||||
|           type: bool | ||||
|  | ||||
|     route: | ||||
|       type: text | ||||
|       label: Administrator path | ||||
|   | ||||
| @@ -27,6 +27,7 @@ use RocketTheme\Toolbox\Session\Session; | ||||
| use Symfony\Component\Yaml\Yaml; | ||||
| use Composer\Semver\Semver; | ||||
| use PicoFeed\Reader\Reader; | ||||
| use RobThree\Auth\TwoFactorAuth; | ||||
|  | ||||
| define('LOGIN_REDIRECT_COOKIE', 'grav-login-redirect'); | ||||
|  | ||||
| @@ -377,6 +378,17 @@ class Admin | ||||
|         $action = []; | ||||
|  | ||||
|         if ($user->authorize('admin.login')) { | ||||
|  | ||||
|             $twofa_admin_enabled = $this->grav['config']->get('plugins.admin.twofa_enabled', false); | ||||
|  | ||||
|             if ($twofa_admin_enabled && isset($user->twofa_enabled) && $user->twofa_enabled == true) { | ||||
|                 $twofa = $this->get2FA(); | ||||
|                 $secret = isset($user->twofa_secret) ? $user->twofa_secret : null; | ||||
|                 if (!(isset($data['2fa_code']) && $data['2fa_code'] == $twofa->getCode($secret))) { | ||||
|                     return false; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             $this->user = $this->session->user = $user; | ||||
|  | ||||
|             /** @var Grav $grav */ | ||||
| @@ -1709,4 +1721,9 @@ class Admin | ||||
|  | ||||
|         return $pagesWithFiles; | ||||
|     } | ||||
|  | ||||
|     public function get2FA() | ||||
|     { | ||||
|         return new TwoFactorAuth($this->grav['config']->get('site.title')); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,7 +1,8 @@ | ||||
| { | ||||
|     "require": { | ||||
|         "composer/semver": "^1.4", | ||||
|         "fguillot/picofeed": "@stable" | ||||
|         "fguillot/picofeed": "@stable", | ||||
|         "robthree/twofactorauth": "^1.6" | ||||
|     }, | ||||
|     "require-dev": { | ||||
|         "codeception/codeception": "^2.1", | ||||
|   | ||||
							
								
								
									
										933
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										933
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -682,3 +682,9 @@ PLUGIN_ADMIN: | ||||
|   CONTENT_PADDING_HELP: "Enable/Disable content padding around content area to provide more space" | ||||
|   ENABLE_AUTO_METADATA: "Auto metadata from Exif" | ||||
|   ENABLE_AUTO_METADATA_HELP: "Automatically generate metadata files for images with exif information" | ||||
|   2FA_TITLE: "2-Factor Authentication" | ||||
|   2FA_LABEL: "Admin Access" | ||||
|   2FA_ENABLED: "2FA Enabled" | ||||
|   2FA_CODE_INPUT: "2FA Code (if enabled)" | ||||
|   2FA_SECRET: "2FA Secret" | ||||
|   2FA_SECRET_HELP: "Scan this code into your Authenticator App or enter the code manually.  Check the Grav docs for  more information" | ||||
|   | ||||
| @@ -7,16 +7,26 @@ form: | ||||
|     method: post | ||||
|  | ||||
|     fields: | ||||
|         - name: username | ||||
|         username: | ||||
|             type: text | ||||
|             placeholder: PLUGIN_ADMIN.USERNAME_EMAIL | ||||
|             autofocus: true | ||||
|             validate: | ||||
|                 required: true | ||||
|  | ||||
|         - name: password | ||||
|         password: | ||||
|             type: password | ||||
|             placeholder: PLUGIN_ADMIN.PASSWORD | ||||
|             validate: | ||||
|                 required: true | ||||
|              | ||||
|         twofa_check: | ||||
|             type: conditional | ||||
|             condition: config.plugins.admin.twofa_enabled     | ||||
|             | ||||
|             fields: | ||||
|             | ||||
|                 2fa_code: | ||||
|                     type: text | ||||
|                     placeholder: PLUGIN_ADMIN.2FA_CODE_INPUT    | ||||
| --- | ||||
|   | ||||
							
								
								
									
										2
									
								
								themes/grav/css-compiled/preset.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								themes/grav/css-compiled/preset.css
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										4
									
								
								themes/grav/css-compiled/template.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								themes/grav/css-compiled/template.css
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -967,8 +967,8 @@ form { | ||||
|     } | ||||
|  | ||||
|     &.switch-toggle input:checked + label { | ||||
|         background: #777; | ||||
|         color: #fff; | ||||
|         color: $content-bg; | ||||
|         background: $content-text; | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -635,8 +635,10 @@ textarea.frontmatter { | ||||
|         height: 39px; | ||||
|     } | ||||
|  | ||||
|     .switch-toggle label { | ||||
|         white-space: nowrap; | ||||
|     .switch-toggle { | ||||
|         line-height: 37px; | ||||
|         margin: 0 5px 5px 0; | ||||
|  | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -131,3 +131,17 @@ | ||||
|         padding: 1rem 3rem; | ||||
|     } | ||||
| } | ||||
|  | ||||
| .twofa-secret { | ||||
|     position: absolute; | ||||
|     opacity: 0; | ||||
|     visibility: hidden; | ||||
|     transition: opacity 600ms, visibility 600ms; | ||||
|  | ||||
|     &.show { | ||||
|         position: static; | ||||
|         visibility: visible; | ||||
|         opacity: 1; | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -16,6 +16,7 @@ | ||||
|         display: inline-block; | ||||
|         cursor: pointer; | ||||
|         padding: 0 15px; | ||||
|         white-space: nowrap; | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,32 @@ | ||||
| {% extends "forms/field.html.twig" %} | ||||
|  | ||||
| {% block input %} | ||||
|     <div class="form-input-wrapper 2fa-wrapper"> | ||||
|         {% set two_fa = admin.get2FA() %} | ||||
|         {% set value = (value is null ? two_fa.createSecret(160) : value) %} | ||||
|  | ||||
|         <img style="border: 1px solid #ddd" src="{{ two_fa.getQRCodeImageAsDataUri(grav.user.email, value) }}" /> | ||||
|         <div class="2fa-secret">{{ value|chunkSplit(4, ' ') }}</div> | ||||
|  | ||||
|         <input type="text" style="display:none;" name="{{ (scope ~ field.name)|fieldName }}" value="{{ value|join(', ') }}" /> | ||||
|     </div> | ||||
|  | ||||
|     <script> | ||||
|         function twofaVisiblity() { | ||||
|             console.log('clicked'); | ||||
|             if ($('#toggle_twofa_enabled1').is(':checked')) { | ||||
|                 console.log('checked'); | ||||
|                 $('.twofa-secret').addClass("show"); | ||||
|             } else { | ||||
|                 console.log('unchecked'); | ||||
|                 $('.twofa-secret').removeClass("show"); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         $( document ).ready(function() { | ||||
|             twofaVisiblity(); | ||||
|             $('.twofa-toggle input').click(twofaVisiblity); | ||||
|         }); | ||||
|  | ||||
|     </script> | ||||
| {% endblock %} | ||||
| @@ -22,7 +22,7 @@ | ||||
|  | ||||
| {% block input %} | ||||
|  | ||||
|     <div class="switch-toggle switch-grav {{ field.size }} switch-{{ field.options|length }}"> | ||||
|     <div class="switch-toggle switch-grav {{ field.size }} switch-{{ field.options|length }} {{ field.classes }}"> | ||||
|         {% set maxLen = 0 %} | ||||
|         {% for text in field.options %} | ||||
|             {% set translation = grav.twig.twig.filters['tu'] is defined ? text|tu : text|t %} | ||||
|   | ||||
| @@ -41,7 +41,7 @@ class AdminTwigExtension extends \Twig_Extension | ||||
|             new \Twig_SimpleFilter('toYaml', [$this, 'toYamlFilter']), | ||||
|             new \Twig_SimpleFilter('fromYaml', [$this, 'fromYamlFilter']), | ||||
|             new \Twig_SimpleFilter('adminNicetime', [$this, 'adminNicetimeFilter']), | ||||
|  | ||||
|             new \Twig_SimpleFilter('chunkSplit', [$this, 'chunkSplitFilter']), | ||||
|         ]; | ||||
|     } | ||||
|  | ||||
| @@ -181,4 +181,9 @@ class AdminTwigExtension extends \Twig_Extension | ||||
|  | ||||
|         return "$difference $periods[$j] {$tense}"; | ||||
|     } | ||||
|  | ||||
|     public function chunkSplitFilter($value, $chars, $split = '-') | ||||
|     { | ||||
|         return chunk_split($value, $chars, $split); | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										4
									
								
								vendor/autoload.php
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								vendor/autoload.php
									
									
									
									
										vendored
									
									
								
							| @@ -2,6 +2,6 @@ | ||||
|  | ||||
| // autoload.php @generated by Composer | ||||
|  | ||||
| require_once __DIR__ . '/composer' . '/autoload_real.php'; | ||||
| require_once __DIR__ . '/composer/autoload_real.php'; | ||||
|  | ||||
| return ComposerAutoloaderInitef6e9937a63bd796f32542b02941ee0e::getLoader(); | ||||
| return ComposerAutoloaderInitf3438a4bfc092aad40a104edf0a3eb02::getLoader(); | ||||
|   | ||||
							
								
								
									
										58
									
								
								vendor/composer/ClassLoader.php
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										58
									
								
								vendor/composer/ClassLoader.php
									
									
									
									
										vendored
									
									
								
							| @@ -53,8 +53,9 @@ class ClassLoader | ||||
|  | ||||
|     private $useIncludePath = false; | ||||
|     private $classMap = array(); | ||||
|  | ||||
|     private $classMapAuthoritative = false; | ||||
|     private $missingClasses = array(); | ||||
|     private $apcuPrefix; | ||||
|  | ||||
|     public function getPrefixes() | ||||
|     { | ||||
| @@ -271,6 +272,26 @@ class ClassLoader | ||||
|         return $this->classMapAuthoritative; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * APCu prefix to use to cache found/not-found classes, if the extension is enabled. | ||||
|      * | ||||
|      * @param string|null $apcuPrefix | ||||
|      */ | ||||
|     public function setApcuPrefix($apcuPrefix) | ||||
|     { | ||||
|         $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * The APCu prefix in use, or null if APCu caching is not enabled. | ||||
|      * | ||||
|      * @return string|null | ||||
|      */ | ||||
|     public function getApcuPrefix() | ||||
|     { | ||||
|         return $this->apcuPrefix; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Registers this instance as an autoloader. | ||||
|      * | ||||
| @@ -313,29 +334,34 @@ class ClassLoader | ||||
|      */ | ||||
|     public function findFile($class) | ||||
|     { | ||||
|         // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731 | ||||
|         if ('\\' == $class[0]) { | ||||
|             $class = substr($class, 1); | ||||
|         } | ||||
|  | ||||
|         // class map lookup | ||||
|         if (isset($this->classMap[$class])) { | ||||
|             return $this->classMap[$class]; | ||||
|         } | ||||
|         if ($this->classMapAuthoritative) { | ||||
|         if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { | ||||
|             return false; | ||||
|         } | ||||
|         if (null !== $this->apcuPrefix) { | ||||
|             $file = apcu_fetch($this->apcuPrefix.$class, $hit); | ||||
|             if ($hit) { | ||||
|                 return $file; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         $file = $this->findFileWithExtension($class, '.php'); | ||||
|  | ||||
|         // Search for Hack files if we are running on HHVM | ||||
|         if ($file === null && defined('HHVM_VERSION')) { | ||||
|         if (false === $file && defined('HHVM_VERSION')) { | ||||
|             $file = $this->findFileWithExtension($class, '.hh'); | ||||
|         } | ||||
|  | ||||
|         if ($file === null) { | ||||
|         if (null !== $this->apcuPrefix) { | ||||
|             apcu_add($this->apcuPrefix.$class, $file); | ||||
|         } | ||||
|  | ||||
|         if (false === $file) { | ||||
|             // Remember that this class does not exist. | ||||
|             return $this->classMap[$class] = false; | ||||
|             $this->missingClasses[$class] = true; | ||||
|         } | ||||
|  | ||||
|         return $file; | ||||
| @@ -348,9 +374,13 @@ class ClassLoader | ||||
|  | ||||
|         $first = $class[0]; | ||||
|         if (isset($this->prefixLengthsPsr4[$first])) { | ||||
|             foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) { | ||||
|                 if (0 === strpos($class, $prefix)) { | ||||
|                     foreach ($this->prefixDirsPsr4[$prefix] as $dir) { | ||||
|             $subPath = $class; | ||||
|             while (false !== $lastPos = strrpos($subPath, '\\')) { | ||||
|                 $subPath = substr($subPath, 0, $lastPos); | ||||
|                 $search = $subPath.'\\'; | ||||
|                 if (isset($this->prefixDirsPsr4[$search])) { | ||||
|                     foreach ($this->prefixDirsPsr4[$search] as $dir) { | ||||
|                         $length = $this->prefixLengthsPsr4[$first][$search]; | ||||
|                         if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { | ||||
|                             return $file; | ||||
|                         } | ||||
| @@ -399,6 +429,8 @@ class ClassLoader | ||||
|         if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { | ||||
|             return $file; | ||||
|         } | ||||
|  | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										2
									
								
								vendor/composer/LICENSE
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/composer/LICENSE
									
									
									
									
										vendored
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
|  | ||||
| Copyright (c) 2016 Nils Adermann, Jordi Boggiano | ||||
| Copyright (c) Nils Adermann, Jordi Boggiano | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
|   | ||||
							
								
								
									
										1
									
								
								vendor/composer/autoload_psr4.php
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								vendor/composer/autoload_psr4.php
									
									
									
									
										vendored
									
									
								
							| @@ -6,5 +6,6 @@ $vendorDir = dirname(dirname(__FILE__)); | ||||
| $baseDir = dirname($vendorDir); | ||||
|  | ||||
| return array( | ||||
|     'RobThree\\Auth\\' => array($vendorDir . '/robthree/twofactorauth/lib'), | ||||
|     'Composer\\Semver\\' => array($vendorDir . '/composer/semver/src'), | ||||
| ); | ||||
|   | ||||
							
								
								
									
										10
									
								
								vendor/composer/autoload_real.php
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								vendor/composer/autoload_real.php
									
									
									
									
										vendored
									
									
								
							| @@ -2,7 +2,7 @@ | ||||
|  | ||||
| // autoload_real.php @generated by Composer | ||||
|  | ||||
| class ComposerAutoloaderInitef6e9937a63bd796f32542b02941ee0e | ||||
| class ComposerAutoloaderInitf3438a4bfc092aad40a104edf0a3eb02 | ||||
| { | ||||
|     private static $loader; | ||||
|  | ||||
| @@ -19,15 +19,15 @@ class ComposerAutoloaderInitef6e9937a63bd796f32542b02941ee0e | ||||
|             return self::$loader; | ||||
|         } | ||||
|  | ||||
|         spl_autoload_register(array('ComposerAutoloaderInitef6e9937a63bd796f32542b02941ee0e', 'loadClassLoader'), true, true); | ||||
|         spl_autoload_register(array('ComposerAutoloaderInitf3438a4bfc092aad40a104edf0a3eb02', 'loadClassLoader'), true, true); | ||||
|         self::$loader = $loader = new \Composer\Autoload\ClassLoader(); | ||||
|         spl_autoload_unregister(array('ComposerAutoloaderInitef6e9937a63bd796f32542b02941ee0e', 'loadClassLoader')); | ||||
|         spl_autoload_unregister(array('ComposerAutoloaderInitf3438a4bfc092aad40a104edf0a3eb02', 'loadClassLoader')); | ||||
|  | ||||
|         $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION'); | ||||
|         $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); | ||||
|         if ($useStaticLoader) { | ||||
|             require_once __DIR__ . '/autoload_static.php'; | ||||
|  | ||||
|             call_user_func(\Composer\Autoload\ComposerStaticInitef6e9937a63bd796f32542b02941ee0e::getInitializer($loader)); | ||||
|             call_user_func(\Composer\Autoload\ComposerStaticInitf3438a4bfc092aad40a104edf0a3eb02::getInitializer($loader)); | ||||
|         } else { | ||||
|             $map = require __DIR__ . '/autoload_namespaces.php'; | ||||
|             foreach ($map as $namespace => $path) { | ||||
|   | ||||
							
								
								
									
										16
									
								
								vendor/composer/autoload_static.php
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										16
									
								
								vendor/composer/autoload_static.php
									
									
									
									
										vendored
									
									
								
							| @@ -4,9 +4,13 @@ | ||||
|  | ||||
| namespace Composer\Autoload; | ||||
|  | ||||
| class ComposerStaticInitef6e9937a63bd796f32542b02941ee0e | ||||
| class ComposerStaticInitf3438a4bfc092aad40a104edf0a3eb02 | ||||
| { | ||||
|     public static $prefixLengthsPsr4 = array ( | ||||
|         'R' =>  | ||||
|         array ( | ||||
|             'RobThree\\Auth\\' => 14, | ||||
|         ), | ||||
|         'C' =>  | ||||
|         array ( | ||||
|             'Composer\\Semver\\' => 16, | ||||
| @@ -14,6 +18,10 @@ class ComposerStaticInitef6e9937a63bd796f32542b02941ee0e | ||||
|     ); | ||||
|  | ||||
|     public static $prefixDirsPsr4 = array ( | ||||
|         'RobThree\\Auth\\' =>  | ||||
|         array ( | ||||
|             0 => __DIR__ . '/..' . '/robthree/twofactorauth/lib', | ||||
|         ), | ||||
|         'Composer\\Semver\\' =>  | ||||
|         array ( | ||||
|             0 => __DIR__ . '/..' . '/composer/semver/src', | ||||
| @@ -40,9 +48,9 @@ class ComposerStaticInitef6e9937a63bd796f32542b02941ee0e | ||||
|     public static function getInitializer(ClassLoader $loader) | ||||
|     { | ||||
|         return \Closure::bind(function () use ($loader) { | ||||
|             $loader->prefixLengthsPsr4 = ComposerStaticInitef6e9937a63bd796f32542b02941ee0e::$prefixLengthsPsr4; | ||||
|             $loader->prefixDirsPsr4 = ComposerStaticInitef6e9937a63bd796f32542b02941ee0e::$prefixDirsPsr4; | ||||
|             $loader->prefixesPsr0 = ComposerStaticInitef6e9937a63bd796f32542b02941ee0e::$prefixesPsr0; | ||||
|             $loader->prefixLengthsPsr4 = ComposerStaticInitf3438a4bfc092aad40a104edf0a3eb02::$prefixLengthsPsr4; | ||||
|             $loader->prefixDirsPsr4 = ComposerStaticInitf3438a4bfc092aad40a104edf0a3eb02::$prefixDirsPsr4; | ||||
|             $loader->prefixesPsr0 = ComposerStaticInitf3438a4bfc092aad40a104edf0a3eb02::$prefixesPsr0; | ||||
|  | ||||
|         }, null, ClassLoader::class); | ||||
|     } | ||||
|   | ||||
							
								
								
									
										172
									
								
								vendor/composer/installed.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										172
									
								
								vendor/composer/installed.json
									
									
									
									
										vendored
									
									
								
							| @@ -1,51 +1,4 @@ | ||||
| [ | ||||
|     { | ||||
|         "name": "zendframework/zendxml", | ||||
|         "version": "1.0.2", | ||||
|         "version_normalized": "1.0.2.0", | ||||
|         "source": { | ||||
|             "type": "git", | ||||
|             "url": "https://github.com/zendframework/ZendXml.git", | ||||
|             "reference": "7b64507bc35d841c9c5802d67f6f87ef8e1a58c9" | ||||
|         }, | ||||
|         "dist": { | ||||
|             "type": "zip", | ||||
|             "url": "https://api.github.com/repos/zendframework/ZendXml/zipball/7b64507bc35d841c9c5802d67f6f87ef8e1a58c9", | ||||
|             "reference": "7b64507bc35d841c9c5802d67f6f87ef8e1a58c9", | ||||
|             "shasum": "" | ||||
|         }, | ||||
|         "require": { | ||||
|             "php": "^5.3.3 || ^7.0" | ||||
|         }, | ||||
|         "require-dev": { | ||||
|             "phpunit/phpunit": "^3.7 || ^4.0", | ||||
|             "squizlabs/php_codesniffer": "^1.5" | ||||
|         }, | ||||
|         "time": "2016-02-04 21:02:08", | ||||
|         "type": "library", | ||||
|         "extra": { | ||||
|             "branch-alias": { | ||||
|                 "dev-master": "1.0-dev" | ||||
|             } | ||||
|         }, | ||||
|         "installation-source": "dist", | ||||
|         "autoload": { | ||||
|             "psr-0": { | ||||
|                 "ZendXml\\": "library/" | ||||
|             } | ||||
|         }, | ||||
|         "notification-url": "https://packagist.org/downloads/", | ||||
|         "license": [ | ||||
|             "BSD-3-Clause" | ||||
|         ], | ||||
|         "description": "Utility library for XML usage, best practices, and security in PHP", | ||||
|         "homepage": "http://packages.zendframework.com/", | ||||
|         "keywords": [ | ||||
|             "security", | ||||
|             "xml", | ||||
|             "zf2" | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "name": "composer/semver", | ||||
|         "version": "1.4.2", | ||||
| @@ -68,7 +21,7 @@ | ||||
|             "phpunit/phpunit": "^4.5 || ^5.0.5", | ||||
|             "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0" | ||||
|         }, | ||||
|         "time": "2016-08-30 16:08:34", | ||||
|         "time": "2016-08-30T16:08:34+00:00", | ||||
|         "type": "library", | ||||
|         "extra": { | ||||
|             "branch-alias": { | ||||
| @@ -111,18 +64,65 @@ | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "name": "fguillot/picofeed", | ||||
|         "version": "v0.1.25", | ||||
|         "version_normalized": "0.1.25.0", | ||||
|         "name": "zendframework/zendxml", | ||||
|         "version": "1.0.2", | ||||
|         "version_normalized": "1.0.2.0", | ||||
|         "source": { | ||||
|             "type": "git", | ||||
|             "url": "https://github.com/fguillot/picoFeed.git", | ||||
|             "reference": "2bf5bc40361e788eda6b1bd5d444630986721e69" | ||||
|             "url": "https://github.com/zendframework/ZendXml.git", | ||||
|             "reference": "7b64507bc35d841c9c5802d67f6f87ef8e1a58c9" | ||||
|         }, | ||||
|         "dist": { | ||||
|             "type": "zip", | ||||
|             "url": "https://api.github.com/repos/fguillot/picoFeed/zipball/2bf5bc40361e788eda6b1bd5d444630986721e69", | ||||
|             "reference": "2bf5bc40361e788eda6b1bd5d444630986721e69", | ||||
|             "url": "https://api.github.com/repos/zendframework/ZendXml/zipball/7b64507bc35d841c9c5802d67f6f87ef8e1a58c9", | ||||
|             "reference": "7b64507bc35d841c9c5802d67f6f87ef8e1a58c9", | ||||
|             "shasum": "" | ||||
|         }, | ||||
|         "require": { | ||||
|             "php": "^5.3.3 || ^7.0" | ||||
|         }, | ||||
|         "require-dev": { | ||||
|             "phpunit/phpunit": "^3.7 || ^4.0", | ||||
|             "squizlabs/php_codesniffer": "^1.5" | ||||
|         }, | ||||
|         "time": "2016-02-04T21:02:08+00:00", | ||||
|         "type": "library", | ||||
|         "extra": { | ||||
|             "branch-alias": { | ||||
|                 "dev-master": "1.0-dev" | ||||
|             } | ||||
|         }, | ||||
|         "installation-source": "dist", | ||||
|         "autoload": { | ||||
|             "psr-0": { | ||||
|                 "ZendXml\\": "library/" | ||||
|             } | ||||
|         }, | ||||
|         "notification-url": "https://packagist.org/downloads/", | ||||
|         "license": [ | ||||
|             "BSD-3-Clause" | ||||
|         ], | ||||
|         "description": "Utility library for XML usage, best practices, and security in PHP", | ||||
|         "homepage": "http://packages.zendframework.com/", | ||||
|         "keywords": [ | ||||
|             "security", | ||||
|             "xml", | ||||
|             "zf2" | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "name": "fguillot/picofeed", | ||||
|         "version": "v0.1.35", | ||||
|         "version_normalized": "0.1.35.0", | ||||
|         "source": { | ||||
|             "type": "git", | ||||
|             "url": "https://github.com/miniflux/picoFeed.git", | ||||
|             "reference": "3a27b47de31eedec075c719f961783c5db7a7b08" | ||||
|         }, | ||||
|         "dist": { | ||||
|             "type": "zip", | ||||
|             "url": "https://api.github.com/repos/miniflux/picoFeed/zipball/3a27b47de31eedec075c719f961783c5db7a7b08", | ||||
|             "reference": "3a27b47de31eedec075c719f961783c5db7a7b08", | ||||
|             "shasum": "" | ||||
|         }, | ||||
|         "require": { | ||||
| @@ -134,10 +134,15 @@ | ||||
|             "php": ">=5.3.0", | ||||
|             "zendframework/zendxml": "^1.0" | ||||
|         }, | ||||
|         "require-dev": { | ||||
|             "phpdocumentor/reflection-docblock": "2.0.4", | ||||
|             "phpunit/phpunit": "4.8.26", | ||||
|             "symfony/yaml": "2.8.7" | ||||
|         }, | ||||
|         "suggest": { | ||||
|             "ext-curl": "PicoFeed will use cURL if present" | ||||
|         }, | ||||
|         "time": "2016-08-30 01:33:18", | ||||
|         "time": "2017-06-20T22:54:47+00:00", | ||||
|         "bin": [ | ||||
|             "picofeed" | ||||
|         ], | ||||
| @@ -158,6 +163,59 @@ | ||||
|             } | ||||
|         ], | ||||
|         "description": "Modern library to handle RSS/Atom feeds", | ||||
|         "homepage": "https://github.com/fguillot/picoFeed" | ||||
|         "homepage": "https://github.com/miniflux/picoFeed" | ||||
|     }, | ||||
|     { | ||||
|         "name": "robthree/twofactorauth", | ||||
|         "version": "1.6", | ||||
|         "version_normalized": "1.6.0.0", | ||||
|         "source": { | ||||
|             "type": "git", | ||||
|             "url": "https://github.com/RobThree/TwoFactorAuth.git", | ||||
|             "reference": "5093ab230cd8f1296d792afb6a49545f37e7fd5a" | ||||
|         }, | ||||
|         "dist": { | ||||
|             "type": "zip", | ||||
|             "url": "https://api.github.com/repos/RobThree/TwoFactorAuth/zipball/5093ab230cd8f1296d792afb6a49545f37e7fd5a", | ||||
|             "reference": "5093ab230cd8f1296d792afb6a49545f37e7fd5a", | ||||
|             "shasum": "" | ||||
|         }, | ||||
|         "require": { | ||||
|             "php": ">=5.3.0" | ||||
|         }, | ||||
|         "require-dev": { | ||||
|             "phpunit/phpunit": "@stable" | ||||
|         }, | ||||
|         "time": "2017-02-17T15:24:54+00:00", | ||||
|         "type": "library", | ||||
|         "installation-source": "dist", | ||||
|         "autoload": { | ||||
|             "psr-4": { | ||||
|                 "RobThree\\Auth\\": "lib" | ||||
|             } | ||||
|         }, | ||||
|         "notification-url": "https://packagist.org/downloads/", | ||||
|         "license": [ | ||||
|             "MIT" | ||||
|         ], | ||||
|         "authors": [ | ||||
|             { | ||||
|                 "name": "Rob Janssen", | ||||
|                 "homepage": "http://robiii.me", | ||||
|                 "role": "Developer" | ||||
|             } | ||||
|         ], | ||||
|         "description": "Two Factor Authentication", | ||||
|         "homepage": "https://github.com/RobThree/TwoFactorAuth", | ||||
|         "keywords": [ | ||||
|             "Authentication", | ||||
|             "MFA", | ||||
|             "Multi Factor Authentication", | ||||
|             "Two Factor Authentication", | ||||
|             "authenticator", | ||||
|             "authy", | ||||
|             "php", | ||||
|             "tfa" | ||||
|         ] | ||||
|     } | ||||
| ] | ||||
|   | ||||
| @@ -31,4 +31,8 @@ abstract class Base | ||||
|         $this->config = $config ?: new Config(); | ||||
|         Logger::setTimezone($this->config->getTimezone()); | ||||
|     } | ||||
|  | ||||
|     public function setConfig(Config $config) { | ||||
|         $this->config = $config; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -2,6 +2,8 @@ | ||||
|  | ||||
| namespace PicoFeed\Client; | ||||
|  | ||||
| use DateTime; | ||||
| use Exception; | ||||
| use LogicException; | ||||
| use PicoFeed\Logging\Logger; | ||||
| use PicoFeed\Config\Config; | ||||
| @@ -55,6 +57,13 @@ abstract class Client | ||||
|      */ | ||||
|     protected $last_modified = ''; | ||||
|  | ||||
|     /** | ||||
|      * Expiration DateTime | ||||
|      * | ||||
|      * @var DateTime | ||||
|      */ | ||||
|     protected $expiration = null; | ||||
|  | ||||
|     /** | ||||
|      * Proxy hostname. | ||||
|      * | ||||
| @@ -97,6 +106,13 @@ abstract class Client | ||||
|      */ | ||||
|     protected $password = ''; | ||||
|  | ||||
|     /** | ||||
|      * CURL options. | ||||
|      * | ||||
|      * @var array | ||||
|      */ | ||||
|     protected $additional_curl_options = array(); | ||||
|  | ||||
|     /** | ||||
|      * Client connection timeout. | ||||
|      * | ||||
| @@ -109,7 +125,7 @@ abstract class Client | ||||
|      * | ||||
|      * @var string | ||||
|      */ | ||||
|     protected $user_agent = 'PicoFeed (https://github.com/fguillot/picoFeed)'; | ||||
|     protected $user_agent = 'PicoFeed (https://github.com/miniflux/picoFeed)'; | ||||
|  | ||||
|     /** | ||||
|      * Real URL used (can be changed after a HTTP redirect). | ||||
| @@ -214,6 +230,9 @@ abstract class Client | ||||
|         $this->handleErrorResponse($response); | ||||
|         $this->handleNormalResponse($response); | ||||
|  | ||||
|         $this->expiration = $this->parseExpiration($response['headers']); | ||||
|         Logger::setMessage(get_called_class().' Expiration: '.$this->expiration->format(DATE_ISO8601)); | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
| @@ -241,6 +260,9 @@ abstract class Client | ||||
|      * Handle Http Error codes | ||||
|      * | ||||
|      * @param array $response Client response | ||||
|      * @throws ForbiddenException | ||||
|      * @throws InvalidUrlException | ||||
|      * @throws UnauthorizedException | ||||
|      */ | ||||
|     protected function handleErrorResponse(array $response) | ||||
|     { | ||||
| @@ -308,7 +330,6 @@ abstract class Client | ||||
|      * Find content type from response headers. | ||||
|      * | ||||
|      * @param array $response Client response | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     public function findContentType(array $response) | ||||
| @@ -324,7 +345,6 @@ abstract class Client | ||||
|     public function findCharset() | ||||
|     { | ||||
|         $result = explode('charset=', $this->content_type); | ||||
|  | ||||
|         return isset($result[1]) ? $result[1] : ''; | ||||
|     } | ||||
|  | ||||
| @@ -333,7 +353,6 @@ abstract class Client | ||||
|      * | ||||
|      * @param array  $response Client response | ||||
|      * @param string $header   Header name | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getHeader(array $response, $header) | ||||
| @@ -345,13 +364,11 @@ abstract class Client | ||||
|      * Set the Last-Modified HTTP header. | ||||
|      * | ||||
|      * @param string $last_modified Header value | ||||
|      * | ||||
|      * @return \PicoFeed\Client\Client | ||||
|      * @return $this | ||||
|      */ | ||||
|     public function setLastModified($last_modified) | ||||
|     { | ||||
|         $this->last_modified = $last_modified; | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
| @@ -369,13 +386,11 @@ abstract class Client | ||||
|      * Set the value of the Etag HTTP header. | ||||
|      * | ||||
|      * @param string $etag Etag HTTP header value | ||||
|      * | ||||
|      * @return \PicoFeed\Client\Client | ||||
|      * @return $this | ||||
|      */ | ||||
|     public function setEtag($etag) | ||||
|     { | ||||
|         $this->etag = $etag; | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
| @@ -402,13 +417,12 @@ abstract class Client | ||||
|     /** | ||||
|      * Set the url. | ||||
|      * | ||||
|      * @param  $url | ||||
|      * @return string | ||||
|      * @return \PicoFeed\Client\Client | ||||
|      */ | ||||
|     public function setUrl($url) | ||||
|     { | ||||
|         $this->url = $url; | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
| @@ -476,13 +490,11 @@ abstract class Client | ||||
|      * Set connection timeout. | ||||
|      * | ||||
|      * @param int $timeout Connection timeout | ||||
|      * | ||||
|      * @return \PicoFeed\Client\Client | ||||
|      * @return $this | ||||
|      */ | ||||
|     public function setTimeout($timeout) | ||||
|     { | ||||
|         $this->timeout = $timeout ?: $this->timeout; | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
| @@ -490,13 +502,11 @@ abstract class Client | ||||
|      * Set a custom user agent. | ||||
|      * | ||||
|      * @param string $user_agent User Agent | ||||
|      * | ||||
|      * @return \PicoFeed\Client\Client | ||||
|      * @return $this | ||||
|      */ | ||||
|     public function setUserAgent($user_agent) | ||||
|     { | ||||
|         $this->user_agent = $user_agent ?: $this->user_agent; | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
| @@ -504,13 +514,11 @@ abstract class Client | ||||
|      * Set the maximum number of HTTP redirections. | ||||
|      * | ||||
|      * @param int $max Maximum | ||||
|      * | ||||
|      * @return \PicoFeed\Client\Client | ||||
|      * @return $this | ||||
|      */ | ||||
|     public function setMaxRedirections($max) | ||||
|     { | ||||
|         $this->max_redirects = $max ?: $this->max_redirects; | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
| @@ -518,13 +526,11 @@ abstract class Client | ||||
|      * Set the maximum size of the HTTP body. | ||||
|      * | ||||
|      * @param int $max Maximum | ||||
|      * | ||||
|      * @return \PicoFeed\Client\Client | ||||
|      * @return $this | ||||
|      */ | ||||
|     public function setMaxBodySize($max) | ||||
|     { | ||||
|         $this->max_body_size = $max ?: $this->max_body_size; | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
| @@ -532,13 +538,11 @@ abstract class Client | ||||
|      * Set the proxy hostname. | ||||
|      * | ||||
|      * @param string $hostname Proxy hostname | ||||
|      * | ||||
|      * @return \PicoFeed\Client\Client | ||||
|      * @return $this | ||||
|      */ | ||||
|     public function setProxyHostname($hostname) | ||||
|     { | ||||
|         $this->proxy_hostname = $hostname ?: $this->proxy_hostname; | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
| @@ -546,13 +550,11 @@ abstract class Client | ||||
|      * Set the proxy port. | ||||
|      * | ||||
|      * @param int $port Proxy port | ||||
|      * | ||||
|      * @return \PicoFeed\Client\Client | ||||
|      * @return $this | ||||
|      */ | ||||
|     public function setProxyPort($port) | ||||
|     { | ||||
|         $this->proxy_port = $port ?: $this->proxy_port; | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
| @@ -560,13 +562,11 @@ abstract class Client | ||||
|      * Set the proxy username. | ||||
|      * | ||||
|      * @param string $username Proxy username | ||||
|      * | ||||
|      * @return \PicoFeed\Client\Client | ||||
|      * @return $this | ||||
|      */ | ||||
|     public function setProxyUsername($username) | ||||
|     { | ||||
|         $this->proxy_username = $username ?: $this->proxy_username; | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
| @@ -574,13 +574,11 @@ abstract class Client | ||||
|      * Set the proxy password. | ||||
|      * | ||||
|      * @param string $password Password | ||||
|      * | ||||
|      * @return \PicoFeed\Client\Client | ||||
|      * @return $this | ||||
|      */ | ||||
|     public function setProxyPassword($password) | ||||
|     { | ||||
|         $this->proxy_password = $password ?: $this->proxy_password; | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
| @@ -589,12 +587,11 @@ abstract class Client | ||||
|      * | ||||
|      * @param string $username Basic Auth username | ||||
|      * | ||||
|      * @return \PicoFeed\Client\Client | ||||
|      * @return $this | ||||
|      */ | ||||
|     public function setUsername($username) | ||||
|     { | ||||
|         $this->username = $username ?: $this->username; | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
| @@ -603,36 +600,46 @@ abstract class Client | ||||
|      * | ||||
|      * @param string $password Basic Auth Password | ||||
|      * | ||||
|      * @return \PicoFeed\Client\Client | ||||
|      * @return $this | ||||
|      */ | ||||
|     public function setPassword($password) | ||||
|     { | ||||
|         $this->password = $password ?: $this->password; | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Set the CURL options. | ||||
|      * | ||||
|      * @param array $options | ||||
|      * @return $this | ||||
|      */ | ||||
|     public function setAdditionalCurlOptions(array $options) | ||||
|     { | ||||
|         $this->additional_curl_options = $options ?: $this->additional_curl_options; | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Enable the passthrough mode. | ||||
|      * | ||||
|      * @return \PicoFeed\Client\Client | ||||
|      * @return $this | ||||
|      */ | ||||
|     public function enablePassthroughMode() | ||||
|     { | ||||
|         $this->passthrough = true; | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Disable the passthrough mode. | ||||
|      * | ||||
|      * @return \PicoFeed\Client\Client | ||||
|      * @return $this | ||||
|      */ | ||||
|     public function disablePassthroughMode() | ||||
|     { | ||||
|         $this->passthrough = false; | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
| @@ -640,8 +647,7 @@ abstract class Client | ||||
|      * Set config object. | ||||
|      * | ||||
|      * @param \PicoFeed\Config\Config $config Config instance | ||||
|      * | ||||
|      * @return \PicoFeed\Client\Client | ||||
|      * @return $this | ||||
|      */ | ||||
|     public function setConfig(Config $config) | ||||
|     { | ||||
| @@ -654,6 +660,7 @@ abstract class Client | ||||
|             $this->setProxyPort($config->getProxyPort()); | ||||
|             $this->setProxyUsername($config->getProxyUsername()); | ||||
|             $this->setProxyPassword($config->getProxyPassword()); | ||||
|             $this->setAdditionalCurlOptions($config->getAdditionalCurlOptions() ?: array()); | ||||
|         } | ||||
|  | ||||
|         return $this; | ||||
| @@ -670,4 +677,36 @@ abstract class Client | ||||
|     { | ||||
|         return $code == 301 || $code == 302 || $code == 303 || $code == 307; | ||||
|     } | ||||
|  | ||||
|     public function parseExpiration(HttpHeaders $headers) | ||||
|     { | ||||
|         try { | ||||
|  | ||||
|             if (isset($headers['Cache-Control'])) { | ||||
|                 if (preg_match('/s-maxage=(\d+)/', $headers['Cache-Control'], $matches)) { | ||||
|                     return new DateTime('+' . $matches[1] . ' seconds'); | ||||
|                 } else if (preg_match('/max-age=(\d+)/', $headers['Cache-Control'], $matches)) { | ||||
|                     return new DateTime('+' . $matches[1] . ' seconds'); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (! empty($headers['Expires'])) { | ||||
|                 return new DateTime($headers['Expires']); | ||||
|             } | ||||
|         } catch (Exception $e) { | ||||
|             Logger::setMessage('Unable to parse expiration date: '.$e->getMessage()); | ||||
|         } | ||||
|  | ||||
|         return new DateTime(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Get expiration date time from "Expires" or "Cache-Control" headers | ||||
|      * | ||||
|      * @return DateTime | ||||
|      */ | ||||
|     public function getExpiration() | ||||
|     { | ||||
|         return $this->expiration ?: new DateTime(); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -11,6 +11,8 @@ use PicoFeed\Logging\Logger; | ||||
|  */ | ||||
| class Curl extends Client | ||||
| { | ||||
|     protected $nbRedirects = 0; | ||||
|  | ||||
|     /** | ||||
|      * HTTP response body. | ||||
|      * | ||||
| @@ -108,8 +110,6 @@ class Curl extends Client | ||||
|                 return $this->handleRedirection($headers['Location']); | ||||
|             } | ||||
|  | ||||
|             header(':', true, $status); | ||||
|  | ||||
|             if (isset($headers['Content-Type'])) { | ||||
|                 header('Content-Type:' .$headers['Content-Type']); | ||||
|             } | ||||
| @@ -136,6 +136,7 @@ class Curl extends Client | ||||
|  | ||||
|         if ($this->etag) { | ||||
|             $headers[] = 'If-None-Match: '.$this->etag; | ||||
|             $headers[] = 'A-IM: feed'; | ||||
|         } | ||||
|  | ||||
|         if ($this->last_modified) { | ||||
| @@ -199,6 +200,9 @@ class Curl extends Client | ||||
|      */ | ||||
|     private function prepareDownloadMode($ch) | ||||
|     { | ||||
|         $this->body = ''; | ||||
|         $this->response_headers = array(); | ||||
|         $this->response_headers_count = 0; | ||||
|         $write_function = 'readBody'; | ||||
|         $header_function = 'readHeaders'; | ||||
|  | ||||
| @@ -212,6 +216,20 @@ class Curl extends Client | ||||
|         return $ch; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Set additional CURL options. | ||||
|      * | ||||
|      * @param resource $ch | ||||
|      * | ||||
|      * @return resource $ch | ||||
|      */ | ||||
|     private function prepareAdditionalCurlOptions($ch){ | ||||
|         foreach( $this->additional_curl_options as $c_op => $c_val ){ | ||||
|             curl_setopt($ch, $c_op, $c_val); | ||||
|         } | ||||
|         return $ch; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Prepare curl context. | ||||
|      * | ||||
| @@ -245,6 +263,7 @@ class Curl extends Client | ||||
|         $ch = $this->prepareDownloadMode($ch); | ||||
|         $ch = $this->prepareProxyContext($ch); | ||||
|         $ch = $this->prepareAuthContext($ch); | ||||
|         $ch = $this->prepareAdditionalCurlOptions($ch); | ||||
|  | ||||
|         return $ch; | ||||
|     } | ||||
| @@ -290,8 +309,12 @@ class Curl extends Client | ||||
|         list($status, $headers) = HttpHeaders::parse(explode("\n", $this->response_headers[$this->response_headers_count - 1])); | ||||
|  | ||||
|         if ($this->isRedirection($status)) { | ||||
|             if (empty($headers['Location'])) { | ||||
|                 $status = 200; | ||||
|             } else { | ||||
|                 return $this->handleRedirection($headers['Location']); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return array( | ||||
|             'status' => $status, | ||||
| @@ -304,12 +327,11 @@ class Curl extends Client | ||||
|      * Handle HTTP redirects | ||||
|      * | ||||
|      * @param string $location Redirected URL | ||||
|      * | ||||
|      * @return array | ||||
|      * @throws MaxRedirectException | ||||
|      */ | ||||
|     private function handleRedirection($location) | ||||
|     { | ||||
|         $nb_redirects = 0; | ||||
|         $result = array(); | ||||
|         $this->url = Url::resolve($location, $this->url); | ||||
|         $this->body = ''; | ||||
| @@ -318,9 +340,9 @@ class Curl extends Client | ||||
|         $this->response_headers_count = 0; | ||||
|  | ||||
|         while (true) { | ||||
|             ++$nb_redirects; | ||||
|             $this->nbRedirects++; | ||||
|  | ||||
|             if ($nb_redirects >= $this->max_redirects) { | ||||
|             if ($this->nbRedirects >= $this->max_redirects) { | ||||
|                 throw new MaxRedirectException('Maximum number of redirections reached'); | ||||
|             } | ||||
|  | ||||
| @@ -349,6 +371,11 @@ class Curl extends Client | ||||
|      * @see    http://curl.haxx.se/libcurl/c/libcurl-errors.html | ||||
|      * | ||||
|      * @param int $errno cURL error code | ||||
|      * @throws InvalidCertificateException | ||||
|      * @throws InvalidUrlException | ||||
|      * @throws MaxRedirectException | ||||
|      * @throws MaxSizeException | ||||
|      * @throws TimeoutException | ||||
|      */ | ||||
|     private function handleError($errno) | ||||
|     { | ||||
| @@ -372,8 +399,7 @@ class Curl extends Client | ||||
|             case 66: // CURLE_SSL_ENGINE_INITFAILED | ||||
|             case 77: // CURLE_SSL_CACERT_BADFILE | ||||
|             case 83: // CURLE_SSL_ISSUER_ERROR | ||||
|                 $msg = 'Invalid SSL certificate caused by CURL error number ' . | ||||
|                         $errno; | ||||
|                 $msg = 'Invalid SSL certificate caused by CURL error number ' . $errno; | ||||
|                 throw new InvalidCertificateException($msg, $errno); | ||||
|             case 47: // CURLE_TOO_MANY_REDIRECTS | ||||
|                 throw new MaxRedirectException('Maximum number of redirections reached', $errno); | ||||
|   | ||||
| @@ -24,7 +24,7 @@ class HttpHeaders implements ArrayAccess | ||||
|  | ||||
|     public function offsetGet($offset) | ||||
|     { | ||||
|         return $this->headers[strtolower($offset)]; | ||||
|         return $this->offsetExists($offset) ? $this->headers[strtolower($offset)] : ''; | ||||
|     } | ||||
|  | ||||
|     public function offsetSet($offset, $value) | ||||
|   | ||||
| @@ -31,6 +31,7 @@ class Stream extends Client | ||||
|  | ||||
|         if ($this->etag) { | ||||
|             $headers[] = 'If-None-Match: '.$this->etag; | ||||
|             $headers[] = 'A-IM: feed'; | ||||
|         } | ||||
|  | ||||
|         if ($this->last_modified) { | ||||
| @@ -104,6 +105,9 @@ class Stream extends Client | ||||
|      * Do the HTTP request. | ||||
|      * | ||||
|      * @return array HTTP response ['body' => ..., 'status' => ..., 'headers' => ...] | ||||
|      * @throws InvalidUrlException | ||||
|      * @throws MaxSizeException | ||||
|      * @throws TimeoutException | ||||
|      */ | ||||
|     public function doRequest() | ||||
|     { | ||||
|   | ||||
| @@ -50,10 +50,8 @@ class Url | ||||
|      * Shortcut method to get an absolute url from relative url. | ||||
|      * | ||||
|      * @static | ||||
|      * | ||||
|      * @param mixed $item_url    Unknown url (can be relative or not) | ||||
|      * @param mixed $website_url Website url | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     public static function resolve($item_url, $website_url) | ||||
| @@ -78,9 +76,7 @@ class Url | ||||
|      * Shortcut method to get a base url. | ||||
|      * | ||||
|      * @static | ||||
|      * | ||||
|      * @param string $url | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     public static function base($url) | ||||
| @@ -94,7 +90,6 @@ class Url | ||||
|      * Get the base URL. | ||||
|      * | ||||
|      * @param string $suffix Add a suffix to the url | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getBaseUrl($suffix = '') | ||||
| @@ -106,7 +101,6 @@ class Url | ||||
|      * Get the absolute URL. | ||||
|      * | ||||
|      * @param string $base_url Use this url as base url | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getAbsoluteUrl($base_url = '') | ||||
| @@ -150,7 +144,8 @@ class Url | ||||
|      * Imported from Guzzle library: https://github.com/guzzle/psr7/blob/master/src/Uri.php#L568-L582 | ||||
|      * | ||||
|      * @param $path | ||||
|      * | ||||
|      * @param string $charUnreserved | ||||
|      * @param string $charSubDelims | ||||
|      * @return string | ||||
|      */ | ||||
|     public function filterPath($path, $charUnreserved = 'a-zA-Z0-9_\-\.~', $charSubDelims = '!\$&\'\(\)\*\+,;=') | ||||
| @@ -226,7 +221,6 @@ class Url | ||||
|      * Get the scheme. | ||||
|      * | ||||
|      * @param string $suffix Suffix to add when there is a scheme | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getScheme($suffix = '') | ||||
| @@ -238,12 +232,12 @@ class Url | ||||
|      * Set the scheme. | ||||
|      * | ||||
|      * @param string $scheme Set a scheme | ||||
|      * | ||||
|      * @return string | ||||
|      * @return $this | ||||
|      */ | ||||
|     public function setScheme($scheme) | ||||
|     { | ||||
|         $this->components['scheme'] = $scheme; | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -260,7 +254,6 @@ class Url | ||||
|      * Get the port. | ||||
|      * | ||||
|      * @param string $prefix Prefix to add when there is a port | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getPort($prefix = '') | ||||
|   | ||||
| @@ -7,9 +7,11 @@ namespace PicoFeed\Config; | ||||
|  * | ||||
|  * @author  Frederic Guillot | ||||
|  * | ||||
|  * @method  \PicoFeed\Config\Config setAdditionalCurlOptions(array $options) | ||||
|  * @method  \PicoFeed\Config\Config setClientTimeout(integer $value) | ||||
|  * @method  \PicoFeed\Config\Config setClientUserAgent(string $value) | ||||
|  * @method  \PicoFeed\Config\Config setMaxRedirections(integer $value) | ||||
|  * @method  \PicoFeed\Config\Config setMaxRecursions(integer $value) | ||||
|  * @method  \PicoFeed\Config\Config setMaxBodySize(integer $value) | ||||
|  * @method  \PicoFeed\Config\Config setProxyHostname(string $value) | ||||
|  * @method  \PicoFeed\Config\Config setProxyPort(integer $value) | ||||
| @@ -36,6 +38,7 @@ namespace PicoFeed\Config; | ||||
|  * @method  integer    getClientTimeout() | ||||
|  * @method  string     getClientUserAgent() | ||||
|  * @method  integer    getMaxRedirections() | ||||
|  * @method  integer    getMaxRecursions() | ||||
|  * @method  integer    getMaxBodySize() | ||||
|  * @method  string     getProxyHostname() | ||||
|  * @method  integer    getProxyPort() | ||||
| @@ -59,6 +62,7 @@ namespace PicoFeed\Config; | ||||
|  * @method  string     getFilterImageProxyUrl() | ||||
|  * @method  \Closure   getFilterImageProxyCallback() | ||||
|  * @method  string     getFilterImageProxyProtocol() | ||||
|  * @method  array      getAdditionalCurlOptions() | ||||
|  */ | ||||
| class Config | ||||
| { | ||||
| @@ -92,5 +96,7 @@ class Config | ||||
|  | ||||
|             return isset($this->container[$parameter]) ? $this->container[$parameter] : $default_value; | ||||
|         } | ||||
|  | ||||
|         return null; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -51,6 +51,7 @@ class Attribute | ||||
|         'td' => array(), | ||||
|         'tbody' => array(), | ||||
|         'thead' => array(), | ||||
|         'h1' => array(), | ||||
|         'h2' => array(), | ||||
|         'h3' => array(), | ||||
|         'h4' => array(), | ||||
|   | ||||
| @@ -13,10 +13,8 @@ class Filter | ||||
|      * Get the Html filter instance. | ||||
|      * | ||||
|      * @static | ||||
|      * | ||||
|      * @param string $html    HTML content | ||||
|      * @param string $website Site URL (used to build absolute URL) | ||||
|      * | ||||
|      * @return Html | ||||
|      */ | ||||
|     public static function html($html, $website) | ||||
| @@ -30,7 +28,7 @@ class Filter | ||||
|      * Escape HTML content. | ||||
|      * | ||||
|      * @static | ||||
|      * | ||||
|      * @param  string $content | ||||
|      * @return string | ||||
|      */ | ||||
|     public static function escape($content) | ||||
| @@ -42,7 +40,6 @@ class Filter | ||||
|      * Remove HTML tags. | ||||
|      * | ||||
|      * @param string $data Input data | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     public function removeHTMLTags($data) | ||||
| @@ -54,9 +51,7 @@ class Filter | ||||
|      * Remove the XML tag from a document. | ||||
|      * | ||||
|      * @static | ||||
|      * | ||||
|      * @param string $data Input data | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     public static function stripXmlTag($data) | ||||
| @@ -80,9 +75,7 @@ class Filter | ||||
|      * Strip head tag from the HTML content. | ||||
|      * | ||||
|      * @static | ||||
|      * | ||||
|      * @param string $data Input data | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     public static function stripHeadTags($data) | ||||
| @@ -94,9 +87,7 @@ class Filter | ||||
|      * Trim whitespace from the begining, the end and inside a string and don't break utf-8 string. | ||||
|      * | ||||
|      * @static | ||||
|      * | ||||
|      * @param string $value Raw data | ||||
|      * | ||||
|      * @return string Normalized data | ||||
|      */ | ||||
|     public static function stripWhiteSpace($value) | ||||
| @@ -112,9 +103,7 @@ class Filter | ||||
|      * Fixes before XML parsing. | ||||
|      * | ||||
|      * @static | ||||
|      * | ||||
|      * @param string $data Raw data | ||||
|      * | ||||
|      * @return string Normalized data | ||||
|      */ | ||||
|     public static function normalizeData($data) | ||||
|   | ||||
| @@ -90,7 +90,6 @@ class Html | ||||
|      * Set config object. | ||||
|      * | ||||
|      * @param \PicoFeed\Config\Config $config Config instance | ||||
|      * | ||||
|      * @return \PicoFeed\Filter\Html | ||||
|      */ | ||||
|     public function setConfig($config) | ||||
| @@ -160,7 +159,8 @@ class Html | ||||
|     /** | ||||
|      * Called after XML parsing. | ||||
|      * | ||||
|      * @param string $content the content that should be filtered | ||||
|      * @param  string $content | ||||
|      * @return string | ||||
|      */ | ||||
|     public function filterRules($content) | ||||
|     { | ||||
|   | ||||
| @@ -42,6 +42,7 @@ class Tag extends Base | ||||
|         'td', | ||||
|         'tbody', | ||||
|         'thead', | ||||
|         'h1', | ||||
|         'h2', | ||||
|         'h3', | ||||
|         'h4', | ||||
| @@ -67,6 +68,8 @@ class Tag extends Base | ||||
|         'abbr', | ||||
|         'iframe', | ||||
|         'q', | ||||
|         'sup', | ||||
|         'sub', | ||||
|     ); | ||||
|  | ||||
|     /** | ||||
| @@ -74,7 +77,6 @@ class Tag extends Base | ||||
|      * | ||||
|      * @param string $tag        Tag name | ||||
|      * @param array  $attributes Attributes dictionary | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function isAllowed($tag, array $attributes) | ||||
| @@ -87,7 +89,6 @@ class Tag extends Base | ||||
|      * | ||||
|      * @param string $tag        Tag name | ||||
|      * @param string $attributes Attributes converted in html | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     public function openHtmlTag($tag, $attributes = '') | ||||
| @@ -99,7 +100,6 @@ class Tag extends Base | ||||
|      * Return the HTML closing tag. | ||||
|      * | ||||
|      * @param string $tag Tag name | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     public function closeHtmlTag($tag) | ||||
| @@ -111,7 +111,6 @@ class Tag extends Base | ||||
|      * Return true is the tag is self-closing. | ||||
|      * | ||||
|      * @param string $tag Tag name | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function isSelfClosingTag($tag) | ||||
| @@ -123,7 +122,6 @@ class Tag extends Base | ||||
|      * Check if a tag is on the whitelist. | ||||
|      * | ||||
|      * @param string $tag Tag name | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function isAllowedTag($tag) | ||||
| @@ -139,7 +137,6 @@ class Tag extends Base | ||||
|      * | ||||
|      * @param string $tag        Tag name | ||||
|      * @param array  $attributes Tag attributes | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function isPixelTracker($tag, array $attributes) | ||||
| @@ -153,7 +150,6 @@ class Tag extends Base | ||||
|      * Remove script tags. | ||||
|      * | ||||
|      * @param string $data Input data | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     public function removeBlacklistedTags($data) | ||||
| @@ -179,7 +175,6 @@ class Tag extends Base | ||||
|      * Remove empty tags. | ||||
|      * | ||||
|      * @param string $data Input data | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     public function removeEmptyTags($data) | ||||
| @@ -191,7 +186,6 @@ class Tag extends Base | ||||
|      * Replace <br/><br/> by only one. | ||||
|      * | ||||
|      * @param string $data Input data | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     public function removeMultipleBreakTags($data) | ||||
| @@ -203,7 +197,6 @@ class Tag extends Base | ||||
|      * Set whitelisted tags adn attributes for each tag. | ||||
|      * | ||||
|      * @param array $values List of tags: ['video' => ['src', 'cover'], 'img' => ['src']] | ||||
|      * | ||||
|      * @return Tag | ||||
|      */ | ||||
|     public function setWhitelistedTags(array $values) | ||||
|   | ||||
| @@ -25,8 +25,7 @@ class Atom extends Parser | ||||
|      * Get the path to the items XML tree. | ||||
|      * | ||||
|      * @param SimpleXMLElement $xml Feed xml | ||||
|      * | ||||
|      * @return SimpleXMLElement | ||||
|      * @return SimpleXMLElement[] | ||||
|      */ | ||||
|     public function getItemsTree(SimpleXMLElement $xml) | ||||
|     { | ||||
| @@ -288,12 +287,25 @@ class Atom extends Parser | ||||
|         $item->setLanguage(XmlParser::getValue($language) ?: $feed->getLanguage()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Find the item categories. | ||||
|      * | ||||
|      * @param SimpleXMLElement      $entry Feed item | ||||
|      * @param Item $item  Item object | ||||
|      * @param Feed $feed  Feed object | ||||
|      */ | ||||
|     public function findItemCategories(SimpleXMLElement $entry, Item $item, Feed $feed) | ||||
|     { | ||||
|         $categories = XmlParser::getXPathResult($entry, 'atom:category/@term', $this->namespaces) | ||||
|                  ?: XmlParser::getXPathResult($entry, 'category/@term'); | ||||
|         $item->setCategoriesFromXml($categories); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Get the URL from a link tag. | ||||
|      * | ||||
|      * @param SimpleXMLElement $xml XML tag | ||||
|      * @param string           $rel Link relationship: alternate, enclosure, related, self, via | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     private function getUrl(SimpleXMLElement $xml, $rel, $fallback = false) | ||||
| @@ -317,7 +329,6 @@ class Atom extends Parser | ||||
|      * | ||||
|      * @param SimpleXMLElement $xml XML tag | ||||
|      * @param string           $rel Link relationship: alternate, enclosure, related, self, via | ||||
|      * | ||||
|      * @return SimpleXMLElement|null | ||||
|      */ | ||||
|     private function findLink(SimpleXMLElement $xml, $rel) | ||||
| @@ -338,7 +349,6 @@ class Atom extends Parser | ||||
|      * Get the entry content. | ||||
|      * | ||||
|      * @param SimpleXMLElement $entry XML Entry | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     private function getContent(SimpleXMLElement $entry) | ||||
|   | ||||
| @@ -38,6 +38,7 @@ class DateParser extends Base | ||||
|         DATE_RFC1123 => null, | ||||
|         DATE_RFC2822 => null, | ||||
|         DATE_RFC3339 => null, | ||||
|         'l, d M Y H:i:s' => null, | ||||
|         'D, d M Y H:i:s' => 25, | ||||
|         'D, d M Y h:i:s' => 25, | ||||
|         'D M d Y H:i:s' => 24, | ||||
|   | ||||
| @@ -13,7 +13,7 @@ class Feed | ||||
|     /** | ||||
|      * Feed items. | ||||
|      * | ||||
|      * @var array | ||||
|      * @var Item[] | ||||
|      */ | ||||
|     public $items = array(); | ||||
|  | ||||
|   | ||||
| @@ -103,6 +103,13 @@ class Item | ||||
|      */ | ||||
|     public $language = ''; | ||||
|  | ||||
|     /** | ||||
|      * Item categories. | ||||
|      * | ||||
|      * @var array | ||||
|      */ | ||||
|     public $categories = array(); | ||||
|  | ||||
|     /** | ||||
|      * Raw XML. | ||||
|      * | ||||
| @@ -169,10 +176,13 @@ class Item | ||||
|         $publishedDate = $this->publishedDate != null ? $this->publishedDate->format(DATE_RFC822) : null; | ||||
|         $updatedDate = $this->updatedDate != null ? $this->updatedDate->format(DATE_RFC822) : null; | ||||
|  | ||||
|         $categoryString = $this->categories != null ? implode(',', $this->categories) : null; | ||||
|  | ||||
|         $output .= 'Item::date = '.$this->date->format(DATE_RFC822).PHP_EOL; | ||||
|         $output .= 'Item::publishedDate = '.$publishedDate.PHP_EOL; | ||||
|         $output .= 'Item::updatedDate = '.$updatedDate.PHP_EOL; | ||||
|         $output .= 'Item::isRTL() = '.($this->isRTL() ? 'true' : 'false').PHP_EOL; | ||||
|         $output .= 'Item::categories = ['.$categoryString.']'.PHP_EOL; | ||||
|         $output .= 'Item::content = '.strlen($this->content).' bytes'.PHP_EOL; | ||||
|  | ||||
|         return $output; | ||||
| @@ -305,6 +315,16 @@ class Item | ||||
|         return $this->language; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Get categories. | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     public function getCategories() | ||||
|     { | ||||
|         return $this->categories; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Get author. | ||||
|      * | ||||
| @@ -433,6 +453,40 @@ class Item | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Set item categories. | ||||
|      * | ||||
|      * @param array $categories | ||||
|      * @return Item | ||||
|      */ | ||||
|     public function setCategories($categories) | ||||
|     { | ||||
|         $this->categories = $categories; | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Set item categories from xml. | ||||
|      * | ||||
|      * @param |SimpleXMLElement[] $categories | ||||
|      * @return Item | ||||
|      */ | ||||
|     public function setCategoriesFromXml($categories) | ||||
|     { | ||||
|         if ($categories !== false) { | ||||
|             $this->setCategories( | ||||
|                 array_map( | ||||
|                     function ($element) { | ||||
|                         return trim((string) $element); | ||||
|                     }, | ||||
|                     $categories | ||||
|                 ) | ||||
|             ); | ||||
|         } | ||||
|          | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Set raw XML. | ||||
|      * | ||||
|   | ||||
| @@ -103,8 +103,8 @@ abstract class Parser implements ParserInterface | ||||
|  | ||||
|     /** | ||||
|      * Parse the document. | ||||
|      * | ||||
|      * @return \PicoFeed\Parser\Feed | ||||
|      * @return Feed | ||||
|      * @throws MalformedXmlException | ||||
|      */ | ||||
|     public function execute() | ||||
|     { | ||||
| @@ -163,6 +163,7 @@ abstract class Parser implements ParserInterface | ||||
|             $this->findItemDate($entry, $item, $feed); | ||||
|             $this->findItemEnclosure($entry, $item, $feed); | ||||
|             $this->findItemLanguage($entry, $item, $feed); | ||||
|             $this->findItemCategories($entry, $item, $feed); | ||||
|  | ||||
|             $this->itemPostProcessor->execute($feed, $item); | ||||
|             $feed->items[] = $item; | ||||
| @@ -222,18 +223,20 @@ abstract class Parser implements ParserInterface | ||||
|     public function findItemDate(SimpleXMLElement $entry, Item $item, Feed $feed) | ||||
|     { | ||||
|         $this->findItemPublishedDate($entry, $item, $feed); | ||||
|         $published = $item->getPublishedDate(); | ||||
|  | ||||
|         $this->findItemUpdatedDate($entry, $item, $feed); | ||||
|         $updated = $item->getUpdatedDate(); | ||||
|  | ||||
|         if ($published === null && $updated === null) { | ||||
|             $item->setDate($feed->getDate()); // We use the feed date if there is no date for the item | ||||
|         } elseif ($published !== null && $updated !== null) { | ||||
|             $item->setDate(max($published, $updated)); // We use the most recent date between published and updated | ||||
|         } else { | ||||
|             $item->setDate($updated ?: $published); | ||||
|         if ($item->getPublishedDate() === null) { | ||||
|             // Use the updated date if available, otherwise use the feed date | ||||
|             $item->setPublishedDate($item->getUpdatedDate() ?: $feed->getDate()); | ||||
|         } | ||||
|  | ||||
|         if ($item->getUpdatedDate() === null) { | ||||
|             // Use the published date as fallback | ||||
|             $item->setUpdatedDate($item->getPublishedDate()); | ||||
|         } | ||||
|  | ||||
|         // Use the most recent of published and updated dates | ||||
|         $item->setDate(max($item->getPublishedDate(), $item->getUpdatedDate())); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -256,7 +259,7 @@ abstract class Parser implements ParserInterface | ||||
|     public function getDateParser() | ||||
|     { | ||||
|         if ($this->dateParser === null) { | ||||
|             return new DateParser($this->config); | ||||
|             $this->dateParser = new DateParser($this->config); | ||||
|         } | ||||
|  | ||||
|         return $this->dateParser; | ||||
| @@ -276,9 +279,7 @@ abstract class Parser implements ParserInterface | ||||
|      * Return true if the given language is "Right to Left". | ||||
|      * | ||||
|      * @static | ||||
|      * | ||||
|      * @param string $language Language: fr-FR, en-US | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     public static function isLanguageRTL($language) | ||||
| @@ -321,12 +322,12 @@ abstract class Parser implements ParserInterface | ||||
|      * Set config object. | ||||
|      * | ||||
|      * @param \PicoFeed\Config\Config $config Config instance | ||||
|      * | ||||
|      * @return \PicoFeed\Parser\Parser | ||||
|      */ | ||||
|     public function setConfig($config) | ||||
|     { | ||||
|         $this->config = $config; | ||||
|         $this->itemPostProcessor->setConfig($config); | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
| @@ -348,7 +349,6 @@ abstract class Parser implements ParserInterface | ||||
|      *                                       scraped | ||||
|      * @param null|\Closure $scraperCallback Callback function that gets called for each | ||||
|      *                                       scraper execution | ||||
|      * | ||||
|      * @return \PicoFeed\Parser\Parser | ||||
|      */ | ||||
|     public function enableContentGrabber($needsRuleFile = false, $scraperCallback = null) | ||||
| @@ -371,7 +371,6 @@ abstract class Parser implements ParserInterface | ||||
|      * Set ignored URLs for the content grabber. | ||||
|      * | ||||
|      * @param array $urls URLs | ||||
|      * | ||||
|      * @return \PicoFeed\Parser\Parser | ||||
|      */ | ||||
|     public function setGrabberIgnoreUrls(array $urls) | ||||
| @@ -384,7 +383,6 @@ abstract class Parser implements ParserInterface | ||||
|      * Register all supported namespaces to be used within an xpath query. | ||||
|      * | ||||
|      * @param SimpleXMLElement $xml Feed xml | ||||
|      * | ||||
|      * @return SimpleXMLElement | ||||
|      */ | ||||
|     public function registerSupportedNamespaces(SimpleXMLElement $xml) | ||||
| @@ -395,6 +393,4 @@ abstract class Parser implements ParserInterface | ||||
|  | ||||
|         return $xml; | ||||
|     } | ||||
|  | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -170,4 +170,13 @@ interface ParserInterface | ||||
|      * @param Feed $feed  Feed object | ||||
|      */ | ||||
|     public function findItemLanguage(SimpleXMLElement $entry, Item $item, Feed $feed); | ||||
|  | ||||
|     /** | ||||
|      * Find the item categories. | ||||
|      * | ||||
|      * @param SimpleXMLElement      $entry Feed item | ||||
|      * @param Item $item  Item object | ||||
|      * @param Feed $feed  Feed object | ||||
|      */ | ||||
|     public function findItemCategories(SimpleXMLElement $entry, Item $item, Feed $feed); | ||||
| } | ||||
|   | ||||
| @@ -27,8 +27,7 @@ class Rss10 extends Parser | ||||
|      * Get the path to the items XML tree. | ||||
|      * | ||||
|      * @param SimpleXMLElement $xml Feed xml | ||||
|      * | ||||
|      * @return SimpleXMLElement | ||||
|      * @return SimpleXMLElement[] | ||||
|      */ | ||||
|     public function getItemsTree(SimpleXMLElement $xml) | ||||
|     { | ||||
| @@ -290,4 +289,17 @@ class Rss10 extends Parser | ||||
|  | ||||
|         $item->setLanguage(XmlParser::getValue($language) ?: $feed->getLanguage()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Find the item categories. | ||||
|      * | ||||
|      * @param SimpleXMLElement      $entry Feed item | ||||
|      * @param Item $item  Item object | ||||
|      * @param Feed $feed  Feed object | ||||
|      */ | ||||
|     public function findItemCategories(SimpleXMLElement $entry, Item $item, Feed $feed) | ||||
|     { | ||||
|         $categories = XmlParser::getXPathResult($entry, 'dc:subject', $this->namespaces); | ||||
|         $item->setCategoriesFromXml($categories); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -28,8 +28,7 @@ class Rss20 extends Parser | ||||
|      * Get the path to the items XML tree. | ||||
|      * | ||||
|      * @param SimpleXMLElement $xml Feed xml | ||||
|      * | ||||
|      * @return SimpleXMLElement | ||||
|      * @return SimpleXMLElement[] | ||||
|      */ | ||||
|     public function getItemsTree(SimpleXMLElement $xml) | ||||
|     { | ||||
| @@ -302,4 +301,17 @@ class Rss20 extends Parser | ||||
|         $language = XmlParser::getXPathResult($entry, 'dc:language', $this->namespaces); | ||||
|         $item->setLanguage(XmlParser::getValue($language) ?: $feed->getLanguage()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Find the item categories. | ||||
|      * | ||||
|      * @param SimpleXMLElement      $entry Feed item | ||||
|      * @param Item $item  Item object | ||||
|      * @param Feed $feed  Feed object | ||||
|      */ | ||||
|     public function findItemCategories(SimpleXMLElement $entry, Item $item, Feed $feed) | ||||
|     { | ||||
|         $categories = XmlParser::getXPathResult($entry, 'category'); | ||||
|         $item->setCategoriesFromXml($categories); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -34,7 +34,7 @@ class XmlParser | ||||
|      * | ||||
|      * @static | ||||
|      * @param string $input XML content | ||||
|      * @return DOMDocument | ||||
|      * @return DOMDocument|bool | ||||
|      */ | ||||
|     public static function getDomDocument($input) | ||||
|     { | ||||
|   | ||||
| @@ -33,5 +33,7 @@ class ContentFilterProcessor extends Base implements ItemProcessorInterface | ||||
|         } else { | ||||
|             Logger::setMessage(get_called_class().': Content filtering disabled'); | ||||
|         } | ||||
|  | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -5,6 +5,7 @@ namespace PicoFeed\Processor; | ||||
| use PicoFeed\Base; | ||||
| use PicoFeed\Parser\Feed; | ||||
| use PicoFeed\Parser\Item; | ||||
| use PicoFeed\Config\Config; | ||||
|  | ||||
| /** | ||||
|  * Item Post Processor | ||||
| @@ -71,7 +72,7 @@ class ItemPostProcessor extends Base | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Checks wheather a specific processor is registered or not | ||||
|      * Checks whether a specific processor is registered or not | ||||
|      * | ||||
|      * @access public | ||||
|      * @param  string $class | ||||
| @@ -93,4 +94,13 @@ class ItemPostProcessor extends Base | ||||
|     { | ||||
|         return isset($this->processors[$class]) ? $this->processors[$class] : null; | ||||
|     } | ||||
|  | ||||
|     public function setConfig(Config $config) | ||||
|     { | ||||
|         foreach ($this->processors as $processor) { | ||||
|             $processor->setConfig($config); | ||||
|         } | ||||
|  | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -30,7 +30,7 @@ class Favicon extends Base | ||||
|         'image/x-icon', | ||||
|         'image/jpeg', | ||||
|         'image/jpg', | ||||
|         'image/svg+xml' | ||||
|         'image/svg+xml', | ||||
|     ); | ||||
|  | ||||
|     /** | ||||
| @@ -95,8 +95,7 @@ class Favicon extends Base | ||||
|      * Download and check if a resource exists. | ||||
|      * | ||||
|      * @param string $url URL | ||||
|      * | ||||
|      * @return \PicoFeed\Client Client instance | ||||
|      * @return \PicoFeed\Client\Client Client instance | ||||
|      */ | ||||
|     public function download($url) | ||||
|     { | ||||
| @@ -118,7 +117,6 @@ class Favicon extends Base | ||||
|      * Check if a remote file exists. | ||||
|      * | ||||
|      * @param string $url URL | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function exists($url) | ||||
| @@ -131,7 +129,6 @@ class Favicon extends Base | ||||
|      * | ||||
|      * @param string $website_link URL | ||||
|      * @param string $favicon_link optional URL | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     public function find($website_link, $favicon_link = '') | ||||
| @@ -165,7 +162,6 @@ class Favicon extends Base | ||||
|      * Extract the icon links from the HTML. | ||||
|      * | ||||
|      * @param string $html HTML | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     public function extract($html) | ||||
| @@ -179,7 +175,7 @@ class Favicon extends Base | ||||
|         $dom = XmlParser::getHtmlDocument($html); | ||||
|  | ||||
|         $xpath = new DOMXpath($dom); | ||||
|         $elements = $xpath->query('//link[@rel="icon" or @rel="shortcut icon" or @rel="icon shortcut"]'); | ||||
|         $elements = $xpath->query('//link[@rel="icon" or @rel="shortcut icon" or @rel="Shortcut Icon" or @rel="icon shortcut"]'); | ||||
|  | ||||
|         for ($i = 0; $i < $elements->length; ++$i) { | ||||
|             $icons[] = $elements->item($i)->getAttribute('href'); | ||||
|   | ||||
| @@ -61,8 +61,8 @@ class Reader extends Base | ||||
|      * @param string $etag Etag HTTP header | ||||
|      * @param string $username HTTP basic auth username | ||||
|      * @param string $password HTTP basic auth password | ||||
|      * | ||||
|      * @return \PicoFeed\Client\Client | ||||
|      * @return Client | ||||
|      * @throws SubscriptionNotFoundException | ||||
|      */ | ||||
|     public function discover($url, $last_modified = '', $etag = '', $username = '', $password = '') | ||||
|     { | ||||
| @@ -130,8 +130,8 @@ class Reader extends Base | ||||
|      * @param string $url Site url | ||||
|      * @param string $content Feed content | ||||
|      * @param string $encoding HTTP encoding | ||||
|      * | ||||
|      * @return \PicoFeed\Parser\Parser | ||||
|      * @throws UnsupportedFeedFormatException | ||||
|      */ | ||||
|     public function getParser($url, $content, $encoding) | ||||
|     { | ||||
| @@ -154,7 +154,6 @@ class Reader extends Base | ||||
|      * Detect the feed format. | ||||
|      * | ||||
|      * @param string $content Feed content | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     public function detectFormat($content) | ||||
| @@ -177,7 +176,7 @@ class Reader extends Base | ||||
|      * Add the prefix "http://" if the end-user just enter a domain name. | ||||
|      * | ||||
|      * @param string $url Url | ||||
|      * @retunr string | ||||
|      * @return string | ||||
|      */ | ||||
|     public function prependScheme($url) | ||||
|     { | ||||
|   | ||||
| @@ -6,7 +6,7 @@ return array( | ||||
|             'body' => array( | ||||
|                 '//div[@class="content"]', | ||||
|             ), | ||||
|             'strip' => array() | ||||
|         ) | ||||
|     ) | ||||
|             'strip' => array(), | ||||
|         ), | ||||
|     ), | ||||
| ); | ||||
|   | ||||
| @@ -5,27 +5,40 @@ return array( | ||||
|             'test_url' => 'http://www.wired.com/gamelife/2013/09/ouya-free-the-games/', | ||||
|             'body' => array( | ||||
|                 '//div[@data-js="gallerySlides"]', | ||||
|                  '//article', | ||||
|                  '//div[starts-with(@class,"post")]', | ||||
|             ), | ||||
|             'strip' => array( | ||||
|                 '//*[@id="linker_widget"]', | ||||
|                 '//*[@class="credit"]', | ||||
|                 '//div[@data-js="slideCount"]', | ||||
|                 '//*[contains(@class="visually-hidden")]', | ||||
|                 '//*[@data-slide-number="_endslate"]', | ||||
|                 '//*[@id="related"]', | ||||
|                 '//*[contains(@class, "bio")]', | ||||
|                 '//*[contains(@class, "entry-footer")]', | ||||
|                 '//*[contains(@class, "mobify_backtotop_link")]', | ||||
|                 '//*[contains(@class, "gallery-navigation")]', | ||||
|                 '//*[contains(@class, "gallery-thumbnail")]', | ||||
|                 '//img[contains(@src, "1x1")]', | ||||
|                 '//a[contains(@href, "creativecommons")]', | ||||
|                 '//a[@href="#start-of-content"]', | ||||
|                 '//h1', | ||||
|                 '//nav', | ||||
|                 '//button', | ||||
|                 '//figure[starts-with(@class,"rad-slide")]', | ||||
|                 '//figure[starts-with(@class,"end-slate")]', | ||||
|                 '//div[contains(@class,"mobile-")]', | ||||
|                 '//div[starts-with(@class,"mob-gallery-launcher")]', | ||||
|                 '//div[contains(@id,"mobile-")]', | ||||
|                 '//span[contains(@class,"slide-count")]', | ||||
|                 '//div[contains(@class,"show-ipad")]', | ||||
|                 '//img[contains(@id,"-hero-bg")]', | ||||
|                 '//div[@data-js="overlayWrap"]', | ||||
|                 '//ul[contains(@class,"metadata")]', | ||||
|                 '//div[@class="opening center"]', | ||||
|                 '//p[contains(@class="byline-mob"]', | ||||
|                 '//div[@id="o-gallery"]', | ||||
|                 '//div[starts-with(@class,"sm-col")]', | ||||
|                 '//div[contains(@class,"pad-b-huge")]', | ||||
|                 '//a[contains(@class,"visually-hidden")]', | ||||
|                 '//*[@class="social"]', | ||||
|                 '//i', | ||||
|                 '//div[@data-js="mobGalleryAd"]', | ||||
|                 '//div[contains(@class,"footer")]', | ||||
|                 '//div[contains(@data-js,"fader")]', | ||||
|                 '//div[@id="sharing"]', | ||||
|                 '//div[contains(@id,"related")]', | ||||
|                 '//div[@id="most-pop"]', | ||||
|                 '//ul[@id="article-tags"]', | ||||
|                 '//style', | ||||
|                 '//section[contains(@class,"footer")]' | ||||
|             ), | ||||
|         ) | ||||
|     ) | ||||
| ); | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'filter' => array( | ||||
|         '%.*%' => array( | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%^/news.*%' => array( | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|   | ||||
| @@ -1,21 +1,24 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|             'test_url' => 'http://www.aljazeera.com/news/2015/09/xi-jinping-seattle-china-150922230118373.html', | ||||
|             'body' => array( | ||||
|                 '//figure[@class="article-content"]', | ||||
|                 '//div[@class="article-body"]', | ||||
|                 '//article[@id="main-story"]', | ||||
|             ), | ||||
|             'strip' => array( | ||||
|                 '//h1', | ||||
|                 '//h3', | ||||
|                 '//script', | ||||
|                 '//header', | ||||
|                 '//ul', | ||||
|                 '//table[contains(@class, "in-article-item")]', | ||||
|                 '//section[contains(@class,"profile")]', | ||||
|                 '//a[@target="_self"]', | ||||
|                 '//div[contains(@id,"_2")]', | ||||
|                 '//div[contains(@id,"_3")]', | ||||
|                 '//img[@class="viewMode"]', | ||||
|                 '//table[contains(@class,"in-article-item")]', | ||||
|                 '//div[@data-embed-type="Brightcove"]', | ||||
|                 '//div[@class="QuoteContainer"]', | ||||
|                 '//div[@class="BottomByLine"]', | ||||
|             ), | ||||
|         ), | ||||
|     ), | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
| @@ -10,6 +9,7 @@ return array( | ||||
|             'strip' => array( | ||||
|             '//p[@class="kindofstory"]', | ||||
|             '//cite[@class="byline"]', | ||||
|             '//div[@class="useful-top"]', | ||||
|             '//div[contains(@class,"related-topics")]', | ||||
|             '//links', | ||||
|             '//sharebar', | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'filter' => array( | ||||
|         '%.*%' => array( | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|   | ||||
| @@ -1,23 +1,25 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|             'test_url' => 'http://arstechnica.com/tech-policy/2015/09/judge-warners-2m-happy-birthday-copyright-is-bogus/', | ||||
|             'body' => array( | ||||
|                 '//header/h2', | ||||
|                 '//section[@id="article-guts"]', | ||||
|                 '//div[@class="superscroll-content show"]', | ||||
|                 '//div[@class="gallery"]', | ||||
|                 '//article', | ||||
|             ), | ||||
|             'next_page' => '//span[@class="numbers"]/a', | ||||
|             'strip' => array( | ||||
|                 '//h4[@class="post-upperdek"]', | ||||
|                 '//h1', | ||||
|                 '//ul[@class="lSPager lSGallery"]', | ||||
|                 '//div[@class="lSAction"]', | ||||
|                 '//section[@class="post-meta"]', | ||||
|                 '//figcaption', | ||||
|                 '//div[@class="post-meta"]', | ||||
|                 '//div[@class="gallery-image-credit"]', | ||||
|                 '//aside', | ||||
|                 '//div[@class="article-expander"]', | ||||
|                 '//div[@class="gallery-image-credit"]', | ||||
|                 '//section[@class="article-author"]', | ||||
|                 '//*[contains(@id,"social-")]', | ||||
|                 '//div[contains(@id,"footer")]', | ||||
|             ), | ||||
|         ), | ||||
|     ), | ||||
| ); | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%/index.php.*comic=.*%' => array( | ||||
|   | ||||
							
								
								
									
										18
									
								
								vendor/fguillot/picofeed/lib/PicoFeed/Rules/backchannel.com.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								vendor/fguillot/picofeed/lib/PicoFeed/Rules/backchannel.com.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| <?php | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|             'test_url' => 'https://medium.com/lessons-learned/917b8b63ae3e', | ||||
|             'body' => array( | ||||
|                 '//div[contains(@class,"section-inner")]', | ||||
|             ), | ||||
|             'strip' => array( | ||||
|                 '//div[contains(@class,"metabar")]', | ||||
|                 '//img[contains(@class,"thumbnail")]', | ||||
|                 '//h1', | ||||
|                 '//blockquote', | ||||
|                 '//p[contains(@class,"graf-after--h4")]' | ||||
|             ), | ||||
|         ), | ||||
|     ), | ||||
| ); | ||||
| @@ -1,11 +1,10 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|             'test_url' => 'http://www.bangkokpost.com/news/politics/704204/new-us-ambassador-arrives-in-bangkok', | ||||
|             'body' => array( | ||||
|             '//div[@class="articleContents"]', | ||||
|             '//article/div[@class="articleContents"]', | ||||
|             ), | ||||
|             'strip' => array( | ||||
|             '//h2', | ||||
| @@ -13,7 +12,6 @@ return array( | ||||
|             '//div[@class="text-size"]', | ||||
|             '//div[@class="relate-story"]', | ||||
|             '//div[@class="text-ads"]', | ||||
|             '//script', | ||||
|             '//ul', | ||||
|             ), | ||||
|         ), | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'filter' => array( | ||||
|         '%.*%' => array( | ||||
|   | ||||
							
								
								
									
										31
									
								
								vendor/fguillot/picofeed/lib/PicoFeed/Rules/bigpicture.ru.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								vendor/fguillot/picofeed/lib/PicoFeed/Rules/bigpicture.ru.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| <?php | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|             'test_url' => 'http://bigpicture.ru/?p=556658', | ||||
|             'body' => array( | ||||
|                 '//div[@class="article container"]', | ||||
|             ), | ||||
|             'strip' => array( | ||||
|                 '//script', | ||||
|                 '//form', | ||||
|                 '//style', | ||||
|                 '//h1', | ||||
|                 '//*[@class="wp-smiley"]', | ||||
|                 '//div[@class="ipmd"]', | ||||
|                 '//div[@class="tags"]', | ||||
|                 '//div[@class="social-button"]', | ||||
|                 '//div[@class="bottom-share"]', | ||||
|                 '//div[@class="raccoonbox"]', | ||||
|                 '//div[@class="yndadvert"]', | ||||
|                 '//div[@class="we-recommend"]', | ||||
|                 '//div[@class="relap-bigpicture_ru-wrapper"]', | ||||
|                 '//div[@id="mmail"]', | ||||
|                 '//div[@id="mobile-ads-cut"]', | ||||
|                 '//div[@id="liquidstorm-alt-html"]', | ||||
|                 '//div[contains(@class, "post-tags")]', | ||||
|                 '//*[contains(text(),"Смотрите также")]', | ||||
|             ), | ||||
|         ), | ||||
|     ), | ||||
| ); | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|   | ||||
							
								
								
									
										22
									
								
								vendor/fguillot/picofeed/lib/PicoFeed/Rules/biztimes.com.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vendor/fguillot/picofeed/lib/PicoFeed/Rules/biztimes.com.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| <?php | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|             'test_url' => 'https://www.biztimes.com/2017/02/10/settlement-would-revive-fowler-lake-condo-project-in-oconomowoc/', | ||||
|             'body' => array( | ||||
|                 '//h2/span[@class="subhead"]', | ||||
|                 '//div[contains(@class,"article-content")]', | ||||
|             ), | ||||
|             'strip' => array( | ||||
|                 '//script', | ||||
|                 '//div[contains(@class,"mobile-article-content")]', | ||||
|                 '//div[contains(@class,"sharedaddy")]', | ||||
|                 '//div[contains(@class,"author-details")]', | ||||
|                 '//div[@class="row ad"]', | ||||
|                 '//div[contains(@class,"relatedposts")]', | ||||
|                 '//div[@class="col-lg-12"]', | ||||
|                 '//div[contains(@class,"widget")]', | ||||
|             ), | ||||
|         ), | ||||
|     ), | ||||
| ); | ||||
							
								
								
									
										15
									
								
								vendor/fguillot/picofeed/lib/PicoFeed/Rules/bleepingcomputer.com.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								vendor/fguillot/picofeed/lib/PicoFeed/Rules/bleepingcomputer.com.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| <?php | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|             'test_url' => 'https://www.bleepingcomputer.com/news/google/chromes-sandbox-feature-infringes-on-three-patents-so-google-must-now-pay-20m/', | ||||
|             'body' => array( | ||||
|                 '//div[@class="article_section"]', | ||||
|             ), | ||||
|             'strip' => array( | ||||
|                 '//*[@itemprop="headline"]', | ||||
|                 '//div[@class="cz-news-story-title-section"]' | ||||
|             ), | ||||
|         ), | ||||
|     ), | ||||
| ); | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|   | ||||
							
								
								
									
										22
									
								
								vendor/fguillot/picofeed/lib/PicoFeed/Rules/brewers.mlb.com.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vendor/fguillot/picofeed/lib/PicoFeed/Rules/brewers.mlb.com.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| <?php | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|             'test_url' => 'http://m.brewers.mlb.com/news/article/161364798', | ||||
|             'body' => array( | ||||
|                 '//article[contains(@class,"article")]', | ||||
|             ), | ||||
|             'strip' => array( | ||||
|                 '//div[contains(@class,"ad-slot")]', | ||||
|                 '//h1', | ||||
|                 '//span[@class="timestamp"]', | ||||
|                 '//div[contains(@class,"contributor-bottom")]', | ||||
|                 '//div[contains(@class,"video")]', | ||||
|                 '//ul[contains(@class,"social")]', | ||||
|                 '//p[@class="tagline"]', | ||||
|                 '//div[contains(@class,"social")]', | ||||
|                 '//div[@class="button-wrap"]', | ||||
|             ), | ||||
|         ), | ||||
|     ), | ||||
| ); | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%/cad/.+%' => array( | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|   | ||||
							
								
								
									
										18
									
								
								vendor/fguillot/picofeed/lib/PicoFeed/Rules/chinafile.com.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								vendor/fguillot/picofeed/lib/PicoFeed/Rules/chinafile.com.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| <?php | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|             'test_url' => 'http://www.chinafile.com/books/shanghai-faithful?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+chinafile%2FAll+%28ChinaFile%29', | ||||
|             'body' => array( | ||||
|             '//div[contains(@class,"pane-featured-photo-panel-pane-1")]', | ||||
|             '//div[contains(@class,"video-above-fold")]', | ||||
|             '//div[@class="sc-media"]', | ||||
|             '//div[contains(@class,"field-name-body")]', | ||||
|             ), | ||||
|             'strip' => array( | ||||
|                 '//div[contains(@class,"cboxes")]', | ||||
|                 '//div[contains(@class,"l-middle")]', | ||||
|             ), | ||||
|         ), | ||||
|     ), | ||||
| ); | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%/comic.*%' => array( | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'grabber' => array( | ||||
| 		 '%^/products.*%' => array( | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'filter' => array( | ||||
|         '%.*%' => array( | ||||
|   | ||||
							
								
								
									
										28
									
								
								vendor/fguillot/picofeed/lib/PicoFeed/Rules/crash.net.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								vendor/fguillot/picofeed/lib/PicoFeed/Rules/crash.net.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| <?php | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|             'test_url' => 'http://www.crash.net/motogp/interview/247550/1/exclusive-andrea-dovizioso-interview.html', | ||||
|             'body' => array( | ||||
|                 '//div[@id="content"]', | ||||
|             ), | ||||
|             'strip' => array( | ||||
|                 '//script', | ||||
|                 '//style', | ||||
|                 '//*[@title="Social Networking"]', | ||||
|                 '//*[@class="crash-ad2"]', | ||||
|                 '//*[@class="clearfix"]', | ||||
|                 '//*[@class="crash-ad2"]', | ||||
|                 '//*[contains(@id, "divCB"]', | ||||
|                 '//*[@class="pnlComment"]', | ||||
|                 '//*[@class="comments-tabs"]', | ||||
|                 '//*[contains(@class, "ad-twocol"]', | ||||
|                 '//*[@class="stories-list"]', | ||||
|                 '//*[contains(@class, "btn")]', | ||||
|                 '//*[@class="content"]', | ||||
|                 '//h3', | ||||
|             ), | ||||
|         ), | ||||
|     ), | ||||
| ); | ||||
|  | ||||
| @@ -1,18 +1,18 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|             'test_url' => 'http://www.csmonitor.com/USA/Politics/2015/0925/John-Boehner-steps-down-Self-sacrificing-but-will-it-lead-to-better-government', | ||||
|             'body' => array( | ||||
|             '//figure[@id="image-top-1"]', | ||||
|             '//div[@id="story-body"]', | ||||
|                 '//h2[@id="summary"]', | ||||
|                 '//div[@class="flex-video youtube"]', | ||||
|                 '//div[contains(@class,"eza-body")]', | ||||
|             ), | ||||
|             'strip' => array( | ||||
|             '//script', | ||||
|             '//img[@title="hide caption"]', | ||||
|                 '//span[@id="breadcrumb"]', | ||||
|                 '//div[@id="byline-wrapper"]', | ||||
|                 '//div[@class="injection"]', | ||||
|                 '//*[contains(@class,"promo_link")]', | ||||
|             '//div[@id="story-embed-column"]', | ||||
|             ), | ||||
|         ), | ||||
|     ), | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|   | ||||
| @@ -1,17 +1,25 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|             'test_url' => 'http://blogs.discovermagazine.com/the-extremo-files/2015/09/11/have-scientists-found-the-worlds-deepest-fish/', | ||||
|             'test_url' => 'http://blogs.discovermagazine.com/neuroskeptic/2017/01/25/publishers-jeffrey-beall/', | ||||
|             'body' => array( | ||||
|             '//div[@class="entry"]', | ||||
|             '//div[@class="contentWell"]', | ||||
|             ), | ||||
|             'strip' => array( | ||||
|             '//h1', | ||||
|             '//div[@class="breadcrumbs"]', | ||||
|             '//div[@class="mobile"]', | ||||
|             '//div[@class="fromIssue"]', | ||||
|             '//div[contains(@class,"belowDeck")]', | ||||
|             '//div[@class="meta"]', | ||||
|             '//div[@class="shareIcons"]', | ||||
|             '//div[@class="categories"]', | ||||
|             '//div[@class="navigation"]', | ||||
|             '//div[@class="heading"]', | ||||
|             '//div[contains(@id,"-ad")]', | ||||
|             '//div[@class="relatedArticles"]', | ||||
|             '//div[@id="disqus_thread"]' | ||||
|             ), | ||||
|          ), | ||||
|     ), | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|   | ||||
							
								
								
									
										22
									
								
								vendor/fguillot/picofeed/lib/PicoFeed/Rules/e-w-e.ru.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vendor/fguillot/picofeed/lib/PicoFeed/Rules/e-w-e.ru.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| <?php | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|             'test_url' => 'http://e-w-e.ru/16-prekrasnyx-izobretenij-zhenshhin/', | ||||
|             'body' => array( | ||||
|                 '//div[contains(@class, "post_text")]', | ||||
|             ), | ||||
|             'strip' => array( | ||||
|                 '//script', | ||||
|                 '//form', | ||||
|                 '//style', | ||||
|                 '//*[@class="views_post"]', | ||||
|                 '//*[@class="adman_mobile"]', | ||||
|                 '//*[@class="adman_desctop"]', | ||||
|                 '//*[contains(@rel, "nofollow")]', | ||||
|                 '//*[contains(@class, "wp-smiley")]', | ||||
|                 '//*[contains(text(),"Источник:")]', | ||||
|             ), | ||||
|         ), | ||||
|     ), | ||||
| ); | ||||
							
								
								
									
										25
									
								
								vendor/fguillot/picofeed/lib/PicoFeed/Rules/economist.com.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								vendor/fguillot/picofeed/lib/PicoFeed/Rules/economist.com.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| <?php | ||||
| return array( | ||||
|     'grabber' => array( | ||||
|         '%.*%' => array( | ||||
|             'test_url' => 'http://www.economist.com/blogs/buttonwood/2017/02/mixed-signals?fsrc=rss', | ||||
|             'body' => array( | ||||
|                 '//article', | ||||
|             ), | ||||
|             'strip' => array( | ||||
|                 '//span[@class="blog-post__siblings-list-header "]', | ||||
|                 '//h1', | ||||
|                 '//aside', | ||||
|                 '//div[@class="blog-post__asideable-wrapper"]', | ||||
|                 '//div[@class="share_inline_header"]', | ||||
|                 '//div[@id="column-right"]', | ||||
|                 '//div[contains(@class,"blog-post__siblings-list-aside")]', | ||||
|                 '//div[@class="video-player__wrapper"]', | ||||
|                 '//div[@class="blog-post__bottom-panel"]', | ||||
|                 '//div[contains(@class,"latest-updates-panel__container")]', | ||||
|                 '//div[contains(@class,"blog-post__asideable-content")]', | ||||
|                 '//div[@aria-label="Advertisement"]' | ||||
|             ), | ||||
|         ), | ||||
|     ), | ||||
| ); | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|  | ||||
| return array( | ||||
|     'filter' => array( | ||||
|         '%.*%' => array( | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user