mirror of
https://github.com/the-djmaze/snappymail.git
synced 2024-09-20 07:35:55 +08:00
Removed: twitter, google, dropbox, facebook, owncloud, modernizr from JavaScript
This commit is contained in:
parent
e8d38a6870
commit
4468d5bd22
|
@ -1,28 +0,0 @@
|
|||
************************************************************************
|
||||
*
|
||||
* ownCloud/Nextcloud - RainLoop Webmail package
|
||||
*
|
||||
* @author RainLoop Team
|
||||
* @copyright 2019 RainLoop Team
|
||||
*
|
||||
* https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud
|
||||
*
|
||||
************************************************************************
|
||||
|
||||
REQUIREMENTS:
|
||||
- ownCloud version 6 or higher
|
||||
or
|
||||
- Nextcloud version 10 or higher
|
||||
|
||||
|
||||
INSTALL:
|
||||
- Unpack the RainLoop Webmail application package in the apps directory of your ownCloud or Nextcloud instance
|
||||
- Make sure the rights are appropriately set
|
||||
|
||||
CONFIGURATION:
|
||||
- ownCloud:
|
||||
1) In the Apps > Enable 'RainLoop' plugin
|
||||
2) In the Settings > Personal > Type your email server login and password
|
||||
- Nextcloud:
|
||||
1) In the Apps > Enable 'RainLoop' plugin after checking 'Enable experimental apps'
|
||||
2) In the Settings > Personal > Type your email server login and password
|
|
@ -1 +0,0 @@
|
|||
0.0
|
|
@ -1,18 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* ownCloud - RainLoop mail plugin
|
||||
*
|
||||
* @author RainLoop Team
|
||||
*
|
||||
* https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud
|
||||
*/
|
||||
|
||||
OCP\User::checkAdminUser();
|
||||
|
||||
OCP\Util::addScript('rainloop', 'admin');
|
||||
|
||||
$oTemplate = new OCP\Template('rainloop', 'admin-local');
|
||||
$oTemplate->assign('rainloop-admin-panel-link', OC_RainLoop_Helper::getAppUrl().'?admin');
|
||||
$oTemplate->assign('rainloop-autologin', OCP\Config::getAppValue('rainloop', 'rainloop-autologin', false));
|
||||
return $oTemplate->fetchPage();
|
|
@ -1,35 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* ownCloud - RainLoop mail plugin
|
||||
*
|
||||
* @author RainLoop Team
|
||||
*
|
||||
* https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud
|
||||
*/
|
||||
|
||||
OCP\JSON::checkAdminUser();
|
||||
OCP\JSON::checkAppEnabled('rainloop');
|
||||
OCP\JSON::callCheck();
|
||||
|
||||
$sUrl = '';
|
||||
$sPath = '';
|
||||
$bAutologin = false;
|
||||
|
||||
if (isset($_POST['appname']) && 'rainloop' === $_POST['appname'])
|
||||
{
|
||||
OCP\Config::setAppValue('rainloop', 'rainloop-autologin', isset($_POST['rainloop-autologin']) ?
|
||||
'1' === $_POST['rainloop-autologin'] : false);
|
||||
|
||||
$bAutologin = OCP\Config::getAppValue('rainloop', 'rainloop-autologin', false);
|
||||
}
|
||||
else
|
||||
{
|
||||
sleep(1);
|
||||
OC_JSON::error(array('Message' => 'Invalid Argument(s)'));
|
||||
return false;
|
||||
}
|
||||
|
||||
sleep(1);
|
||||
OCP\JSON::success(array('Message' => 'Saved successfully'));
|
||||
return true;
|
|
@ -1,46 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* ownCloud - RainLoop mail plugin
|
||||
*
|
||||
* @author RainLoop Team
|
||||
*
|
||||
* https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud
|
||||
*/
|
||||
|
||||
OCP\JSON::checkLoggedIn();
|
||||
OCP\JSON::checkAppEnabled('rainloop');
|
||||
OCP\JSON::callCheck();
|
||||
|
||||
$sEmail = '';
|
||||
$sLogin = '';
|
||||
|
||||
if (isset($_POST['appname'], $_POST['rainloop-password'], $_POST['rainloop-email']) && 'rainloop' === $_POST['appname'])
|
||||
{
|
||||
$sUser = OCP\User::getUser();
|
||||
|
||||
$sPostEmail = $_POST['rainloop-email'];
|
||||
|
||||
OCP\Config::setUserValue($sUser, 'rainloop', 'rainloop-email', $sPostEmail);
|
||||
|
||||
$sPass = $_POST['rainloop-password'];
|
||||
if ('******' !== $sPass && '' !== $sPass)
|
||||
{
|
||||
include_once OC_App::getAppPath('rainloop').'/lib/RainLoopHelper.php';
|
||||
|
||||
OCP\Config::setUserValue($sUser, 'rainloop', 'rainloop-password',
|
||||
OC_RainLoop_Helper::encodePassword($sPass, md5($sPostEmail)));
|
||||
}
|
||||
|
||||
$sEmail = OCP\Config::getUserValue($sUser, 'rainloop', 'rainloop-email', '');
|
||||
}
|
||||
else
|
||||
{
|
||||
sleep(1);
|
||||
OC_JSON::error(array('Message' => 'Invalid argument(s)', 'Email' => $sEmail));
|
||||
return false;
|
||||
}
|
||||
|
||||
sleep(1);
|
||||
OCP\JSON::success(array('Message' => 'Saved successfully', 'Email' => $sEmail));
|
||||
return true;
|
|
@ -1,36 +0,0 @@
|
|||
<?php
|
||||
|
||||
OCP\User::checkLoggedIn();
|
||||
|
||||
if (@file_exists(__DIR__.'/app/index.php'))
|
||||
{
|
||||
include_once OC_App::getAppPath('rainloop').'/lib/RainLoopHelper.php';
|
||||
|
||||
OC_RainLoop_Helper::regRainLoopDataFunction();
|
||||
|
||||
if (isset($_GET['OwnCloudAuth']))
|
||||
{
|
||||
$sEmail = '';
|
||||
$sEncodedPassword = '';
|
||||
|
||||
$sUser = OCP\User::getUser();
|
||||
|
||||
if (OCP\Config::getAppValue('rainloop', 'rainloop-autologin', false))
|
||||
{
|
||||
$sEmail = $sUser;
|
||||
$sEncodedPassword = OCP\Config::getUserValue($sUser, 'rainloop', 'rainloop-autologin-password', '');
|
||||
}
|
||||
else
|
||||
{
|
||||
$sEmail = OCP\Config::getUserValue($sUser, 'rainloop', 'rainloop-email', '');
|
||||
$sEncodedPassword = OCP\Config::getUserValue($sUser, 'rainloop', 'rainloop-password', '');
|
||||
}
|
||||
|
||||
$sDecodedPassword = OC_RainLoop_Helper::decodePassword($sEncodedPassword, md5($sEmail));
|
||||
|
||||
$_ENV['___rainloop_owncloud_email'] = $sEmail;
|
||||
$_ENV['___rainloop_owncloud_password'] = $sDecodedPassword;
|
||||
}
|
||||
|
||||
include __DIR__.'/app/index.php';
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* ownCloud - RainLoop mail plugin
|
||||
*
|
||||
* @author RainLoop Team
|
||||
*
|
||||
* https://github.com/RainLoop/owncloud
|
||||
*/
|
||||
|
||||
OC::$CLASSPATH['OC_RainLoop_Helper'] = OC_App::getAppPath('rainloop') . '/lib/RainLoopHelper.php';
|
||||
|
||||
OCP\App::registerAdmin('rainloop', 'admin');
|
||||
OCP\App::registerPersonal('rainloop', 'personal');
|
||||
|
||||
if (OCP\Config::getAppValue('rainloop', 'rainloop-autologin', false))
|
||||
{
|
||||
OCP\Util::connectHook('OC_User', 'post_login', 'OC_RainLoop_Helper', 'login');
|
||||
OCP\Util::connectHook('OC_User', 'post_setPassword', 'OC_RainLoop_Helper', 'changePassword');
|
||||
}
|
||||
|
||||
OCP\Util::connectHook('OC_User', 'logout', 'OC_RainLoop_Helper', 'logout');
|
||||
|
||||
OCP\Util::addScript('rainloop', 'rainloop');
|
||||
|
||||
OCP\App::addNavigationEntry(array(
|
||||
'id' => 'rainloop_index',
|
||||
'order' => 10,
|
||||
'href' => OCP\Util::linkToRoute('rainloop_index'),
|
||||
'icon' => OCP\Util::imagePath('rainloop', 'mail.png'),
|
||||
'name' => 'Email'
|
||||
));
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<info xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd">
|
||||
<id>rainloop</id>
|
||||
<name>RainLoop</name>
|
||||
<summary>RainLoop Webmail</summary>
|
||||
<description>Simple, modern and fast web-based email client.</description>
|
||||
<version>0.0</version>
|
||||
<licence></licence>
|
||||
<author>RainLoop Team</author>
|
||||
<require>6.0</require>
|
||||
<ocsid>165254</ocsid>
|
||||
<dependencies>
|
||||
<php min-version="5.4" />
|
||||
<owncloud min-version="6" max-version="9.2" />
|
||||
<nextcloud min-version="8" max-version="11" />
|
||||
</dependencies>
|
||||
<category>tools</category>
|
||||
<website>https://www.rainloop.net/</website>
|
||||
<bugs>https://github.com/RainLoop/rainloop-webmail/issues</bugs>
|
||||
</info>
|
|
@ -1,13 +0,0 @@
|
|||
<?php
|
||||
|
||||
$this->create('rainloop_index', '/')
|
||||
->actionInclude('rainloop/index.php');
|
||||
|
||||
$this->create('rainloop_app', '/app/')
|
||||
->actionInclude('rainloop/app.php');
|
||||
|
||||
$this->create('rainloop_ajax_personal', 'ajax/personal.php')
|
||||
->actionInclude('rainloop/ajax/personal.php');
|
||||
|
||||
$this->create('rainloop_ajax_admin', 'ajax/admin.php')
|
||||
->actionInclude('rainloop/ajax/admin.php');
|
|
@ -1,6 +0,0 @@
|
|||
/*
|
||||
Empty style sheet!
|
||||
Only needed to give you the opportunity to theme the owncloud part
|
||||
of the rainloop app with the theming system integrated in ownCoud
|
||||
if the rainloop app is activated.
|
||||
*/
|
Binary file not shown.
Before Width: | Height: | Size: 832 B |
Binary file not shown.
Before Width: | Height: | Size: 478 B |
|
@ -1,26 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* ownCloud - RainLoop mail plugin
|
||||
*
|
||||
* @author RainLoop Team
|
||||
*
|
||||
* https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud
|
||||
*/
|
||||
|
||||
OCP\User::checkLoggedIn();
|
||||
OCP\App::checkAppEnabled('rainloop');
|
||||
OCP\App::setActiveNavigationEntry('rainloop_index');
|
||||
|
||||
// Load the empty file ../css/style.css, that's needed to allow theming of
|
||||
// the ownCloud header and navigation if rainloop is the active app.
|
||||
OCP\Util::addStyle('rainloop', 'style');
|
||||
|
||||
include_once OC_App::getAppPath('rainloop').'/lib/RainLoopHelper.php';
|
||||
|
||||
$sUrl = OC_RainLoop_Helper::normalizeUrl(OC_RainLoop_Helper::getAppUrl());
|
||||
|
||||
$oTemplate = new OCP\Template('rainloop', 'index', 'user');
|
||||
$oTemplate->assign('rainloop-iframe-url', OC_RainLoop_Helper::normalizeUrl($sUrl).'?OwnCloudAuth');
|
||||
|
||||
$oTemplate->printpage();
|
|
@ -1,12 +0,0 @@
|
|||
|
||||
/**
|
||||
* ownCloud - RainLoop mail plugin
|
||||
*
|
||||
* @author RainLoop Team
|
||||
*
|
||||
* https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud
|
||||
*/
|
||||
|
||||
$(function() {
|
||||
RainLoopFormHelper('#mail-rainloop-admin-form', 'admin.php');
|
||||
});
|
|
@ -1,12 +0,0 @@
|
|||
|
||||
/**
|
||||
* ownCloud - RainLoop mail plugin
|
||||
*
|
||||
* @author RainLoop Team
|
||||
*
|
||||
* https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud
|
||||
*/
|
||||
|
||||
$(function() {
|
||||
RainLoopFormHelper('#mail-rainloop-personal-form', 'personal.php');
|
||||
});
|
|
@ -1,76 +0,0 @@
|
|||
|
||||
function RainLoopFormHelper(sID, sAjaxFile, fCallback)
|
||||
{
|
||||
try
|
||||
{
|
||||
var
|
||||
oForm = $(sID),
|
||||
oSubmit = $('#rainloop-save-button', oForm),
|
||||
sSubmitValue = oSubmit.val(),
|
||||
oDesc = oForm.find('.rainloop-result-desc')
|
||||
;
|
||||
|
||||
oSubmit.click(function (oEvent) {
|
||||
|
||||
var oDefAjax = null;
|
||||
|
||||
oEvent.preventDefault();
|
||||
|
||||
oForm
|
||||
.addClass('rainloop-ajax')
|
||||
.removeClass('rainloop-error')
|
||||
.removeClass('rainloop-success')
|
||||
;
|
||||
|
||||
oDesc.text('');
|
||||
oSubmit.val('...');
|
||||
|
||||
oDefAjax = $.ajax({
|
||||
'type': 'POST',
|
||||
'async': true,
|
||||
'url': OC.filePath('rainloop', 'ajax', sAjaxFile),
|
||||
'data': oForm.serialize(),
|
||||
'dataType': 'json',
|
||||
'global': true
|
||||
});
|
||||
|
||||
oDefAjax.always(function (oData) {
|
||||
|
||||
var bResult = false;
|
||||
|
||||
oForm.removeClass('rainloop-ajax');
|
||||
oSubmit.val(sSubmitValue);
|
||||
|
||||
if (oData)
|
||||
{
|
||||
bResult = 'success' === oData['status'];
|
||||
if (oData['Message'])
|
||||
{
|
||||
oDesc.text(oData['Message']);
|
||||
}
|
||||
}
|
||||
|
||||
if (bResult)
|
||||
{
|
||||
oForm.addClass('rainloop-success');
|
||||
}
|
||||
else
|
||||
{
|
||||
oForm.addClass('rainloop-error');
|
||||
if ('' === oDesc.text())
|
||||
{
|
||||
oDesc.text('Error');
|
||||
}
|
||||
}
|
||||
|
||||
if (fCallback)
|
||||
{
|
||||
fCallback(bResult, oData);
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
catch(e) {}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
$(function (window, document) {
|
||||
|
||||
var
|
||||
buffer = 5,
|
||||
ifr = document.getElementById('rliframe')
|
||||
;
|
||||
|
||||
|
||||
function pageY(elem) {
|
||||
return elem.offsetParent ? (elem.offsetTop + pageY(elem.offsetParent)) : elem.offsetTop;
|
||||
}
|
||||
|
||||
function resizeIframe() {
|
||||
var height = document.documentElement.clientHeight;
|
||||
height -= pageY(ifr) + buffer;
|
||||
height = (height < 0) ? 0 : height;
|
||||
ifr.style.height = height + 'px';
|
||||
}
|
||||
|
||||
if (ifr)
|
||||
{
|
||||
ifr.onload = resizeIframe;
|
||||
window.onresize = resizeIframe;
|
||||
}
|
||||
|
||||
}(window, document));
|
|
@ -1,352 +0,0 @@
|
|||
<?php
|
||||
|
||||
class OC_RainLoop_Helper
|
||||
{
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getAppUrl()
|
||||
{
|
||||
if (class_exists('\\OCP\\Util'))
|
||||
{
|
||||
return OCP\Util::linkToRoute('rainloop_app');
|
||||
}
|
||||
|
||||
$sRequestUri = empty($_SERVER['REQUEST_URI']) ? '': trim($_SERVER['REQUEST_URI']);
|
||||
$sRequestUri = preg_replace('/index.php\/.+$/', 'index.php/', $sRequestUri);
|
||||
$sRequestUri = $sRequestUri.'apps/rainloop/app/';
|
||||
|
||||
return '/'.ltrim($sRequestUri, '/\\');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sPath
|
||||
* @param string $sEmail
|
||||
* @param string $sPassword
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getSsoHash($sPath, $sEmail, $sPassword)
|
||||
{
|
||||
$SsoHash = '';
|
||||
|
||||
$sPath = rtrim(trim($sPath), '\\/').'/index.php';
|
||||
if (file_exists($sPath))
|
||||
{
|
||||
self::regRainLoopDataFunction();
|
||||
|
||||
$_ENV['RAINLOOP_INCLUDE_AS_API'] = true;
|
||||
include $sPath;
|
||||
|
||||
if (class_exists('\\RainLoop\\Api'))
|
||||
{
|
||||
$SsoHash = \RainLoop\Api::GetUserSsoHash($sEmail, $sPassword);
|
||||
}
|
||||
}
|
||||
|
||||
return $SsoHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sUrl
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function normalizeUrl($sUrl)
|
||||
{
|
||||
$sUrl = rtrim(trim($sUrl), '/\\');
|
||||
if ('.php' !== strtolower(substr($sUrl, -4)))
|
||||
{
|
||||
$sUrl .= '/';
|
||||
}
|
||||
|
||||
return $sUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public static function mcryptSupported()
|
||||
{
|
||||
return function_exists('mcrypt_encrypt') &&
|
||||
function_exists('mcrypt_decrypt') &&
|
||||
defined('MCRYPT_RIJNDAEL_256') &&
|
||||
defined('MCRYPT_MODE_ECB');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function openSslSupportedMethod()
|
||||
{
|
||||
$method = 'AES-256-CBC';
|
||||
return function_exists('openssl_encrypt') &&
|
||||
function_exists('openssl_decrypt') &&
|
||||
function_exists('openssl_random_pseudo_bytes') &&
|
||||
function_exists('openssl_cipher_iv_length') &&
|
||||
function_exists('openssl_get_cipher_methods') &&
|
||||
defined('OPENSSL_RAW_DATA') && defined('OPENSSL_ZERO_PADDING') &&
|
||||
in_array($method, openssl_get_cipher_methods()) ? $method : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sMethod
|
||||
* @param string $sPassword
|
||||
* @param string $sSalt
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function encodePasswordSsl($sMethod, $sPassword, $sSalt)
|
||||
{
|
||||
$sData = base64_encode($sPassword);
|
||||
|
||||
$iv = @openssl_random_pseudo_bytes(openssl_cipher_iv_length($sMethod));
|
||||
$r = @openssl_encrypt($sData, $sMethod, md5($sSalt), OPENSSL_RAW_DATA, $iv);
|
||||
|
||||
return @base64_encode(base64_encode($r).'|'.base64_encode($iv));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sMethod
|
||||
* @param string $sPassword
|
||||
* @param string $sSalt
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function decodePasswordSsl($sMethod, $sPassword, $sSalt)
|
||||
{
|
||||
$sLine = base64_decode(trim($sPassword));
|
||||
$aParts = explode('|', $sLine, 2);
|
||||
|
||||
if (is_array($aParts) && !empty($aParts[0]) && !empty($aParts[1])) {
|
||||
|
||||
$sData = @base64_decode($aParts[0]);
|
||||
$iv = @base64_decode($aParts[1]);
|
||||
|
||||
return @base64_decode(trim(
|
||||
@openssl_decrypt($sData, $sMethod, md5($sSalt), OPENSSL_RAW_DATA, $iv)
|
||||
));
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sPassword
|
||||
* @param string $sSalt
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function encodePassword($sPassword, $sSalt)
|
||||
{
|
||||
$method = self::openSslSupportedMethod();
|
||||
if ($method)
|
||||
{
|
||||
return self::encodePasswordSsl($method, $sPassword, $sSalt);
|
||||
}
|
||||
else if (self::mcryptSupported())
|
||||
{
|
||||
return @trim(base64_encode(
|
||||
@mcrypt_encrypt(MCRYPT_RIJNDAEL_256, md5($sSalt), base64_encode($sPassword), MCRYPT_MODE_ECB)
|
||||
));
|
||||
}
|
||||
|
||||
return @trim(base64_encode($sPassword));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sPassword
|
||||
* @param string $sSalt
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function decodePassword($sPassword, $sSalt)
|
||||
{
|
||||
$method = self::openSslSupportedMethod();
|
||||
if ($method)
|
||||
{
|
||||
return self::decodePasswordSsl($method, $sPassword, $sSalt);
|
||||
}
|
||||
else if (self::mcryptSupported())
|
||||
{
|
||||
return @base64_decode(trim(
|
||||
@mcrypt_decrypt(MCRYPT_RIJNDAEL_256, md5($sSalt), base64_decode(trim($sPassword)), MCRYPT_MODE_ECB)
|
||||
));
|
||||
}
|
||||
|
||||
return @base64_decode(trim($sPassword));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $aParams
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function login($aParams)
|
||||
{
|
||||
if (isset($aParams['uid'], $aParams['password']))
|
||||
{
|
||||
$sUser = $aParams['uid'];
|
||||
|
||||
$sEmail = $sUser;
|
||||
$sPassword = $aParams['password'];
|
||||
|
||||
return OCP\Config::setUserValue($sUser, 'rainloop', 'rainloop-autologin-password',
|
||||
self::encodePassword($sPassword, md5($sEmail)));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function logout()
|
||||
{
|
||||
OCP\Config::setUserValue(
|
||||
OCP\User::getUser(), 'rainloop', 'rainloop-autologin-password', '');
|
||||
|
||||
$sApiPath = __DIR__.'/../app/index.php';
|
||||
if (file_exists($sApiPath))
|
||||
{
|
||||
self::regRainLoopDataFunction();
|
||||
|
||||
$_ENV['RAINLOOP_INCLUDE_AS_API'] = true;
|
||||
include $sApiPath;
|
||||
|
||||
if (class_exists('\\RainLoop\\Api'))
|
||||
{
|
||||
\RainLoop\Api::LogoutCurrentLogginedUser();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function changePassword($aParams)
|
||||
{
|
||||
if (isset($aParams['uid'], $aParams['password']))
|
||||
{
|
||||
$sUser = $aParams['uid'];
|
||||
|
||||
$sEmail = $sUser;
|
||||
$sPassword = $aParams['password'];
|
||||
|
||||
OCP\Util::writeLog('rainloop', 'rainloop|login: Setting new RainLoop password for '.$sEmail, OCP\Util::DEBUG);
|
||||
|
||||
OCP\Config::setUserValue($sUser, 'rainloop', 'rainloop-autologin-password',
|
||||
self::encodePassword($sPassword, md5($sEmail)));
|
||||
|
||||
OCP\Config::setUserValue($sUser, 'rainloop', 'rainloop-password',
|
||||
self::encodePassword($sPassword, md5($sEmail)));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function regRainLoopDataFunction()
|
||||
{
|
||||
if (!@function_exists('__get_custom_data_full_path'))
|
||||
{
|
||||
$_ENV['RAINLOOP_OWNCLOUD'] = true;
|
||||
|
||||
function __get_custom_data_full_path()
|
||||
{
|
||||
$sData = __DIR__.'/../../data/';
|
||||
if (class_exists('OC_Config'))
|
||||
{
|
||||
$sData = rtrim(trim(OC_Config::getValue('datadirectory', '')), '\\/').'/';
|
||||
}
|
||||
else if (class_exists('OC'))
|
||||
{
|
||||
$sData = rtrim(trim(
|
||||
OC::$server->getSystemConfig()->getValue(
|
||||
'datadirectory',
|
||||
isset(\OC::$SERVERROOT) ? \OC::$SERVERROOT.'/data' : ''
|
||||
)
|
||||
), '\\/').'/';
|
||||
}
|
||||
|
||||
return @is_dir($sData) ? $sData.'rainloop-storage' : '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function mimeContentType($filename) {
|
||||
|
||||
$mime_types = array(
|
||||
|
||||
'woff' => 'application/font-woff',
|
||||
|
||||
'txt' => 'text/plain',
|
||||
'htm' => 'text/html',
|
||||
'html' => 'text/html',
|
||||
'php' => 'text/html',
|
||||
'css' => 'text/css',
|
||||
'js' => 'application/javascript',
|
||||
'json' => 'application/json',
|
||||
'xml' => 'application/xml',
|
||||
'swf' => 'application/x-shockwave-flash',
|
||||
'flv' => 'video/x-flv',
|
||||
|
||||
// images
|
||||
'png' => 'image/png',
|
||||
'jpe' => 'image/jpeg',
|
||||
'jpeg' => 'image/jpeg',
|
||||
'jpg' => 'image/jpeg',
|
||||
'gif' => 'image/gif',
|
||||
'bmp' => 'image/bmp',
|
||||
'ico' => 'image/vnd.microsoft.icon',
|
||||
'tiff' => 'image/tiff',
|
||||
'tif' => 'image/tiff',
|
||||
'svg' => 'image/svg+xml',
|
||||
'svgz' => 'image/svg+xml',
|
||||
|
||||
// archives
|
||||
'zip' => 'application/zip',
|
||||
'rar' => 'application/x-rar-compressed',
|
||||
'exe' => 'application/x-msdownload',
|
||||
'msi' => 'application/x-msdownload',
|
||||
'cab' => 'application/vnd.ms-cab-compressed',
|
||||
|
||||
// audio/video
|
||||
'mp3' => 'audio/mpeg',
|
||||
'qt' => 'video/quicktime',
|
||||
'mov' => 'video/quicktime',
|
||||
|
||||
// adobe
|
||||
'pdf' => 'application/pdf',
|
||||
'psd' => 'image/vnd.adobe.photoshop',
|
||||
'ai' => 'application/postscript',
|
||||
'eps' => 'application/postscript',
|
||||
'ps' => 'application/postscript',
|
||||
|
||||
// ms office
|
||||
'doc' => 'application/msword',
|
||||
'rtf' => 'application/rtf',
|
||||
'xls' => 'application/vnd.ms-excel',
|
||||
'ppt' => 'application/vnd.ms-powerpoint',
|
||||
|
||||
// open office
|
||||
'odt' => 'application/vnd.oasis.opendocument.text',
|
||||
'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
|
||||
);
|
||||
|
||||
if (0 < strpos($filename, '.'))
|
||||
{
|
||||
$ext = strtolower(array_pop(explode('.',$filename)));
|
||||
if (array_key_exists($ext, $mime_types))
|
||||
{
|
||||
return $mime_types[$ext];
|
||||
}
|
||||
else if (function_exists('finfo_open'))
|
||||
{
|
||||
$finfo = finfo_open(FILEINFO_MIME);
|
||||
$mimetype = finfo_file($finfo, $filename);
|
||||
finfo_close($finfo);
|
||||
return $mimetype;
|
||||
}
|
||||
}
|
||||
|
||||
return 'application/octet-stream';
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* ownCloud - RainLoop mail plugin
|
||||
*
|
||||
* @author RainLoop Team
|
||||
*
|
||||
* https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud
|
||||
*/
|
||||
|
||||
OCP\User::checkLoggedIn();
|
||||
OCP\App::checkAppEnabled('rainloop');
|
||||
|
||||
OCP\Util::addScript('rainloop', 'personal');
|
||||
|
||||
if (OCP\Config::getAppValue('rainloop', 'rainloop-autologin', false))
|
||||
{
|
||||
$oTemplate = new OCP\Template('rainloop', 'empty');
|
||||
}
|
||||
else
|
||||
{
|
||||
$sUser = OCP\User::getUser();
|
||||
|
||||
$oTemplate = new OCP\Template('rainloop', 'personal');
|
||||
|
||||
$sEmail = OCP\Config::getUserValue($sUser, 'rainloop', 'rainloop-email', '');
|
||||
$sPass = OCP\Config::getUserValue($sUser, 'rainloop', 'rainloop-password', '');
|
||||
|
||||
$oTemplate->assign('rainloop-email', $sEmail);
|
||||
$oTemplate->assign('rainloop-password', 0 === strlen($sPass) && 0 === strlen($sEmail) ? '' : '******');
|
||||
}
|
||||
|
||||
return $oTemplate->fetchPage();
|
|
@ -1,29 +0,0 @@
|
|||
<div class="section">
|
||||
<form id="mail-rainloop-admin-form" action="#" method="post">
|
||||
<input type="hidden" name="requesttoken" value="<?php echo $_['requesttoken'] ?>" id="requesttoken">
|
||||
<input type="hidden" name="appname" value="rainloop">
|
||||
|
||||
<fieldset class="personalblock">
|
||||
<h2><?php p($l->t('RainLoop Webmail')); ?></h2>
|
||||
<br />
|
||||
<?php if ($_['rainloop-admin-panel-link']): ?>
|
||||
<p>
|
||||
<a href="<?php echo $_['rainloop-admin-panel-link'] ?>" target="_blank" style="text-decoration: underline">
|
||||
<?php p($l->t('Go to RainLoop Webmail admin panel')); ?>
|
||||
</a>
|
||||
</p>
|
||||
<br />
|
||||
<?php endif; ?>
|
||||
<p>
|
||||
<input type="checkbox" id="rainloop-autologin" id="rainloop-autologin" name="rainloop-autologin" value="1" <?php if ($_['rainloop-autologin']): ?>checked="checked"<?php endif; ?> />
|
||||
<label for="rainloop-autologin">
|
||||
<?php p($l->t('Automatically login with ownCloud user credentials')); ?>
|
||||
</label>
|
||||
<br />
|
||||
<br />
|
||||
<input type="button" id="rainloop-save-button" name="rainloop-save-button" value="<?php p($l->t('Save')); ?>" />
|
||||
<span class="rainloop-result-desc"></span>
|
||||
</p>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
|
@ -1 +0,0 @@
|
|||
<!-- empty -->
|
|
@ -1,3 +0,0 @@
|
|||
<div style="margin: 20px">
|
||||
<?php p($l->t('RainLoop Webmail is not configured yet.')); ?>
|
||||
</div>
|
|
@ -1,3 +0,0 @@
|
|||
<div style="box-sizing: border-box; width: 100%; height: 100%; padding: 0px; margin: 0px; background-color: #383c43; position: relative; overflow: hidden;"
|
||||
><iframe id="rliframe" style="border: none; width: 100%; height: 100%; position: absolute; top: 0px; left: 0px; right: 0px; bottom: 0px;" tabindex="-1" frameborder="0"
|
||||
src="<?php echo $_['rainloop-iframe-url']; ?>"></iframe></div><?php OCP\Util::addScript('rainloop', 'resize');
|
|
@ -1,20 +0,0 @@
|
|||
<div class="section">
|
||||
<form id="mail-rainloop-personal-form" action="#" method="post">
|
||||
<input type="hidden" name="requesttoken" value="<?php echo $_['requesttoken'] ?>" id="requesttoken">
|
||||
<input type="hidden" name="appname" value="rainloop">
|
||||
|
||||
<fieldset class="personalblock">
|
||||
<h2><?php p($l->t('RainLoop Webmail')); ?></h2>
|
||||
<p>
|
||||
<input type="text" id="rainloop-email" name="rainloop-email"
|
||||
value="<?php echo $_['rainloop-email']; ?>" placeholder="<?php p($l->t('Email')); ?>" />
|
||||
|
||||
<input type="password" id="rainloop-password" name="rainloop-password" autocomplete="current-password"
|
||||
value="<?php echo $_['rainloop-password']; ?>" placeholder="<?php p($l->t('Password')); ?>" />
|
||||
|
||||
<input type="button" id="rainloop-save-button" name="rainloop-save-button" value="<?php p($l->t('Save')); ?>" />
|
||||
<span class="rainloop-result-desc"></span>
|
||||
</p>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
|
@ -26,7 +26,6 @@ import * as Settings from 'Storage/Settings';
|
|||
|
||||
import LanguageStore from 'Stores/Language';
|
||||
import ThemeStore from 'Stores/Theme';
|
||||
import SocialStore from 'Stores/Social';
|
||||
|
||||
import { routeOff, setHash } from 'Knoin/Knoin';
|
||||
import { AbstractBoot } from 'Knoin/AbstractBoot';
|
||||
|
@ -38,7 +37,6 @@ class AbstractApp extends AbstractBoot {
|
|||
constructor() {
|
||||
super();
|
||||
|
||||
this.googlePreviewSupportedCache = null;
|
||||
this.isLocalAutocomplete = true;
|
||||
this.iframe = null;
|
||||
this.lastErrorTime = 0;
|
||||
|
@ -140,18 +138,6 @@ class AbstractApp extends AbstractBoot {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
googlePreviewSupported() {
|
||||
if (null === this.googlePreviewSupportedCache) {
|
||||
this.googlePreviewSupportedCache =
|
||||
!!Settings.settingsGet('AllowGoogleSocial') && !!Settings.settingsGet('AllowGoogleSocialPreview');
|
||||
}
|
||||
|
||||
return this.googlePreviewSupportedCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} title
|
||||
*/
|
||||
|
@ -345,7 +331,6 @@ class AbstractApp extends AbstractBoot {
|
|||
|
||||
LanguageStore.populate();
|
||||
ThemeStore.populate();
|
||||
SocialStore.populate();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
104
dev/App/User.js
104
dev/App/User.js
|
@ -56,17 +56,13 @@ import {
|
|||
mailBox,
|
||||
root,
|
||||
openPgpWorkerJs,
|
||||
openPgpJs,
|
||||
socialGoogle,
|
||||
socialTwitter,
|
||||
socialFacebook
|
||||
openPgpJs
|
||||
} from 'Common/Links';
|
||||
|
||||
import * as Events from 'Common/Events';
|
||||
|
||||
import { getNotification, i18n } from 'Common/Translator';
|
||||
|
||||
import SocialStore from 'Stores/Social';
|
||||
import AppStore from 'Stores/User/App';
|
||||
import SettingsStore from 'Stores/User/Settings';
|
||||
import NotificationStore from 'Stores/User/Notification';
|
||||
|
@ -160,8 +156,6 @@ class AppUser extends AbstractApp {
|
|||
.removeAttr('style');
|
||||
}, Magics.Time1s);
|
||||
}
|
||||
|
||||
this.socialUsers = _.bind(this.socialUsers, this);
|
||||
}
|
||||
|
||||
remote() {
|
||||
|
@ -854,72 +848,6 @@ class AppUser extends AbstractApp {
|
|||
}
|
||||
}
|
||||
|
||||
googleConnect() {
|
||||
window.open(
|
||||
socialGoogle(),
|
||||
'Google',
|
||||
'left=200,top=100,width=650,height=600,menubar=no,status=no,resizable=yes,scrollbars=yes'
|
||||
);
|
||||
}
|
||||
|
||||
twitterConnect() {
|
||||
window.open(
|
||||
socialTwitter(),
|
||||
'Twitter',
|
||||
'left=200,top=100,width=650,height=350,menubar=no,status=no,resizable=yes,scrollbars=yes'
|
||||
);
|
||||
}
|
||||
|
||||
facebookConnect() {
|
||||
window.open(
|
||||
socialFacebook(),
|
||||
'Facebook',
|
||||
'left=200,top=100,width=650,height=335,menubar=no,status=no,resizable=yes,scrollbars=yes'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean=} fireAllActions = false
|
||||
*/
|
||||
socialUsers(fireAllActions = false) {
|
||||
if (true === fireAllActions) {
|
||||
SocialStore.google.loading(true);
|
||||
SocialStore.facebook.loading(true);
|
||||
SocialStore.twitter.loading(true);
|
||||
}
|
||||
|
||||
Remote.socialUsers((result, data) => {
|
||||
if (StorageResultType.Success === result && data && data.Result) {
|
||||
SocialStore.google.userName(data.Result.Google || '');
|
||||
SocialStore.facebook.userName(data.Result.Facebook || '');
|
||||
SocialStore.twitter.userName(data.Result.Twitter || '');
|
||||
} else {
|
||||
SocialStore.google.userName('');
|
||||
SocialStore.facebook.userName('');
|
||||
SocialStore.twitter.userName('');
|
||||
}
|
||||
|
||||
SocialStore.google.loading(false);
|
||||
SocialStore.facebook.loading(false);
|
||||
SocialStore.twitter.loading(false);
|
||||
});
|
||||
}
|
||||
|
||||
googleDisconnect() {
|
||||
SocialStore.google.loading(true);
|
||||
Remote.googleDisconnect(this.socialUsers);
|
||||
}
|
||||
|
||||
facebookDisconnect() {
|
||||
SocialStore.facebook.loading(true);
|
||||
Remote.facebookDisconnect(this.socialUsers);
|
||||
}
|
||||
|
||||
twitterDisconnect() {
|
||||
SocialStore.twitter.loading(true);
|
||||
Remote.twitterDisconnect(this.socialUsers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} query
|
||||
* @param {Function} autocompleteCallback
|
||||
|
@ -1198,10 +1126,7 @@ class AppUser extends AbstractApp {
|
|||
let contactsSyncInterval = pInt(Settings.settingsGet('ContactsSyncInterval'));
|
||||
|
||||
const jsHash = Settings.appSettingsGet('jsHash'),
|
||||
startupUrl = pString(Settings.settingsGet('StartupUrl')),
|
||||
allowGoogle = Settings.settingsGet('AllowGoogleSocial'),
|
||||
allowFacebook = Settings.settingsGet('AllowFacebookSocial'),
|
||||
allowTwitter = Settings.settingsGet('AllowTwitterSocial');
|
||||
startupUrl = pString(Settings.settingsGet('StartupUrl'));
|
||||
|
||||
if (progressJs) {
|
||||
progressJs.set(90);
|
||||
|
@ -1276,10 +1201,6 @@ class AppUser extends AbstractApp {
|
|||
// false ? AboutUserScreen : null
|
||||
]);
|
||||
|
||||
if (allowGoogle || allowFacebook || allowTwitter) {
|
||||
this.socialUsers(true);
|
||||
}
|
||||
|
||||
Events.sub('interval.2m', () => this.folderInformation(getFolderInboxName()));
|
||||
Events.sub('interval.3m', () => {
|
||||
const sF = FolderStore.currentFolderFullNameRaw();
|
||||
|
@ -1356,27 +1277,6 @@ class AppUser extends AbstractApp {
|
|||
this.bootstartLoginScreen();
|
||||
}
|
||||
|
||||
if (allowGoogle) {
|
||||
window['rl_' + jsHash + '_google_service'] = () => {
|
||||
SocialStore.google.loading(true);
|
||||
this.socialUsers();
|
||||
};
|
||||
}
|
||||
|
||||
if (allowFacebook) {
|
||||
window['rl_' + jsHash + '_facebook_service'] = () => {
|
||||
SocialStore.facebook.loading(true);
|
||||
this.socialUsers();
|
||||
};
|
||||
}
|
||||
|
||||
if (allowTwitter) {
|
||||
window['rl_' + jsHash + '_twitter_service'] = () => {
|
||||
SocialStore.twitter.loading(true);
|
||||
this.socialUsers();
|
||||
};
|
||||
}
|
||||
|
||||
Events.sub('interval.1m', () => momentReload());
|
||||
|
||||
runHook('rl-start-screens');
|
||||
|
|
|
@ -255,35 +255,6 @@ export function exportContactsCsv() {
|
|||
return SERVER_PREFIX + '/Raw/' + SUB_QUERY_PREFIX + '/' + AUTH_PREFIX + '/ContactsCsv/';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} xauth = false
|
||||
* @returns {string}
|
||||
*/
|
||||
export function socialGoogle(xauth = false) {
|
||||
return (
|
||||
SERVER_PREFIX +
|
||||
'SocialGoogle' +
|
||||
('' !== AUTH_PREFIX ? '/' + SUB_QUERY_PREFIX + '/' + AUTH_PREFIX + '/' : '') +
|
||||
(xauth ? '&xauth=1' : '')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
export function socialTwitter() {
|
||||
return SERVER_PREFIX + 'SocialTwitter' + ('' !== AUTH_PREFIX ? '/' + SUB_QUERY_PREFIX + '/' + AUTH_PREFIX + '/' : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
export function socialFacebook() {
|
||||
return (
|
||||
SERVER_PREFIX + 'SocialFacebook' + ('' !== AUTH_PREFIX ? '/' + SUB_QUERY_PREFIX + '/' + AUTH_PREFIX + '/' : '')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} path
|
||||
* @returns {string}
|
||||
|
|
|
@ -909,55 +909,6 @@ class RemoteUserAjax extends AbstractAjaxRemote {
|
|||
clearUserBackground(fCallback) {
|
||||
this.defaultRequest(fCallback, 'ClearUserBackground');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {?Function} fCallback
|
||||
*/
|
||||
facebookUser(fCallback) {
|
||||
this.defaultRequest(fCallback, 'SocialFacebookUserInformation');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {?Function} fCallback
|
||||
*/
|
||||
facebookDisconnect(fCallback) {
|
||||
this.defaultRequest(fCallback, 'SocialFacebookDisconnect');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {?Function} fCallback
|
||||
*/
|
||||
twitterUser(fCallback) {
|
||||
this.defaultRequest(fCallback, 'SocialTwitterUserInformation');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {?Function} fCallback
|
||||
*/
|
||||
twitterDisconnect(fCallback) {
|
||||
this.defaultRequest(fCallback, 'SocialTwitterDisconnect');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {?Function} fCallback
|
||||
*/
|
||||
googleUser(fCallback) {
|
||||
this.defaultRequest(fCallback, 'SocialGoogleUserInformation');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {?Function} fCallback
|
||||
*/
|
||||
googleDisconnect(fCallback) {
|
||||
this.defaultRequest(fCallback, 'SocialGoogleDisconnect');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {?Function} fCallback
|
||||
*/
|
||||
socialUsers(fCallback) {
|
||||
this.defaultRequest(fCallback, 'SocialUsers');
|
||||
}
|
||||
}
|
||||
|
||||
export default new RemoteUserAjax();
|
||||
|
|
|
@ -8,7 +8,6 @@ import { DomainsAdminSettings } from 'Settings/Admin/Domains';
|
|||
import { LoginAdminSettings } from 'Settings/Admin/Login';
|
||||
import { ContactsAdminSettings } from 'Settings/Admin/Contacts';
|
||||
import { SecurityAdminSettings } from 'Settings/Admin/Security';
|
||||
import { SocialAdminSettings } from 'Settings/Admin/Social';
|
||||
import { PluginsAdminSettings } from 'Settings/Admin/Plugins';
|
||||
import { PackagesAdminSettings } from 'Settings/Admin/Packages';
|
||||
import { AboutAdminSettings } from 'Settings/Admin/About';
|
||||
|
@ -57,13 +56,6 @@ class SettingsAdminScreen extends AbstractSettingsScreen {
|
|||
|
||||
addSettingsViewModel(SecurityAdminSettings, 'AdminSettingsSecurity', 'TABS_LABELS/LABEL_SECURITY_NAME', 'security');
|
||||
|
||||
addSettingsViewModel(
|
||||
SocialAdminSettings,
|
||||
'AdminSettingsSocial',
|
||||
'TABS_LABELS/LABEL_INTEGRATION_NAME',
|
||||
'integrations'
|
||||
);
|
||||
|
||||
addSettingsViewModel(PluginsAdminSettings, 'AdminSettingsPlugins', 'TABS_LABELS/LABEL_PLUGINS_NAME', 'plugins');
|
||||
|
||||
addSettingsViewModel(PackagesAdminSettings, 'AdminSettingsPackages', 'TABS_LABELS/LABEL_PACKAGES_NAME', 'packages');
|
||||
|
|
|
@ -16,7 +16,6 @@ import { ContactsUserSettings } from 'Settings/User/Contacts';
|
|||
import { AccountsUserSettings } from 'Settings/User/Accounts';
|
||||
import { FiltersUserSettings } from 'Settings/User/Filters';
|
||||
import { SecurityUserSettings } from 'Settings/User/Security';
|
||||
import { SocialUserSettings } from 'Settings/User/Social';
|
||||
import { ChangePasswordUserSettings } from 'Settings/User/ChangePassword';
|
||||
import { TemplatesUserSettings } from 'Settings/User/Templates';
|
||||
import { FoldersUserSettings } from 'Settings/User/Folders';
|
||||
|
@ -80,15 +79,6 @@ class SettingsUserScreen extends AbstractSettingsScreen {
|
|||
addSettingsViewModel(SecurityUserSettings, 'SettingsSecurity', 'SETTINGS_LABELS/LABEL_SECURITY_NAME', 'security');
|
||||
}
|
||||
|
||||
if (
|
||||
AccountStore.isRootAccount() &&
|
||||
((Settings.settingsGet('AllowGoogleSocial') && Settings.settingsGet('AllowGoogleSocialAuth')) ||
|
||||
Settings.settingsGet('AllowFacebookSocial') ||
|
||||
Settings.settingsGet('AllowTwitterSocial'))
|
||||
) {
|
||||
addSettingsViewModel(SocialUserSettings, 'SettingsSocial', 'SETTINGS_LABELS/LABEL_SOCIAL_NAME', 'social');
|
||||
}
|
||||
|
||||
if (Settings.settingsGet('ChangePasswordIsAllowed')) {
|
||||
addSettingsViewModel(
|
||||
ChangePasswordUserSettings,
|
||||
|
|
|
@ -1,105 +0,0 @@
|
|||
import _ from '_';
|
||||
import ko from 'ko';
|
||||
|
||||
import { SaveSettingsStep, Magics } from 'Common/Enums';
|
||||
import { settingsSaveHelperSimpleFunction, trim, boolToAjax } from 'Common/Utils';
|
||||
|
||||
import SocialStore from 'Stores/Social';
|
||||
|
||||
import Remote from 'Remote/Admin/Ajax';
|
||||
|
||||
class SocialAdminSettings {
|
||||
constructor() {
|
||||
this.googleEnable = SocialStore.google.enabled;
|
||||
this.googleEnableAuth = SocialStore.google.capa.auth;
|
||||
this.googleEnableAuthGmail = SocialStore.google.capa.authGmail;
|
||||
this.googleEnableDrive = SocialStore.google.capa.drive;
|
||||
this.googleEnablePreview = SocialStore.google.capa.preview;
|
||||
|
||||
this.googleEnableRequireClientSettings = SocialStore.google.require.clientSettings;
|
||||
this.googleEnableRequireApiKey = SocialStore.google.require.apiKeySettings;
|
||||
|
||||
this.googleClientID = SocialStore.google.clientID;
|
||||
this.googleClientSecret = SocialStore.google.clientSecret;
|
||||
this.googleApiKey = SocialStore.google.apiKey;
|
||||
|
||||
this.googleTrigger1 = ko.observable(SaveSettingsStep.Idle);
|
||||
this.googleTrigger2 = ko.observable(SaveSettingsStep.Idle);
|
||||
this.googleTrigger3 = ko.observable(SaveSettingsStep.Idle);
|
||||
|
||||
this.facebookSupported = SocialStore.facebook.supported;
|
||||
this.facebookEnable = SocialStore.facebook.enabled;
|
||||
this.facebookAppID = SocialStore.facebook.appID;
|
||||
this.facebookAppSecret = SocialStore.facebook.appSecret;
|
||||
|
||||
this.facebookTrigger1 = ko.observable(SaveSettingsStep.Idle);
|
||||
this.facebookTrigger2 = ko.observable(SaveSettingsStep.Idle);
|
||||
|
||||
this.twitterEnable = SocialStore.twitter.enabled;
|
||||
this.twitterConsumerKey = SocialStore.twitter.consumerKey;
|
||||
this.twitterConsumerSecret = SocialStore.twitter.consumerSecret;
|
||||
|
||||
this.twitterTrigger1 = ko.observable(SaveSettingsStep.Idle);
|
||||
this.twitterTrigger2 = ko.observable(SaveSettingsStep.Idle);
|
||||
|
||||
this.dropboxEnable = SocialStore.dropbox.enabled;
|
||||
this.dropboxApiKey = SocialStore.dropbox.apiKey;
|
||||
|
||||
this.dropboxTrigger1 = ko.observable(SaveSettingsStep.Idle);
|
||||
}
|
||||
|
||||
onBuild() {
|
||||
_.delay(() => {
|
||||
const f1 = settingsSaveHelperSimpleFunction(this.facebookTrigger1, this),
|
||||
f2 = settingsSaveHelperSimpleFunction(this.facebookTrigger2, this),
|
||||
f3 = settingsSaveHelperSimpleFunction(this.twitterTrigger1, this),
|
||||
f4 = settingsSaveHelperSimpleFunction(this.twitterTrigger2, this),
|
||||
f5 = settingsSaveHelperSimpleFunction(this.googleTrigger1, this),
|
||||
f6 = settingsSaveHelperSimpleFunction(this.googleTrigger2, this),
|
||||
f7 = settingsSaveHelperSimpleFunction(this.googleTrigger3, this),
|
||||
f8 = settingsSaveHelperSimpleFunction(this.dropboxTrigger1, this);
|
||||
|
||||
this.facebookEnable.subscribe((value) => {
|
||||
if (this.facebookSupported()) {
|
||||
Remote.saveAdminConfig(null, {
|
||||
'FacebookEnable': boolToAjax(value)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.facebookAppID.subscribe((value) => {
|
||||
if (this.facebookSupported()) {
|
||||
Remote.saveAdminConfig(f1, {
|
||||
'FacebookAppID': trim(value)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.facebookAppSecret.subscribe((value) => {
|
||||
if (this.facebookSupported()) {
|
||||
Remote.saveAdminConfig(f2, {
|
||||
'FacebookAppSecret': trim(value)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.twitterEnable.subscribe(Remote.saveAdminConfigHelper('TwitterEnable', boolToAjax));
|
||||
this.twitterConsumerKey.subscribe(Remote.saveAdminConfigHelper('TwitterConsumerKey', trim, f3));
|
||||
this.twitterConsumerSecret.subscribe(Remote.saveAdminConfigHelper('TwitterConsumerSecret', trim, f4));
|
||||
|
||||
this.googleEnable.subscribe(Remote.saveAdminConfigHelper('GoogleEnable', boolToAjax));
|
||||
this.googleEnableAuth.subscribe(Remote.saveAdminConfigHelper('GoogleEnableAuth', boolToAjax));
|
||||
this.googleEnableAuthGmail.subscribe(Remote.saveAdminConfigHelper('GoogleEnableAuthGmail', boolToAjax));
|
||||
this.googleEnableDrive.subscribe(Remote.saveAdminConfigHelper('GoogleEnableDrive', boolToAjax));
|
||||
this.googleEnablePreview.subscribe(Remote.saveAdminConfigHelper('GoogleEnablePreview', boolToAjax));
|
||||
this.googleClientID.subscribe(Remote.saveAdminConfigHelper('GoogleClientID', trim, f5));
|
||||
this.googleClientSecret.subscribe(Remote.saveAdminConfigHelper('GoogleClientSecret', trim, f6));
|
||||
this.googleApiKey.subscribe(Remote.saveAdminConfigHelper('GoogleApiKey', trim, f7));
|
||||
|
||||
this.dropboxEnable.subscribe(Remote.saveAdminConfigHelper('DropboxEnable', boolToAjax));
|
||||
this.dropboxApiKey.subscribe(Remote.saveAdminConfigHelper('DropboxApiKey', trim, f8));
|
||||
}, Magics.Time50ms);
|
||||
}
|
||||
}
|
||||
|
||||
export { SocialAdminSettings, SocialAdminSettings as default };
|
|
@ -1,69 +0,0 @@
|
|||
import SocialStore from 'Stores/Social';
|
||||
|
||||
import { getApp } from 'Helper/Apps/User';
|
||||
|
||||
import { command } from 'Knoin/Knoin';
|
||||
|
||||
class SocialUserSettings {
|
||||
constructor() {
|
||||
this.googleEnable = SocialStore.google.enabled;
|
||||
this.googleEnableAuth = SocialStore.google.capa.auth;
|
||||
this.googleEnableAuthGmail = SocialStore.google.capa.authGmail;
|
||||
this.googleEnableDrive = SocialStore.google.capa.drive;
|
||||
this.googleEnablePreview = SocialStore.google.capa.preview;
|
||||
|
||||
this.googleActions = SocialStore.google.loading;
|
||||
this.googleLoggined = SocialStore.google.loggined;
|
||||
this.googleUserName = SocialStore.google.userName;
|
||||
|
||||
this.facebookEnable = SocialStore.facebook.enabled;
|
||||
|
||||
this.facebookActions = SocialStore.facebook.loading;
|
||||
this.facebookLoggined = SocialStore.facebook.loggined;
|
||||
this.facebookUserName = SocialStore.facebook.userName;
|
||||
|
||||
this.twitterEnable = SocialStore.twitter.enabled;
|
||||
|
||||
this.twitterActions = SocialStore.twitter.loading;
|
||||
this.twitterLoggined = SocialStore.twitter.loggined;
|
||||
this.twitterUserName = SocialStore.twitter.userName;
|
||||
}
|
||||
|
||||
@command((self) => !self.googleLoggined() && !self.googleActions())
|
||||
connectGoogleCommand() {
|
||||
if (!this.googleLoggined()) {
|
||||
getApp().googleConnect();
|
||||
}
|
||||
}
|
||||
|
||||
@command()
|
||||
disconnectGoogleCommand() {
|
||||
getApp().googleDisconnect();
|
||||
}
|
||||
|
||||
@command((self) => !self.facebookLoggined() && !self.facebookActions())
|
||||
connectFacebookCommand() {
|
||||
if (!this.facebookLoggined()) {
|
||||
getApp().facebookConnect();
|
||||
}
|
||||
}
|
||||
|
||||
@command()
|
||||
disconnectFacebookCommand() {
|
||||
getApp().facebookDisconnect();
|
||||
}
|
||||
|
||||
@command((self) => !self.twitterLoggined() && !self.twitterActions())
|
||||
connectTwitterCommand() {
|
||||
if (!this.twitterLoggined()) {
|
||||
getApp().twitterConnect();
|
||||
}
|
||||
}
|
||||
|
||||
@command()
|
||||
disconnectTwitterCommand() {
|
||||
getApp().twitterDisconnect();
|
||||
}
|
||||
}
|
||||
|
||||
export { SocialUserSettings, SocialUserSettings as default };
|
|
@ -1,103 +0,0 @@
|
|||
import window from 'window';
|
||||
import ko from 'ko';
|
||||
import $ from '$';
|
||||
import * as Settings from 'Storage/Settings';
|
||||
|
||||
class SocialStore {
|
||||
constructor() {
|
||||
this.google = {};
|
||||
this.twitter = {};
|
||||
this.facebook = {};
|
||||
this.dropbox = {};
|
||||
|
||||
// Google
|
||||
this.google.enabled = ko.observable(false);
|
||||
|
||||
this.google.clientID = ko.observable('');
|
||||
this.google.clientSecret = ko.observable('');
|
||||
this.google.apiKey = ko.observable('');
|
||||
|
||||
this.google.loading = ko.observable(false);
|
||||
this.google.userName = ko.observable('');
|
||||
|
||||
this.google.loggined = ko.computed(() => '' !== this.google.userName());
|
||||
|
||||
this.google.capa = {};
|
||||
this.google.capa.auth = ko.observable(false);
|
||||
this.google.capa.authGmail = ko.observable(false);
|
||||
this.google.capa.drive = ko.observable(false);
|
||||
this.google.capa.preview = ko.observable(false);
|
||||
|
||||
this.google.require = {};
|
||||
this.google.require.clientSettings = ko.computed(
|
||||
() =>
|
||||
this.google.enabled() && (this.google.capa.auth() || this.google.capa.authGmail() || this.google.capa.drive())
|
||||
);
|
||||
|
||||
this.google.require.apiKeySettings = ko.computed(() => this.google.enabled() && this.google.capa.drive());
|
||||
|
||||
// Facebook
|
||||
this.facebook.enabled = ko.observable(false);
|
||||
this.facebook.appID = ko.observable('');
|
||||
this.facebook.appSecret = ko.observable('');
|
||||
this.facebook.loading = ko.observable(false);
|
||||
this.facebook.userName = ko.observable('');
|
||||
this.facebook.supported = ko.observable(false);
|
||||
|
||||
this.facebook.loggined = ko.computed(() => '' !== this.facebook.userName());
|
||||
|
||||
// Twitter
|
||||
this.twitter.enabled = ko.observable(false);
|
||||
this.twitter.consumerKey = ko.observable('');
|
||||
this.twitter.consumerSecret = ko.observable('');
|
||||
this.twitter.loading = ko.observable(false);
|
||||
this.twitter.userName = ko.observable('');
|
||||
|
||||
this.twitter.loggined = ko.computed(() => '' !== this.twitter.userName());
|
||||
|
||||
// Dropbox
|
||||
this.dropbox.enabled = ko.observable(false);
|
||||
this.dropbox.apiKey = ko.observable('');
|
||||
}
|
||||
|
||||
populate() {
|
||||
this.google.enabled(!!Settings.settingsGet('AllowGoogleSocial'));
|
||||
this.google.clientID(Settings.settingsGet('GoogleClientID'));
|
||||
this.google.clientSecret(Settings.settingsGet('GoogleClientSecret'));
|
||||
this.google.apiKey(Settings.settingsGet('GoogleApiKey'));
|
||||
|
||||
this.google.capa.auth(!!Settings.settingsGet('AllowGoogleSocialAuth'));
|
||||
this.google.capa.authGmail(!!Settings.settingsGet('AllowGoogleSocialAuthGmail'));
|
||||
this.google.capa.drive(!!Settings.settingsGet('AllowGoogleSocialDrive'));
|
||||
this.google.capa.preview(!!Settings.settingsGet('AllowGoogleSocialPreview'));
|
||||
|
||||
this.facebook.enabled(!!Settings.settingsGet('AllowFacebookSocial'));
|
||||
this.facebook.appID(Settings.settingsGet('FacebookAppID'));
|
||||
this.facebook.appSecret(Settings.settingsGet('FacebookAppSecret'));
|
||||
this.facebook.supported(!!Settings.settingsGet('SupportedFacebookSocial'));
|
||||
|
||||
this.twitter.enabled = ko.observable(!!Settings.settingsGet('AllowTwitterSocial'));
|
||||
this.twitter.consumerKey = ko.observable(Settings.settingsGet('TwitterConsumerKey'));
|
||||
this.twitter.consumerSecret = ko.observable(Settings.settingsGet('TwitterConsumerSecret'));
|
||||
|
||||
this.dropbox.enabled(!!Settings.settingsGet('AllowDropboxSocial'));
|
||||
this.dropbox.apiKey(Settings.settingsGet('DropboxApiKey'));
|
||||
}
|
||||
|
||||
appendDropbox() {
|
||||
if (!window.Dropbox && this.dropbox.enabled() && this.dropbox.apiKey()) {
|
||||
if (!window.document.getElementById('dropboxjs')) {
|
||||
const script = window.document.createElement('script');
|
||||
script.type = 'text/javascript';
|
||||
script.src = 'https://www.dropbox.com/static/api/2/dropins.js';
|
||||
$(script)
|
||||
.attr('id', 'dropboxjs')
|
||||
.attr('data-app-key', this.dropbox.apiKey());
|
||||
|
||||
window.document.body.appendChild(script);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new SocialStore();
|
|
@ -1,47 +0,0 @@
|
|||
@keyframes redirectingRotation {
|
||||
to {
|
||||
transform: rotate(1turn);
|
||||
}
|
||||
}
|
||||
|
||||
html, body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.social-icon-wrp {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.social-icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
font-size: 140px;
|
||||
margin: -60px 0 0 -60px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.redirecting:after{
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-top: -120px;
|
||||
margin-left: -120px;
|
||||
|
||||
border: 8px solid transparent;
|
||||
border-color: transparent;
|
||||
border-top-color: #999;
|
||||
animation: redirectingRotation 1s infinite ease-in-out;
|
||||
|
||||
border-radius: 50%;
|
||||
z-index: 1;
|
||||
}
|
|
@ -49,7 +49,6 @@ import AccountStore from 'Stores/User/Account';
|
|||
import FolderStore from 'Stores/User/Folder';
|
||||
import PgpStore from 'Stores/User/Pgp';
|
||||
import MessageStore from 'Stores/User/Message';
|
||||
import SocialStore from 'Stores/Social';
|
||||
|
||||
import Remote from 'Remote/User/Ajax';
|
||||
|
||||
|
@ -329,21 +328,6 @@ class ComposePopupView extends AbstractViewNext {
|
|||
this.showBcc.subscribe(this.resizerTrigger);
|
||||
this.showReplyTo.subscribe(this.resizerTrigger);
|
||||
|
||||
this.dropboxEnabled = SocialStore.dropbox.enabled;
|
||||
this.dropboxApiKey = SocialStore.dropbox.apiKey;
|
||||
|
||||
this.driveEnabled = ko.observable(
|
||||
bXMLHttpRequestSupported &&
|
||||
!!Settings.settingsGet('AllowGoogleSocial') &&
|
||||
!!Settings.settingsGet('AllowGoogleSocialDrive') &&
|
||||
!!Settings.settingsGet('GoogleClientID') &&
|
||||
!!Settings.settingsGet('GoogleApiKey')
|
||||
);
|
||||
|
||||
this.driveVisible = ko.observable(false);
|
||||
|
||||
this.driveCallback = _.bind(this.driveCallback, this);
|
||||
|
||||
this.onMessageUploadAttachments = _.bind(this.onMessageUploadAttachments, this);
|
||||
|
||||
this.bDisabeCloseOnEsc = true;
|
||||
|
@ -530,28 +514,6 @@ class ComposePopupView extends AbstractViewNext {
|
|||
}
|
||||
}
|
||||
|
||||
@command((self) => self.dropboxEnabled())
|
||||
dropboxCommand() {
|
||||
if (window.Dropbox) {
|
||||
window.Dropbox.choose({
|
||||
success: (files) => {
|
||||
if (files && files[0] && files[0].link) {
|
||||
this.addDropboxAttachment(files[0]);
|
||||
}
|
||||
},
|
||||
linkType: 'direct',
|
||||
multiselect: false
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@command((self) => self.driveEnabled())
|
||||
driveCommand() {
|
||||
this.driveOpenPopup();
|
||||
return true;
|
||||
}
|
||||
|
||||
autosaveFunction() {
|
||||
if (
|
||||
this.modalVisibility() &&
|
||||
|
@ -1289,16 +1251,6 @@ class ComposePopupView extends AbstractViewNext {
|
|||
Events.sub('window.resize.real', this.resizerTrigger);
|
||||
Events.sub('window.resize.real', _.debounce(this.resizerTrigger, Magics.Time50ms));
|
||||
|
||||
SocialStore.appendDropbox();
|
||||
|
||||
if (this.driveEnabled()) {
|
||||
$.getScript('https://apis.google.com/js/api.js', () => {
|
||||
if (window.gapi) {
|
||||
this.driveVisible(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
window.setInterval(() => {
|
||||
if (this.modalVisibility() && this.oEditor) {
|
||||
this.oEditor.resize();
|
||||
|
@ -1306,137 +1258,6 @@ class ComposePopupView extends AbstractViewNext {
|
|||
}, Magics.Time5s);
|
||||
}
|
||||
|
||||
driveCallback(accessToken, data) {
|
||||
if (
|
||||
data &&
|
||||
window.XMLHttpRequest &&
|
||||
window.google &&
|
||||
data[window.google.picker.Response.ACTION] === window.google.picker.Action.PICKED &&
|
||||
data[window.google.picker.Response.DOCUMENTS] &&
|
||||
data[window.google.picker.Response.DOCUMENTS][0] &&
|
||||
data[window.google.picker.Response.DOCUMENTS][0].id
|
||||
) {
|
||||
const request = new window.XMLHttpRequest();
|
||||
request.open(
|
||||
'GET',
|
||||
'https://www.googleapis.com/drive/v2/files/' + data[window.google.picker.Response.DOCUMENTS][0].id
|
||||
);
|
||||
request.setRequestHeader('Authorization', 'Bearer ' + accessToken);
|
||||
request.addEventListener('load', () => {
|
||||
if (request && request.responseText) {
|
||||
const response = window.JSON.parse(request.responseText),
|
||||
fExport = (item, mimeType, ext) => {
|
||||
if (item && item.exportLinks) {
|
||||
if (item.exportLinks[mimeType]) {
|
||||
response.downloadUrl = item.exportLinks[mimeType];
|
||||
response.title = item.title + '.' + ext;
|
||||
response.mimeType = mimeType;
|
||||
} else if (item.exportLinks['application/pdf']) {
|
||||
response.downloadUrl = item.exportLinks['application/pdf'];
|
||||
response.title = item.title + '.pdf';
|
||||
response.mimeType = 'application/pdf';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (response && !response.downloadUrl && response.mimeType && response.exportLinks) {
|
||||
switch (response.mimeType.toString().toLowerCase()) {
|
||||
case 'application/vnd.google-apps.document':
|
||||
fExport(response, 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'docx');
|
||||
break;
|
||||
case 'application/vnd.google-apps.spreadsheet':
|
||||
fExport(response, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'xlsx');
|
||||
break;
|
||||
case 'application/vnd.google-apps.drawing':
|
||||
fExport(response, 'image/png', 'png');
|
||||
break;
|
||||
case 'application/vnd.google-apps.presentation':
|
||||
fExport(response, 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'pptx');
|
||||
break;
|
||||
default:
|
||||
fExport(response, 'application/pdf', 'pdf');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (response && response.downloadUrl) {
|
||||
this.addDriveAttachment(response, accessToken);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
request.send();
|
||||
}
|
||||
}
|
||||
|
||||
driveCreatePiker(authToken) {
|
||||
if (window.gapi && authToken && authToken.access_token) {
|
||||
window.gapi.load('picker', {
|
||||
callback: () => {
|
||||
if (window.google && window.google.picker) {
|
||||
const drivePicker = new window.google.picker.PickerBuilder()
|
||||
// .addView(window.google.picker.ViewId.FOLDERS)
|
||||
.addView(window.google.picker.ViewId.DOCS)
|
||||
.setAppId(Settings.settingsGet('GoogleClientID'))
|
||||
.setOAuthToken(authToken.access_token)
|
||||
.setCallback(_.bind(this.driveCallback, this, authToken.access_token))
|
||||
.enableFeature(window.google.picker.Feature.NAV_HIDDEN)
|
||||
// .setOrigin(window.location.protocol + '//' + window.location.host)
|
||||
.build();
|
||||
|
||||
drivePicker.setVisible(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
driveOpenPopup() {
|
||||
if (window.gapi) {
|
||||
window.gapi.load('auth', {
|
||||
callback: () => {
|
||||
const authToken = window.gapi.auth.getToken(),
|
||||
fResult = (authResult) => {
|
||||
if (authResult && !authResult.error) {
|
||||
const token = window.gapi.auth.getToken();
|
||||
if (token) {
|
||||
this.driveCreatePiker(token);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
if (!authToken) {
|
||||
window.gapi.auth.authorize(
|
||||
{
|
||||
'client_id': Settings.settingsGet('GoogleClientID'),
|
||||
'scope': 'https://www.googleapis.com/auth/drive.readonly',
|
||||
'immediate': true
|
||||
},
|
||||
(authResult) => {
|
||||
if (!fResult(authResult)) {
|
||||
window.gapi.auth.authorize(
|
||||
{
|
||||
'client_id': Settings.settingsGet('GoogleClientID'),
|
||||
'scope': 'https://www.googleapis.com/auth/drive.readonly',
|
||||
'immediate': false
|
||||
},
|
||||
fResult
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.driveCreatePiker(authToken);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} id
|
||||
* @returns {?Object}
|
||||
|
@ -1643,83 +1464,6 @@ class ComposePopupView extends AbstractViewNext {
|
|||
return attachment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} dropboxFile
|
||||
* @returns {boolean}
|
||||
*/
|
||||
addDropboxAttachment(dropboxFile) {
|
||||
const attachmentSizeLimit = pInt(Settings.settingsGet('AttachmentLimit')),
|
||||
mSize = dropboxFile.bytes,
|
||||
attachment = this.addAttachmentHelper(dropboxFile.link, dropboxFile.name, mSize);
|
||||
|
||||
if (0 < mSize && 0 < attachmentSizeLimit && attachmentSizeLimit < mSize) {
|
||||
attachment.uploading(false).complete(true);
|
||||
attachment.error(i18n('UPLOAD/ERROR_FILE_IS_TOO_BIG'));
|
||||
return false;
|
||||
}
|
||||
|
||||
Remote.composeUploadExternals(
|
||||
(statusResult, data) => {
|
||||
let result = false;
|
||||
attachment.uploading(false).complete(true);
|
||||
|
||||
if (StorageResultType.Success === statusResult && data && data.Result) {
|
||||
if (data.Result[attachment.id]) {
|
||||
result = true;
|
||||
attachment.tempName(data.Result[attachment.id]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
attachment.error(getUploadErrorDescByCode(UploadErrorCode.FileNoUploaded));
|
||||
}
|
||||
},
|
||||
[dropboxFile.link]
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} driveFile
|
||||
* @param {string} accessToken
|
||||
* @returns {boolean}
|
||||
*/
|
||||
addDriveAttachment(driveFile, accessToken) {
|
||||
const attachmentSizeLimit = pInt(Settings.settingsGet('AttachmentLimit')),
|
||||
size = driveFile.fileSize ? pInt(driveFile.fileSize) : 0,
|
||||
attachment = this.addAttachmentHelper(driveFile.downloadUrl, driveFile.title, size);
|
||||
|
||||
if (0 < size && 0 < attachmentSizeLimit && attachmentSizeLimit < size) {
|
||||
attachment.uploading(false).complete(true);
|
||||
attachment.error(i18n('UPLOAD/ERROR_FILE_IS_TOO_BIG'));
|
||||
return false;
|
||||
}
|
||||
|
||||
Remote.composeUploadDrive(
|
||||
(statusResult, data) => {
|
||||
let result = false;
|
||||
attachment.uploading(false).complete(true);
|
||||
|
||||
if (StorageResultType.Success === statusResult && data && data.Result) {
|
||||
if (data.Result[attachment.id]) {
|
||||
result = true;
|
||||
attachment.tempName(data.Result[attachment.id][0]);
|
||||
attachment.size(pInt(data.Result[attachment.id][1]));
|
||||
}
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
attachment.error(getUploadErrorDescByCode(UploadErrorCode.FileNoUploaded));
|
||||
}
|
||||
},
|
||||
driveFile.downloadUrl,
|
||||
accessToken
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MessageModel} message
|
||||
* @param {string} type
|
||||
|
|
|
@ -14,7 +14,6 @@ import {
|
|||
import { trim, inArray, pInt, convertLangName, triggerAutocompleteInputChange } from 'Common/Utils';
|
||||
|
||||
import { $win } from 'Common/Globals';
|
||||
import { socialFacebook, socialGoogle, socialTwitter } from 'Common/Links';
|
||||
import { getNotification, getNotificationFromResponse, reload as translatorReload } from 'Common/Translator';
|
||||
|
||||
import * as Plugins from 'Common/Plugins';
|
||||
|
@ -141,19 +140,6 @@ class LoginUserView extends AbstractViewNext {
|
|||
|
||||
this.signMeVisibility = ko.computed(() => LoginSignMeType.Unused !== this.signMeType());
|
||||
|
||||
this.facebookLoginEnabled = ko.observable(false);
|
||||
this.googleLoginEnabled = ko.observable(false);
|
||||
this.googleGmailLoginEnabled = ko.observable(false);
|
||||
this.twitterLoginEnabled = ko.observable(false);
|
||||
|
||||
this.socialLoginEnabled = ko.computed(() => {
|
||||
const bF = this.facebookLoginEnabled(),
|
||||
bG = this.googleLoginEnabled(),
|
||||
bT = this.twitterLoginEnabled();
|
||||
|
||||
return bF || bG || bT;
|
||||
});
|
||||
|
||||
if (Settings.settingsGet('AdditionalLoginError') && !this.submitError()) {
|
||||
this.submitError(Settings.settingsGet('AdditionalLoginError'));
|
||||
}
|
||||
|
@ -163,30 +149,6 @@ class LoginUserView extends AbstractViewNext {
|
|||
return `left=200,top=100,width=${wh},height=${wh},menubar=no,status=no,resizable=yes,scrollbars=yes`;
|
||||
}
|
||||
|
||||
@command((self) => !self.submitRequest() && self.facebookLoginEnabled())
|
||||
facebookCommand() {
|
||||
window.open(socialFacebook(), 'Facebook', this.windowOpenFeatures(500));
|
||||
return true;
|
||||
}
|
||||
|
||||
@command((self) => !self.submitRequest() && self.googleLoginEnabled())
|
||||
googleCommand() {
|
||||
window.open(socialGoogle(), 'Google', this.windowOpenFeatures(550));
|
||||
return true;
|
||||
}
|
||||
|
||||
@command((self) => !self.submitRequest() && self.googleGmailLoginEnabled())
|
||||
googleGmailCommand() {
|
||||
window.open(socialGoogle(true), 'Google', this.windowOpenFeatures(550));
|
||||
return true;
|
||||
}
|
||||
|
||||
@command((self) => !self.submitRequest() && self.twitterLoginEnabled())
|
||||
twitterCommand() {
|
||||
window.open(socialTwitter(), 'Twitter', this.windowOpenFeatures(500));
|
||||
return true;
|
||||
}
|
||||
|
||||
@command((self) => !self.submitRequest())
|
||||
submitCommand() {
|
||||
triggerAutocompleteInputChange();
|
||||
|
@ -328,25 +290,7 @@ class LoginUserView extends AbstractViewNext {
|
|||
onBuild() {
|
||||
const signMeLocal = Local.get(ClientSideKeyName.LastSignMe),
|
||||
signMe = (Settings.settingsGet('SignMe') || 'unused').toLowerCase(),
|
||||
jsHash = Settings.appSettingsGet('jsHash'),
|
||||
fSocial = (iErrorCode) => {
|
||||
iErrorCode = pInt(iErrorCode);
|
||||
if (0 === iErrorCode) {
|
||||
this.submitRequest(true);
|
||||
getApp().loginAndLogoutReload(false);
|
||||
} else {
|
||||
this.submitError(getNotification(iErrorCode));
|
||||
}
|
||||
};
|
||||
|
||||
this.facebookLoginEnabled(!!Settings.settingsGet('AllowFacebookSocial'));
|
||||
this.twitterLoginEnabled(!!Settings.settingsGet('AllowTwitterSocial'));
|
||||
this.googleLoginEnabled(
|
||||
!!Settings.settingsGet('AllowGoogleSocial') && !!Settings.settingsGet('AllowGoogleSocialAuth')
|
||||
);
|
||||
this.googleGmailLoginEnabled(
|
||||
!!Settings.settingsGet('AllowGoogleSocial') && !!Settings.settingsGet('AllowGoogleSocialAuthGmail')
|
||||
);
|
||||
jsHash = Settings.appSettingsGet('jsHash');
|
||||
|
||||
switch (signMe) {
|
||||
case LoginSignMeTypeAsString.DefaultOff:
|
||||
|
@ -375,18 +319,6 @@ class LoginUserView extends AbstractViewNext {
|
|||
this.email(AppStore.devEmail);
|
||||
this.password(AppStore.devPassword);
|
||||
|
||||
if (this.googleLoginEnabled() || this.googleGmailLoginEnabled()) {
|
||||
window['rl_' + jsHash + '_google_login_service'] = fSocial;
|
||||
}
|
||||
|
||||
if (this.facebookLoginEnabled()) {
|
||||
window['rl_' + jsHash + '_facebook_login_service'] = fSocial;
|
||||
}
|
||||
|
||||
if (this.twitterLoginEnabled()) {
|
||||
window['rl_' + jsHash + '_twitter_login_service'] = fSocial;
|
||||
}
|
||||
|
||||
_.delay(() => {
|
||||
LanguageStore.language.subscribe((value) => {
|
||||
this.langRequest(true);
|
||||
|
|
|
@ -43,7 +43,6 @@ import { attachmentDownload } from 'Common/Links';
|
|||
|
||||
import { getUserPic, storeMessageFlagsToCache } from 'Common/Cache';
|
||||
|
||||
import SocialStore from 'Stores/Social';
|
||||
import AppStore from 'Stores/User/App';
|
||||
import SettingsStore from 'Stores/User/Settings';
|
||||
import AccountStore from 'Stores/User/Account';
|
||||
|
@ -144,46 +143,6 @@ class MessageViewMailBoxUserView extends AbstractViewNext {
|
|||
this.downloadAsZipLoading = ko.observable(false);
|
||||
this.downloadAsZipError = ko.observable(false).extend({ falseTimeout: 7000 });
|
||||
|
||||
this.saveToOwnCloudAllowed = ko.computed(
|
||||
() => -1 < inArray('owncloud', this.attachmentsActions()) && this.allowAttachmnetControls()
|
||||
);
|
||||
|
||||
this.saveToOwnCloudLoading = ko.observable(false);
|
||||
this.saveToOwnCloudSuccess = ko.observable(false).extend({ falseTimeout: 2000 });
|
||||
this.saveToOwnCloudError = ko.observable(false).extend({ falseTimeout: 7000 });
|
||||
|
||||
this.saveToOwnCloudSuccess.subscribe((v) => {
|
||||
if (v) {
|
||||
this.saveToOwnCloudError(false);
|
||||
}
|
||||
});
|
||||
|
||||
this.saveToOwnCloudError.subscribe((v) => {
|
||||
if (v) {
|
||||
this.saveToOwnCloudSuccess(false);
|
||||
}
|
||||
});
|
||||
|
||||
this.saveToDropboxAllowed = ko.computed(
|
||||
() => -1 < inArray('dropbox', this.attachmentsActions()) && this.allowAttachmnetControls()
|
||||
);
|
||||
|
||||
this.saveToDropboxLoading = ko.observable(false);
|
||||
this.saveToDropboxSuccess = ko.observable(false).extend({ falseTimeout: 2000 });
|
||||
this.saveToDropboxError = ko.observable(false).extend({ falseTimeout: 7000 });
|
||||
|
||||
this.saveToDropboxSuccess.subscribe((v) => {
|
||||
if (v) {
|
||||
this.saveToDropboxError(false);
|
||||
}
|
||||
});
|
||||
|
||||
this.saveToDropboxError.subscribe((v) => {
|
||||
if (v) {
|
||||
this.saveToDropboxSuccess(false);
|
||||
}
|
||||
});
|
||||
|
||||
this.showAttachmnetControls.subscribe((v) => {
|
||||
if (this.message()) {
|
||||
_.each(this.message().attachments(), (item) => {
|
||||
|
@ -243,9 +202,6 @@ class MessageViewMailBoxUserView extends AbstractViewNext {
|
|||
this.spamCommand = createCommandActionHelper(FolderType.Spam, true);
|
||||
this.notSpamCommand = createCommandActionHelper(FolderType.NotSpam, true);
|
||||
|
||||
this.dropboxEnabled = SocialStore.dropbox.enabled;
|
||||
this.dropboxApiKey = SocialStore.dropbox.apiKey;
|
||||
|
||||
// viewer
|
||||
|
||||
this.viewBodyTopValue = ko.observable(0);
|
||||
|
@ -606,8 +562,6 @@ class MessageViewMailBoxUserView extends AbstractViewNext {
|
|||
Local.set(ClientSideKeyName.MessageHeaderFullInfo, value ? '1' : '0');
|
||||
});
|
||||
|
||||
SocialStore.appendDropbox();
|
||||
|
||||
this.oHeaderDom = $('.messageItemHeader', dom);
|
||||
this.oHeaderDom = this.oHeaderDom[0] ? this.oHeaderDom : null;
|
||||
|
||||
|
@ -956,77 +910,6 @@ class MessageViewMailBoxUserView extends AbstractViewNext {
|
|||
}
|
||||
}
|
||||
|
||||
saveToOwnCloud() {
|
||||
const hashes = this.getAttachmentsHashes();
|
||||
if (0 < hashes.length) {
|
||||
Promises.attachmentsActions('OwnCloud', hashes, this.saveToOwnCloudLoading)
|
||||
.then((result) => {
|
||||
if (result && result.Result) {
|
||||
this.saveToOwnCloudSuccess(true);
|
||||
} else {
|
||||
this.saveToOwnCloudError(true);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.saveToOwnCloudError(true);
|
||||
});
|
||||
} else {
|
||||
this.highlightUnselectedAttachments(true);
|
||||
}
|
||||
}
|
||||
|
||||
saveToDropbox() {
|
||||
const files = [],
|
||||
hashes = this.getAttachmentsHashes();
|
||||
|
||||
if (0 < hashes.length) {
|
||||
if (window.Dropbox) {
|
||||
Promises.attachmentsActions('Dropbox', hashes, this.saveToDropboxLoading)
|
||||
.then((result) => {
|
||||
if (result && result.Result && result.Result.Url && result.Result.ShortLife && result.Result.Files) {
|
||||
if (window.Dropbox && isArray(result.Result.Files)) {
|
||||
_.each(result.Result.Files, (item) => {
|
||||
files.push({
|
||||
url: result.Result.Url + attachmentDownload(item.Hash, result.Result.ShortLife),
|
||||
filename: item.FileName
|
||||
});
|
||||
});
|
||||
|
||||
window.Dropbox.save({
|
||||
files: files,
|
||||
progress: () => {
|
||||
this.saveToDropboxLoading(true);
|
||||
this.saveToDropboxError(false);
|
||||
this.saveToDropboxSuccess(false);
|
||||
},
|
||||
cancel: () => {
|
||||
this.saveToDropboxSuccess(false);
|
||||
this.saveToDropboxError(false);
|
||||
this.saveToDropboxLoading(false);
|
||||
},
|
||||
success: () => {
|
||||
this.saveToDropboxSuccess(true);
|
||||
this.saveToDropboxLoading(false);
|
||||
},
|
||||
error: () => {
|
||||
this.saveToDropboxError(true);
|
||||
this.saveToDropboxLoading(false);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.saveToDropboxError(true);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.saveToDropboxError(true);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.highlightUnselectedAttachments(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MessageModel} oMessage
|
||||
* @returns {void}
|
||||
|
|
|
@ -21,7 +21,6 @@ window.progressJs.onbeforeend(() => {
|
|||
}
|
||||
});
|
||||
|
||||
require('../vendors/modernizr/modernizr-custom.js');
|
||||
require('Common/Booter');
|
||||
|
||||
if (window.__runBoot) {
|
||||
|
|
|
@ -8,7 +8,6 @@ const { css, cssLint } = require('./tasks/css');
|
|||
const { vendors } = require('./tasks/vendors');
|
||||
const { watchCss } = require('./tasks/watch');
|
||||
const { rainloop } = require('./tasks/rainloop');
|
||||
const { owncloud } = require('./tasks/owncloud');
|
||||
|
||||
const clean = gulp.series(cleanStatic);
|
||||
|
||||
|
@ -27,5 +26,4 @@ exports.default = build;
|
|||
exports.watchCss = watchCss;
|
||||
|
||||
exports.rainloop = gulp.series(build, rainloop);
|
||||
exports.owncloud = gulp.series(build, owncloud);
|
||||
exports.all = gulp.series(exports.rainloop, exports.owncloud);
|
||||
exports.all = gulp.series(exports.rainloop);
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
"description": "Simple, modern & fast web-based email client",
|
||||
"private": true,
|
||||
"version": "1.14.0",
|
||||
"ownCloudVersion": "5.3.0",
|
||||
"homepage": "https://www.rainloop.net",
|
||||
"author": {
|
||||
"name": "RainLoop Team",
|
||||
|
|
|
@ -1,700 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace TijsVerkoyen\CssToInlineStyles;
|
||||
|
||||
/**
|
||||
* CSS to Inline Styles class
|
||||
*
|
||||
* @author Tijs Verkoyen <php-css-to-inline-styles@verkoyen.eu>
|
||||
* @version 1.1.0
|
||||
* @copyright Copyright (c), Tijs Verkoyen. All rights reserved.
|
||||
* @license BSD License
|
||||
*/
|
||||
class CssToInlineStyles
|
||||
{
|
||||
/**
|
||||
* The CSS to use
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $css;
|
||||
|
||||
/**
|
||||
* The processed CSS rules
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $cssRules;
|
||||
|
||||
/**
|
||||
* Should the generated HTML be cleaned
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $cleanup = false;
|
||||
|
||||
/**
|
||||
* The encoding to use.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $encoding = 'UTF-8';
|
||||
|
||||
/**
|
||||
* The HTML to process
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $html;
|
||||
|
||||
/**
|
||||
* Use inline-styles block as CSS
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $useInlineStylesBlock = false;
|
||||
|
||||
/*
|
||||
* Strip original style tags
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $stripOriginalStyleTags = false;
|
||||
|
||||
/**
|
||||
* Creates an instance, you could set the HTML and CSS here, or load it
|
||||
* later.
|
||||
*
|
||||
* @return void
|
||||
* @param string[optional] $html The HTML to process.
|
||||
* @param string[optional] $css The CSS to use.
|
||||
*/
|
||||
public function __construct($html = null, $css = null)
|
||||
{
|
||||
if($html !== null) $this->setHTML($html);
|
||||
if($css !== null) $this->setCSS($css);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a CSS-selector into an xPath-query
|
||||
*
|
||||
* @return string
|
||||
* @param string $selector The CSS-selector.
|
||||
*/
|
||||
private function buildXPathQuery($selector)
|
||||
{
|
||||
// redefine
|
||||
$selector = (string) $selector;
|
||||
|
||||
// the CSS selector
|
||||
$cssSelector = array(
|
||||
// E F, Matches any F element that is a descendant of an E element
|
||||
'/(\w)\s+(\w)/',
|
||||
// E > F, Matches any F element that is a child of an element E
|
||||
'/(\w)\s*>\s*(\w)/',
|
||||
// E:first-child, Matches element E when E is the first child of its parent
|
||||
'/(\w):first-child/',
|
||||
// E + F, Matches any F element immediately preceded by an element
|
||||
'/(\w)\s*\+\s*(\w)/',
|
||||
// E[foo], Matches any E element with the "foo" attribute set (whatever the value)
|
||||
'/(\w)\[([\w\-]+)]/',
|
||||
// E[foo="warning"], Matches any E element whose "foo" attribute value is exactly equal to "warning"
|
||||
'/(\w)\[([\w\-]+)\=\"(.*)\"]/',
|
||||
// div.warning, HTML only. The same as DIV[class~="warning"]
|
||||
'/(\w+|\*)+\.([\w\-]+)+/',
|
||||
// .warning, HTML only. The same as *[class~="warning"]
|
||||
'/\.([\w\-]+)/',
|
||||
// E#myid, Matches any E element with id-attribute equal to "myid"
|
||||
'/(\w+)+\#([\w\-]+)/',
|
||||
// #myid, Matches any element with id-attribute equal to "myid"
|
||||
'/\#([\w\-]+)/'
|
||||
);
|
||||
|
||||
// the xPath-equivalent
|
||||
$xPathQuery = array(
|
||||
// E F, Matches any F element that is a descendant of an E element
|
||||
'\1//\2',
|
||||
// E > F, Matches any F element that is a child of an element E
|
||||
'\1/\2',
|
||||
// E:first-child, Matches element E when E is the first child of its parent
|
||||
'*[1]/self::\1',
|
||||
// E + F, Matches any F element immediately preceded by an element
|
||||
'\1/following-sibling::*[1]/self::\2',
|
||||
// E[foo], Matches any E element with the "foo" attribute set (whatever the value)
|
||||
'\1 [ @\2 ]',
|
||||
// E[foo="warning"], Matches any E element whose "foo" attribute value is exactly equal to "warning"
|
||||
'\1[ contains( concat( " ", @\2, " " ), concat( " ", "\3", " " ) ) ]',
|
||||
// div.warning, HTML only. The same as DIV[class~="warning"]
|
||||
'\1[ contains( concat( " ", @class, " " ), concat( " ", "\2", " " ) ) ]',
|
||||
// .warning, HTML only. The same as *[class~="warning"]
|
||||
'*[ contains( concat( " ", @class, " " ), concat( " ", "\1", " " ) ) ]',
|
||||
// E#myid, Matches any E element with id-attribute equal to "myid"
|
||||
'\1[ @id = "\2" ]',
|
||||
// #myid, Matches any element with id-attribute equal to "myid"
|
||||
'*[ @id = "\1" ]'
|
||||
);
|
||||
|
||||
// return
|
||||
$xPath = (string) '//' . preg_replace($cssSelector, $xPathQuery, $selector);
|
||||
|
||||
return str_replace('] *', ']//*', $xPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the specifity for the CSS-selector
|
||||
*
|
||||
* @return int
|
||||
* @param string $selector The selector to calculate the specifity for.
|
||||
*/
|
||||
private function calculateCSSSpecifity($selector)
|
||||
{
|
||||
// cleanup selector
|
||||
$selector = str_replace(array('>', '+'), array(' > ', ' + '), $selector);
|
||||
|
||||
// init var
|
||||
$specifity = 0;
|
||||
|
||||
// split the selector into chunks based on spaces
|
||||
$chunks = explode(' ', $selector);
|
||||
|
||||
// loop chunks
|
||||
foreach ($chunks as $chunk) {
|
||||
// an ID is important, so give it a high specifity
|
||||
if(strstr($chunk, '#') !== false) $specifity += 100;
|
||||
|
||||
// classes are more important than a tag, but less important then an ID
|
||||
elseif(strstr($chunk, '.')) $specifity += 10;
|
||||
|
||||
// anything else isn't that important
|
||||
else $specifity += 1;
|
||||
}
|
||||
|
||||
// return
|
||||
return $specifity;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Cleanup the generated HTML
|
||||
*
|
||||
* @return string
|
||||
* @param string $html The HTML to cleanup.
|
||||
*/
|
||||
private function cleanupHTML($html)
|
||||
{
|
||||
// remove classes
|
||||
$html = preg_replace('/(\s)+class="(.*)"(\s)+/U', ' ', $html);
|
||||
|
||||
// remove IDs
|
||||
$html = preg_replace('/(\s)+id="(.*)"(\s)+/U', ' ', $html);
|
||||
|
||||
// return
|
||||
return $html;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts the loaded HTML into an HTML-string with inline styles based on the loaded CSS
|
||||
*
|
||||
* @return string
|
||||
* @param bool[optional] $outputXHTML Should we output valid XHTML?
|
||||
*/
|
||||
public function convert($outputXHTML = false)
|
||||
{
|
||||
// redefine
|
||||
$outputXHTML = (bool) $outputXHTML;
|
||||
|
||||
// validate
|
||||
if($this->html == null) throw new Exception('No HTML provided.');
|
||||
|
||||
// should we use inline style-block
|
||||
if ($this->useInlineStylesBlock) {
|
||||
// init var
|
||||
$matches = array();
|
||||
|
||||
// match the style blocks
|
||||
preg_match_all('|<style(.*)>(.*)</style>|isU', $this->html, $matches);
|
||||
|
||||
// any style-blocks found?
|
||||
if (!empty($matches[2])) {
|
||||
// add
|
||||
foreach($matches[2] as $match) $this->css .= trim($match) ."\n";
|
||||
}
|
||||
}
|
||||
|
||||
// process css
|
||||
$this->processCSS();
|
||||
|
||||
// create new DOMDocument
|
||||
$document = new \DOMDocument('1.0', $this->getEncoding());
|
||||
|
||||
// set error level
|
||||
libxml_use_internal_errors(true);
|
||||
|
||||
// load HTML
|
||||
// $document->loadHTML($this->html);
|
||||
$document->loadHTML('<'.'?xml version="1.0" encoding="'.$this->getEncoding().'"?'.'><head><meta http-equiv="Content-Type" content="text/html; charset='.$this->getEncoding().'"></head>'.$this->html);
|
||||
|
||||
// create new XPath
|
||||
$xPath = new \DOMXPath($document);
|
||||
|
||||
// any rules?
|
||||
if (!empty($this->cssRules)) {
|
||||
// loop rules
|
||||
foreach ($this->cssRules as $rule) {
|
||||
// init var
|
||||
$query = $this->buildXPathQuery($rule['selector']);
|
||||
|
||||
// validate query
|
||||
if($query === false) continue;
|
||||
|
||||
// search elements
|
||||
$elements = $xPath->query($query);
|
||||
|
||||
// validate elements
|
||||
if($elements === false) continue;
|
||||
|
||||
// loop found elements
|
||||
foreach ($elements as $element) {
|
||||
// no styles stored?
|
||||
if ($element->attributes->getNamedItem(
|
||||
'data-css-to-inline-styles-original-styles'
|
||||
) == null) {
|
||||
// init var
|
||||
$originalStyle = '';
|
||||
if ($element->attributes->getNamedItem('style') !== null) {
|
||||
$originalStyle = $element->attributes->getNamedItem('style')->value;
|
||||
}
|
||||
|
||||
// store original styles
|
||||
$element->setAttribute(
|
||||
'data-css-to-inline-styles-original-styles',
|
||||
$originalStyle
|
||||
);
|
||||
|
||||
// clear the styles
|
||||
$element->setAttribute('style', '');
|
||||
}
|
||||
|
||||
// init var
|
||||
$properties = array();
|
||||
|
||||
// get current styles
|
||||
$stylesAttribute = $element->attributes->getNamedItem('style');
|
||||
|
||||
// any styles defined before?
|
||||
if ($stylesAttribute !== null) {
|
||||
// get value for the styles attribute
|
||||
$definedStyles = (string) $stylesAttribute->value;
|
||||
|
||||
// split into properties
|
||||
$definedProperties = (array) explode(';', $definedStyles);
|
||||
|
||||
// loop properties
|
||||
foreach ($definedProperties as $property) {
|
||||
// validate property
|
||||
if($property == '') continue;
|
||||
|
||||
// split into chunks
|
||||
$chunks = (array) explode(':', trim($property), 2);
|
||||
|
||||
// validate
|
||||
if(!isset($chunks[1])) continue;
|
||||
|
||||
// loop chunks
|
||||
$properties[$chunks[0]] = trim($chunks[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// add new properties into the list
|
||||
foreach ($rule['properties'] as $key => $value) {
|
||||
$properties[$key] = $value;
|
||||
}
|
||||
|
||||
// build string
|
||||
$propertyChunks = array();
|
||||
|
||||
// build chunks
|
||||
foreach ($properties as $key => $values) {
|
||||
foreach ((array) $values as $value) {
|
||||
$propertyChunks[] = $key . ': ' . $value . ';';
|
||||
}
|
||||
}
|
||||
|
||||
// build properties string
|
||||
$propertiesString = implode(' ', $propertyChunks);
|
||||
|
||||
// set attribute
|
||||
if ($propertiesString != '') {
|
||||
$element->setAttribute('style', $propertiesString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reapply original styles
|
||||
$query = $this->buildXPathQuery(
|
||||
'*[@data-css-to-inline-styles-original-styles]'
|
||||
);
|
||||
|
||||
// validate query
|
||||
if($query === false) return;
|
||||
|
||||
// search elements
|
||||
$elements = $xPath->query($query);
|
||||
|
||||
// loop found elements
|
||||
foreach ($elements as $element) {
|
||||
// get the original styles
|
||||
$originalStyle = $element->attributes->getNamedItem(
|
||||
'data-css-to-inline-styles-original-styles'
|
||||
)->value;
|
||||
|
||||
if ($originalStyle != '') {
|
||||
$originalProperties = array();
|
||||
$originalStyles = (array) explode(';', $originalStyle);
|
||||
|
||||
foreach ($originalStyles as $property) {
|
||||
// validate property
|
||||
if($property == '') continue;
|
||||
|
||||
// split into chunks
|
||||
$chunks = (array) explode(':', trim($property), 2);
|
||||
|
||||
// validate
|
||||
if(!isset($chunks[1])) continue;
|
||||
|
||||
// loop chunks
|
||||
$originalProperties[$chunks[0]] = trim($chunks[1]);
|
||||
}
|
||||
|
||||
// get current styles
|
||||
$stylesAttribute = $element->attributes->getNamedItem('style');
|
||||
$properties = array();
|
||||
|
||||
// any styles defined before?
|
||||
if ($stylesAttribute !== null) {
|
||||
// get value for the styles attribute
|
||||
$definedStyles = (string) $stylesAttribute->value;
|
||||
|
||||
// split into properties
|
||||
$definedProperties = (array) explode(';', $definedStyles);
|
||||
|
||||
// loop properties
|
||||
foreach ($definedProperties as $property) {
|
||||
// validate property
|
||||
if($property == '') continue;
|
||||
|
||||
// split into chunks
|
||||
$chunks = (array) explode(':', trim($property), 2);
|
||||
|
||||
// validate
|
||||
if(!isset($chunks[1])) continue;
|
||||
|
||||
// loop chunks
|
||||
$properties[$chunks[0]] = trim($chunks[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// add new properties into the list
|
||||
foreach ($originalProperties as $key => $value) {
|
||||
$properties[$key] = $value;
|
||||
}
|
||||
|
||||
// build string
|
||||
$propertyChunks = array();
|
||||
|
||||
// build chunks
|
||||
foreach ($properties as $key => $values) {
|
||||
foreach ((array) $values as $value) {
|
||||
$propertyChunks[] = $key . ': ' . $value . ';';
|
||||
}
|
||||
}
|
||||
|
||||
// build properties string
|
||||
$propertiesString = implode(' ', $propertyChunks);
|
||||
|
||||
// set attribute
|
||||
if($propertiesString != '') $element->setAttribute(
|
||||
'style', $propertiesString
|
||||
);
|
||||
}
|
||||
|
||||
// remove placeholder
|
||||
$element->removeAttribute(
|
||||
'data-css-to-inline-styles-original-styles'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// should we output XHTML?
|
||||
if ($outputXHTML) {
|
||||
// set formating
|
||||
$document->formatOutput = true;
|
||||
|
||||
// get the HTML as XML
|
||||
$html = $document->saveXML(null, LIBXML_NOEMPTYTAG);
|
||||
|
||||
// get start of the XML-declaration
|
||||
$startPosition = strpos($html, '<?xml');
|
||||
|
||||
// valid start position?
|
||||
if ($startPosition !== false) {
|
||||
// get end of the xml-declaration
|
||||
$endPosition = strpos($html, '?>', $startPosition);
|
||||
|
||||
// remove the XML-header
|
||||
$html = ltrim(substr($html, $endPosition + 1));
|
||||
}
|
||||
}
|
||||
|
||||
// just regular HTML 4.01 as it should be used in newsletters
|
||||
else {
|
||||
// get the HTML
|
||||
$html = $document->saveHTML();
|
||||
}
|
||||
|
||||
// cleanup the HTML if we need to
|
||||
if($this->cleanup) $html = $this->cleanupHTML($html);
|
||||
|
||||
// strip original style tags if we need to
|
||||
if ($this->stripOriginalStyleTags) {
|
||||
$html = $this->stripOriginalStyleTags($html);
|
||||
}
|
||||
|
||||
// return
|
||||
return $html;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the encoding to use
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getEncoding()
|
||||
{
|
||||
return $this->encoding;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process the loaded CSS
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function processCSS()
|
||||
{
|
||||
// init vars
|
||||
$css = (string) $this->css;
|
||||
|
||||
// remove newlines
|
||||
$css = str_replace(array("\r", "\n"), '', $css);
|
||||
|
||||
// replace double quotes by single quotes
|
||||
$css = str_replace('"', '\'', $css);
|
||||
|
||||
// remove comments
|
||||
$css = preg_replace('|/\*.*?\*/|', '', $css);
|
||||
|
||||
// remove spaces
|
||||
$css = preg_replace('/\s\s+/', ' ', $css);
|
||||
|
||||
// rules are splitted by }
|
||||
$rules = (array) explode('}', $css);
|
||||
|
||||
// init var
|
||||
$i = 1;
|
||||
|
||||
// loop rules
|
||||
foreach ($rules as $rule) {
|
||||
// split into chunks
|
||||
$chunks = explode('{', $rule);
|
||||
|
||||
// invalid rule?
|
||||
if(!isset($chunks[1])) continue;
|
||||
|
||||
// set the selectors
|
||||
$selectors = trim($chunks[0]);
|
||||
|
||||
// get cssProperties
|
||||
$cssProperties = trim($chunks[1]);
|
||||
|
||||
// split multiple selectors
|
||||
$selectors = (array) explode(',', $selectors);
|
||||
|
||||
// loop selectors
|
||||
foreach ($selectors as $selector) {
|
||||
// cleanup
|
||||
$selector = trim($selector);
|
||||
|
||||
// build an array for each selector
|
||||
$ruleSet = array();
|
||||
|
||||
// store selector
|
||||
$ruleSet['selector'] = $selector;
|
||||
|
||||
// process the properties
|
||||
$ruleSet['properties'] = $this->processCSSProperties(
|
||||
$cssProperties
|
||||
);
|
||||
|
||||
// calculate specifity
|
||||
$ruleSet['specifity'] = $this->calculateCSSSpecifity(
|
||||
$selector
|
||||
) + $i;
|
||||
|
||||
// add into global rules
|
||||
$this->cssRules[] = $ruleSet;
|
||||
}
|
||||
|
||||
// increment
|
||||
$i++;
|
||||
}
|
||||
|
||||
// sort based on specifity
|
||||
if (!empty($this->cssRules)) {
|
||||
usort($this->cssRules, array(__CLASS__, 'sortOnSpecifity'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the CSS-properties
|
||||
*
|
||||
* @return array
|
||||
* @param string $propertyString The CSS-properties.
|
||||
*/
|
||||
private function processCSSProperties($propertyString)
|
||||
{
|
||||
// split into chunks
|
||||
$properties = (array) explode(';', $propertyString);
|
||||
|
||||
// init var
|
||||
$pairs = array();
|
||||
|
||||
// loop properties
|
||||
foreach ($properties as $property) {
|
||||
// split into chunks
|
||||
$chunks = (array) explode(':', $property, 2);
|
||||
|
||||
// validate
|
||||
if(!isset($chunks[1])) continue;
|
||||
|
||||
// cleanup
|
||||
$chunks[0] = trim($chunks[0]);
|
||||
$chunks[1] = trim($chunks[1]);
|
||||
|
||||
// add to pairs array
|
||||
if(!isset($pairs[$chunks[0]]) ||
|
||||
!in_array($chunks[1], $pairs[$chunks[0]])) {
|
||||
$pairs[$chunks[0]][] = $chunks[1];
|
||||
}
|
||||
}
|
||||
|
||||
// sort the pairs
|
||||
ksort($pairs);
|
||||
|
||||
// return
|
||||
return $pairs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the IDs and classes be removed?
|
||||
*
|
||||
* @return void
|
||||
* @param bool[optional] $on Should we enable cleanup?
|
||||
*/
|
||||
public function setCleanup($on = true)
|
||||
{
|
||||
$this->cleanup = (bool) $on;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set CSS to use
|
||||
*
|
||||
* @return void
|
||||
* @param string $css The CSS to use.
|
||||
*/
|
||||
public function setCSS($css)
|
||||
{
|
||||
$this->css = (string) $css;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the encoding to use with the DOMDocument
|
||||
*
|
||||
* @return void
|
||||
* @param string $encoding The encoding to use.
|
||||
*/
|
||||
public function setEncoding($encoding)
|
||||
{
|
||||
$this->encoding = (string) $encoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set HTML to process
|
||||
*
|
||||
* @return void
|
||||
* @param string $html The HTML to process.
|
||||
*/
|
||||
public function setHTML($html)
|
||||
{
|
||||
$this->html = (string) $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set use of inline styles block
|
||||
* If this is enabled the class will use the style-block in the HTML.
|
||||
*
|
||||
* @return void
|
||||
* @param bool[optional] $on Should we process inline styles?
|
||||
*/
|
||||
public function setUseInlineStylesBlock($on = true)
|
||||
{
|
||||
$this->useInlineStylesBlock = (bool) $on;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set strip original style tags
|
||||
* If this is enabled the class will remove all style tags in the HTML.
|
||||
*
|
||||
* @return void
|
||||
* @param bool[optional] $onShould we process inline styles?
|
||||
*/
|
||||
public function setStripOriginalStyleTags($on = true)
|
||||
{
|
||||
$this->stripOriginalStyleTags = (bool) $on;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip style tags into the generated HTML
|
||||
*
|
||||
* @return string
|
||||
* @param string $html The HTML to strip style tags.
|
||||
*/
|
||||
private function stripOriginalStyleTags($html)
|
||||
{
|
||||
return preg_replace('|<style(.*)>(.*)</style>|isU', '', $html);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort an array on the specifity element
|
||||
*
|
||||
* @return int
|
||||
* @param array $e1 The first element.
|
||||
* @param array $e2 The second element.
|
||||
*/
|
||||
private static function sortOnSpecifity($e1, $e2)
|
||||
{
|
||||
// validate
|
||||
if(!isset($e1['specifity']) || !isset($e2['specifity'])) return 0;
|
||||
|
||||
// lower
|
||||
if($e1['specifity'] < $e2['specifity']) return -1;
|
||||
|
||||
// higher
|
||||
if($e1['specifity'] > $e2['specifity']) return 1;
|
||||
|
||||
// fallback
|
||||
return 0;
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 RainLoop Team
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -1 +0,0 @@
|
|||
Plugin used for processing complex embedded styles in mail messages. In some cases, it can improve rendering HTML mails.
|
|
@ -1 +0,0 @@
|
|||
1.3
|
|
@ -1,29 +0,0 @@
|
|||
<?php
|
||||
|
||||
class ConvertHeadersStylesPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||
{
|
||||
public function Init()
|
||||
{
|
||||
$this->addHook('filter.result-message', 'FilterResultMessage');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \MailSo\Mail\Message &$oMessage
|
||||
*/
|
||||
public function FilterResultMessage(&$oMessage)
|
||||
{
|
||||
if ($oMessage)
|
||||
{
|
||||
$sHtml = $oMessage->Html();
|
||||
if ($sHtml && 0 < strlen($sHtml))
|
||||
{
|
||||
include_once __DIR__.'/CssToInlineStyles.php';
|
||||
|
||||
$oCSSToInlineStyles = new \TijsVerkoyen\CssToInlineStyles\CssToInlineStyles($sHtml);
|
||||
$oCSSToInlineStyles->setEncoding('utf-8');
|
||||
$oCSSToInlineStyles->setUseInlineStylesBlock(true);
|
||||
$oMessage->SetHtml($oCSSToInlineStyles->convert().'<!-- convert-headers-styles-plugin -->');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 RainLoop Team
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -1,3 +0,0 @@
|
|||
A CAPTCHA is a program that can generate and grade tests that humans can pass but current computer programs cannot.
|
||||
For example, humans can read distorted text as the one shown below, but current computer programs can't.
|
||||
More info at http://www.google.com/recaptcha
|
|
@ -1 +0,0 @@
|
|||
1.9
|
|
@ -1,139 +0,0 @@
|
|||
<?php
|
||||
|
||||
class RecaptchaPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||
{
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function Init()
|
||||
{
|
||||
$this->UseLangs(true);
|
||||
|
||||
$this->addJs('js/recaptcha.js');
|
||||
|
||||
$this->addHook('ajax.action-pre-call', 'AjaxActionPreCall');
|
||||
$this->addHook('filter.ajax-response', 'FilterAjaxResponse');
|
||||
|
||||
$this->addTemplate('templates/PluginLoginReCaptchaGroup.html');
|
||||
$this->addTemplateHook('Login', 'BottomControlGroup', 'PluginLoginReCaptchaGroup');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function configMapping()
|
||||
{
|
||||
return array(
|
||||
\RainLoop\Plugins\Property::NewInstance('public_key')->SetLabel('Public Key')
|
||||
->SetAllowedInJs(true)
|
||||
->SetDefaultValue(''),
|
||||
\RainLoop\Plugins\Property::NewInstance('private_key')->SetLabel('Private Key')
|
||||
->SetDefaultValue(''),
|
||||
\RainLoop\Plugins\Property::NewInstance('error_limit')->SetLabel('Limit')
|
||||
->SetType(\RainLoop\Enumerations\PluginPropertyType::SELECTION)
|
||||
->SetDefaultValue(array(0, 1, 2, 3, 4, 5))
|
||||
->SetDescription('')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private function getCaptchaCacherKey()
|
||||
{
|
||||
return 'Captcha/Login/'.\RainLoop\Utils::GetConnectionToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
private function getLimit()
|
||||
{
|
||||
$iConfigLimit = $this->Config()->Get('plugin', 'error_limit', 0);
|
||||
if (0 < $iConfigLimit)
|
||||
{
|
||||
$oCacher = $this->Manager()->Actions()->Cacher();
|
||||
$sLimit = $oCacher && $oCacher->IsInited() ? $oCacher->Get($this->getCaptchaCacherKey()) : '0';
|
||||
|
||||
if (0 < strlen($sLimit) && is_numeric($sLimit))
|
||||
{
|
||||
$iConfigLimit -= (int) $sLimit;
|
||||
}
|
||||
}
|
||||
|
||||
return $iConfigLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function FilterAppDataPluginSection($bAdmin, $bAuth, &$aData)
|
||||
{
|
||||
if (!$bAdmin && !$bAuth && is_array($aData))
|
||||
{
|
||||
$aData['show_captcha_on_login'] = 1 > $this->getLimit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sAction
|
||||
*/
|
||||
public function AjaxActionPreCall($sAction)
|
||||
{
|
||||
if ('Login' === $sAction && 0 >= $this->getLimit())
|
||||
{
|
||||
require_once __DIR__.'/recaptchalib.php';
|
||||
|
||||
$oResp = recaptcha_check_answer(
|
||||
$this->Config()->Get('plugin', 'private_key', ''),
|
||||
isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '',
|
||||
$this->Manager()->Actions()->GetActionParam('RecaptchaChallenge', ''),
|
||||
$this->Manager()->Actions()->GetActionParam('RecaptchaResponse', '')
|
||||
);
|
||||
|
||||
if (!$oResp || !isset($oResp->is_valid) || !$oResp->is_valid)
|
||||
{
|
||||
$this->Manager()->Actions()->Logger()->WriteDump($oResp);
|
||||
throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CaptchaError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sAction
|
||||
* @param array $aResponseItem
|
||||
*/
|
||||
public function FilterAjaxResponse($sAction, &$aResponseItem)
|
||||
{
|
||||
if ('Login' === $sAction && $aResponseItem && isset($aResponseItem['Result']))
|
||||
{
|
||||
$oCacher = $this->Manager()->Actions()->Cacher();
|
||||
$iConfigLimit = (int) $this->Config()->Get('plugin', 'error_limit', 0);
|
||||
$sKey = $this->getCaptchaCacherKey();
|
||||
|
||||
if (0 < $iConfigLimit && $oCacher && $oCacher->IsInited())
|
||||
{
|
||||
if (false === $aResponseItem['Result'])
|
||||
{
|
||||
$iLimit = 0;
|
||||
$sLimut = $oCacher->Get($sKey);
|
||||
if (0 < strlen($sLimut) && is_numeric($sLimut))
|
||||
{
|
||||
$iLimit = (int) $sLimut;
|
||||
}
|
||||
|
||||
$oCacher->Set($sKey, ++$iLimit);
|
||||
|
||||
if ($iConfigLimit <= $iLimit)
|
||||
{
|
||||
$aResponseItem['Captcha'] = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$oCacher->Delete($sKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
|
||||
$(function () {
|
||||
|
||||
var
|
||||
bStarted = false,
|
||||
bShown = false
|
||||
;
|
||||
|
||||
function ShowRecaptcha()
|
||||
{
|
||||
if (window.Recaptcha)
|
||||
{
|
||||
if (bShown)
|
||||
{
|
||||
window.Recaptcha.reload();
|
||||
}
|
||||
else
|
||||
{
|
||||
window.Recaptcha.create(window.rl.pluginSettingsGet('recaptcha', 'public_key'), 'recaptcha-place', {
|
||||
'theme': 'custom',
|
||||
'lang': window.rl.settingsGet('Language')
|
||||
});
|
||||
}
|
||||
|
||||
bShown = true;
|
||||
}
|
||||
}
|
||||
|
||||
function StartRecaptcha()
|
||||
{
|
||||
if (!window.Recaptcha)
|
||||
{
|
||||
$.getScript('//www.google.com/recaptcha/api/js/recaptcha_ajax.js', ShowRecaptcha);
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowRecaptcha();
|
||||
}
|
||||
}
|
||||
|
||||
if (window.rl)
|
||||
{
|
||||
window.rl.addHook('view-model-on-show', function (sName, oViewModel) {
|
||||
if (!bStarted && oViewModel &&
|
||||
('View:RainLoop:Login' === sName || 'View/App/Login' === sName || 'LoginViewModel' === sName || 'LoginAppView' === sName) &&
|
||||
window.rl.pluginSettingsGet('recaptcha', 'show_captcha_on_login'))
|
||||
{
|
||||
bStarted = true;
|
||||
StartRecaptcha();
|
||||
}
|
||||
});
|
||||
|
||||
window.rl.addHook('ajax-default-request', function (sAction, oParameters) {
|
||||
if ('Login' === sAction && oParameters && bShown && window.Recaptcha)
|
||||
{
|
||||
oParameters['RecaptchaChallenge'] = window.Recaptcha.get_challenge();
|
||||
oParameters['RecaptchaResponse'] = window.Recaptcha.get_response();
|
||||
}
|
||||
});
|
||||
|
||||
window.rl.addHook('ajax-default-response', function (sAction, oData, sType) {
|
||||
if ('Login' === sAction)
|
||||
{
|
||||
if (!oData || 'success' !== sType || !oData['Result'])
|
||||
{
|
||||
if (bShown && window.Recaptcha)
|
||||
{
|
||||
window.Recaptcha.reload();
|
||||
}
|
||||
else if (oData && oData['Captcha'])
|
||||
{
|
||||
StartRecaptcha();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
|
@ -1,2 +0,0 @@
|
|||
[PLUGIN]
|
||||
LABEL_ENTER_THE_WORDS_ABOVE = "Enter the words above"
|
|
@ -1,2 +0,0 @@
|
|||
[PLUGIN]
|
||||
LABEL_ENTER_THE_WORDS_ABOVE = "Введите слова с изображения"
|
|
@ -1,280 +0,0 @@
|
|||
<?php
|
||||
/*
|
||||
* This is a PHP library that handles calling reCAPTCHA.
|
||||
* - Documentation and latest version
|
||||
* http://recaptcha.net/plugins/php/
|
||||
* - Get a reCAPTCHA API Key
|
||||
* https://www.google.com/recaptcha/admin/create
|
||||
* - Discussion group
|
||||
* http://groups.google.com/group/recaptcha
|
||||
*
|
||||
* Copyright (c) 2007 reCAPTCHA -- http://recaptcha.net
|
||||
* AUTHORS:
|
||||
* Mike Crawford
|
||||
* Ben Maurer
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The reCAPTCHA server URL's
|
||||
*/
|
||||
define("RECAPTCHA_API_SERVER", "http://www.google.com/recaptcha/api");
|
||||
define("RECAPTCHA_API_SECURE_SERVER", "https://www.google.com/recaptcha/api");
|
||||
define("RECAPTCHA_VERIFY_SERVER", "www.google.com");
|
||||
|
||||
/**
|
||||
* Encodes the given data into a query string format
|
||||
* @param $data - array of string elements to be encoded
|
||||
* @return string - encoded request
|
||||
*/
|
||||
function _recaptcha_qsencode ($data) {
|
||||
$req = "";
|
||||
foreach ( $data as $key => $value )
|
||||
$req .= $key . '=' . urlencode( stripslashes($value) ) . '&';
|
||||
|
||||
// Cut the last '&'
|
||||
$req=substr($req,0,strlen($req)-1);
|
||||
return $req;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Submits an HTTP POST to a reCAPTCHA server
|
||||
* @param string $host
|
||||
* @param string $path
|
||||
* @param array $data
|
||||
* @param int port
|
||||
* @return array response
|
||||
*/
|
||||
function _recaptcha_http_post($host, $path, $data, $port = 80) {
|
||||
|
||||
$req = _recaptcha_qsencode ($data);
|
||||
|
||||
$http_request = "POST $path HTTP/1.0\r\n";
|
||||
$http_request .= "Host: $host\r\n";
|
||||
$http_request .= "Content-Type: application/x-www-form-urlencoded;\r\n";
|
||||
$http_request .= "Content-Length: " . strlen($req) . "\r\n";
|
||||
$http_request .= "User-Agent: reCAPTCHA/PHP\r\n";
|
||||
$http_request .= "\r\n";
|
||||
$http_request .= $req;
|
||||
|
||||
$response = '';
|
||||
if( false == ( $fs = @fsockopen($host, $port, $errno, $errstr, 10) ) ) {
|
||||
die ('Could not open socket');
|
||||
}
|
||||
|
||||
fwrite($fs, $http_request);
|
||||
|
||||
while ( !feof($fs) )
|
||||
$response .= fgets($fs, 1160); // One TCP-IP packet
|
||||
fclose($fs);
|
||||
$response = explode("\r\n\r\n", $response, 2);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Gets the challenge HTML (javascript and non-javascript version).
|
||||
* This is called from the browser, and the resulting reCAPTCHA HTML widget
|
||||
* is embedded within the HTML form it was called from.
|
||||
* @param string $pubkey A public key for reCAPTCHA
|
||||
* @param string $error The error given by reCAPTCHA (optional, default is null)
|
||||
* @param boolean $use_ssl Should the request be made over ssl? (optional, default is false)
|
||||
|
||||
* @return string - The HTML to be embedded in the user's form.
|
||||
*/
|
||||
function recaptcha_get_html ($pubkey, $error = null, $use_ssl = false)
|
||||
{
|
||||
if ($pubkey == null || $pubkey == '') {
|
||||
die ("To use reCAPTCHA you must get an API key from <a href='https://www.google.com/recaptcha/admin/create'>https://www.google.com/recaptcha/admin/create</a>");
|
||||
}
|
||||
|
||||
if ($use_ssl) {
|
||||
$server = RECAPTCHA_API_SECURE_SERVER;
|
||||
} else {
|
||||
$server = RECAPTCHA_API_SERVER;
|
||||
}
|
||||
|
||||
$errorpart = "";
|
||||
if ($error) {
|
||||
$errorpart = "&error=" . $error;
|
||||
}
|
||||
return '<script type="text/javascript" data-cfasync="false" src="'. $server . '/challenge?k=' . $pubkey . $errorpart . '"></script>
|
||||
|
||||
<noscript>
|
||||
<iframe src="'. $server . '/noscript?k=' . $pubkey . $errorpart . '" height="300" width="500" frameborder="0"></iframe><br/>
|
||||
<textarea name="recaptcha_challenge_field" rows="3" cols="40"></textarea>
|
||||
<input type="hidden" name="recaptcha_response_field" value="manual_challenge"/>
|
||||
</noscript>';
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A ReCaptchaResponse is returned from recaptcha_check_answer()
|
||||
*/
|
||||
class ReCaptchaResponse {
|
||||
var $is_valid;
|
||||
var $error;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calls an HTTP POST function to verify if the user's guess was correct
|
||||
* @param string $privkey
|
||||
* @param string $remoteip
|
||||
* @param string $challenge
|
||||
* @param string $response
|
||||
* @param array $extra_params an array of extra variables to post to the server
|
||||
* @return ReCaptchaResponse
|
||||
*/
|
||||
function recaptcha_check_answer ($privkey, $remoteip, $challenge, $response, $extra_params = array())
|
||||
{
|
||||
if ($privkey == null || $privkey == '') {
|
||||
$recaptcha_response = new ReCaptchaResponse();
|
||||
$recaptcha_response->is_valid = false;
|
||||
$recaptcha_response->error = 'To use reCAPTCHA you must get an API key from <a href=\'https://www.google.com/recaptcha/admin/create\'>https://www.google.com/recaptcha/admin/create</a>';
|
||||
return $recaptcha_response;
|
||||
}
|
||||
|
||||
if ($remoteip == null || $remoteip == '') {
|
||||
$recaptcha_response = new ReCaptchaResponse();
|
||||
$recaptcha_response->is_valid = false;
|
||||
$recaptcha_response->error = 'For security reasons, you must pass the remote ip to reCAPTCHA';
|
||||
return $recaptcha_response;
|
||||
}
|
||||
|
||||
//discard spam submissions
|
||||
if ($challenge == null || strlen($challenge) == 0 || $response == null || strlen($response) == 0) {
|
||||
$recaptcha_response = new ReCaptchaResponse();
|
||||
$recaptcha_response->is_valid = false;
|
||||
$recaptcha_response->error = 'incorrect-captcha-sol';
|
||||
return $recaptcha_response;
|
||||
}
|
||||
|
||||
$response = _recaptcha_http_post (RECAPTCHA_VERIFY_SERVER, "/recaptcha/api/verify",
|
||||
array (
|
||||
'privatekey' => $privkey,
|
||||
'remoteip' => $remoteip,
|
||||
'challenge' => $challenge,
|
||||
'response' => $response
|
||||
) + $extra_params
|
||||
);
|
||||
|
||||
$answers = explode ("\n", $response [1]);
|
||||
$recaptcha_response = new ReCaptchaResponse();
|
||||
|
||||
if (trim ($answers [0]) == 'true') {
|
||||
$recaptcha_response->is_valid = true;
|
||||
}
|
||||
else {
|
||||
$recaptcha_response->is_valid = false;
|
||||
$recaptcha_response->error = $answers [1];
|
||||
}
|
||||
return $recaptcha_response;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* gets a URL where the user can sign up for reCAPTCHA. If your application
|
||||
* has a configuration page where you enter a key, you should provide a link
|
||||
* using this function.
|
||||
* @param string $domain The domain where the page is hosted
|
||||
* @param string $appname The name of your application
|
||||
*/
|
||||
function recaptcha_get_signup_url ($domain = null, $appname = null) {
|
||||
return "https://www.google.com/recaptcha/admin/create?" . _recaptcha_qsencode (array ('domains' => $domain, 'app' => $appname));
|
||||
}
|
||||
|
||||
function _recaptcha_aes_pad($val) {
|
||||
$block_size = 16;
|
||||
$numpad = $block_size - (strlen ($val) % $block_size);
|
||||
return str_pad($val, strlen ($val) + $numpad, chr($numpad));
|
||||
}
|
||||
|
||||
/* Mailhide related code */
|
||||
|
||||
function _recaptcha_aes_encrypt($val,$ky) {
|
||||
if (! function_exists ("mcrypt_encrypt")) {
|
||||
die ("To use reCAPTCHA Mailhide, you need to have the mcrypt php module installed.");
|
||||
}
|
||||
$mode=MCRYPT_MODE_CBC;
|
||||
$enc=MCRYPT_RIJNDAEL_128;
|
||||
$val=_recaptcha_aes_pad($val);
|
||||
return mcrypt_encrypt($enc, $ky, $val, $mode, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0");
|
||||
}
|
||||
|
||||
|
||||
function _recaptcha_mailhide_urlbase64 ($x) {
|
||||
return strtr(base64_encode ($x), '+/', '-_');
|
||||
}
|
||||
|
||||
/* gets the reCAPTCHA Mailhide url for a given email, public key and private key */
|
||||
function recaptcha_mailhide_url($pubkey, $privkey, $email) {
|
||||
if ($pubkey == '' || $pubkey == null || $privkey == "" || $privkey == null) {
|
||||
die ("To use reCAPTCHA Mailhide, you have to sign up for a public and private key, " .
|
||||
"you can do so at <a href='http://www.google.com/recaptcha/mailhide/apikey'>http://www.google.com/recaptcha/mailhide/apikey</a>");
|
||||
}
|
||||
|
||||
|
||||
$ky = pack('H*', $privkey);
|
||||
$cryptmail = _recaptcha_aes_encrypt ($email, $ky);
|
||||
|
||||
return "http://www.google.com/recaptcha/mailhide/d?k=" . $pubkey . "&c=" . _recaptcha_mailhide_urlbase64 ($cryptmail);
|
||||
}
|
||||
|
||||
/**
|
||||
* gets the parts of the email to expose to the user.
|
||||
* eg, given johndoe@example,com return ["john", "example.com"].
|
||||
* the email is then displayed as john...@example.com
|
||||
*/
|
||||
function _recaptcha_mailhide_email_parts ($email) {
|
||||
$arr = preg_split("/@/", $email );
|
||||
|
||||
if (strlen ($arr[0]) <= 4) {
|
||||
$arr[0] = substr ($arr[0], 0, 1);
|
||||
} else if (strlen ($arr[0]) <= 6) {
|
||||
$arr[0] = substr ($arr[0], 0, 3);
|
||||
} else {
|
||||
$arr[0] = substr ($arr[0], 0, 4);
|
||||
}
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets html to display an email address given a public an private key.
|
||||
* to get a key, go to:
|
||||
*
|
||||
* http://www.google.com/recaptcha/mailhide/apikey
|
||||
*/
|
||||
function recaptcha_mailhide_html($pubkey, $privkey, $email) {
|
||||
$emailparts = _recaptcha_mailhide_email_parts ($email);
|
||||
$url = recaptcha_mailhide_url ($pubkey, $privkey, $email);
|
||||
|
||||
return htmlentities($emailparts[0]) . "<a href='" . htmlentities ($url) .
|
||||
"' onclick=\"window.open('" . htmlentities ($url) . "', '', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=500,height=300'); return false;\" title=\"Reveal this e-mail address\">...</a>@" . htmlentities ($emailparts [1]);
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
<div class="recaptcha-control-group" id="recaptcha-place" style="display: none">
|
||||
<div class="controls">
|
||||
<div id="recaptcha_image" style="border-radius: 3px"></div>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<div class="input-append">
|
||||
|
||||
<input class="i18n input-block-level inputLoginForm inputCAPTCHA" type="text" autocomplete="off"
|
||||
id="recaptcha_response_field" data-i18n-placeholder="PLUGIN/LABEL_ENTER_THE_WORDS_ABOVE"
|
||||
data-i18n="[placeholder]PLUGIN/LABEL_ENTER_THE_WORDS_ABOVE" />
|
||||
|
||||
<span class="add-on">
|
||||
<i class="icon-repeat" onclick="Recaptcha.reload()" style="cursor: pointer"></i>
|
||||
</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -172,7 +172,7 @@ class Api
|
|||
}
|
||||
}
|
||||
|
||||
protected static function Version() : string
|
||||
public static function Version() : string
|
||||
{
|
||||
return APP_VERSION;
|
||||
}
|
||||
|
|
|
@ -64,10 +64,6 @@ config.paths.css = {
|
|||
'vendors/Progress.js/minified/progressjs.min.css',
|
||||
'dev/Styles/_progressjs.css'
|
||||
]
|
||||
},
|
||||
social: {
|
||||
name: 'social.css',
|
||||
src: ['vendors/fontastic/styles.css', 'dev/Styles/_social.css']
|
||||
}
|
||||
};
|
||||
|
||||
|
|
27
tasks/css.js
27
tasks/css.js
|
@ -41,19 +41,6 @@ const cssMainBuild = () => {
|
|||
.pipe(livereload());
|
||||
};
|
||||
|
||||
const cssSocialBuild = () => {
|
||||
const autoprefixer = require('gulp-autoprefixer'),
|
||||
src = config.paths.css.social.src;
|
||||
return gulp
|
||||
.src(src)
|
||||
.pipe(expect.real({ errorOnFailure: true }, src))
|
||||
.pipe(concat(config.paths.css.social.name))
|
||||
.pipe(autoprefixer())
|
||||
.pipe(replace(/\.\.\/(img|images|fonts|svg)\//g, '$1/'))
|
||||
.pipe(eol('\n', true))
|
||||
.pipe(gulp.dest(config.paths.staticCSS));
|
||||
};
|
||||
|
||||
const cssMainMin = () => {
|
||||
const cleanCss = require('gulp-clean-css');
|
||||
return gulp
|
||||
|
@ -64,18 +51,8 @@ const cssMainMin = () => {
|
|||
.pipe(gulp.dest(config.paths.staticCSS));
|
||||
};
|
||||
|
||||
const cssSocialMin = () => {
|
||||
const cleanCss = require('gulp-clean-css');
|
||||
return gulp
|
||||
.src(config.paths.staticCSS + config.paths.css.social.name)
|
||||
.pipe(cleanCss())
|
||||
.pipe(rename({ suffix: '.min' }))
|
||||
.pipe(eol('\n', true))
|
||||
.pipe(gulp.dest(config.paths.staticCSS));
|
||||
};
|
||||
|
||||
const cssBuild = gulp.parallel(cssMainBuild, cssSocialBuild);
|
||||
const cssMin = gulp.parallel(cssMainMin, cssSocialMin);
|
||||
const cssBuild = gulp.parallel(cssMainBuild);
|
||||
const cssMin = gulp.parallel(cssMainMin);
|
||||
|
||||
const cssLint = (done) => done();
|
||||
|
||||
|
|
|
@ -1,96 +0,0 @@
|
|||
/* RainLoop Webmail (c) RainLoop Team | Licensed under AGPL 3 */
|
||||
/* eslint-disable consistent-return */
|
||||
|
||||
const gulp = require('gulp');
|
||||
const fs = require('node-fs');
|
||||
|
||||
const pkg = require('../package.json');
|
||||
const { config } = require('./config');
|
||||
const { copy, zip, del } = require('./common');
|
||||
const { rainloopBuild } = require('./rainloop');
|
||||
|
||||
const owncloudCopy = () => {
|
||||
const versionFull = pkg.ownCloudVersion,
|
||||
dist = config.releasesPath + '/owncloud/' + versionFull + '/src/';
|
||||
|
||||
fs.mkdirSync(dist, '0777', true);
|
||||
fs.mkdirSync(dist + 'rainloop', '0777', true);
|
||||
|
||||
return gulp
|
||||
.src('build/owncloud/rainloop-app/**/*', { base: 'build/owncloud/rainloop-app/' })
|
||||
.pipe(gulp.dest(dist + 'rainloop'));
|
||||
};
|
||||
|
||||
const owncloudCopyRainLoop = () => {
|
||||
const versionFull = pkg.ownCloudVersion,
|
||||
dist = config.releasesPath + '/owncloud/' + versionFull + '/src/rainloop/';
|
||||
if (config.rainloopBuilded && config.destPath) {
|
||||
return gulp.src(config.destPath + '/src/**/*', { base: config.destPath + '/src/' }).pipe(gulp.dest(dist + 'app/'));
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const owncloudCopyRainLoopClean = (done) => {
|
||||
if (config.cleanPath) {
|
||||
return del(config.cleanPath);
|
||||
}
|
||||
done();
|
||||
};
|
||||
|
||||
const owncloudSetup = (done) => {
|
||||
const versionFull = pkg.ownCloudVersion,
|
||||
dist = config.releasesPath + '/owncloud/' + versionFull + '/src/';
|
||||
fs.writeFileSync(
|
||||
dist + 'rainloop/appinfo/info.xml',
|
||||
fs
|
||||
.readFileSync(dist + 'rainloop/appinfo/info.xml', 'utf8')
|
||||
.replace('<version>0.0</version>', '<version>' + versionFull + '</version>')
|
||||
.replace(
|
||||
'<licence></licence>',
|
||||
'<licence>' + (config.community ? 'AGPLv3' : 'RainLoop Software License') + '</licence>'
|
||||
)
|
||||
);
|
||||
|
||||
fs.writeFileSync(dist + 'rainloop/appinfo/version', versionFull);
|
||||
fs.writeFileSync(dist + 'rainloop/VERSION', versionFull);
|
||||
|
||||
config.destPath = config.releasesPath + '/owncloud/' + versionFull + '/';
|
||||
config.cleanPath = dist;
|
||||
config.zipSrcPath = dist;
|
||||
config.zipFile = 'rainloop-owncloud-app-' + (config.community ? '' : 'standard-') + versionFull + '.zip';
|
||||
config.zipFileShort = 'rainloop' + (config.community ? '' : '-standard') + '.zip';
|
||||
|
||||
done();
|
||||
};
|
||||
|
||||
const owncloudZip = (done) => {
|
||||
if (config.destPath && config.zipSrcPath && config.zipFile) {
|
||||
return zip(config.zipSrcPath, config.destPath, config.zipFile);
|
||||
}
|
||||
|
||||
done();
|
||||
};
|
||||
|
||||
const owncloudClean = (done) => {
|
||||
if (config.cleanPath) {
|
||||
return del(config.cleanPath);
|
||||
}
|
||||
|
||||
done();
|
||||
};
|
||||
|
||||
const owncloudShortname = (done) => {
|
||||
copy(config.destPath + config.zipFile, config.destPath + config.zipFileShort, done);
|
||||
};
|
||||
|
||||
exports.owncloud = gulp.series(
|
||||
owncloudCopy,
|
||||
rainloopBuild,
|
||||
owncloudCopyRainLoop,
|
||||
owncloudCopyRainLoopClean,
|
||||
owncloudSetup,
|
||||
owncloudZip,
|
||||
owncloudClean,
|
||||
owncloudShortname
|
||||
);
|
14
vendors/modernizr/config.json
vendored
14
vendors/modernizr/config.json
vendored
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"minify": false,
|
||||
"options": [
|
||||
"setClasses"
|
||||
],
|
||||
"feature-detects": [
|
||||
"test/css/animations",
|
||||
"test/css/backgroundsize",
|
||||
"test/css/boxshadow",
|
||||
"test/css/rgba",
|
||||
"test/css/textshadow",
|
||||
"test/css/transitions"
|
||||
]
|
||||
}
|
905
vendors/modernizr/modernizr-custom.js
vendored
905
vendors/modernizr/modernizr-custom.js
vendored
|
@ -1,905 +0,0 @@
|
|||
/*!
|
||||
* modernizr v3.3.1
|
||||
* Build http://modernizr.com/download?-backgroundsize-boxshadow-cssanimations-csstransitions-rgba-textshadow-setclasses-dontmin
|
||||
*
|
||||
* Copyright (c)
|
||||
* Faruk Ates
|
||||
* Paul Irish
|
||||
* Alex Sexton
|
||||
* Ryan Seddon
|
||||
* Patrick Kettner
|
||||
* Stu Cox
|
||||
* Richard Herrera
|
||||
|
||||
* MIT License
|
||||
*/
|
||||
|
||||
/*
|
||||
* Modernizr tests which native CSS3 and HTML5 features are available in the
|
||||
* current UA and makes the results available to you in two ways: as properties on
|
||||
* a global `Modernizr` object, and as classes on the `<html>` element. This
|
||||
* information allows you to progressively enhance your pages with a granular level
|
||||
* of control over the experience.
|
||||
*/
|
||||
|
||||
;(function(window, document, undefined){
|
||||
var classes = [];
|
||||
|
||||
|
||||
var tests = [];
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* ModernizrProto is the constructor for Modernizr
|
||||
*
|
||||
* @class
|
||||
* @access public
|
||||
*/
|
||||
|
||||
var ModernizrProto = {
|
||||
// The current version, dummy
|
||||
_version: '3.3.1',
|
||||
|
||||
// Any settings that don't work as separate modules
|
||||
// can go in here as configuration.
|
||||
_config: {
|
||||
'classPrefix': '',
|
||||
'enableClasses': true,
|
||||
'enableJSClass': true,
|
||||
'usePrefixes': true
|
||||
},
|
||||
|
||||
// Queue of tests
|
||||
_q: [],
|
||||
|
||||
// Stub these for people who are listening
|
||||
on: function(test, cb) {
|
||||
// I don't really think people should do this, but we can
|
||||
// safe guard it a bit.
|
||||
// -- NOTE:: this gets WAY overridden in src/addTest for actual async tests.
|
||||
// This is in case people listen to synchronous tests. I would leave it out,
|
||||
// but the code to *disallow* sync tests in the real version of this
|
||||
// function is actually larger than this.
|
||||
var self = this;
|
||||
setTimeout(function() {
|
||||
cb(self[test]);
|
||||
}, 0);
|
||||
},
|
||||
|
||||
addTest: function(name, fn, options) {
|
||||
tests.push({name: name, fn: fn, options: options});
|
||||
},
|
||||
|
||||
addAsyncTest: function(fn) {
|
||||
tests.push({name: null, fn: fn});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Fake some of Object.create so we can force non test results to be non "own" properties.
|
||||
var Modernizr = function() {};
|
||||
Modernizr.prototype = ModernizrProto;
|
||||
|
||||
// Leak modernizr globally when you `require` it rather than force it here.
|
||||
// Overwrite name so constructor name is nicer :D
|
||||
Modernizr = new Modernizr();
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* is returns a boolean if the typeof an obj is exactly type.
|
||||
*
|
||||
* @access private
|
||||
* @function is
|
||||
* @param {*} obj - A thing we want to check the type of
|
||||
* @param {string} type - A string to compare the typeof against
|
||||
* @returns {boolean}
|
||||
*/
|
||||
|
||||
function is(obj, type) {
|
||||
return typeof obj === type;
|
||||
}
|
||||
;
|
||||
|
||||
/**
|
||||
* Run through all tests and detect their support in the current UA.
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
|
||||
function testRunner() {
|
||||
var featureNames;
|
||||
var feature;
|
||||
var aliasIdx;
|
||||
var result;
|
||||
var nameIdx;
|
||||
var featureName;
|
||||
var featureNameSplit;
|
||||
|
||||
for (var featureIdx in tests) {
|
||||
if (tests.hasOwnProperty(featureIdx)) {
|
||||
featureNames = [];
|
||||
feature = tests[featureIdx];
|
||||
// run the test, throw the return value into the Modernizr,
|
||||
// then based on that boolean, define an appropriate className
|
||||
// and push it into an array of classes we'll join later.
|
||||
//
|
||||
// If there is no name, it's an 'async' test that is run,
|
||||
// but not directly added to the object. That should
|
||||
// be done with a post-run addTest call.
|
||||
if (feature.name) {
|
||||
featureNames.push(feature.name.toLowerCase());
|
||||
|
||||
if (feature.options && feature.options.aliases && feature.options.aliases.length) {
|
||||
// Add all the aliases into the names list
|
||||
for (aliasIdx = 0; aliasIdx < feature.options.aliases.length; aliasIdx++) {
|
||||
featureNames.push(feature.options.aliases[aliasIdx].toLowerCase());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run the test, or use the raw value if it's not a function
|
||||
result = is(feature.fn, 'function') ? feature.fn() : feature.fn;
|
||||
|
||||
|
||||
// Set each of the names on the Modernizr object
|
||||
for (nameIdx = 0; nameIdx < featureNames.length; nameIdx++) {
|
||||
featureName = featureNames[nameIdx];
|
||||
// Support dot properties as sub tests. We don't do checking to make sure
|
||||
// that the implied parent tests have been added. You must call them in
|
||||
// order (either in the test, or make the parent test a dependency).
|
||||
//
|
||||
// Cap it to TWO to make the logic simple and because who needs that kind of subtesting
|
||||
// hashtag famous last words
|
||||
featureNameSplit = featureName.split('.');
|
||||
|
||||
if (featureNameSplit.length === 1) {
|
||||
Modernizr[featureNameSplit[0]] = result;
|
||||
} else {
|
||||
// cast to a Boolean, if not one already
|
||||
/* jshint -W053 */
|
||||
if (Modernizr[featureNameSplit[0]] && !(Modernizr[featureNameSplit[0]] instanceof Boolean)) {
|
||||
Modernizr[featureNameSplit[0]] = new Boolean(Modernizr[featureNameSplit[0]]);
|
||||
}
|
||||
|
||||
Modernizr[featureNameSplit[0]][featureNameSplit[1]] = result;
|
||||
}
|
||||
|
||||
classes.push((result ? '' : 'no-') + featureNameSplit.join('-'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
;
|
||||
|
||||
/**
|
||||
* docElement is a convenience wrapper to grab the root element of the document
|
||||
*
|
||||
* @access private
|
||||
* @returns {HTMLElement|SVGElement} The root element of the document
|
||||
*/
|
||||
|
||||
var docElement = document.documentElement;
|
||||
|
||||
|
||||
/**
|
||||
* A convenience helper to check if the document we are running in is an SVG document
|
||||
*
|
||||
* @access private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
|
||||
var isSVG = docElement.nodeName.toLowerCase() === 'svg';
|
||||
|
||||
|
||||
/**
|
||||
* setClasses takes an array of class names and adds them to the root element
|
||||
*
|
||||
* @access private
|
||||
* @function setClasses
|
||||
* @param {string[]} classes - Array of class names
|
||||
*/
|
||||
|
||||
// Pass in an and array of class names, e.g.:
|
||||
// ['no-webp', 'borderradius', ...]
|
||||
function setClasses(classes) {
|
||||
var className = docElement.className;
|
||||
var classPrefix = Modernizr._config.classPrefix || '';
|
||||
|
||||
if (isSVG) {
|
||||
className = className.baseVal;
|
||||
}
|
||||
|
||||
// Change `no-js` to `js` (independently of the `enableClasses` option)
|
||||
// Handle classPrefix on this too
|
||||
if (Modernizr._config.enableJSClass) {
|
||||
var reJS = new RegExp('(^|\\s)' + classPrefix + 'no-js(\\s|$)');
|
||||
className = className.replace(reJS, '$1' + classPrefix + 'js$2');
|
||||
}
|
||||
|
||||
if (Modernizr._config.enableClasses) {
|
||||
// Add the new classes
|
||||
className += ' ' + classPrefix + classes.join(' ' + classPrefix);
|
||||
isSVG ? docElement.className.baseVal = className : docElement.className = className;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
/**
|
||||
* createElement is a convenience wrapper around document.createElement. Since we
|
||||
* use createElement all over the place, this allows for (slightly) smaller code
|
||||
* as well as abstracting away issues with creating elements in contexts other than
|
||||
* HTML documents (e.g. SVG documents).
|
||||
*
|
||||
* @access private
|
||||
* @function createElement
|
||||
* @returns {HTMLElement|SVGElement} An HTML or SVG element
|
||||
*/
|
||||
|
||||
function createElement() {
|
||||
if (typeof document.createElement !== 'function') {
|
||||
// This is the case in IE7, where the type of createElement is "object".
|
||||
// For this reason, we cannot call apply() as Object is not a Function.
|
||||
return document.createElement(arguments[0]);
|
||||
} else if (isSVG) {
|
||||
return document.createElementNS.call(document, 'http://www.w3.org/2000/svg', arguments[0]);
|
||||
} else {
|
||||
return document.createElement.apply(document, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
;
|
||||
/*!
|
||||
{
|
||||
"name": "CSS rgba",
|
||||
"caniuse": "css3-colors",
|
||||
"property": "rgba",
|
||||
"tags": ["css"],
|
||||
"notes": [{
|
||||
"name": "CSSTricks Tutorial",
|
||||
"href": "https://css-tricks.com/rgba-browser-support/"
|
||||
}]
|
||||
}
|
||||
!*/
|
||||
|
||||
Modernizr.addTest('rgba', function() {
|
||||
var style = createElement('a').style;
|
||||
style.cssText = 'background-color:rgba(150,255,150,.5)';
|
||||
|
||||
return ('' + style.backgroundColor).indexOf('rgba') > -1;
|
||||
});
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* contains checks to see if a string contains another string
|
||||
*
|
||||
* @access private
|
||||
* @function contains
|
||||
* @param {string} str - The string we want to check for substrings
|
||||
* @param {string} substr - The substring we want to search the first string for
|
||||
* @returns {boolean}
|
||||
*/
|
||||
|
||||
function contains(str, substr) {
|
||||
return !!~('' + str).indexOf(substr);
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
/**
|
||||
* cssToDOM takes a kebab-case string and converts it to camelCase
|
||||
* e.g. box-sizing -> boxSizing
|
||||
*
|
||||
* @access private
|
||||
* @function cssToDOM
|
||||
* @param {string} name - String name of kebab-case prop we want to convert
|
||||
* @returns {string} The camelCase version of the supplied name
|
||||
*/
|
||||
|
||||
function cssToDOM(name) {
|
||||
return name.replace(/([a-z])-([a-z])/g, function(str, m1, m2) {
|
||||
return m1 + m2.toUpperCase();
|
||||
}).replace(/^-/, '');
|
||||
}
|
||||
;
|
||||
|
||||
/**
|
||||
* If the browsers follow the spec, then they would expose vendor-specific style as:
|
||||
* elem.style.WebkitBorderRadius
|
||||
* instead of something like the following, which would be technically incorrect:
|
||||
* elem.style.webkitBorderRadius
|
||||
|
||||
* Webkit ghosts their properties in lowercase but Opera & Moz do not.
|
||||
* Microsoft uses a lowercase `ms` instead of the correct `Ms` in IE8+
|
||||
* erik.eae.net/archives/2008/03/10/21.48.10/
|
||||
|
||||
* More here: github.com/Modernizr/Modernizr/issues/issue/21
|
||||
*
|
||||
* @access private
|
||||
* @returns {string} The string representing the vendor-specific style properties
|
||||
*/
|
||||
|
||||
var omPrefixes = 'Moz O ms Webkit';
|
||||
|
||||
|
||||
var cssomPrefixes = (ModernizrProto._config.usePrefixes ? omPrefixes.split(' ') : []);
|
||||
ModernizrProto._cssomPrefixes = cssomPrefixes;
|
||||
|
||||
|
||||
/**
|
||||
* List of JavaScript DOM values used for tests
|
||||
*
|
||||
* @memberof Modernizr
|
||||
* @name Modernizr._domPrefixes
|
||||
* @optionName Modernizr._domPrefixes
|
||||
* @optionProp domPrefixes
|
||||
* @access public
|
||||
* @example
|
||||
*
|
||||
* Modernizr._domPrefixes is exactly the same as [_prefixes](#modernizr-_prefixes), but rather
|
||||
* than kebab-case properties, all properties are their Capitalized variant
|
||||
*
|
||||
* ```js
|
||||
* Modernizr._domPrefixes === [ "Moz", "O", "ms", "Webkit" ];
|
||||
* ```
|
||||
*/
|
||||
|
||||
var domPrefixes = (ModernizrProto._config.usePrefixes ? omPrefixes.toLowerCase().split(' ') : []);
|
||||
ModernizrProto._domPrefixes = domPrefixes;
|
||||
|
||||
|
||||
/**
|
||||
* fnBind is a super small [bind](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) polyfill.
|
||||
*
|
||||
* @access private
|
||||
* @function fnBind
|
||||
* @param {function} fn - a function you want to change `this` reference to
|
||||
* @param {object} that - the `this` you want to call the function with
|
||||
* @returns {function} The wrapped version of the supplied function
|
||||
*/
|
||||
|
||||
function fnBind(fn, that) {
|
||||
return function() {
|
||||
return fn.apply(that, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
/**
|
||||
* testDOMProps is a generic DOM property test; if a browser supports
|
||||
* a certain property, it won't return undefined for it.
|
||||
*
|
||||
* @access private
|
||||
* @function testDOMProps
|
||||
* @param {array.<string>} props - An array of properties to test for
|
||||
* @param {object} obj - An object or Element you want to use to test the parameters again
|
||||
* @param {boolean|object} elem - An Element to bind the property lookup again. Use `false` to prevent the check
|
||||
*/
|
||||
function testDOMProps(props, obj, elem) {
|
||||
var item;
|
||||
|
||||
for (var i in props) {
|
||||
if (props[i] in obj) {
|
||||
|
||||
// return the property name as a string
|
||||
if (elem === false) {
|
||||
return props[i];
|
||||
}
|
||||
|
||||
item = obj[props[i]];
|
||||
|
||||
// let's bind a function
|
||||
if (is(item, 'function')) {
|
||||
// bind to obj unless overriden
|
||||
return fnBind(item, elem || obj);
|
||||
}
|
||||
|
||||
// return the unbound function or obj or value
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
/**
|
||||
* Create our "modernizr" element that we do most feature tests on.
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
|
||||
var modElem = {
|
||||
elem: createElement('modernizr')
|
||||
};
|
||||
|
||||
// Clean up this element
|
||||
Modernizr._q.push(function() {
|
||||
delete modElem.elem;
|
||||
});
|
||||
|
||||
|
||||
|
||||
var mStyle = {
|
||||
style: modElem.elem.style
|
||||
};
|
||||
|
||||
// kill ref for gc, must happen before mod.elem is removed, so we unshift on to
|
||||
// the front of the queue.
|
||||
Modernizr._q.unshift(function() {
|
||||
delete mStyle.style;
|
||||
});
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* domToCSS takes a camelCase string and converts it to kebab-case
|
||||
* e.g. boxSizing -> box-sizing
|
||||
*
|
||||
* @access private
|
||||
* @function domToCSS
|
||||
* @param {string} name - String name of camelCase prop we want to convert
|
||||
* @returns {string} The kebab-case version of the supplied name
|
||||
*/
|
||||
|
||||
function domToCSS(name) {
|
||||
return name.replace(/([A-Z])/g, function(str, m1) {
|
||||
return '-' + m1.toLowerCase();
|
||||
}).replace(/^ms-/, '-ms-');
|
||||
}
|
||||
;
|
||||
|
||||
/**
|
||||
* getBody returns the body of a document, or an element that can stand in for
|
||||
* the body if a real body does not exist
|
||||
*
|
||||
* @access private
|
||||
* @function getBody
|
||||
* @returns {HTMLElement|SVGElement} Returns the real body of a document, or an
|
||||
* artificially created element that stands in for the body
|
||||
*/
|
||||
|
||||
function getBody() {
|
||||
// After page load injecting a fake body doesn't work so check if body exists
|
||||
var body = document.body;
|
||||
|
||||
if (!body) {
|
||||
// Can't use the real body create a fake one.
|
||||
body = createElement(isSVG ? 'svg' : 'body');
|
||||
body.fake = true;
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
/**
|
||||
* injectElementWithStyles injects an element with style element and some CSS rules
|
||||
*
|
||||
* @access private
|
||||
* @function injectElementWithStyles
|
||||
* @param {string} rule - String representing a css rule
|
||||
* @param {function} callback - A function that is used to test the injected element
|
||||
* @param {number} [nodes] - An integer representing the number of additional nodes you want injected
|
||||
* @param {string[]} [testnames] - An array of strings that are used as ids for the additional nodes
|
||||
* @returns {boolean}
|
||||
*/
|
||||
|
||||
function injectElementWithStyles(rule, callback, nodes, testnames) {
|
||||
var mod = 'modernizr';
|
||||
var style;
|
||||
var ret;
|
||||
var node;
|
||||
var docOverflow;
|
||||
var div = createElement('div');
|
||||
var body = getBody();
|
||||
|
||||
if (parseInt(nodes, 10)) {
|
||||
// In order not to give false positives we create a node for each test
|
||||
// This also allows the method to scale for unspecified uses
|
||||
while (nodes--) {
|
||||
node = createElement('div');
|
||||
node.id = testnames ? testnames[nodes] : mod + (nodes + 1);
|
||||
div.appendChild(node);
|
||||
}
|
||||
}
|
||||
|
||||
style = createElement('style');
|
||||
style.type = 'text/css';
|
||||
style.id = 's' + mod;
|
||||
|
||||
// IE6 will false positive on some tests due to the style element inside the test div somehow interfering offsetHeight, so insert it into body or fakebody.
|
||||
// Opera will act all quirky when injecting elements in documentElement when page is served as xml, needs fakebody too. #270
|
||||
(!body.fake ? div : body).appendChild(style);
|
||||
body.appendChild(div);
|
||||
|
||||
if (style.styleSheet) {
|
||||
style.styleSheet.cssText = rule;
|
||||
} else {
|
||||
style.appendChild(document.createTextNode(rule));
|
||||
}
|
||||
div.id = mod;
|
||||
|
||||
if (body.fake) {
|
||||
//avoid crashing IE8, if background image is used
|
||||
body.style.background = '';
|
||||
//Safari 5.13/5.1.4 OSX stops loading if ::-webkit-scrollbar is used and scrollbars are visible
|
||||
body.style.overflow = 'hidden';
|
||||
docOverflow = docElement.style.overflow;
|
||||
docElement.style.overflow = 'hidden';
|
||||
docElement.appendChild(body);
|
||||
}
|
||||
|
||||
ret = callback(div, rule);
|
||||
// If this is done after page load we don't want to remove the body so check if body exists
|
||||
if (body.fake) {
|
||||
body.parentNode.removeChild(body);
|
||||
docElement.style.overflow = docOverflow;
|
||||
// Trigger layout so kinetic scrolling isn't disabled in iOS6+
|
||||
docElement.offsetHeight;
|
||||
} else {
|
||||
div.parentNode.removeChild(div);
|
||||
}
|
||||
|
||||
return !!ret;
|
||||
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
/**
|
||||
* nativeTestProps allows for us to use native feature detection functionality if available.
|
||||
* some prefixed form, or false, in the case of an unsupported rule
|
||||
*
|
||||
* @access private
|
||||
* @function nativeTestProps
|
||||
* @param {array} props - An array of property names
|
||||
* @param {string} value - A string representing the value we want to check via @supports
|
||||
* @returns {boolean|undefined} A boolean when @supports exists, undefined otherwise
|
||||
*/
|
||||
|
||||
// Accepts a list of property names and a single value
|
||||
// Returns `undefined` if native detection not available
|
||||
function nativeTestProps(props, value) {
|
||||
var i = props.length;
|
||||
// Start with the JS API: http://www.w3.org/TR/css3-conditional/#the-css-interface
|
||||
if ('CSS' in window && 'supports' in window.CSS) {
|
||||
// Try every prefixed variant of the property
|
||||
while (i--) {
|
||||
if (window.CSS.supports(domToCSS(props[i]), value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Otherwise fall back to at-rule (for Opera 12.x)
|
||||
else if ('CSSSupportsRule' in window) {
|
||||
// Build a condition string for every prefixed variant
|
||||
var conditionText = [];
|
||||
while (i--) {
|
||||
conditionText.push('(' + domToCSS(props[i]) + ':' + value + ')');
|
||||
}
|
||||
conditionText = conditionText.join(' or ');
|
||||
return injectElementWithStyles('@supports (' + conditionText + ') { #modernizr { position: absolute; } }', function(node) {
|
||||
return getComputedStyle(node, null).position == 'absolute';
|
||||
});
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
;
|
||||
|
||||
// testProps is a generic CSS / DOM property test.
|
||||
|
||||
// In testing support for a given CSS property, it's legit to test:
|
||||
// `elem.style[styleName] !== undefined`
|
||||
// If the property is supported it will return an empty string,
|
||||
// if unsupported it will return undefined.
|
||||
|
||||
// We'll take advantage of this quick test and skip setting a style
|
||||
// on our modernizr element, but instead just testing undefined vs
|
||||
// empty string.
|
||||
|
||||
// Property names can be provided in either camelCase or kebab-case.
|
||||
|
||||
function testProps(props, prefixed, value, skipValueTest) {
|
||||
skipValueTest = is(skipValueTest, 'undefined') ? false : skipValueTest;
|
||||
|
||||
// Try native detect first
|
||||
if (!is(value, 'undefined')) {
|
||||
var result = nativeTestProps(props, value);
|
||||
if (!is(result, 'undefined')) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise do it properly
|
||||
var afterInit, i, propsLength, prop, before;
|
||||
|
||||
// If we don't have a style element, that means we're running async or after
|
||||
// the core tests, so we'll need to create our own elements to use
|
||||
|
||||
// inside of an SVG element, in certain browsers, the `style` element is only
|
||||
// defined for valid tags. Therefore, if `modernizr` does not have one, we
|
||||
// fall back to a less used element and hope for the best.
|
||||
var elems = ['modernizr', 'tspan'];
|
||||
while (!mStyle.style) {
|
||||
afterInit = true;
|
||||
mStyle.modElem = createElement(elems.shift());
|
||||
mStyle.style = mStyle.modElem.style;
|
||||
}
|
||||
|
||||
// Delete the objects if we created them.
|
||||
function cleanElems() {
|
||||
if (afterInit) {
|
||||
delete mStyle.style;
|
||||
delete mStyle.modElem;
|
||||
}
|
||||
}
|
||||
|
||||
propsLength = props.length;
|
||||
for (i = 0; i < propsLength; i++) {
|
||||
prop = props[i];
|
||||
before = mStyle.style[prop];
|
||||
|
||||
if (contains(prop, '-')) {
|
||||
prop = cssToDOM(prop);
|
||||
}
|
||||
|
||||
if (mStyle.style[prop] !== undefined) {
|
||||
|
||||
// If value to test has been passed in, do a set-and-check test.
|
||||
// 0 (integer) is a valid property value, so check that `value` isn't
|
||||
// undefined, rather than just checking it's truthy.
|
||||
if (!skipValueTest && !is(value, 'undefined')) {
|
||||
|
||||
// Needs a try catch block because of old IE. This is slow, but will
|
||||
// be avoided in most cases because `skipValueTest` will be used.
|
||||
try {
|
||||
mStyle.style[prop] = value;
|
||||
} catch (e) {}
|
||||
|
||||
// If the property value has changed, we assume the value used is
|
||||
// supported. If `value` is empty string, it'll fail here (because
|
||||
// it hasn't changed), which matches how browsers have implemented
|
||||
// CSS.supports()
|
||||
if (mStyle.style[prop] != before) {
|
||||
cleanElems();
|
||||
return prefixed == 'pfx' ? prop : true;
|
||||
}
|
||||
}
|
||||
// Otherwise just return true, or the property name if this is a
|
||||
// `prefixed()` call
|
||||
else {
|
||||
cleanElems();
|
||||
return prefixed == 'pfx' ? prop : true;
|
||||
}
|
||||
}
|
||||
}
|
||||
cleanElems();
|
||||
return false;
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
/**
|
||||
* testProp() investigates whether a given style property is recognized
|
||||
* Property names can be provided in either camelCase or kebab-case.
|
||||
*
|
||||
* @memberof Modernizr
|
||||
* @name Modernizr.testProp
|
||||
* @access public
|
||||
* @optionName Modernizr.testProp()
|
||||
* @optionProp testProp
|
||||
* @function testProp
|
||||
* @param {string} prop - Name of the CSS property to check
|
||||
* @param {string} [value] - Name of the CSS value to check
|
||||
* @param {boolean} [useValue] - Whether or not to check the value if @supports isn't supported
|
||||
* @returns {boolean}
|
||||
* @example
|
||||
*
|
||||
* Just like [testAllProps](#modernizr-testallprops), only it does not check any vendor prefixed
|
||||
* version of the string.
|
||||
*
|
||||
* Note that the property name must be provided in camelCase (e.g. boxSizing not box-sizing)
|
||||
*
|
||||
* ```js
|
||||
* Modernizr.testProp('pointerEvents') // true
|
||||
* ```
|
||||
*
|
||||
* You can also provide a value as an optional second argument to check if a
|
||||
* specific value is supported
|
||||
*
|
||||
* ```js
|
||||
* Modernizr.testProp('pointerEvents', 'none') // true
|
||||
* Modernizr.testProp('pointerEvents', 'penguin') // false
|
||||
* ```
|
||||
*/
|
||||
|
||||
var testProp = ModernizrProto.testProp = function(prop, value, useValue) {
|
||||
return testProps([prop], undefined, value, useValue);
|
||||
};
|
||||
|
||||
/*!
|
||||
{
|
||||
"name": "CSS textshadow",
|
||||
"property": "textshadow",
|
||||
"caniuse": "css-textshadow",
|
||||
"tags": ["css"],
|
||||
"knownBugs": ["FF3.0 will false positive on this test"]
|
||||
}
|
||||
!*/
|
||||
|
||||
Modernizr.addTest('textshadow', testProp('textShadow', '1px 1px'));
|
||||
|
||||
|
||||
/**
|
||||
* testPropsAll tests a list of DOM properties we want to check against.
|
||||
* We specify literally ALL possible (known and/or likely) properties on
|
||||
* the element including the non-vendor prefixed one, for forward-
|
||||
* compatibility.
|
||||
*
|
||||
* @access private
|
||||
* @function testPropsAll
|
||||
* @param {string} prop - A string of the property to test for
|
||||
* @param {string|object} [prefixed] - An object to check the prefixed properties on. Use a string to skip
|
||||
* @param {HTMLElement|SVGElement} [elem] - An element used to test the property and value against
|
||||
* @param {string} [value] - A string of a css value
|
||||
* @param {boolean} [skipValueTest] - An boolean representing if you want to test if value sticks when set
|
||||
*/
|
||||
function testPropsAll(prop, prefixed, elem, value, skipValueTest) {
|
||||
|
||||
var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1),
|
||||
props = (prop + ' ' + cssomPrefixes.join(ucProp + ' ') + ucProp).split(' ');
|
||||
|
||||
// did they call .prefixed('boxSizing') or are we just testing a prop?
|
||||
if (is(prefixed, 'string') || is(prefixed, 'undefined')) {
|
||||
return testProps(props, prefixed, value, skipValueTest);
|
||||
|
||||
// otherwise, they called .prefixed('requestAnimationFrame', window[, elem])
|
||||
} else {
|
||||
props = (prop + ' ' + (domPrefixes).join(ucProp + ' ') + ucProp).split(' ');
|
||||
return testDOMProps(props, prefixed, elem);
|
||||
}
|
||||
}
|
||||
|
||||
// Modernizr.testAllProps() investigates whether a given style property,
|
||||
// or any of its vendor-prefixed variants, is recognized
|
||||
//
|
||||
// Note that the property names must be provided in the camelCase variant.
|
||||
// Modernizr.testAllProps('boxSizing')
|
||||
ModernizrProto.testAllProps = testPropsAll;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* testAllProps determines whether a given CSS property is supported in the browser
|
||||
*
|
||||
* @memberof Modernizr
|
||||
* @name Modernizr.testAllProps
|
||||
* @optionName Modernizr.testAllProps()
|
||||
* @optionProp testAllProps
|
||||
* @access public
|
||||
* @function testAllProps
|
||||
* @param {string} prop - String naming the property to test (either camelCase or kebab-case)
|
||||
* @param {string} [value] - String of the value to test
|
||||
* @param {boolean} [skipValueTest=false] - Whether to skip testing that the value is supported when using non-native detection
|
||||
* @example
|
||||
*
|
||||
* testAllProps determines whether a given CSS property, in some prefixed form,
|
||||
* is supported by the browser.
|
||||
*
|
||||
* ```js
|
||||
* testAllProps('boxSizing') // true
|
||||
* ```
|
||||
*
|
||||
* It can optionally be given a CSS value in string form to test if a property
|
||||
* value is valid
|
||||
*
|
||||
* ```js
|
||||
* testAllProps('display', 'block') // true
|
||||
* testAllProps('display', 'penguin') // false
|
||||
* ```
|
||||
*
|
||||
* A boolean can be passed as a third parameter to skip the value check when
|
||||
* native detection (@supports) isn't available.
|
||||
*
|
||||
* ```js
|
||||
* testAllProps('shapeOutside', 'content-box', true);
|
||||
* ```
|
||||
*/
|
||||
|
||||
function testAllProps(prop, value, skipValueTest) {
|
||||
return testPropsAll(prop, undefined, undefined, value, skipValueTest);
|
||||
}
|
||||
ModernizrProto.testAllProps = testAllProps;
|
||||
|
||||
/*!
|
||||
{
|
||||
"name": "CSS Animations",
|
||||
"property": "cssanimations",
|
||||
"caniuse": "css-animation",
|
||||
"polyfills": ["transformie", "csssandpaper"],
|
||||
"tags": ["css"],
|
||||
"warnings": ["Android < 4 will pass this test, but can only animate a single property at a time"],
|
||||
"notes": [{
|
||||
"name" : "Article: 'Dispelling the Android CSS animation myths'",
|
||||
"href": "https://goo.gl/OGw5Gm"
|
||||
}]
|
||||
}
|
||||
!*/
|
||||
/* DOC
|
||||
Detects whether or not elements can be animated using CSS
|
||||
*/
|
||||
|
||||
Modernizr.addTest('cssanimations', testAllProps('animationName', 'a', true));
|
||||
|
||||
/*!
|
||||
{
|
||||
"name": "Background Size",
|
||||
"property": "backgroundsize",
|
||||
"tags": ["css"],
|
||||
"knownBugs": ["This will false positive in Opera Mini - https://github.com/Modernizr/Modernizr/issues/396"],
|
||||
"notes": [{
|
||||
"name": "Related Issue",
|
||||
"href": "https://github.com/Modernizr/Modernizr/issues/396"
|
||||
}]
|
||||
}
|
||||
!*/
|
||||
|
||||
Modernizr.addTest('backgroundsize', testAllProps('backgroundSize', '100%', true));
|
||||
|
||||
/*!
|
||||
{
|
||||
"name": "CSS Transitions",
|
||||
"property": "csstransitions",
|
||||
"caniuse": "css-transitions",
|
||||
"tags": ["css"]
|
||||
}
|
||||
!*/
|
||||
|
||||
Modernizr.addTest('csstransitions', testAllProps('transition', 'all', true));
|
||||
|
||||
/*!
|
||||
{
|
||||
"name": "Box Shadow",
|
||||
"property": "boxshadow",
|
||||
"caniuse": "css-boxshadow",
|
||||
"tags": ["css"],
|
||||
"knownBugs": [
|
||||
"WebOS false positives on this test.",
|
||||
"The Kindle Silk browser false positives"
|
||||
]
|
||||
}
|
||||
!*/
|
||||
|
||||
Modernizr.addTest('boxshadow', testAllProps('boxShadow', '1px 1px', true));
|
||||
|
||||
|
||||
// Run each test
|
||||
testRunner();
|
||||
|
||||
// Remove the "no-js" class if it exists
|
||||
setClasses(classes);
|
||||
|
||||
delete ModernizrProto.addTest;
|
||||
delete ModernizrProto.addAsyncTest;
|
||||
|
||||
// Run the things that are supposed to run after the tests
|
||||
for (var i = 0; i < Modernizr._q.length; i++) {
|
||||
Modernizr._q[i]();
|
||||
}
|
||||
|
||||
// Leak Modernizr namespace
|
||||
window.Modernizr = Modernizr;
|
||||
|
||||
|
||||
;
|
||||
|
||||
})(window, document);
|
Loading…
Reference in a new issue