%PDF- %PDF-
| Direktori : /home/vacivi36/intranet.vacivitta.com.br/protected/humhub/models/ |
| Current File : /home/vacivi36/intranet.vacivitta.com.br/protected/humhub/models/UrlOembed.php |
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\models;
use humhub\modules\admin\models\forms\OEmbedSettingsForm;
use humhub\modules\ui\icon\widgets\Icon;
use humhub\modules\user\models\User;
use humhub\widgets\Button;
use humhub\events\OembedFetchEvent;
use humhub\libs\RestrictedCallException;
use humhub\libs\UrlOembedClient;
use humhub\libs\UrlOembedHttpClient;
use yii\helpers\Html;
use yii\helpers\Json;
use yii\db\ActiveRecord;
use Yii;
/**
* UrlOembed records hold already loaded oembed previews.
*
* This class is used to fetch and save oembed results by means of the [[preload()]] and [[getOEmbed()]] methods.
*
* [[preload()]] can be used to preload oembed results for a given text string.
*
* [[getOEmbed()]] can be used to fetch a single oembed record for a given Url.
*
* All successfull results of `preload()` or `getOEmbed()` will be cached and saved in the `url_oembed` table.
*
* @property string $url
* @property string $preview
*/
class UrlOembed extends ActiveRecord
{
/**
* @event
* @since 1.4
*/
const EVENT_FETCH = 'fetch';
/**
* @var int Maximum amount of remote fetch calls per request
*/
public static $maxUrlFetchLimit = 5;
/**
* @var int Maximum amount of local db fetch calls per request
*/
public static $maxUrlLoadLimit = 100;
/**
* @var int Counter for remote fetch calls
*/
protected static $urlsFetched = 0;
/**
* @var int Counter for local db fetch calls
*/
protected static $urlsLoaded = 0;
/**
* @var array Internal request cache
*/
protected static $cache = [];
/**
* @var UrlOembedClient
*/
protected static $client;
/**
* @var array Allowed oembed types
*/
public static $allowed_types = ['video', 'rich', 'photo'];
/**
* @inheritdoc
*/
public static function tableName()
{
return 'url_oembed';
}
/**
* @inheritdoc
*/
public function rules()
{
return [
[['url', 'preview'], 'required'],
[['preview'], 'string'],
[['url'], 'string', 'max' => 180],
];
}
/**
* @inheritdoc
*/
public function attributeLabels()
{
return [
'url' => 'Url',
'preview' => 'Preview',
];
}
/**
* Returns the OEmbed API Url if exists
*
* @return string|null
*/
public function getProviderUrl()
{
foreach (static::getProviders() as $provider) {
if (isset($provider['pattern']) && preg_match($provider['pattern'], $this->url)) {
return str_replace("%url%", urlencode($this->url), $provider['endpoint']);
}
}
return null;
}
/**
* Flushes internal caches and fetch counters
*/
public static function flush()
{
static::$cache = [];
static::$urlsFetched = 0;
static::$urlsLoaded = 0;
}
/**
* Returns a OEmbed html string for a given $url or null in the following cases:
*
* - There is no OEmbed provider available for the given url
* - The OEmbed provider does not return a valid response
* - A fetch counter restriction exceeded
*
* @param string $url
* @param bool $forceAllowedDomain true - to don't check the domain and always display media content
* @return string|null
*/
public static function getOEmbed($url, bool $forceAllowedDomain = false)
{
$oembedFetchEvent = new OembedFetchEvent(['url' => $url]);
(new UrlOembed())->trigger(static::EVENT_FETCH, $oembedFetchEvent);
if ($result = $oembedFetchEvent->getResult()) {
return $result;
}
try {
$url = trim($url);
if (static::hasOEmbedSupport($url)) {
if (!$forceAllowedDomain && !self::isAllowedDomain($url)) {
$result = self::confirmationContent($url);
} else {
$urlOembed = static::findExistingOembed($url);
$result = $urlOembed ? $urlOembed->preview : self::loadUrl($url);
}
if (!empty($result)) {
return trim(preg_replace('/\s+/', ' ', $result));
}
}
} catch (RestrictedCallException $re) {
Yii::warning($re);
}
return null;
}
/**
* Parses the given $text string for urls an saves new loaded OEmbed instances for.
*
* This method will only execute a remote fetch call if:
*
* - There was a provider found for the given url
* - The same url has not been fetched before
* - The max fetch counters are not exceeded
*
* @param string $text
*/
public static function preload($text)
{
preg_replace_callback('/http(.*?)(\s|$)/i', function ($match) {
$url = trim($match[0]);
if (!static::hasOEmbedSupport($url)) {
return;
}
try {
if (!static::findExistingOembed($url)) {
static::loadUrl($url);
}
} catch (RestrictedCallException $re) {
Yii::warning($re);
}
}, $text);
}
/**
* Checks if there is an existing UrlOembed record for the given $url.
*
* > Note: Results will be cached for this request if an record was found.
*
* @param $url
* @return UrlOembed|null
* @throws RestrictedCallException
*/
public static function findExistingOembed($url)
{
if (array_key_exists($url, static::$cache)) {
return static::$cache[$url];
}
if (static::$urlsLoaded >= static::$maxUrlLoadLimit) {
throw new RestrictedCallException('Max url db load limit exceeded.');
}
static::$urlsLoaded++;
$record = static::findOne(['url' => $url]);
if ($record) {
static::$cache[$url] = $record;
}
return $record;
}
/**
* Fetches the oembed result for a given $url and saves an UrlOembed record to the database.
* A Remote fetch for new urls is only executed in case there is a related provider for the given url configured.
*
* @param string $url
* @param string $customProviderUrl
* @return string|null
*/
public static function loadUrl($url, $customProviderUrl = '')
{
try {
$urlOembed = static::findExistingOembed($url);
if (!$urlOembed) {
$urlOembed = new static(['url' => $url]);
}
$providerUrl = $customProviderUrl != '' ? $customProviderUrl : $urlOembed->getProviderUrl();
if (empty($providerUrl)) {
return null;
}
$data = static::fetchUrl($providerUrl);
$html = static::buildHtmlPreview($url, $data);
$urlOembed->preview = $html ?: '';
$urlOembed->save();
static::$cache[$url] = $urlOembed;
return $html;
} catch (RestrictedCallException $re) {
Yii::warning($re);
}
return null;
}
/**
* Builds the oembed preview html result in case the given $data array is valid.
*
* @param $url
* @param []|null $data
* @param UrlOembed $urlOembed
* @return string|null
*/
protected static function buildHtmlPreview($url, $data = null)
{
if (static::validateOembedResponse($data)) {
return Html::tag('div', $data['html'], [
'data' => [
'guid' => uniqid('oembed-', true),
'richtext-feature' => 1,
'oembed-provider' => Html::encode(static::getProviderByUrl($url)),
'url' => Html::encode($url)
],
'class' => 'oembed_snippet',
]);
}
return null;
}
/**
* Replace the embedded content with confirmation before display it
*
* @param string $url
* @return string
*/
protected static function confirmationContent(string $url): string
{
$urlData = parse_url($url);
$urlPrefix = $urlData['host'] ?? $url;
$html = Html::tag('strong', Yii::t('base', 'Allow content from external source')) .
Html::tag('br') .
Yii::t('base', 'Do you want to enable content from \'{urlPrefix}\'?', ['urlPrefix' => Html::tag('strong', $urlPrefix)]) .
Html::tag('br') .
Html::tag('label', '<input type="checkbox"> ' . Yii::t('base', 'Always allow content from this provider!')) .
Html::tag('br') .
Button::info(Yii::t('base', 'Confirm'))->action('oembed.display')->sm();
$html = Icon::get('info-circle') .
Html::tag('div', $html) .
Html::tag('div', '', ['class' => 'clearfix']);
return Html::tag('div', $html, [
'data-url' => $url,
'class' => 'oembed_confirmation',
]);
}
/**
* Validates the given $data array.
*
* @param []|null $data
* @return bool
*/
protected static function validateOembedResponse($data = null)
{
return !empty($data) &&
isset($data['html'], $data['type'])
&& in_array($data['type'], static::$allowed_types, true);
}
/**
* Checks if a given URL Supports OEmbed
*
* @param string $url
* @return boolean
*/
public static function hasOEmbedSupport($url)
{
return static::getProviderByUrl($url) != null;
}
/**
* @param $url
* @return mixed|null
*/
public static function getProviderByUrl($url)
{
foreach (static::getProviders() as $provider) {
if (isset($provider['pattern']) && preg_match($provider['pattern'], $url)) {
return $provider['endpoint'];
}
}
return null;
}
/**
* Executes the remote fetch call in case the [$maxUrlFetchLimit] is not reached.
*
* @param string $url
* @return array|null
* @throws RestrictedCallException
*/
protected static function fetchUrl($url)
{
if (static::$urlsFetched >= static::$maxUrlFetchLimit) {
throw new RestrictedCallException('Max url fetch limit exceeded.');
}
static::$urlsFetched++;
return static::getClient()->fetchUrl($url);
}
/**
* Returns the UrlOembedClient responsible for fetching OEmbed results.
*
* @return UrlOembedClient
*/
public static function getClient()
{
if (!static::$client) {
static::$client = new UrlOembedHttpClient();
}
return static::$client;
}
/**
* Sets the UrlOembedClient responsible for fetching OEmbed results.
*
* @param null $client
*/
public static function setClient($client = null)
{
static::$client = $client;
}
/**
* Returns all available OEmbed providers
*
* @return array
*/
public static function getProviders()
{
$providers = Yii::$app->settings->get('oembedProviders');
if (!empty($providers)) {
return Json::decode($providers);
}
return [];
}
/**
* Saves an array of available OEmbed providers
*
* @param array $providers
*/
public static function setProviders($providers)
{
Yii::$app->settings->set('oembedProviders', Json::encode($providers));
}
/**
* Check the domain is always allowed to display for current User
*
* @param string $url Domain or full URL
* @return array
*/
public static function isAllowedDomain(string $url): bool
{
$oembedSettings = new OEmbedSettingsForm();
if (!$oembedSettings->requestConfirmation) {
return true;
}
if (Yii::$app->user->isGuest) {
return true;
}
if (preg_match('#^(https?:)?//#i',$url)) {
$url = parse_url($url);
if (!isset($url['host'])) {
return false;
}
$url = $url['host'];
}
return array_search($url, self::getAllowedDomains()) !== false;
}
/**
* Get domains always allowed to be displayed for current User
*
* @return array
*/
public static function getAllowedDomains(): array
{
if (Yii::$app->user->isGuest) {
return [];
}
/* @var User $user */
$user = Yii::$app->user->getIdentity();
$allowedUrls = $user->settings->get('allowedOembedUrls');
return empty($allowedUrls) ? [] : explode(',', $allowedUrls);
}
/**
* Add a new allowed domain for oembed URLs to the current User settings
*
* @param string $domain
* @return bool
*/
public static function saveAllowedDomain(string $domain): bool
{
if (Yii::$app->user->isGuest) {
return false;
}
if (self::isAllowedDomain($domain)) {
return true;
}
$allowedUrls = self::getAllowedDomains();
$allowedUrls[] = $domain;
/* @var User $user */
$user = Yii::$app->user->getIdentity();
$user->settings->set('allowedOembedUrls', implode(',', $allowedUrls));
return true;
}
}