Merge conflicts resolved

This commit is contained in:
Manoj Vivek 2020-10-18 21:11:16 +05:30
commit 02cd39304a
2674 changed files with 817 additions and 468776 deletions

View file

@ -352,6 +352,24 @@
"contributions": [
"code"
]
},
{
"login": "sidthesloth92",
"name": "Dinesh Balaji",
"avatar_url": "https://avatars3.githubusercontent.com/u/4656109?v=4",
"profile": "http://dbwriteups.wordpress.com",
"contributions": [
"code"
]
},
{
"login": "med1001",
"name": "MedBMoussa",
"avatar_url": "https://avatars3.githubusercontent.com/u/26111211?v=4",
"profile": "https://github.com/med1001",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 5,

37
.github/ISSUE_TEMPLATE/01-bug-report.md vendored Normal file
View file

@ -0,0 +1,37 @@
---
name: "\U0001F41E Bug report"
about: Report a bug in Responsively
---
<!-- 🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅
Hi there! 😄
To expedite issue processing please search open and closed issues before submitting a new one. Existing issues often contain information about workarounds, resolution, or progress updates.
🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅 -->
# 🐞 bug report
### ✍️ Description
<!-- A clear and concise description of the problem. -->
### 🕵🏼‍♂️ Is this a regression?
<!-- Did this behavior use to work in the previous version? -->
### 🔬 Minimal Reproduction
<!-- Clear steps to re-produce the issue. -->
### 🌍 Your Environment
<!-- Press `Ctrl/Cmd + F1` and paste it here. -->
<pre><code>
</code></pre>
### 🔥 Exception or Error or Screenshot
<pre><code>
</code></pre>

View file

@ -0,0 +1,18 @@
---
name: "\U0001F680 Feature request"
about: Suggest a feature for Responsively.
---
# 🚀 Feature Request
### 📝 Description
<!-- A clear and concise description of the problem or missing capability. -->
### ✨ Describe the solution you'd like
<!-- If you have a solution in mind, please describe it. -->
### ✍️ Describe alternatives you've considered
<!-- Have you considered any alternative solutions or workarounds? -->

13
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,13 @@
# ✨ Pull Request
### 📓 Referenced Issue
<!-- Please link the related issue. Use # before the issue number and use the verbs 'fixes', 'resolves' to auto-link it, for eg, Fixes: #&lt;issue-number&gt; -->
### About the PR
<!-- Please provide a description of your solution if it is not clear in the related issue or if the PR has a breaking change. If there is an interesting topic to discuss or you have questions or there is an issue with electron or another library that you have used. -->
### 🖼️ Testing Scenarios / Screenshots
<!-- Please include screenshots or gif to showcase the final output. Also, try to explain the testing you did to validate your change. -->

1
.github/opencollective.yml vendored Normal file
View file

@ -0,0 +1 @@
collective: responsively

View file

@ -63,11 +63,16 @@ Please visit the website to know more about the application - https://responsive
## Download
The application is available for Mac, Windows and Linux platforms. Please download it from here - https://github.com/responsively-org/responsively-app/releases
Alternatively, MacOS users can use brew to install it:
Alternatively, MacOS users can use [`brew`](https://formulae.brew.sh/cask/responsively) to install it:
```bash
brew cask install responsively
```
Also, Windows users can use [`chocolatey`](https://chocolatey.org/packages/responsively/) to install it:
```bash
choco install responsively
```
Follow on Twitter for future updates - [![Twitter Follow](https://img.shields.io/twitter/follow/ResponsivelyApp?style=social)](https://twitter.com/ResponsivelyApp)
## Issues
@ -147,6 +152,8 @@ Thanks go to these wonderful people ([emoji key](https://allcontributors.org/doc
<td align="center"><a href="http://ruisaraiva.dev"><img src="https://avatars2.githubusercontent.com/u/7356098?v=4" width="100px;" alt=""/><br /><sub><b>Rui Saraiva</b></sub></a><br /><a href="https://github.com/responsively-org/responsively-app/commits?author=ruisaraiva19" title="Code">💻</a></td>
<td align="center"><a href="http://www.bakirci.nl"><img src="https://avatars2.githubusercontent.com/u/9880089?v=4" width="100px;" alt=""/><br /><sub><b>Mehmet Bakirci</b></sub></a><br /><a href="https://github.com/responsively-org/responsively-app/commits?author=MBakirci" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/JLambertazzo"><img src="https://avatars0.githubusercontent.com/u/42924425?v=4" width="100px;" alt=""/><br /><sub><b>Julien Bertazzo Lambert</b></sub></a><br /><a href="https://github.com/responsively-org/responsively-app/commits?author=JLambertazzo" title="Code">💻</a></td>
<td align="center"><a href="http://dbwriteups.wordpress.com"><img src="https://avatars3.githubusercontent.com/u/4656109?v=4" width="100px;" alt=""/><br /><sub><b>Dinesh Balaji</b></sub></a><br /><a href="https://github.com/responsively-org/responsively-app/commits?author=sidthesloth92" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/med1001"><img src="https://avatars3.githubusercontent.com/u/26111211?v=4" width="100px;" alt=""/><br /><sub><b>MedBMoussa</b></sub></a><br /><a href="https://github.com/responsively-org/responsively-app/commits?author=med1001" title="Code">💻</a></td>
</tr>
</table>

View file

@ -1,5 +1,4 @@
import React, {Fragment} from 'react';
import {Switch, Route} from 'react-router';
import Box from '@material-ui/core/Box';
import Paper from '@material-ui/core/Paper';
import {makeStyles} from '@material-ui/core/styles';
@ -9,15 +8,13 @@ import LeftIconsPaneContainer from './containers/LeftIconsPaneContainer';
import StatusBarContainer from './containers/StatusBarContainer';
import DevToolResizerContainer from './containers/DevToolResizerContainer';
const Routes = () => {
const AppContent = () => {
const classes = useStyles();
return (
<Fragment>
<Paper elevation={0} className={classes.root}>
<div className={classes.contentColumn}>
<Switch>
<Route path={routes.HOME} component={Browser} />
</Switch>
<Browser />
</div>
</Paper>
<StatusBarContainer />
@ -69,4 +66,4 @@ const useStyles = makeStyles(theme => ({
},
}));
export default Routes;
export default AppContent;

View file

@ -15,9 +15,11 @@ import {
DELETE_STORAGE,
ADDRESS_CHANGE,
STOP_LOADING,
TOGGLE_DEVICE_DESIGN_MODE_STATE,
} from '../constants/pubsubEvents';
import {getBounds, getDefaultDevToolsWindowSize} from '../reducers/browser';
import {DEVTOOLS_MODES} from '../constants/previewerLayouts';
import {normalizeZoomLevel} from '../utils/browserUtils';
export const NEW_ADDRESS = 'NEW_ADDRESS';
export const NEW_PAGE_META_FIELD = 'NEW_PAGE_META_FIELD';
@ -44,6 +46,10 @@ export const NEW_FOCUSED_DEVICE = 'NEW_FOCUSED_DEVICE';
export const TOGGLE_ALL_DEVICES_MUTED = 'TOGGLE_ALL_DEVICES_MUTED';
export const TOGGLE_DEVICE_MUTED = 'TOGGLE_DEVICE_MUTED';
export const NEW_THEME = 'NEW_THEME';
export const TOGGLE_ALL_DEVICES_DESIGN_MODE = 'TOGGLE_ALL_DEVICES_DESIGN_MODE';
export const TOGGLE_DEVICE_DESIGN_MODE = 'TOGGLE_DEVICE_DESIGN_MODE';
export const SET_HEADER_VISIBILITY = 'SET_HEADER_VISIBILITY';
export const SET_LEFT_PANE_VISIBILITY = 'SET_LEFT_PANE_VISIBILITY';
export function newAddress(address) {
return {
@ -208,6 +214,19 @@ export function toggleDeviceMuted(deviceId, isMuted) {
};
}
export function toggleAllDevicesDesignMode() {
return {
type: TOGGLE_ALL_DEVICES_DESIGN_MODE,
};
}
export function toggleDeviceDesignMode(deviceId) {
return {
type: TOGGLE_DEVICE_DESIGN_MODE,
deviceId,
};
}
export function onAddressChange(newURL, force) {
return (dispatch: Dispatch, getState: RootStateType) => {
const {
@ -249,12 +268,13 @@ export function onZoomChange(newLevel) {
const {
browser: {zoomLevel},
} = getState();
const normalizedZoomLevel = normalizeZoomLevel(newLevel);
if (newLevel === zoomLevel) {
if (normalizedZoomLevel === zoomLevel) {
return;
}
dispatch(newZoomLevel(newLevel));
dispatch(newZoomLevel(normalizedZoomLevel));
};
}
@ -720,6 +740,23 @@ export function onDeviceMutedChange(deviceId, isMuted) {
};
}
export function onToggleAllDeviceDesignMode() {
return (dispatch: Dispatch, getState: RootStateType) => {
const {
browser: {allDevicesInDesignMode},
} = getState();
const next = !allDevicesInDesignMode;
pubsub.publish(TOGGLE_DEVICE_DESIGN_MODE_STATE, [{designMode: next}]);
dispatch(toggleAllDevicesDesignMode());
};
}
export function onToggleDeviceDesignMode(deviceId) {
return (dispatch: Dispatch, getState: RootStateType) => {
dispatch(toggleDeviceDesignMode(deviceId));
};
}
export function toggleInspector() {
return (dispatch: Dispatch, getState: RootStateType) => {
const {
@ -848,3 +885,25 @@ export function setTheme(theme) {
theme,
};
}
/**
* Shows/Hides the top control pane.
* @param {boolean} isVisible Shows the top pane when true and hides when false.
*/
export function setHeaderVisibility(isVisible: boolean) {
return {
type: SET_HEADER_VISIBILITY,
isVisible,
};
}
/**
* Shows/Hides the left control pane.
* @param {boolean} isVisible Shows the left control pane when true and hides when false.
*/
export function setLeftPaneVisibility(isVisible: boolean) {
return {
type: SET_LEFT_PANE_VISIBILITY,
isVisible,
};
}

View file

@ -3,7 +3,6 @@ import type {Dispatch, GetState} from '../reducers/types';
import {
SET_NETWORK_TROTTLING_PROFILE,
CLEAR_NETWORK_CACHE,
SET_NETWORK_PROXY_PROFILE,
} from '../constants/pubsubEvents';
import {convertToProxyConfig, proxyRuleToString} from '../utils/proxyUtils';
import {ipcRenderer} from 'electron';

View file

@ -11,7 +11,6 @@ import logo from '../../../resources/logo.svg';
function updateNotificationStatus(id, action) {
const notifications = settings.get(APP_NOTIFICATION) || [];
const commonClasses = useCommonStyles();
const notificationStatusObject = {
id,
@ -39,6 +38,8 @@ function checkIfInteracted(id) {
const AppNotification = () => {
const [notificationInteracted, setNotificationInteracted] = useState(false);
const [data, setData] = useState(null);
const commonClasses = useCommonStyles();
useEffect(() => {
if (process.env.NODE_ENV === 'development') {
return;

View file

@ -10,6 +10,7 @@
padding: 10px 20px 20px 20px;
min-width: 250px;
max-width: 320px;
color: #f8f8f8;
}
.titleContainer {

View file

@ -9,6 +9,7 @@ import CloseIcon from '@material-ui/icons/Close';
import AddIcon from '@material-ui/icons/Add';
import Dialog from '@material-ui/core/Dialog';
import AppBar from '@material-ui/core/AppBar';
import Alert from '@material-ui/lab/Alert';
import Toolbar from '@material-ui/core/Toolbar';
import Typography from '@material-ui/core/Typography';
import {DragDropContext, Droppable, Draggable} from 'react-beautiful-dnd';
@ -16,6 +17,7 @@ import LightBulbIcon from '../icons/LightBulb';
import DeviceList from './DeviceList';
import AddDeviceContainer from '../../containers/AddDeviceContainer';
import ErrorBoundary from '../ErrorBoundary';
import {recommendedMaxNumberOfDevices} from '../../utils/deviceManagerUtils';
import styles from './styles.css';
@ -29,6 +31,8 @@ function DeviceManager(props) {
inactiveFiltered: [],
});
const [maxDevicesWarning, setMaxDevicesWarning] = useState(false);
useEffect(() => {
const activeDevices = props.browser.devices;
const activeDevicesById = activeDevices.reduce((acc, val) => {
@ -36,6 +40,8 @@ function DeviceManager(props) {
return acc;
}, {});
setMaxDevicesWarning(activeDevices.length >= recommendedMaxNumberOfDevices);
const currentInactiveDevicesById = devices.inactive.reduce((acc, val) => {
acc[val.id] = val;
return acc;
@ -104,6 +110,7 @@ function DeviceManager(props) {
const updateDevices = devices => {
const active = [...devices.active];
const inactive = [...devices.inactive];
setMaxDevicesWarning(active.length >= recommendedMaxNumberOfDevices);
setDevices({active, inactive});
props.setActiveDevices(active);
};
@ -115,7 +122,10 @@ function DeviceManager(props) {
color="primary"
aria-label="upload picture"
component="span"
onClick={() => setOpen(true)}
onClick={() => {
props.onDevToolsClose(null, true);
setOpen(true);
}}
className={styles.editButton}
>
Customize
@ -133,6 +143,12 @@ function DeviceManager(props) {
</Toolbar>
</AppBar>
<div className={styles.container}>
{maxDevicesWarning && (
<Alert severity="warning" className={classes.maxDevicesWarning}>
Adding more than {recommendedMaxNumberOfDevices} devices may
slow down the system.
</Alert>
)}
<Typography variant="body1" className={classes.toolTip}>
<span></span>Drag and drop the devices across to re-order them.
</Typography>
@ -190,6 +206,10 @@ const useStyles = makeStyles(theme => ({
color: theme.palette.text.primary,
width: 'fit-content',
},
maxDevicesWarning: {
position: 'absolute',
top: '80px',
},
}));
export default DeviceManager;

View file

@ -69,6 +69,7 @@ class ErrorBoundary extends React.Component {
const styles = theme => ({
errorBoundaryContainer: {
background: theme.palette.background.default,
overflowY: 'auto',
display: 'flex',
flexDirection: 'column',

View file

@ -12,23 +12,35 @@ import InputAdornment from '@material-ui/core/InputAdornment';
import {remote, ipcRenderer} from 'electron';
import cx from 'classnames';
import {
makeStyles,
Popper,
Fade,
Paper,
Typography,
ClickAwayListener,
} from '@material-ui/core';
import {useTheme} from '@material-ui/core/styles';
import {useTheme, makeStyles} from '@material-ui/core/styles';
import styles from './styles.css';
import useCommonStyles from '../useCommonStyles';
import helpScreenshot from './help-screenshot.png';
const useStyles = makeStyles({
const useStyles = makeStyles(theme => ({
adornedEnd: {
paddingRight: 0,
},
});
extensionsHelp: {
display: 'flex',
flexDirection: 'column',
width: 500,
padding: 12,
color: 'white',
borderRadius: 4,
background: theme.palette.mode({
light: theme.palette.secondary.main,
dark: '#313131',
}),
boxShadow: '3px 3px 6px 1px black',
},
}));
export default function ExtensionsManager({triggerNavigationReload}) {
const {BrowserWindow} = remote;
@ -114,22 +126,25 @@ export default function ExtensionsManager({triggerNavigationReload}) {
return (
<>
<Popper open={helpOpen} anchorEl={anchorEl} placement="bottom" transition>
<Popper
open={helpOpen}
anchorEl={anchorEl}
placement="bottom-start"
transition
>
{({TransitionProps}) => (
<ClickAwayListener onClickAway={toggleHelp}>
<Fade {...TransitionProps} timeout={350}>
<Paper>
<div className={styles.extensionsHelp}>
<div className={classes.extensionsHelp}>
<p className={cx(styles.extensionsHelpText)}>
Find the extension on Chrome Web Store and copy the
extension ID from the address bar(as shown below).
Find the extension on Chrome Web Store and copy the extension
ID from the address bar(as shown below).
</p>
<img
className={styles.extensionsHelpImg}
src={helpScreenshot}
/>
</div>
</Paper>
</Fade>
</ClickAwayListener>
)}

View file

@ -49,19 +49,9 @@
margin-top: 12px !important;
}
.extensionsHelp {
display: flex;
flex-direction: column;
width: 500px;
padding: 12px;
color: white;
border-radius: 4px;
background: #252526;
box-shadow: 3px 3px 6px 1px black;
}
.extensionsHelpText {
font-size: 12px;
margin-top: 0;
}
.extensionsHelpImg {

View file

@ -10,14 +10,15 @@ import PermissionPopup from '../PermissionPopup';
import NavigationControlsContainer from '../../containers/NavigationControlsContainer';
import BookmarksBar from '../../containers/BookmarksBarContainer';
import AppNotification from '../AppNotification/AppNotification';
import Logo from '../icons/Logo';
import ZenButton from '../ZenButton';
import cx from 'classnames';
const Header = () => {
const Header = props => {
const classes = useStyles();
return (
<div className={classes.container}>
<div className={cx([classes.container, {zenMode: !props.isHeaderVisible}])}>
<div className={classes.firstRow}>
<Logo className={classes.logo} width={40} height={40} />
<Grid
@ -52,23 +53,46 @@ const Header = () => {
pauseOnHover
toastClassName={classes.darkToast}
/>
<AppNotification />
<ZenButton
active={!props.isHeaderVisible}
onClick={() => props.setHeaderVisibility(!props.isHeaderVisible)}
/>
</div>
);
};
const useStyles = makeStyles(theme => ({
container: {
position: 'relative',
background: theme.palette.background.l1,
display: 'flex',
flexDirection: 'column',
width: '100%',
padding: os.platform() === 'darwin' ? '20px 0 5px' : '0 0 0',
padding: os.platform() === 'darwin' ? '0 0 5px' : '0 0 0',
boxShadow: `0 ${theme.palette.mode({
light: '0px',
dark: '3px',
})} 5px rgba(0, 0, 0, 0.35)`,
zIndex: 500,
transform: 'translateY(0)',
transition: 'transform .1s ease-out',
'& .zenButton': {
background: theme.palette.background.l1,
display: 'none',
position: 'absolute',
bottom: '0px',
left: '50%',
transform: 'translate(-50%, 100%)',
},
'&:hover .zenButton': {
display: 'flex',
},
'&.zenMode': {
transform: 'translateY(-100%)',
},
'&.zenMode .zenButton': {
display: 'flex',
},
},
firstRow: {
display: 'flex',

View file

@ -0,0 +1,20 @@
import React from 'react';
import {makeStyles} from '@material-ui/core/styles';
/**
* Application toolbar that appears at the top of a window.
*/
const HorizontalSpacer = () => {
const classes = useStyles();
return <div className={classes.container} />;
};
const useStyles = makeStyles(theme => ({
container: {
background: theme.palette.background.l1,
padding: '11px 0',
zIndex: '10',
},
}));
export default HorizontalSpacer;

View file

@ -9,6 +9,7 @@ import NetworkIcon from '../icons/Network';
import Logo from '../icons/Logo';
import Gift from '../icons/Gift';
import Headway from '../Headway';
import ZenButton from '../ZenButton';
import styles from './styles.css';
import useCommonStyles from '../useCommonStyles';
@ -23,14 +24,38 @@ import {makeStyles} from '@material-ui/core/styles';
const useStyles = makeStyles(theme => ({
container: {
position: 'relative',
backgroundColor: theme.palette.background.l2,
display: 'flex',
flexFlow: 'column',
marginTop: '-50px',
paddingTop: '50px',
width: 50,
boxShadow: `0 ${theme.palette.mode({
light: '3px',
dark: '3px',
})} 5px rgba(0, 0, 0, 0.35)`,
zIndex: 1,
transform: 'translateX(0)',
transition: 'transform .1s ease-out',
'& .zenButton': {
position: 'absolute',
background: theme.palette.background.l2,
top: '50%',
right: '0',
transformOrigin: 'center',
transform: 'translate(100%, -50%) rotate(-90deg) translateY(-30px)',
display: 'none',
},
'&:hover .zenButton': {
display: 'flex',
},
'&.zenMode': {
transform: 'translateX(-100%)',
},
'&.zenMode .zenButton': {
display: 'flex',
},
},
leftPaneIcon: {
'& svg': {
@ -55,7 +80,11 @@ const LeftIconsPane = props => {
props.openDrawerAndSetContent(content);
};
return (
<div className={mStyles.container}>
<div
className={`${mStyles.container} ${
props.isLeftPaneVisible ? '' : 'zenMode'
}`}
>
<Grid
container
spacing={1}
@ -118,6 +147,12 @@ const LeftIconsPane = props => {
</Grid>
</Grid>
<Headway />
{!props.drawer.open && (
<ZenButton
active={!props.isLeftPaneVisible}
onClick={() => props.setLeftPaneVisibility(!props.isLeftPaneVisible)}
/>
)}
</div>
);
};

View file

@ -96,7 +96,7 @@ const useStyles = makeStyles(theme => ({
marginBottom: 0,
},
wilcardsAndMoreLink: {
color: 'white',
color: theme.palette.text.normal,
textDecoration: 'underline',
},
bypassListField: {

View file

@ -7,6 +7,11 @@ import {CAPABILITIES} from '../../constants/devices';
import useCommonStyles from '../useCommonStyles';
import {getDeviceIcon} from '../../utils/iconUtils';
import KebabMenu from '../KebabMenu';
import {
FLIP_ORIENTATION_ALL_DEVICES,
SCREENSHOT_ALL_DEVICES,
} from '../../constants/pubsubEvents';
import pubsub from 'pubsub.js';
function Renderer(props) {
const {device, hidden, transmitNavigatorStatus} = props;
@ -62,6 +67,22 @@ function Renderer(props) {
</div>
</div>
<KebabMenu>
<KebabMenu.Item
onClick={() =>
pubsub.publish(SCREENSHOT_ALL_DEVICES, [{deviceId: device.id}])
}
>
Full Page Screenshot
</KebabMenu.Item>
<KebabMenu.Item
onClick={() =>
pubsub.publish(FLIP_ORIENTATION_ALL_DEVICES, [
{deviceId: device.id},
])
}
>
Tilt Device
</KebabMenu.Item>
<KebabMenu.Item
onClick={props.device.isMuted ? _unmuteDevice : _muteDevice}
>

View file

@ -9,6 +9,7 @@ import ScrollUpIcon from '../icons/ScrollUp';
import ScreenshotIcon from '../icons/FullScreenshot';
import DeviceRotateIcon from '../icons/DeviceRotate';
import InspectElementIcon from '../icons/InspectElement';
import DesignModeIcon from '../icons/DesignMode';
import MutedIcon from '../icons/Muted';
import UnmutedIcon from '../icons/Unmuted';
import useCommonStyles from '../useCommonStyles';
@ -33,6 +34,7 @@ const ScrollControls = ({
toggleInspector,
toggleCSSEditor,
onAllDevicesMutedChange,
onToggleAllDeviceDesignMode,
}) => {
const classes = useStyles();
const theme = useTheme();
@ -44,7 +46,6 @@ const ScrollControls = ({
};
return (
<>
<div className={classes.container}>
<Grid container spacing={1} alignItems="center">
<Grid item className={commonClasses.icon}>
@ -108,11 +109,28 @@ const ScrollControls = ({
</div>
</Tooltip>
</Grid>
<Grid
item
className={cx(commonClasses.icon, {
[commonClasses.iconSelected]: browser.allDevicesInDesignMode,
})}
>
<Tooltip
title={
browser.allDevicesInDesignMode
? 'Disable Design Mode on all devices'
: 'Enable Design Mode on all devices'
}
>
<div onClick={onToggleAllDeviceDesignMode}>
<DesignModeIcon {...{...iconProps, ...{height: 22, width: 22}}} />
</div>
</Tooltip>
</Grid>
<ToggleTouch iconProps={iconProps} />
<ZoomContainer iconProps={iconProps} />
</Grid>
</div>
</>
);
};

View file

@ -7,8 +7,6 @@ import {withStyles, withTheme} from '@material-ui/core/styles';
import debounce from 'lodash/debounce';
import pubsub from 'pubsub.js';
import BugIcon from '../icons/Bug';
import MutedIcon from '../icons/Muted';
import UnmutedIcon from '../icons/Unmuted';
import FullScreenshotIcon from '../icons/FullScreenshot';
import ScreenshotIcon from '../icons/Screenshot';
import DeviceRotateIcon from '../icons/DeviceRotate';
@ -30,8 +28,10 @@ import {
OPEN_CONSOLE_FOR_DEVICE,
PROXY_AUTH_ERROR,
APPLY_CSS,
TOGGLE_DEVICE_DESIGN_MODE_STATE,
} from '../../constants/pubsubEvents';
import {CAPABILITIES} from '../../constants/devices';
import {DESIGN_MODE_JS_VALUES} from '../../constants/values';
import styles from './style.module.css';
import {styles as commonStyles} from '../useCommonStyles';
@ -45,9 +45,11 @@ import Maximize from '../icons/Maximize';
import Minimize from '../icons/Minimize';
import Focus from '../icons/Focus';
import Unfocus from '../icons/Unfocus';
import DesignModeIcon from '../icons/DesignMode';
import {captureOnSentry} from '../../utils/logUtils';
import {getBrowserSyncEmbedScriptURL} from '../../services/browserSync';
import Spinner from '../Spinner';
import {isSslValidationFailed} from '../../utils/generalUtils';
const {BrowserWindow} = remote;
@ -141,6 +143,12 @@ class WebView extends Component {
this.subscriptions.push(
pubsub.subscribe(TOGGLE_DEVICE_MUTED_STATE, this.processToggleMuteEvent)
);
this.subscriptions.push(
pubsub.subscribe(
TOGGLE_DEVICE_DESIGN_MODE_STATE,
this.changeDesignModeState
)
);
this.subscriptions.push(
pubsub.subscribe(
@ -201,6 +209,9 @@ class WebView extends Component {
id: this.props.device.id,
loading: false,
});
this.changeDesignModeState({
designMode: !!this.props.device.designMode,
});
});
this.webviewRef.current.addEventListener(
'did-fail-load',
@ -271,6 +282,12 @@ class WebView extends Component {
this._unmuteWebView();
}
}
if (prevProps.device.designMode !== this.props.device.designMode) {
this.changeDesignModeState({
designMode: !!this.props.device.designMode,
});
}
}
getWebContentsId() {
@ -394,10 +411,14 @@ class WebView extends Component {
processScreenshotEvent = async ({
now,
fullScreen = true,
deviceId,
}: {
now?: Date,
fullScreen?: boolean,
}) => {
if (deviceId && this.props.device.id !== deviceId) {
return;
}
this.setState({screenshotInProgress: true});
try {
await this.closeBrowserSyncSocket(this.webviewRef.current);
@ -421,8 +442,9 @@ class WebView extends Component {
this.setState({screenshotInProgress: false});
};
processFlipOrientationEvent = () => {
if (!this.isMobile) {
processFlipOrientationEvent = (message = {}) => {
const {deviceId} = message;
if (deviceId && this.props.device.id !== deviceId) {
return;
}
this._flipOrientation();
@ -432,6 +454,16 @@ class WebView extends Component {
this.getWebContents().setAudioMuted(muted);
};
changeDesignModeState = ({designMode}) => {
this.webviewRef.current
.executeJavaScript(
`document.designMode = "${
designMode ? DESIGN_MODE_JS_VALUES.ON : DESIGN_MODE_JS_VALUES.OFF
}";`
)
.catch(captureOnSentry);
};
processOpenDevToolsInspectorEvent = message => {
const {
x: webViewX,
@ -667,6 +699,11 @@ class WebView extends Component {
});
};
_toggleDesignMode = () => {
const {id: deviceId} = this.props.device;
this.props.onToggleDeviceDesignMode(deviceId);
};
_focusDevice = () => {
this.props.setPreviewLayout(INDIVIDUAL_LAYOUT);
this.props.setFocusedDevice(this.props.device.id);
@ -935,27 +972,6 @@ class WebView extends Component {
<ScreenshotIcon height={18} />
</div>
</Tooltip>
<Tooltip title="Full Page Screenshot">
<div
className={cx(styles.webViewToolbarIcons, classes.icon)}
onClick={this.processScreenshotEvent}
>
<FullScreenshotIcon height={18} />
</div>
</Tooltip>
{this.isMobile ? (
<Tooltip title="Tilt Device">
<div
className={cx(styles.webViewToolbarIcons, classes.icon, {
[classes.iconSelected]: this.state.isTilted,
})}
onClick={this._flipOrientation}
>
<DeviceRotateIcon height={17} />
</div>
</Tooltip>
) : null}
<Tooltip title="Disable event mirroring">
<div
className={cx(styles.webViewToolbarIcons, classes.icon, {
@ -966,6 +982,20 @@ class WebView extends Component {
<UnplugIcon height={30} />
</div>
</Tooltip>
<Tooltip
title={`${
this.props.device.designMode ? 'Disable' : 'Enable'
} Design Mode`}
>
<div
className={cx(styles.webViewToolbarIcons, classes.icon, {
[classes.iconSelected]: this.props.device.designMode,
})}
onClick={this._toggleDesignMode}
>
<DesignModeIcon height={20} />
</div>
</Tooltip>
</div>
<div className={cx(styles.webViewToolbarRight)}>
<Tooltip
@ -1008,9 +1038,17 @@ class WebView extends Component {
>
<p>ERROR: {errorCode}</p>
<p className={cx(styles.errorDesc)}>{errorDesc}</p>
{proxyAuthError && (
<p className={cx(styles.errorDesc)}>Proxy Authentication Error</p>
)}
{isSslValidationFailed(errorCode) && (
<p className={cx(classes.errorHelpSuggestion)}>
If you wish to proceed, you can disable the SSL validation in
the user preferences.
</p>
)}
</div>
{this._getWebViewTag(deviceStyles, containerWidth, containerHeight)}
</div>
@ -1061,5 +1099,12 @@ const webViewStyles = theme => ({
bottom: 0,
},
},
errorHelpSuggestion: {
position: 'absolute',
top: '25%',
width: '100%',
padding: 35,
background: theme.palette.primary.main,
},
});
export default withStyles(webViewStyles)(withTheme(WebView));

View file

@ -53,6 +53,7 @@
display: none;
z-index: 1;
text-align: center;
color: #f8f8f8;
}
.deviceOverlay.overlayEnabled {
@ -62,6 +63,7 @@
.errorDesc {
font-size: 20px;
word-break: break-all;
}
.iconWrapperS {

View file

@ -0,0 +1,47 @@
// @flow
import React from 'react';
import Tooltip from '@material-ui/core/Tooltip';
import {useTheme, makeStyles} from '@material-ui/core/styles';
import Chevron from '../icons/Chevron';
import cx from 'classnames';
/**
* Button with a Chevron in the middle used for toggling zen mode on/off.
* @param active Indicates whether zen mode is on or not.
* @param onClick Callback function for when the button is clicked.
*/
const ZenButton = ({active = false, onClick}) => {
const classes = useStyles();
const theme = useTheme();
return (
<div className={cx(['zenButton', classes.container])} onClick={onClick}>
<Tooltip title="Hide/Show">
<div className={cx([classes.icon, {invert: active}])}>
<Chevron width={19} height={8} color={theme.palette.lightIcon.main} />
</div>
</Tooltip>
</div>
);
};
const useStyles = makeStyles(theme => ({
container: {
alignItems: 'center',
borderRadius: '0 0 8px 8px',
display: 'flex',
height: '20px',
justifyContent: 'center',
textAlign: 'center',
width: '80px',
cursor: 'pointer',
boxShadow: '0 5px 5px rgba(0, 0, 0, 0.35)',
},
icon: {
marginTop: '-5px',
'&.invert': {
transform: 'rotateX(180deg) translateY(-5px)',
},
},
}));
export default ZenButton;

View file

@ -16,6 +16,7 @@ import styles from './styles.module.css';
import useCommonStyles from '../useCommonStyles';
import './otherStyles.css';
import {Tooltip} from '@material-ui/core';
import {MIN_ZOOM_LEVEL, MAX_ZOOM_LEVEL} from '../../constants';
function BrowserZoom(props) {
const [showExpanded, setShowExpanded] = useState(false);
@ -51,7 +52,7 @@ function BrowserZoom(props) {
}
};
const value = Math.round(props.browser.zoomLevel * 100);
const zoomLevel = props.browser.zoomLevel;
return (
<div
@ -69,7 +70,11 @@ function BrowserZoom(props) {
})}
>
<ToggleButtonGroup value={[]} onChange={_zoomChange}>
<ToggleButton value="zoomOut" disabled={value === 20} disableRipple>
<ToggleButton
value="zoomOut"
disabled={zoomLevel === MIN_ZOOM_LEVEL}
disableRipple
>
&ndash;
</ToggleButton>
<Typography
@ -79,9 +84,13 @@ function BrowserZoom(props) {
'MuiToggleButton-root'
)}
>
{value}%
{Math.round(props.browser.zoomLevel * 100)}%
</Typography>
<ToggleButton value="zoomIn" disabled={value === 200} disableRipple>
<ToggleButton
value="zoomIn"
disabled={zoomLevel === MAX_ZOOM_LEVEL}
disableRipple
>
+
</ToggleButton>
</ToggleButtonGroup>

View file

@ -0,0 +1,22 @@
import React from 'react';
/**
* Flattened Chevron icon.
*/
export default ({width, height, color, padding, margin}) => (
<svg
height={height}
width={width}
style={{padding, margin}}
viewBox={`0 0 ${width} ${height}`}
xmlns="http://www.w3.org/2000/svg"
fill="none"
>
<path
d="M1 6.5L9.5 2L18 6.5"
stroke={color}
strokeWidth="2"
strokeLinecap="round"
/>
</svg>
);

View file

@ -0,0 +1,30 @@
import React, {Fragment} from 'react';
export default ({width, height, color, padding, margin}) => (
<Fragment>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
version="1.1"
x="0px"
y="0px"
height={height}
width={width}
fill={color}
style={{padding, margin}}
className="designModeIcon"
viewBox="0 0 36 36"
>
<path
d="M28 30H6V8h13.22l2-2H6a2 2 0 0 0-2 2v22a2 2 0 0 0 2 2h22a2 2 0 0 0 2-2V15l-2 2z"
className="clr-i-outline clr-i-outline-path-1"
fill="currentColor"
/>
<path
d="M33.53 5.84l-3.37-3.37a1.61 1.61 0 0 0-2.28 0L14.17 16.26l-1.11 4.81A1.61 1.61 0 0 0 14.63 23a1.69 1.69 0 0 0 .37 0l4.85-1.07L33.53 8.12a1.61 1.61 0 0 0 0-2.28zM18.81 20.08l-3.66.81l.85-3.63L26.32 6.87l2.82 2.82zM30.27 8.56l-2.82-2.82L29 4.16L31.84 7z"
className="clr-i-outline clr-i-outline-path-2"
fill="currentColor"
/>
</svg>
</Fragment>
);

View file

@ -31,6 +31,9 @@ const lightTheme = {
l10: '#d2d2d2',
l20: '#8a8a8a',
},
border: {
color: '#000000',
},
header: {
main: '#F5F5F5',
},
@ -72,6 +75,9 @@ const darkTheme = {
l10: '#9e9e9e',
l20: '#aeaeae',
},
border: {
color: '#ffffff',
},
header: {
main: '#252526',
},

View file

@ -49,6 +49,7 @@ export type Device = {
type: DeviceType,
source: Source,
isMuted: boolean,
designMode: boolean,
};
function getOS(device) {

View file

@ -0,0 +1,16 @@
// @flow
/**
* Default zoom level for the application.
*/
export const DEFAULT_ZOOM_LEVEL = 0.6;
/**
* Minimum zoom threshold for the application.
*/
export const MIN_ZOOM_LEVEL = 0.2;
/**
* Maximum zoom threshold for the application.
*/
export const MAX_ZOOM_LEVEL = 2.0;

View file

@ -24,3 +24,6 @@ export const HIDE_PERMISSION_POPUP_DUE_TO_RELOAD =
'HIDE_PERMISSION_POPUP_DUE_TO_RELOAD';
export const PERMISSION_MANAGEMENT_PREFERENCE_CHANGED =
'PERMISSION_MANAGEMENT_PREFERENCE_CHANGED';
export const TOGGLE_DEVICE_DESIGN_MODE_STATE =
'TOGGLE_DEVICE_DESIGN_MODE_STATE';

View file

@ -2,3 +2,13 @@ export const SCREENSHOT_MECHANISM = {
V1: 'V1',
V2: 'V2',
};
export const SSL_ERROR_CODES = {
FIRST: -200,
LAST: -299,
};
export const DESIGN_MODE_JS_VALUES = {
ON: 'on',
OFF: 'off',
};

View file

@ -3,18 +3,20 @@ import React, {Fragment} from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import Grid from '@material-ui/core/Grid';
import Header from '../../components/Header';
import DevicePreviewerContainer from '../DevicePreviewerContainer';
import DrawerContainer from '../DrawerContainer';
import * as BrowserActions from '../../actions/browser';
import {DEVTOOLS_MODES} from '../../constants/previewerLayouts';
import LeftIconsPaneContainer from '../LeftIconsPaneContainer';
type Props = {};
import HeaderContainer from '../HeaderContainer';
import os from 'os';
import HorizontalSpacer from '../../components/HorizontalSpacer';
import AppNotification from '../../components/AppNotification/AppNotification';
const Browser = ({browser}) => (
<Fragment>
<Header />
{os.platform() === 'darwin' && <HorizontalSpacer />}
<HeaderContainer />
<div style={{display: 'flex', height: '100%'}}>
<LeftIconsPaneContainer />
<div
@ -60,6 +62,7 @@ const Browser = ({browser}) => (
) : null}
</div>
</div>
<AppNotification />
</Fragment>
);

View file

@ -0,0 +1,19 @@
// @flow
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import Header from '../../components/Header';
import * as BrowserActions from '../../actions/browser';
function mapStateToProps(state) {
return {
isHeaderVisible: state.browser.isHeaderVisible,
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(BrowserActions, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(Header);

View file

@ -1,5 +1,4 @@
// @flow
import React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
@ -9,6 +8,7 @@ import * as BrowserActions from '../../actions/browser';
function mapStateToProps(state) {
return {
drawer: state.browser.drawer,
isLeftPaneVisible: state.browser.isLeftPaneVisible,
};
}

View file

@ -1,11 +1,10 @@
import React, {Component} from 'react';
import {Provider} from 'react-redux';
import {ConnectedRouter} from 'connected-react-router';
import log from 'electron-log';
import {makeStyles} from '@material-ui/core/styles';
import {ThemeProvider} from '@material-ui/styles';
import {remote} from 'electron';
import Routes from '../Routes';
import AppContent from '../AppContent';
import ErrorBoundary from '../components/ErrorBoundary';
import {
registerShortcut,
@ -29,22 +28,19 @@ import {toggleBookmarkUrl} from '../actions/bookmarks';
import pubsub from 'pubsub.js';
import {PROXY_AUTH_ERROR} from '../constants/pubsubEvents';
import useCreateTheme from '../components/useCreateTheme';
import {DEFAULT_ZOOM_LEVEL} from '../constants';
function App({history}) {
function App() {
const theme = useCreateTheme();
return (
<ThemeProvider theme={theme}>
{process.env.NODE_ENV !== 'development' ? (
<ErrorBoundary>
<ConnectedRouter history={history}>
<Routes />
</ConnectedRouter>
<AppContent />
</ErrorBoundary>
) : (
<ConnectedRouter history={history}>
<Routes />
</ConnectedRouter>
<AppContent />
)}
</ThemeProvider>
);
@ -95,7 +91,7 @@ export default class Root extends Component {
registerShortcut(
{id: 'ZoomReset', title: 'Zoom Reset', accelerators: ['mod+0']},
() => {
store.dispatch(onZoomChange(0.6));
store.dispatch(onZoomChange(DEFAULT_ZOOM_LEVEL));
},
true
);
@ -226,10 +222,10 @@ export default class Root extends Component {
};
render() {
const {store, history} = this.props;
const {store} = this.props;
return (
<Provider store={store}>
<App history={history} />
<App />
</Provider>
);
}

View file

@ -4,9 +4,10 @@ const {promisify} = require('util');
const Jimp = require('jimp');
const os = require('os');
const path = require('path');
const UUID = require('uuid/v4');
const uuid = require('uuid');
const fs = require('fs-extra');
const UUID = uuid.v4;
const tempDir = path.join(os.tmpdir(), UUID());
registerPromiseWorker(({images, direction, resultFilename}) => {

View file

@ -6,7 +6,7 @@ import {remote} from 'electron';
import {render} from 'react-dom';
import {AppContainer} from 'react-hot-loader';
import Root from './containers/Root';
import {configureStore, history} from './store/configureStore';
import {configureStore} from './store/configureStore';
import './app.global.css';
import * as Sentry from '@sentry/electron';
import appMetadata from './services/db/appMetadata';
@ -40,7 +40,7 @@ const store = configureStore();
render(
<AppContainer>
<Root store={store} history={history} />
<Root store={store} />
</AppContainer>,
document.getElementById('root')
);
@ -51,7 +51,7 @@ if (module.hot) {
const NextRoot = require('./containers/Root').default;
render(
<AppContainer>
<NextRoot store={store} history={history} />
<NextRoot store={store} />
</AppContainer>,
document.getElementById('root')
);

View file

@ -63,6 +63,19 @@ migrateDeviceSchema();
if (process.env.NODE_ENV !== 'development') {
Sentry.init({
dsn: 'https://f2cdbc6a88aa4a068a738d4e4cfd3e12@sentry.io/1553155',
environment: process.env.NODE_ENV,
beforeSend: (event, hint) => {
// Suppress address already in use error
if (
(event?.exception?.values?.[0]?.value || '').indexOf(
'listen EADDRINUSE: address already in use'
) > -1
) {
return null;
}
event.tags = {appVersion: app.getVersion()};
return event;
},
});
}
@ -355,6 +368,7 @@ const createWindow = async () => {
} else {
mainWindow.show();
}
mainWindow.maximize();
onResize();
});

View file

@ -26,6 +26,10 @@ import {
NEW_CSS_EDITOR_STATUS,
NEW_CSS_EDITOR_POSITION,
NEW_CSS_EDITOR_CONTENT,
TOGGLE_ALL_DEVICES_DESIGN_MODE,
TOGGLE_DEVICE_DESIGN_MODE,
SET_HEADER_VISIBILITY,
SET_LEFT_PANE_VISIBILITY,
} from '../actions/browser';
import {
CHANGE_ACTIVE_THROTTLING_PROFILE,
@ -56,6 +60,8 @@ import {
saveLastOpenedAddress,
} from '../utils/navigatorUtils';
import {updateExistingUrl} from '../services/searchUrlSuggestions';
import {normalizeZoomLevel} from '../utils/browserUtils';
import {DEFAULT_ZOOM_LEVEL} from '../constants';
export const FILTER_FIELDS = {
OS: 'OS',
@ -188,6 +194,9 @@ export type BrowserStateType = {
windowSize: WindowSizeType,
allDevicesMuted: boolean,
networkConfiguration: NetworkConfigurationType,
allDevicesInDesignMode: boolean,
isHeaderVisible: boolean,
isLeftPaneVisible: boolean,
};
let _activeDevices = null;
@ -220,13 +229,14 @@ function _getActiveDevices() {
activeDevices.forEach(device => {
device.loading = false;
device.isMuted = false;
device.designMode = false;
});
}
return activeDevices;
}
function _getUserPreferences(): UserPreferenceType {
return settings.get(USER_PREFERENCES);
return settings.get(USER_PREFERENCES) || {};
}
function _setUserPreferences(userPreferences) {
@ -306,7 +316,8 @@ export default function browser(
? getLastOpenedAddress()
: getHomepage(),
currentPageMeta: {},
zoomLevel: _getUserPreferences().zoomLevel || 0.6,
zoomLevel:
normalizeZoomLevel(_getUserPreferences().zoomLevel) || DEFAULT_ZOOM_LEVEL,
theme: _getUserPreferences().theme,
previousZoomLevel: null,
scrollPosition: {x: 0, y: 0},
@ -341,6 +352,9 @@ export default function browser(
windowSize: getWindowSize(),
allDevicesMuted: false,
networkConfiguration: _getNetworkConfiguration(),
allDevicesInDesignMode: false,
isHeaderVisible: true,
isLeftPaneVisible: true,
},
action: Action
) {
@ -538,6 +552,42 @@ export default function browser(
proxy: action.profile,
},
};
case TOGGLE_ALL_DEVICES_DESIGN_MODE:
const nextDevices = state.devices;
const nextDesginModeForAll = !state.allDevicesInDesignMode;
nextDevices.forEach(d => (d.designMode = nextDesginModeForAll));
return {
...state,
allDevicesInDesignMode: nextDesginModeForAll,
devices: nextDevices,
};
case TOGGLE_DEVICE_DESIGN_MODE:
const deviceIndex = state.devices.findIndex(
x => x.id === action.deviceId
);
if (deviceIndex === -1) return {...state};
const nextDesignModeForDevice = !state.devices[deviceIndex].designMode;
state.devices[deviceIndex] = {
...state.devices[deviceIndex],
designMode: nextDesignModeForDevice,
};
return {
...state,
allDevicesInDesignMode: state.devices.every(x => x.designMode),
devices: [...state.devices],
};
case SET_HEADER_VISIBILITY:
return {
...state,
isHeaderVisible: action.isVisible,
};
case SET_LEFT_PANE_VISIBILITY:
return {
...state,
isLeftPaneVisible: action.isVisible,
};
default:
return state;
}

View file

@ -1,13 +1,11 @@
// @flow
import {combineReducers} from 'redux';
import {connectRouter} from 'connected-react-router';
import browser from './browser';
import bookmarks from './bookmarks';
import statusBar from './statusBar';
export default function createRootReducer(history: History) {
export default function createRootReducer() {
return combineReducers({
router: connectRouter(history),
browser,
bookmarks,
statusBar,

View file

@ -1,13 +1,9 @@
import {createStore, applyMiddleware, compose} from 'redux';
import thunk from 'redux-thunk';
import {createHashHistory} from 'history';
import {routerMiddleware, routerActions} from 'connected-react-router';
import {createLogger} from 'redux-logger';
import createRootReducer from '../reducers';
const history = createHashHistory();
const rootReducer = createRootReducer(history);
const rootReducer = createRootReducer();
const configureStore = initialState => {
// Redux Configuration
@ -28,14 +24,9 @@ const configureStore = initialState => {
middleware.push(logger);
}
// Router Middleware
const router = routerMiddleware(history);
middleware.push(router);
// Redux DevTools Configuration
const actionCreators = {
...routerActions,
};
const actionCreators = {};
// If Redux DevTools Extension is installed use it, otherwise use Redux compose
/* eslint-disable no-underscore-dangle */
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
@ -64,4 +55,4 @@ const configureStore = initialState => {
return store;
};
export default {configureStore, history};
export default {configureStore};

View file

@ -8,5 +8,3 @@ const selectedConfigureStore =
: configureStoreDev;
export const {configureStore} = selectedConfigureStore;
export const {history} = selectedConfigureStore;

View file

@ -1,13 +1,9 @@
// @flow
import {createStore, applyMiddleware} from 'redux';
import thunk from 'redux-thunk';
import {createHashHistory} from 'history';
import {routerMiddleware} from 'connected-react-router';
import createRootReducer from '../reducers';
const history = createHashHistory();
const rootReducer = createRootReducer(history);
const router = routerMiddleware(history);
const rootReducer = createRootReducer();
const heap = () => next => action => {
window.requestIdleCallback(() => {
if (window.heap) {
@ -19,10 +15,10 @@ const heap = () => next => action => {
});
return next(action);
};
const enhancer = applyMiddleware(thunk, router, heap);
const enhancer = applyMiddleware(thunk, heap);
function configureStore(initialState) {
return createStore(rootReducer, initialState, enhancer);
}
export default {configureStore, history};
export default {configureStore};

View file

@ -11,7 +11,7 @@ const useStyles = makeStyles(theme => ({
height: '40vh',
marginBottom: '10px',
marginTop: '10px',
border: '1px white solid',
border: `1px solid ${theme.palette.border.color}`,
padding: '0 10px',
borderRadius: '4px',
},

View file

@ -0,0 +1,19 @@
// @flow
import {MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL} from '../constants';
/**
* Ensures that the given zoom level stays between MIN_ZOOM_LEVEL and MAX_ZOOM_LEVEL and returns it.
* @param zoomLevel The zoom level to be normalized.
*/
export function normalizeZoomLevel(zoomLevel: number): number {
if (zoomLevel < MIN_ZOOM_LEVEL) {
return MIN_ZOOM_LEVEL;
}
if (zoomLevel > MAX_ZOOM_LEVEL) {
return MAX_ZOOM_LEVEL;
}
return zoomLevel;
}

View file

@ -0,0 +1,18 @@
import os from 'os';
export const MIN_NUMBER_OF_DEVICES = 2;
export function getRecommendedMaxNumberOfDevices() {
const logicalCpuInfos = os.cpus();
const cpuSpeed = logicalCpuInfos[0].speed;
const cpuCount = logicalCpuInfos.length;
const value = Math.max(
MIN_NUMBER_OF_DEVICES,
Math.trunc(cpuCount * (cpuSpeed / 2000))
);
return value;
}
export const recommendedMaxNumberOfDevices = getRecommendedMaxNumberOfDevices();

View file

@ -2,6 +2,7 @@ import {app} from 'electron';
import path from 'path';
import fs from 'fs';
import os from 'os';
import {SSL_ERROR_CODES} from '../constants/values';
export const getPackageJson = () => {
let appPath;
@ -36,3 +37,9 @@ export const getEnvironmentInfo = () => {
osInfo,
};
};
export function isSslValidationFailed(errorCode) {
return (
errorCode <= SSL_ERROR_CODES.FIRST && errorCode >= SSL_ERROR_CODES.LAST
);
}

View file

@ -1,7 +1,7 @@
{
"name": "Responsively-App",
"productName": "ResponsivelyApp",
"version": "0.13.0",
"version": "0.14.1",
"description": "A developer-friendly browser for developing responsive web apps",
"scripts": {
"build": "concurrently \"yarn build-main\" \"yarn build-renderer\"",
@ -225,12 +225,11 @@
"babel-plugin-transform-react-remove-prop-types": "^0.4.20",
"chalk": "^2.4.1",
"concurrently": "^5.2.0",
"connected-react-router": "^6.5.2",
"cross-env": "^5.2.0",
"cross-spawn": "^6.0.5",
"css-loader": "^1.0.1",
"detect-port": "^1.3.0",
"electron": "^9.1.1",
"electron": "^9.3.1",
"electron-builder": "^22.8.0",
"electron-devtools-installer": "^3.1.1",
"enzyme": "^3.7.0",
@ -306,7 +305,6 @@
"electron-updater": "^4.3.1",
"electron-util": "^0.14.2",
"framer-motion": "^2.2.0",
"history": "^4.7.2",
"jimp": "^0.12.1",
"lodash": "^4.17.19",
"merge-img": "^2.1.3",
@ -324,8 +322,6 @@
"react-redux": "^7.1.0",
"react-resizable": "^1.10.1",
"react-rnd": "^10.2.2",
"react-router": "^5.0.1",
"react-router-dom": "^5.0.1",
"react-select": "^3.1.0",
"react-switch": "^5.0.1",
"react-tabs": "^3.0.0",

View file

@ -66,7 +66,7 @@ Check it out our GitHub repo - [![GitHub stars](https://img.shields.io/github/st
Follow on Twitter for future updates - [![Twitter Follow](https://img.shields.io/twitter/follow/ResponsivelyApp?style=social)](https://twitter.com/ResponsivelyApp)
Sponsor this project on [Open Collective](opencollective.com/responsively)
Sponsor this project on [Open Collective](https://opencollective.com/responsively)
Come say hi to us on [Slack](https://join.slack.com/t/responsively/shared_invite/zt-haoieftz-IsMw64H6jXC23pJ16ROLzw)!
</description>

View file

@ -3,26 +3,10 @@ const {version, build, productName} = require('../package.json');
const path = require('path');
const fs = require('fs');
const RELATIVE_FOLDER_PATH = '../release';
const getZipFile = () => {
const product = (build || {}).productName || productName;
const zipName = `${product}-${version}.zip`;
const zipPath = path.resolve(__dirname, RELATIVE_FOLDER_PATH, zipName);
if (fs.existsSync(zipPath)) {
console.log(`\nIncluded '${zipName}' in publish files`);
return zipPath;
}
throw new Error(`Expected zip file '${zipPath}' not found`);
};
const getExtraPublishFiles = () =>
generateChecksums().then(files => {
const zipFile = getZipFile();
const all = [zipFile, ...files];
console.log(`\nExtra Files Included:\n${all.join('\n')}`);
return all;
console.log(`\nExtra Files Included:\n${files.join('\n')}`);
return files;
});
exports.default = getExtraPublishFiles;

View file

@ -1,6 +1,17 @@
const path = require('path');
const fs = require('fs');
const crypto = require('crypto');
const pkg = require('../package.json');
const version = pkg.version;
const requiredFiles = [
`Responsively-App-${version}.x86_64.rpm`,
`ResponsivelyApp-${version}-mac.zip`,
`ResponsivelyApp-${version}.AppImage`,
`ResponsivelyApp-${version}.dmg`,
`ResponsivelyApp ${version}.exe`,
`ResponsivelyApp Setup ${version}.exe`,
];
function hashFile(file, algorithm = 'sha512', encoding = 'hex', options = {}) {
return new Promise((resolve, reject) => {
@ -31,17 +42,16 @@ const SKIP_SUFFIX_LIST = [CHECKSUM_SUFFIX, '.yml', '.yaml', '.txt'];
const generateChecksums = async () => {
const result = [];
const installerPath = path.resolve(__dirname, RELATIVE_FOLDER_PATH);
const files = await fs.promises.readdir(installerPath);
console.log("\nGenerating checksum files for files in: '%s'", installerPath);
for (const file of files) {
for (const file of requiredFiles) {
if (SKIP_SUFFIX_LIST.some(s => file.endsWith(s))) continue;
const filePath = path.join(installerPath, file);
const stat = await fs.promises.stat(filePath);
if (stat.isFile()) {
const checksumFile = `${file}${CHECKSUM_SUFFIX}`;
const checksumFile = `${file.replace(/ /g, '-')}${CHECKSUM_SUFFIX}`;
const checksumFilePath = path.join(installerPath, checksumFile);
const checksum = await hashFile(filePath);
await fs.promises.writeFile(checksumFilePath, checksum);

View file

@ -5193,13 +5193,6 @@ connect@3.6.6:
parseurl "~1.3.2"
utils-merge "1.0.1"
connected-react-router@^6.5.2:
version "6.8.0"
resolved "https://registry.yarnpkg.com/connected-react-router/-/connected-react-router-6.8.0.tgz#ddc687b31d498322445d235d660798489fa56cae"
integrity sha512-E64/6krdJM3Ag3MMmh2nKPtMbH15s3JQDuaYJvOVXzu6MbHbDyIvuwLOyhQIuP4Om9zqEfZYiVyflROibSsONg==
dependencies:
prop-types "^15.7.2"
console-browserify@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336"
@ -6358,10 +6351,10 @@ electron-util@^0.14.2:
electron-is-dev "^1.1.0"
new-github-issue-url "^0.2.1"
electron@^9.1.1:
version "9.1.1"
resolved "https://registry.yarnpkg.com/electron/-/electron-9.1.1.tgz#d52c9873be4113287c3eb2b02f85bad6644b100e"
integrity sha512-BYvroBLV9x7G4iN33P/IxeZqwjl62/9VuBAF1CoM0m6OeheaiLog1ZMKLlCqVXycJvvrAvLHc454DDEmwnqqhA==
electron@^9.3.1:
version "9.3.1"
resolved "https://registry.yarnpkg.com/electron/-/electron-9.3.1.tgz#e301932c5c0537d8c9a8850d216d3ba454dbf55c"
integrity sha512-DScrhqBT4a54KfdF0EoipALpHmdQTn3m7SSCtbpTcEcG+UDUiXad2cOfW6DHeVH7N+CVDKDG12q2PhVJjXkFAA==
dependencies:
"@electron/get" "^1.0.1"
"@types/node" "^12.0.12"
@ -8372,18 +8365,6 @@ highlight-es@^1.0.0:
is-es2016-keyword "^1.0.0"
js-tokens "^3.0.0"
history@^4.7.2, history@^4.9.0:
version "4.10.1"
resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3"
integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==
dependencies:
"@babel/runtime" "^7.1.2"
loose-envify "^1.2.0"
resolve-pathname "^3.0.0"
tiny-invariant "^1.0.2"
tiny-warning "^1.0.0"
value-equal "^1.0.1"
hmac-drbg@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
@ -8393,7 +8374,7 @@ hmac-drbg@^1.0.0:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
@ -10600,7 +10581,7 @@ longest-streak@^2.0.1:
resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.4.tgz#b8599957da5b5dab64dee3fe316fa774597d90e4"
integrity sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@ -11000,14 +10981,6 @@ min-indent@^1.0.0:
resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
mini-create-react-context@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.4.0.tgz#df60501c83151db69e28eac0ef08b4002efab040"
integrity sha512-b0TytUgFSbgFJGzJqXPKCFCBWigAjpjo+Fl7Vf7ZbKRDptszpppKxXH6DRXEABZ/gcEQczeb0iZ7JvL8e8jjCA==
dependencies:
"@babel/runtime" "^7.5.5"
tiny-warning "^1.0.3"
mini-css-extract-plugin@^0.4.4:
version "0.4.5"
resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.4.5.tgz#c99e9e78d54f3fa775633aee5933aeaa4e80719a"
@ -13157,7 +13130,7 @@ react-input-autosize@^2.2.2:
dependencies:
prop-types "^15.5.8"
react-is@^16.12.0, react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-is@^16.8.6, react-is@^16.9.0:
react-is@^16.12.0, react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-is@^16.8.6, react-is@^16.9.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@ -13202,35 +13175,6 @@ react-rnd@^10.2.2:
react-draggable "4.4.3"
tslib "2.0.0"
react-router-dom@^5.0.1:
version "5.2.0"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.2.0.tgz#9e65a4d0c45e13289e66c7b17c7e175d0ea15662"
integrity sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==
dependencies:
"@babel/runtime" "^7.1.2"
history "^4.9.0"
loose-envify "^1.3.1"
prop-types "^15.6.2"
react-router "5.2.0"
tiny-invariant "^1.0.2"
tiny-warning "^1.0.0"
react-router@5.2.0, react-router@^5.0.1:
version "5.2.0"
resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.2.0.tgz#424e75641ca8747fbf76e5ecca69781aa37ea293"
integrity sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==
dependencies:
"@babel/runtime" "^7.1.2"
history "^4.9.0"
hoist-non-react-statics "^3.1.0"
loose-envify "^1.3.1"
mini-create-react-context "^0.4.0"
path-to-regexp "^1.7.0"
prop-types "^15.6.2"
react-is "^16.6.0"
tiny-invariant "^1.0.2"
tiny-warning "^1.0.0"
react-select@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/react-select/-/react-select-3.1.0.tgz#ab098720b2e9fe275047c993f0d0caf5ded17c27"
@ -13827,11 +13771,6 @@ resolve-from@^5.0.0:
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69"
integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
resolve-pathname@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd"
integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==
resolve-url@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
@ -15618,12 +15557,12 @@ timsort@^0.3.0:
resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4"
integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=
tiny-invariant@^1.0.2, tiny-invariant@^1.0.4, tiny-invariant@^1.0.6:
tiny-invariant@^1.0.4, tiny-invariant@^1.0.6:
version "1.1.0"
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"
integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==
tiny-warning@^1.0.0, tiny-warning@^1.0.2, tiny-warning@^1.0.3:
tiny-warning@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
@ -16316,11 +16255,6 @@ validator@^13.1.1:
resolved "https://registry.yarnpkg.com/validator/-/validator-13.1.1.tgz#f8811368473d2173a9d8611572b58c5783f223bf"
integrity sha512-8GfPiwzzRoWTg7OV1zva1KvrSemuMkv07MA9TTl91hfhe+wKrsrgVN4H2QSFd/U/FhiU3iWPYVgvbsOGwhyFWw==
value-equal@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c"
integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==
vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"

3
website/.gitignore vendored
View file

@ -1,3 +0,0 @@
dist
node_modules
.DS_Store

View file

@ -1,7 +0,0 @@
# LICENSE #
Leap Bootstrap Theme by Medium Rare
Use of this theme is subject to the license terms set out on the Bootstrap Themes site:
https://themes.getbootstrap.com/licenses/

View file

@ -1,30 +0,0 @@
# README #
Leap Bootstrap Theme by Medium Rare
### Where are the docs? ###
* Formal documentation is located at http://leap.mediumra.re/documentation/index.html - accessible from the **Documentation** link on most demo pages.
* You can find lists of the styled components at pages/components-leap.html and pages/components-bootstrap.html
### Getting Set Up (optional) ###
Setup instructions are located in the docs mentioned above.
The short version:
* npm install -g gulp-cli
* npm install
* gulp
### Getting Support ###
Medium Rare provides support for bugfixes and guidance on using the theme.
To access support, find the support link in your Bootstrap Marketplace dashboard.
### Giving Feedback ###
We strive to improve our products and we rely on feedback from our customers.
Please feel free to share any feedback about Leap via twitter @mrareweb or feedback(at)mrare.co.

View file

@ -1,136 +0,0 @@
-
files: "clipboard.min.js"
from: "node_modules/clipboard/dist"
to: "pages/assets/js"
-
files: "jquery.min.js"
from: "node_modules/jquery/dist"
to: "pages/assets/js"
-
files: "jquery.countdown.min.js"
from: "node_modules/jquery-countdown/dist"
to: "pages/assets/js"
-
files: "flatpickr.min.js"
from: "node_modules/flatpickr/dist"
to: "pages/assets/js"
-
files: "flatpickr.min.css"
from: "node_modules/flatpickr/dist"
to: "scss/custom/components/plugins"
-
files: "flickity.pkgd.min.js"
from: "node_modules/flickity/dist"
to: "pages/assets/js"
-
files: "flickity.css"
from: "node_modules/flickity/dist"
to: "scss/custom/components/plugins"
-
files: "ion.rangeSlider.min.js"
from: "node_modules/ion-rangeslider/js"
to: "pages/assets/js"
-
files: "ion.rangeSlider.css"
from: "node_modules/ion-rangeslider/css"
to: "scss/custom/components/plugins"
-
files: "isotope.pkgd.min.js"
from: "node_modules/isotope-layout/dist"
to: "pages/assets/js"
-
files: "jquery.fancybox.min.css"
from: "node_modules/@fancyapps/fancybox/dist"
to: "scss/custom/components/plugins"
-
files: "jquery.fancybox.min.js"
from: "node_modules/@fancyapps/fancybox/dist"
to: "pages/assets/js"
-
files: "popper.min.js"
from: "node_modules/popper.js/dist/umd"
to: "pages/assets/js"
-
files: "popper.min.js.map"
from: "node_modules/popper.js/dist/umd"
to: "pages/assets/js"
-
files: "prism.js"
from: "node_modules/prismjs"
to: "pages/assets/js"
-
files: "prism.css"
from: "node_modules/prismjs/themes"
to: "scss/custom/components/plugins"
-
files: "prism-okaidia.css"
from: "node_modules/prismjs/themes"
to: "scss/custom/components/plugins"
-
files:
- "scrollMonitor.js"
- "scrollMonitor.js.map"
from: "node_modules/scrollmonitor"
to: "pages/assets/js"
-
files: "smooth-scroll.polyfills.min.js"
from: "node_modules/smooth-scroll/dist"
to: "pages/assets/js"
-
files:
- "svg-injector.umd.production.js"
- "svg-injector.umd.production.js.map"
from: "node_modules/@tanem/svg-injector/dist"
to: "pages/assets/js"
-
files:
- "typed.min.js"
- "typed.min.js.map"
from: "node_modules/typed.js/lib"
to: "pages/assets/js"
-
files: "aos.css"
from: "node_modules/aos/dist"
to: "scss/custom/components/plugins"
-
files: "aos.js"
from: "node_modules/aos/dist"
to: "pages/assets/js"
-
files: "twitterFetcher_min.js"
from: "node_modules/twitter-fetcher/js"
to: "pages/assets/js"
-
files:
- "jarallax.min.js"
- "jarallax-video.min.js"
- "jarallax-element.min.js"
- "jarallax.min.js.map"
- "jarallax-video.min.js.map"
- "jarallax-element.min.js.map"
from: "node_modules/jarallax/dist"
to: "pages/assets/js"
-
files: "jarallax.css"
from: "node_modules/jarallax/dist"
to: "scss/custom/components/plugins"
-
files: "plyr.css"
from: "node_modules/plyr/dist"
to: "scss/custom/components/plugins"
-
files:
- "plyr.polyfilled.min.js"
- "plyr.polyfilled.min.js.map"
from: "node_modules/plyr/dist"
to: "pages/assets/js"
-
files:
- "*.woff"
- "*.woff2"
from: "node_modules/inter-ui/Inter UI (web)"
to: "assets/fonts"
-
files: "jquery.smartWizard.min.js"
from: "node_modules/smartwizard/dist/js"
to: "pages/assets/js"

View file

@ -1,45 +0,0 @@
<?php
// SETTINGS FOR MAILCHIMP SUBSCRIPTION
// Log in to MailChimp and create an API key under:
// [ Account ] -> [ Extras ] -> [ API Keys ]
$apiKey = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX-us8';
// Find your list ID by opening the list in MailChimp, then:
// [ Settings ] -> [ List name and defaults ]
$listId = 'abcdefghij';
// Form data to use as email field
// (The name="..." value from the email field in your HTML form)
$emailField = 'email';
// Fields to be submitted to your MailChimp list along with the email address
// In this example, "NAME" is the field in your MailChimp list,
// and $_POST["name"] is the form data from the user to fill that field
$mergeFields = array(
"NAME" => $_POST["name"],
);
// What the user's status will be after submitting.
// Options are 'pending', 'subscribed', 'unsubscribed', 'cleaned'
// We recommend 'pending' as this will result in the user receiving
// an opt-in confirmation email from MailChimp.
// For single opt-in use 'subscribed'.
$status = 'pending';
// Text to show user upon successful subscribe operation
$successMessage = "Thanks for subscribing, please check your inbox for confirmation.";
// Text to show when the user is already subscribed to the list
$alreadySubscribed = "You are already subscribed to this list.";
// Text to show when the user is already subscribed to the list
$checkConfirmation = "Your subscription is pending, check your inbox for a confirmation link.";
// Google reCAPTCHA
// If your form is configured with a reCAPTCHA widget, this secret key will be used to validate with Google's server.
$recaptchaSecretKey = 'insert-your-recaptcha-secret-key-here';
$recaptchaErrorMessage = 'There was a problem verifying the Google reCaptcha. Please try again.';
require('vendor/mediumrare/mailchimp_subscribe.php');
?>

View file

@ -1,75 +0,0 @@
<?php
// error_reporting(-1);
// ini_set('display_errors', 'On');
/*----------------------------------------------------------------------------*\
|* Email settings for sending all emails from your website forms. *|
\*============================================================================*/
// Set the details of your SMTP server here:
// These details will be used to log in to the SMTP server and send an email
// to the site admin when a user submits a form.
// Optionally, you can send an email to the user to confirm receipt of their form submission.
// Outgoing Server Settings - replace values on the right of the = sign with your own.
// These 3 settings are are required.
// We do not recommend using Gmail as a server to send email.
// We recommend that you set up an email address on your hosting provider to perform the sending.
// (see cPanel -> email accounts). Using an account at your host improves deliverability.
// These are the Outgoing Server (SMTP) details provided by your email host
$outgoingServerAddress = 'server.company.com'; // consult your hosting provider.
$outgoingServerPort = '25'; // '587' , '25' - consult your hosting provider
$outgoingServerSecurity = 'tls'; // 'ssl' , 'tls' , null - consult your hosting provider.
// Sending Account Settings - replace these details with an email account held on the SMTP server entered above.
// This will also be used as the account to send the confirmation to the user.
$sendingAccountUsername = 'insert_your_account_here';
$sendingAccountPassword = 'insert_your_password_here';
// Recipient (To:) Details - Change this to the email details of who will receive all the emails from the website.
$recipientEmail = 'recipient@email.com'; // Where to send the admin email.
$recipientName = 'Recipient Name'; // Name of admin to receive email from website.
// Email details - Change these to suit your website needs
$emailSubject = 'A message from a form on your website'; // Subject of the email that the admin will see.
$websiteName = 'Edit your company website name'; // This is used as the "From name".
$adminEmailTemplate = 'email_to_admin.html'; // Name of template (in templates folder) to use for email to admin.
// Success Message to display in browser
$successMessage = 'Thank you, a member of our team will be in touch shortly.';
// Google reCAPTCHA
// If your form is configured with a reCAPTCHA widget, this secret key will be used to validate with Google's server.
$recaptchaSecretKey = 'optionally_insert_your_recaptcha_secret_key_here';
$recaptchaErrorMessage = 'There was a problem verifying the Google reCaptcha. Please try again.';
// Send User a Confirmation Email?
$sendConfirmationToUser = true; // leave false to disable confirmation, set to true to enable.
$userEmailField = "contact-email"; // What part of form data to use as an address to send confirmation email.
$userNameField = "contact-name"; // What part of form data to use as the user's name on confirmation email.
$confirmationEmailTemplate = "confirmation_to_user.html"; // Name of template (in templates folder) to use for email to user.
$confirmationSubject = "Thanks for testing our contact form!"; // The subject of the confirmation email.
$confirmationFromName = "A Template by Medium Rare"; // Used in the "from" field of the email.
$confirmationReplyTo = "admin@yourcompany.com"; // If the user wants to reply to the confirmation email, where should it go?
// The text replacements to use when constructing the confirmation email body
// Eg. By default, this will replace [[name]] in the email template with
// the 'name' field sent through the form. ucfirst sets the first letter to uppercase.
$confirmationReplacements = array(
"[[contact-name]]" => ucfirst($_POST["contact-name"]),
"[[mRareAddress]]" => 'http://mrare.co',
);
// Save to CSV file to keep a text record of the form entries
// This file should be password protected!
$saveToCSV = true;
$saveToCSVFileName = "csv/csv_forms_email_1.csv";
/*----------------------------------------------------------------------------*\
|* You do not need to edit anything below this line, the rest is automatic. *|
\*============================================================================*/
include('vendor/mediumrare/smtp_email.php');
?>

View file

@ -1,23 +0,0 @@
<!DOCTYPE html>
<html>
<body>
<h1>Thanks for submitting a form on our website.</h1>
<P>
Dear [[contact-name]],
</P>
<p>
We're glad you took the time to send us a message.
</p>
<p>
Just so you know, this is an automated email sent from our website.
</p>
<p>
We'll be in touch with you soon to respond to your enquiry.
</p>
<p>
Regards, <br />
Company Name Here <br />
</p>
</body>
</html>

View file

@ -1,32 +0,0 @@
<!DOCTYPE html>
<html>
<body>
<h1>EMAIL NOTICE</h1>
<p>
<ul>
<li>
Name: [[contact-name]]
</li>
<li>
Email: [[contact-email]]
</li>
<li>
Company: [[contact-company]]
</li>
<li>
Phone: [[contact-phone]]
</li>
<li>
Message: [[contact-message]]
</li>
</ul>
</p>
<h6>End of email</h6>
</body>
</html>

View file

@ -1,489 +0,0 @@
<?php
namespace DrewM\MailChimp;
/**
* Super-simple, minimum abstraction MailChimp API v3 wrapper
* MailChimp API v3: http://developer.mailchimp.com
* This wrapper: https://github.com/drewm/mailchimp-api
*
* @author Drew McLellan <drew.mclellan@gmail.com>
* @version 2.5
*/
class MailChimp
{
private $api_key;
private $api_endpoint = 'https://<dc>.api.mailchimp.com/3.0';
const TIMEOUT = 10;
/* SSL Verification
Read before disabling:
http://snippets.webaware.com.au/howto/stop-turning-off-curlopt_ssl_verifypeer-and-fix-your-php-config/
*/
public $verify_ssl = true;
private $request_successful = false;
private $last_error = '';
private $last_response = array();
private $last_request = array();
/**
* Create a new instance
*
* @param string $api_key Your MailChimp API key
* @param string $api_endpoint Optional custom API endpoint
*
* @throws \Exception
*/
public function __construct($api_key, $api_endpoint = null)
{
if (!function_exists('curl_init') || !function_exists('curl_setopt')) {
throw new \Exception("cURL support is required, but can't be found.");
}
$this->api_key = $api_key;
if ($api_endpoint === null) {
if (strpos($this->api_key, '-') === false) {
throw new \Exception("Invalid MailChimp API key supplied.");
}
list(, $data_center) = explode('-', $this->api_key);
$this->api_endpoint = str_replace('<dc>', $data_center, $this->api_endpoint);
} else {
$this->api_endpoint = $api_endpoint;
}
$this->last_response = array('headers' => null, 'body' => null);
}
/**
* Create a new instance of a Batch request. Optionally with the ID of an existing batch.
*
* @param string $batch_id Optional ID of an existing batch, if you need to check its status for example.
*
* @return Batch New Batch object.
*/
public function new_batch($batch_id = null)
{
return new Batch($this, $batch_id);
}
/**
* @return string The url to the API endpoint
*/
public function getApiEndpoint()
{
return $this->api_endpoint;
}
/**
* Convert an email address into a 'subscriber hash' for identifying the subscriber in a method URL
*
* @param string $email The subscriber's email address
*
* @return string Hashed version of the input
*/
public function subscriberHash($email)
{
return md5(strtolower($email));
}
/**
* Was the last request successful?
*
* @return bool True for success, false for failure
*/
public function success()
{
return $this->request_successful;
}
/**
* Get the last error returned by either the network transport, or by the API.
* If something didn't work, this should contain the string describing the problem.
*
* @return string|false describing the error
*/
public function getLastError()
{
return $this->last_error ?: false;
}
/**
* Get an array containing the HTTP headers and the body of the API response.
*
* @return array Assoc array with keys 'headers' and 'body'
*/
public function getLastResponse()
{
return $this->last_response;
}
/**
* Get an array containing the HTTP headers and the body of the API request.
*
* @return array Assoc array
*/
public function getLastRequest()
{
return $this->last_request;
}
/**
* Make an HTTP DELETE request - for deleting data
*
* @param string $method URL of the API request method
* @param array $args Assoc array of arguments (if any)
* @param int $timeout Timeout limit for request in seconds
*
* @return array|false Assoc array of API response, decoded from JSON
*/
public function delete($method, $args = array(), $timeout = self::TIMEOUT)
{
return $this->makeRequest('delete', $method, $args, $timeout);
}
/**
* Make an HTTP GET request - for retrieving data
*
* @param string $method URL of the API request method
* @param array $args Assoc array of arguments (usually your data)
* @param int $timeout Timeout limit for request in seconds
*
* @return array|false Assoc array of API response, decoded from JSON
*/
public function get($method, $args = array(), $timeout = self::TIMEOUT)
{
return $this->makeRequest('get', $method, $args, $timeout);
}
/**
* Make an HTTP PATCH request - for performing partial updates
*
* @param string $method URL of the API request method
* @param array $args Assoc array of arguments (usually your data)
* @param int $timeout Timeout limit for request in seconds
*
* @return array|false Assoc array of API response, decoded from JSON
*/
public function patch($method, $args = array(), $timeout = self::TIMEOUT)
{
return $this->makeRequest('patch', $method, $args, $timeout);
}
/**
* Make an HTTP POST request - for creating and updating items
*
* @param string $method URL of the API request method
* @param array $args Assoc array of arguments (usually your data)
* @param int $timeout Timeout limit for request in seconds
*
* @return array|false Assoc array of API response, decoded from JSON
*/
public function post($method, $args = array(), $timeout = self::TIMEOUT)
{
return $this->makeRequest('post', $method, $args, $timeout);
}
/**
* Make an HTTP PUT request - for creating new items
*
* @param string $method URL of the API request method
* @param array $args Assoc array of arguments (usually your data)
* @param int $timeout Timeout limit for request in seconds
*
* @return array|false Assoc array of API response, decoded from JSON
*/
public function put($method, $args = array(), $timeout = self::TIMEOUT)
{
return $this->makeRequest('put', $method, $args, $timeout);
}
/**
* Performs the underlying HTTP request. Not very exciting.
*
* @param string $http_verb The HTTP verb to use: get, post, put, patch, delete
* @param string $method The API method to be called
* @param array $args Assoc array of parameters to be passed
* @param int $timeout
*
* @return array|false Assoc array of decoded result
*/
private function makeRequest($http_verb, $method, $args = array(), $timeout = self::TIMEOUT)
{
$url = $this->api_endpoint . '/' . $method;
$response = $this->prepareStateForRequest($http_verb, $method, $url, $timeout);
$httpHeader = array(
'Accept: application/vnd.api+json',
'Content-Type: application/vnd.api+json',
'Authorization: apikey ' . $this->api_key
);
if (isset($args["language"])) {
$httpHeader[] = "Accept-Language: " . $args["language"];
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, $httpHeader);
curl_setopt($ch, CURLOPT_USERAGENT, 'DrewM/MailChimp-API/3.0 (github.com/drewm/mailchimp-api)');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_VERBOSE, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $this->verify_ssl);
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
curl_setopt($ch, CURLOPT_ENCODING, '');
curl_setopt($ch, CURLINFO_HEADER_OUT, true);
switch ($http_verb) {
case 'post':
curl_setopt($ch, CURLOPT_POST, true);
$this->attachRequestPayload($ch, $args);
break;
case 'get':
$query = http_build_query($args, '', '&');
curl_setopt($ch, CURLOPT_URL, $url . '?' . $query);
break;
case 'delete':
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
break;
case 'patch':
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH');
$this->attachRequestPayload($ch, $args);
break;
case 'put':
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
$this->attachRequestPayload($ch, $args);
break;
}
$responseContent = curl_exec($ch);
$response['headers'] = curl_getinfo($ch);
$response = $this->setResponseState($response, $responseContent, $ch);
$formattedResponse = $this->formatResponse($response);
curl_close($ch);
$isSuccess = $this->determineSuccess($response, $formattedResponse, $timeout);
return is_array($formattedResponse) ? $formattedResponse : $isSuccess;
}
/**
* @param string $http_verb
* @param string $method
* @param string $url
* @param integer $timeout
*
* @return array
*/
private function prepareStateForRequest($http_verb, $method, $url, $timeout)
{
$this->last_error = '';
$this->request_successful = false;
$this->last_response = array(
'headers' => null, // array of details from curl_getinfo()
'httpHeaders' => null, // array of HTTP headers
'body' => null // content of the response
);
$this->last_request = array(
'method' => $http_verb,
'path' => $method,
'url' => $url,
'body' => '',
'timeout' => $timeout,
);
return $this->last_response;
}
/**
* Get the HTTP headers as an array of header-name => header-value pairs.
*
* The "Link" header is parsed into an associative array based on the
* rel names it contains. The original value is available under
* the "_raw" key.
*
* @param string $headersAsString
*
* @return array
*/
private function getHeadersAsArray($headersAsString)
{
$headers = array();
foreach (explode("\r\n", $headersAsString) as $i => $line) {
if ($i === 0) { // HTTP code
continue;
}
$line = trim($line);
if (empty($line)) {
continue;
}
list($key, $value) = explode(': ', $line);
if ($key == 'Link') {
$value = array_merge(
array('_raw' => $value),
$this->getLinkHeaderAsArray($value)
);
}
$headers[$key] = $value;
}
return $headers;
}
/**
* Extract all rel => URL pairs from the provided Link header value
*
* Mailchimp only implements the URI reference and relation type from
* RFC 5988, so the value of the header is something like this:
*
* 'https://us13.api.mailchimp.com/schema/3.0/Lists/Instance.json; rel="describedBy",
* <https://us13.admin.mailchimp.com/lists/members/?id=XXXX>; rel="dashboard"'
*
* @param string $linkHeaderAsString
*
* @return array
*/
private function getLinkHeaderAsArray($linkHeaderAsString)
{
$urls = array();
if (preg_match_all('/<(.*?)>\s*;\s*rel="(.*?)"\s*/', $linkHeaderAsString, $matches)) {
foreach ($matches[2] as $i => $relName) {
$urls[$relName] = $matches[1][$i];
}
}
return $urls;
}
/**
* Encode the data and attach it to the request
*
* @param resource $ch cURL session handle, used by reference
* @param array $data Assoc array of data to attach
*/
private function attachRequestPayload(&$ch, $data)
{
$encoded = json_encode($data);
$this->last_request['body'] = $encoded;
curl_setopt($ch, CURLOPT_POSTFIELDS, $encoded);
}
/**
* Decode the response and format any error messages for debugging
*
* @param array $response The response from the curl request
*
* @return array|false The JSON decoded into an array
*/
private function formatResponse($response)
{
$this->last_response = $response;
if (!empty($response['body'])) {
return json_decode($response['body'], true);
}
return false;
}
/**
* Do post-request formatting and setting state from the response
*
* @param array $response The response from the curl request
* @param string $responseContent The body of the response from the curl request
* @param resource $ch The curl resource
*
* @return array The modified response
*/
private function setResponseState($response, $responseContent, $ch)
{
if ($responseContent === false) {
$this->last_error = curl_error($ch);
} else {
$headerSize = $response['headers']['header_size'];
$response['httpHeaders'] = $this->getHeadersAsArray(substr($responseContent, 0, $headerSize));
$response['body'] = substr($responseContent, $headerSize);
if (isset($response['headers']['request_header'])) {
$this->last_request['headers'] = $response['headers']['request_header'];
}
}
return $response;
}
/**
* Check if the response was successful or a failure. If it failed, store the error.
*
* @param array $response The response from the curl request
* @param array|false $formattedResponse The response body payload from the curl request
* @param int $timeout The timeout supplied to the curl request.
*
* @return bool If the request was successful
*/
private function determineSuccess($response, $formattedResponse, $timeout)
{
$status = $this->findHTTPStatus($response, $formattedResponse);
if ($status >= 200 && $status <= 299) {
$this->request_successful = true;
return true;
}
if (isset($formattedResponse['detail'])) {
$this->last_error = sprintf('%d: %s', $formattedResponse['status'], $formattedResponse['detail']);
return false;
}
if ($timeout > 0 && $response['headers'] && $response['headers']['total_time'] >= $timeout) {
$this->last_error = sprintf('Request timed out after %f seconds.', $response['headers']['total_time']);
return false;
}
$this->last_error = 'Unknown error, call getLastResponse() to find out what happened.';
return false;
}
/**
* Find the HTTP status code from the headers or API response body
*
* @param array $response The response from the curl request
* @param array|false $formattedResponse The response body payload from the curl request
*
* @return int HTTP status code
*/
private function findHTTPStatus($response, $formattedResponse)
{
if (!empty($response['headers']) && isset($response['headers']['http_code'])) {
return (int)$response['headers']['http_code'];
}
if (!empty($response['body']) && isset($formattedResponse['status'])) {
return (int)$formattedResponse['status'];
}
return 418;
}
}

View file

View file

@ -1,26 +0,0 @@
<?php
// Get Basic HTML template for sending email with user's input.
$pathToTemplate = 'templates/'.$adminEmailTemplate;
set_error_handler(
function ($severity, $message, $file, $line) {
$response->status = 'error';
$response->message = 'Failed to open admin email template file. Email to admin was not sent. Error message:'.$message;
echo(json_encode($response));
exit;
}
);
try{
$adminEmailTemplate = file_get_contents($pathToTemplate);
} catch (Exception $err) {
exit;
}
$postValues = array_values($_POST);
// Take all Post array keys and
$postKeys = array_map(function($value) {return '[['.$value.']]';}, array_keys($_POST));
$htmlContent = str_replace($postKeys, $postValues, $adminEmailTemplate);

View file

@ -1,26 +0,0 @@
<?php
// Get HTML template to send to User as confirmation.
$pathToTemplate = 'templates/'.$confirmationEmailTemplate;
set_error_handler(
function ($severity, $message, $file, $line) {
$response->status = 'error';
$response->message = 'Failed to open user confirmation email template file. Confirmation was not sent. ';
echo(json_encode($response));
exit;
}
);
try{
$confirmationEmailTemplate = file_get_contents($pathToTemplate);
} catch (Exception $err) {
exit;
}
restore_error_handler();
$confirmationHtmlContent = strtr($confirmationEmailTemplate, $confirmationReplacements);
$confirmationTextContent = filter_var($confirmationHtmlContent, FILTER_SANITIZE_STRING);

View file

@ -1,34 +0,0 @@
<?php
// If the form has been submitted with a captcha, check it - if it fails from Google, exit the script after returning an error message.
$data = array(
'secret' => $recaptchaSecretKey,
'response' => $_POST['g-recaptcha-response']
);
$verify = curl_init();
curl_setopt($verify, CURLOPT_URL, "https://www.google.com/recaptcha/api/siteverify");
curl_setopt($verify, CURLOPT_POST, true);
curl_setopt($verify, CURLOPT_POSTFIELDS, http_build_query($data));
curl_setopt($verify, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($verify, CURLOPT_RETURNTRANSFER, true);
$gResponse = curl_exec($verify);
$gResponse = json_decode( $gResponse , true );
if($gResponse['success'] == false)
{
$response->status = "error";
$response->message = $recaptchaErrorMessage;
$response->errorDetail = json_encode($gResponse);
$response->errorName = 'Google reCAPTCHA verification error';
echo(json_encode($response));
exit;
}else{
$response->recaptchaDetail = $gResponse;
unset($_POST['g-recaptcha-response']);
}
?>

View file

@ -1,16 +0,0 @@
<?php
// Basic sanitization to remove tags to safeguard email
function util_array_trim(array &$array, $filter = false)
{
array_walk_recursive($array, function (&$value) use ($filter) {
$value = trim($value);
if ($filter) {
$value = filter_var($value, FILTER_SANITIZE_STRING);
}
});
return $array;
}
$_POST = util_array_trim($_POST, true);

View file

@ -1,16 +0,0 @@
<?php
// Save to CSV file
$file = fopen($saveToCSVFileName, 'a');
$data = array_values($_POST);
$data = array_merge(array( date("Y-m-d H:i:s")), $data);
fputcsv_eol($file, $data,"\n");
fclose($file);
function fputcsv_eol($fp, $array, $eol) {
fputcsv($fp, $array,',', '"');
if("\n" != $eol && 0 === fseek($fp, -1, SEEK_CUR)) {
fwrite($fp, $eol);
}
}

View file

@ -1,34 +0,0 @@
<?php
// Require the Swift Mailer library
require_once 'vendor/swiftmailer/swift_required.php';
$transport = Swift_SmtpTransport::newInstance( $outgoingServerAddress, $outgoingServerPort, $outgoingServerSecurity )
->setUsername( $sendingAccountUsername )
->setPassword( $sendingAccountPassword );
$mailer = Swift_Mailer::newInstance($transport);
$fromArray = array($sendingAccountUsername => $websiteName);
$sentMessages = 0;
$message = Swift_Message::newInstance($emailSubject)
->setSender(array($sendingAccountUsername => $websiteName))
->setFrom($fromArray)
->setReplyTo($_POST[$userEmailField])
->setTo(array($recipientEmail => $recipientName))
->setBody($textContent, 'text/plain')
->addPart($htmlContent, 'text/html');
// Send the message or catch an error if it occurs.
try{
$sentMessages = $mailer->send($message);
$response->status = "success";
} catch(Exception $e){
$response->status = "error";
$response->message = $e->getMessage();
echo(json_encode($response));
}
?>

View file

@ -1,29 +0,0 @@
<?php
// Require the Swift Mailer library
require_once 'vendor/swiftmailer/swift_required.php';
$mailer = Swift_Mailer::newInstance($transport);
$fromArray = array($sendingAccountUsername => $confirmationFromName);
$message = Swift_Message::newInstance($confirmationSubject)
->setSender(array($sendingAccountUsername => $confirmationFromName))
->setFrom($fromArray)
->setReplyTo($confirmationReplyTo)
->setTo(array($_POST[$userEmailField] => $_POST[$userNameField]))
->setBody($confirmationTextContent, 'text/plain')
->addPart($confirmationHtmlContent, 'text/html');
// Send the message or catch an error if it occurs.
try{
$sentMessages = $mailer->send($message);
$response->status = "success";
} catch(Exception $e){
$response->status = "error";
$response->message = $e->getMessage();
echo(json_encode($response));
}
?>

View file

@ -1,15 +0,0 @@
<?php
$textContent = "";
// Creating the message text using fields sent through POST
foreach ($_POST as $key => $value)
{
if($key !== 'g-recaptcha-response' && $key !== 'captcha'){// Sets of checkboxes will be shown as comma-separated values as they are passed in as an array.
if(is_array($value)){
$value = implode(', ' , $value);
}
$textContent .= ucfirst($key).": ".$value.PHP_EOL;
}
}
?>

View file

@ -1,82 +0,0 @@
<?php
//error_reporting(-1);
//ini_set('display_errors', 'On');
// Include the MailChimp API wrapper
// https://github.com/drewm/mailchimp-api
include('vendor/drewm/MailChimp.php');
use \DrewM\MailChimp\MailChimp;
$MailChimp = new MailChimp($apiKey);
// Set up response object to be returned to browser as JSON
$response = (object) array('status' => '', 'message' => $successMessage);
// If the form has been submitted with a captcha, verify it - if it fails from Google,
// exit the script after returning an error message.
if(isset($_POST['g-recaptcha-response'])){
require('include/recaptcha_v2.php');
}
// Make sure there are no blank fields which will cause an error in MailChimp
foreach ($mergeFields as $key => $value)
{
if(empty($value)){
$mergeFields[$key] = ' ';
}
}
// Check if email is subscribed to the list already
$subscriber_hash = $MailChimp->subscriberHash($_POST[$emailField]);
$result = $MailChimp->get("lists/$listId/members/$subscriber_hash");
// Check result of MailChimp operation
if ($MailChimp->success()) {
// Success message
$response->status = "success";
if(json_decode($MailChimp->getLastResponse()['body'])->status === 'subscribed'){
$response->message = $alreadySubscribed;
}
if(json_decode($MailChimp->getLastResponse()['body'])->status === 'pending'){
$response->message = $checkConfirmation;
}
echo json_encode($response);
exit;
} else {
// If not found in list, it is 404 and the script should continue else, show the error.
if(json_decode($MailChimp->getLastResponse()['body'])->status !== 404){
handle_error($MailChimp);
}
}
// Submit subscriber data to MailChimp
// For parameters doc, refer to: http://developer.mailchimp.com/documentation/mailchimp/reference/lists/members/
// For wrapper's doc, visit: https://github.com/drewm/mailchimp-api
$result = $MailChimp->post("lists/$listId/members", [
'email_address' => $_POST[$emailField],
'mergeFields' => $mergeFields,
'status' => $status,
]);
// Check result of MailChimp operation
if ($MailChimp->success()) {
// Success message
$response->status = "success";
$response->message = $successMessage;
} else {
// Display error
handle_error($MailChimp);
}
echo json_encode($response);
exit;
function handle_error($MailChimp) {
$response->status = "error";
$response->message = $MailChimp->getLastError();
$response->errorDetail = $MailChimp->getLastResponse()['body'];
$response->errorName = 'MailChimp subscribe error';
echo json_encode($response);
exit;
}
?>

View file

@ -1,50 +0,0 @@
<?php
//error_reporting(-1);
//ini_set('display_errors', 'On');
// Basic sanitization to remove tags from user input
require('include/sanitize_post.php');
// Set default timezone as some servers do not have this set.
if(isset($timeZone) && $timeZone != ""){
date_default_timezone_set($timeZone);
}
else{
date_default_timezone_set("UTC");
}
// Set up response object to be returned to browser as JSON
$response = (object) array('status' => '', 'message' => $successMessage);
// If the form has been submitted with a captcha, verify it - if it fails from Google,
// exit the script after returning an error message.
if(isset($_POST['g-recaptcha-response'])){
require('include/recaptcha_v2.php');
}
// Load template and make replacements
require('include/load_template_admin.php');
// Assemble the text for the plain-text version of the message.
require('include/text_content.php');
// Send the email via SMTP using Swiftmailer
require('include/send_smtp_admin.php');
if($saveToCSV === true){
require('include/save_csv.php');
}
// Send confirmation email to user
if($sendConfirmationToUser === true){
// Load user confirmation template and makre replacements
require('include/load_template_user.php');
// Send the email to the user
require('include/send_smtp_user.php');
}
echo(json_encode($response));
exit;
?>

View file

@ -1,80 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* General utility class in Swift Mailer, not to be instantiated.
*
*
* @author Chris Corbyn
*/
abstract class Swift
{
public static $initialized = false;
public static $inits = array();
/** Swift Mailer Version number generated during dist release process */
const VERSION = '@SWIFT_VERSION_NUMBER@';
/**
* Registers an initializer callable that will be called the first time
* a SwiftMailer class is autoloaded.
*
* This enables you to tweak the default configuration in a lazy way.
*
* @param mixed $callable A valid PHP callable that will be called when autoloading the first Swift class
*/
public static function init($callable)
{
self::$inits[] = $callable;
}
/**
* Internal autoloader for spl_autoload_register().
*
* @param string $class
*/
public static function autoload($class)
{
// Don't interfere with other autoloaders
if (0 !== strpos($class, 'Swift_')) {
return;
}
$path = dirname(__FILE__).'/'.str_replace('_', '/', $class).'.php';
if (!file_exists($path)) {
return;
}
require $path;
if (self::$inits && !self::$initialized) {
self::$initialized = true;
foreach (self::$inits as $init) {
call_user_func($init);
}
}
}
/**
* Configure autoloading using Swift Mailer.
*
* This is designed to play nicely with other autoloaders.
*
* @param mixed $callable A valid PHP callable that will be called when autoloading the first Swift class
*/
public static function registerAutoload($callable = null)
{
if (null !== $callable) {
self::$inits[] = $callable;
}
spl_autoload_register(array('Swift', 'autoload'));
}
}

View file

@ -1,71 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Attachment class for attaching files to a {@link Swift_Mime_Message}.
*
* @author Chris Corbyn
*/
class Swift_Attachment extends Swift_Mime_Attachment
{
/**
* Create a new Attachment.
*
* Details may be optionally provided to the constructor.
*
* @param string|Swift_OutputByteStream $data
* @param string $filename
* @param string $contentType
*/
public function __construct($data = null, $filename = null, $contentType = null)
{
call_user_func_array(
array($this, 'Swift_Mime_Attachment::__construct'),
Swift_DependencyContainer::getInstance()
->createDependenciesFor('mime.attachment')
);
$this->setBody($data);
$this->setFilename($filename);
if ($contentType) {
$this->setContentType($contentType);
}
}
/**
* Create a new Attachment.
*
* @param string|Swift_OutputByteStream $data
* @param string $filename
* @param string $contentType
*
* @return Swift_Mime_Attachment
*/
public static function newInstance($data = null, $filename = null, $contentType = null)
{
return new self($data, $filename, $contentType);
}
/**
* Create a new Attachment from a filesystem path.
*
* @param string $path
* @param string $contentType optional
*
* @return Swift_Mime_Attachment
*/
public static function fromPath($path, $contentType = null)
{
return self::newInstance()->setFile(
new Swift_ByteStream_FileByteStream($path),
$contentType
);
}
}

View file

@ -1,179 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Provides the base functionality for an InputStream supporting filters.
*
* @author Chris Corbyn
*/
abstract class Swift_ByteStream_AbstractFilterableInputStream implements Swift_InputByteStream, Swift_Filterable
{
/**
* Write sequence.
*/
protected $_sequence = 0;
/**
* StreamFilters.
*/
private $_filters = array();
/**
* A buffer for writing.
*/
private $_writeBuffer = '';
/**
* Bound streams.
*
* @var Swift_InputByteStream[]
*/
private $_mirrors = array();
/**
* Commit the given bytes to the storage medium immediately.
*
* @param string $bytes
*/
abstract protected function _commit($bytes);
/**
* Flush any buffers/content with immediate effect.
*/
abstract protected function _flush();
/**
* Add a StreamFilter to this InputByteStream.
*
* @param Swift_StreamFilter $filter
* @param string $key
*/
public function addFilter(Swift_StreamFilter $filter, $key)
{
$this->_filters[$key] = $filter;
}
/**
* Remove an already present StreamFilter based on its $key.
*
* @param string $key
*/
public function removeFilter($key)
{
unset($this->_filters[$key]);
}
/**
* Writes $bytes to the end of the stream.
*
* @param string $bytes
*
* @return int
*
* @throws Swift_IoException
*/
public function write($bytes)
{
$this->_writeBuffer .= $bytes;
foreach ($this->_filters as $filter) {
if ($filter->shouldBuffer($this->_writeBuffer)) {
return;
}
}
$this->_doWrite($this->_writeBuffer);
return ++$this->_sequence;
}
/**
* For any bytes that are currently buffered inside the stream, force them
* off the buffer.
*
* @throws Swift_IoException
*/
public function commit()
{
$this->_doWrite($this->_writeBuffer);
}
/**
* Attach $is to this stream.
*
* The stream acts as an observer, receiving all data that is written.
* All {@link write()} and {@link flushBuffers()} operations will be mirrored.
*
* @param Swift_InputByteStream $is
*/
public function bind(Swift_InputByteStream $is)
{
$this->_mirrors[] = $is;
}
/**
* Remove an already bound stream.
*
* If $is is not bound, no errors will be raised.
* If the stream currently has any buffered data it will be written to $is
* before unbinding occurs.
*
* @param Swift_InputByteStream $is
*/
public function unbind(Swift_InputByteStream $is)
{
foreach ($this->_mirrors as $k => $stream) {
if ($is === $stream) {
if ($this->_writeBuffer !== '') {
$stream->write($this->_writeBuffer);
}
unset($this->_mirrors[$k]);
}
}
}
/**
* Flush the contents of the stream (empty it) and set the internal pointer
* to the beginning.
*
* @throws Swift_IoException
*/
public function flushBuffers()
{
if ($this->_writeBuffer !== '') {
$this->_doWrite($this->_writeBuffer);
}
$this->_flush();
foreach ($this->_mirrors as $stream) {
$stream->flushBuffers();
}
}
/** Run $bytes through all filters */
private function _filter($bytes)
{
foreach ($this->_filters as $filter) {
$bytes = $filter->filter($bytes);
}
return $bytes;
}
/** Just write the bytes to the stream */
private function _doWrite($bytes)
{
$this->_commit($this->_filter($bytes));
foreach ($this->_mirrors as $stream) {
$stream->write($bytes);
}
$this->_writeBuffer = '';
}
}

View file

@ -1,184 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Allows reading and writing of bytes to and from an array.
*
* @author Chris Corbyn
*/
class Swift_ByteStream_ArrayByteStream implements Swift_InputByteStream, Swift_OutputByteStream
{
/**
* The internal stack of bytes.
*
* @var string[]
*/
private $_array = array();
/**
* The size of the stack
*
* @var int
*/
private $_arraySize = 0;
/**
* The internal pointer offset.
*
* @var int
*/
private $_offset = 0;
/**
* Bound streams.
*
* @var Swift_InputByteStream[]
*/
private $_mirrors = array();
/**
* Create a new ArrayByteStream.
*
* If $stack is given the stream will be populated with the bytes it contains.
*
* @param mixed $stack of bytes in string or array form, optional
*/
public function __construct($stack = null)
{
if (is_array($stack)) {
$this->_array = $stack;
$this->_arraySize = count($stack);
} elseif (is_string($stack)) {
$this->write($stack);
} else {
$this->_array = array();
}
}
/**
* Reads $length bytes from the stream into a string and moves the pointer
* through the stream by $length.
*
* If less bytes exist than are requested the
* remaining bytes are given instead. If no bytes are remaining at all, boolean
* false is returned.
*
* @param int $length
*
* @return string
*/
public function read($length)
{
if ($this->_offset == $this->_arraySize) {
return false;
}
// Don't use array slice
$end = $length + $this->_offset;
$end = $this->_arraySize<$end
?$this->_arraySize
:$end;
$ret = '';
for (; $this->_offset < $end; ++$this->_offset) {
$ret .= $this->_array[$this->_offset];
}
return $ret;
}
/**
* Writes $bytes to the end of the stream.
*
* @param string $bytes
*/
public function write($bytes)
{
$to_add = str_split($bytes);
foreach ($to_add as $value) {
$this->_array[] = $value;
}
$this->_arraySize = count($this->_array);
foreach ($this->_mirrors as $stream) {
$stream->write($bytes);
}
}
/**
* Not used.
*/
public function commit()
{
}
/**
* Attach $is to this stream.
*
* The stream acts as an observer, receiving all data that is written.
* All {@link write()} and {@link flushBuffers()} operations will be mirrored.
*
* @param Swift_InputByteStream $is
*/
public function bind(Swift_InputByteStream $is)
{
$this->_mirrors[] = $is;
}
/**
* Remove an already bound stream.
*
* If $is is not bound, no errors will be raised.
* If the stream currently has any buffered data it will be written to $is
* before unbinding occurs.
*
* @param Swift_InputByteStream $is
*/
public function unbind(Swift_InputByteStream $is)
{
foreach ($this->_mirrors as $k => $stream) {
if ($is === $stream) {
unset($this->_mirrors[$k]);
}
}
}
/**
* Move the internal read pointer to $byteOffset in the stream.
*
* @param int $byteOffset
*
* @return bool
*/
public function setReadPointer($byteOffset)
{
if ($byteOffset > $this->_arraySize) {
$byteOffset = $this->_arraySize;
} elseif ($byteOffset < 0) {
$byteOffset = 0;
}
$this->_offset = $byteOffset;
}
/**
* Flush the contents of the stream (empty it) and set the internal pointer
* to the beginning.
*/
public function flushBuffers()
{
$this->_offset = 0;
$this->_array = array();
$this->_arraySize = 0;
foreach ($this->_mirrors as $stream) {
$stream->flushBuffers();
}
}
}

View file

@ -1,229 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Allows reading and writing of bytes to and from a file.
*
* @author Chris Corbyn
*/
class Swift_ByteStream_FileByteStream extends Swift_ByteStream_AbstractFilterableInputStream implements Swift_FileStream
{
/** The internal pointer offset */
private $_offset = 0;
/** The path to the file */
private $_path;
/** The mode this file is opened in for writing */
private $_mode;
/** A lazy-loaded resource handle for reading the file */
private $_reader;
/** A lazy-loaded resource handle for writing the file */
private $_writer;
/** If magic_quotes_runtime is on, this will be true */
private $_quotes = false;
/** If stream is seekable true/false, or null if not known */
private $_seekable = null;
/**
* Create a new FileByteStream for $path.
*
* @param string $path
* @param bool $writable if true
*/
public function __construct($path, $writable = false)
{
if (empty($path)) {
throw new Swift_IoException('The path cannot be empty');
}
$this->_path = $path;
$this->_mode = $writable ? 'w+b' : 'rb';
if (function_exists('get_magic_quotes_runtime') && @get_magic_quotes_runtime() == 1) {
$this->_quotes = true;
}
}
/**
* Get the complete path to the file.
*
* @return string
*/
public function getPath()
{
return $this->_path;
}
/**
* Reads $length bytes from the stream into a string and moves the pointer
* through the stream by $length.
*
* If less bytes exist than are requested the
* remaining bytes are given instead. If no bytes are remaining at all, boolean
* false is returned.
*
* @param int $length
*
* @return string|bool
*
* @throws Swift_IoException
*/
public function read($length)
{
$fp = $this->_getReadHandle();
if (!feof($fp)) {
if ($this->_quotes) {
ini_set('magic_quotes_runtime', 0);
}
$bytes = fread($fp, $length);
if ($this->_quotes) {
ini_set('magic_quotes_runtime', 1);
}
$this->_offset = ftell($fp);
// If we read one byte after reaching the end of the file
// feof() will return false and an empty string is returned
if ($bytes === '' && feof($fp)) {
$this->_resetReadHandle();
return false;
}
return $bytes;
}
$this->_resetReadHandle();
return false;
}
/**
* Move the internal read pointer to $byteOffset in the stream.
*
* @param int $byteOffset
*
* @return bool
*/
public function setReadPointer($byteOffset)
{
if (isset($this->_reader)) {
$this->_seekReadStreamToPosition($byteOffset);
}
$this->_offset = $byteOffset;
}
/** Just write the bytes to the file */
protected function _commit($bytes)
{
fwrite($this->_getWriteHandle(), $bytes);
$this->_resetReadHandle();
}
/** Not used */
protected function _flush()
{
}
/** Get the resource for reading */
private function _getReadHandle()
{
if (!isset($this->_reader)) {
if (!$this->_reader = fopen($this->_path, 'rb')) {
throw new Swift_IoException(
'Unable to open file for reading [' . $this->_path . ']'
);
}
if ($this->_offset <> 0) {
$this->_getReadStreamSeekableStatus();
$this->_seekReadStreamToPosition($this->_offset);
}
}
return $this->_reader;
}
/** Get the resource for writing */
private function _getWriteHandle()
{
if (!isset($this->_writer)) {
if (!$this->_writer = fopen($this->_path, $this->_mode)) {
throw new Swift_IoException(
'Unable to open file for writing [' . $this->_path . ']'
);
}
}
return $this->_writer;
}
/** Force a reload of the resource for reading */
private function _resetReadHandle()
{
if (isset($this->_reader)) {
fclose($this->_reader);
$this->_reader = null;
}
}
/** Check if ReadOnly Stream is seekable */
private function _getReadStreamSeekableStatus()
{
$metas = stream_get_meta_data($this->_reader);
$this->_seekable = $metas['seekable'];
}
/** Streams in a readOnly stream ensuring copy if needed */
private function _seekReadStreamToPosition($offset)
{
if ($this->_seekable===null) {
$this->_getReadStreamSeekableStatus();
}
if ($this->_seekable === false) {
$currentPos = ftell($this->_reader);
if ($currentPos<$offset) {
$toDiscard = $offset-$currentPos;
fread($this->_reader, $toDiscard);
return;
}
$this->_copyReadStream();
}
fseek($this->_reader, $offset, SEEK_SET);
}
/** Copy a readOnly Stream to ensure seekability */
private function _copyReadStream()
{
if ($tmpFile = fopen('php://temp/maxmemory:4096', 'w+b')) {
/* We have opened a php:// Stream Should work without problem */
} elseif (function_exists('sys_get_temp_dir') && is_writable(sys_get_temp_dir()) && ($tmpFile = tmpfile())) {
/* We have opened a tmpfile */
} else {
throw new Swift_IoException('Unable to copy the file to make it seekable, sys_temp_dir is not writable, php://memory not available');
}
$currentPos = ftell($this->_reader);
fclose($this->_reader);
$source = fopen($this->_path, 'rb');
if (!$source) {
throw new Swift_IoException('Unable to open file for copying [' . $this->_path . ']');
}
fseek($tmpFile, 0, SEEK_SET);
while (!feof($source)) {
fwrite($tmpFile, fread($source, 4096));
}
fseek($tmpFile, $currentPos, SEEK_SET);
fclose($source);
$this->_reader = $tmpFile;
}
}

View file

@ -1,42 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* @author Romain-Geissler
*/
class Swift_ByteStream_TemporaryFileByteStream extends Swift_ByteStream_FileByteStream
{
public function __construct()
{
$filePath = tempnam(sys_get_temp_dir(), 'FileByteStream');
if ($filePath === false) {
throw new Swift_IoException('Failed to retrieve temporary file name.');
}
parent::__construct($filePath, true);
}
public function getContent()
{
if (($content = file_get_contents($this->getPath())) === false) {
throw new Swift_IoException('Failed to get temporary file content.');
}
return $content;
}
public function __destruct()
{
if (file_exists($this->getPath())) {
@unlink($this->getPath());
}
}
}

View file

@ -1,67 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Analyzes characters for a specific character set.
*
* @author Chris Corbyn
* @author Xavier De Cock <xdecock@gmail.com>
*/
interface Swift_CharacterReader
{
const MAP_TYPE_INVALID = 0x01;
const MAP_TYPE_FIXED_LEN = 0x02;
const MAP_TYPE_POSITIONS = 0x03;
/**
* Returns the complete character map
*
* @param string $string
* @param int $startOffset
* @param array $currentMap
* @param mixed $ignoredChars
*
* @return int
*/
public function getCharPositions($string, $startOffset, &$currentMap, &$ignoredChars);
/**
* Returns the mapType, see constants.
*
* @return int
*/
public function getMapType();
/**
* Returns an integer which specifies how many more bytes to read.
*
* A positive integer indicates the number of more bytes to fetch before invoking
* this method again.
*
* A value of zero means this is already a valid character.
* A value of -1 means this cannot possibly be a valid character.
*
* @param integer[] $bytes
* @param int $size
*
* @return int
*/
public function validateByteSequence($bytes, $size);
/**
* Returns the number of bytes which should be read to start each character.
*
* For fixed width character sets this should be the number of octets-per-character.
* For multibyte character sets this will probably be 1.
*
* @return int
*/
public function getInitialByteSize();
}

View file

@ -1,97 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Provides fixed-width byte sizes for reading fixed-width character sets.
*
* @author Chris Corbyn
* @author Xavier De Cock <xdecock@gmail.com>
*/
class Swift_CharacterReader_GenericFixedWidthReader implements Swift_CharacterReader
{
/**
* The number of bytes in a single character.
*
* @var int
*/
private $_width;
/**
* Creates a new GenericFixedWidthReader using $width bytes per character.
*
* @param int $width
*/
public function __construct($width)
{
$this->_width = $width;
}
/**
* Returns the complete character map.
*
* @param string $string
* @param int $startOffset
* @param array $currentMap
* @param mixed $ignoredChars
*
* @return int
*/
public function getCharPositions($string, $startOffset, &$currentMap, &$ignoredChars)
{
$strlen = strlen($string);
// % and / are CPU intensive, so, maybe find a better way
$ignored = $strlen % $this->_width;
$ignoredChars = substr($string, - $ignored);
$currentMap = $this->_width;
return ($strlen - $ignored) / $this->_width;
}
/**
* Returns the mapType.
*
* @return int
*/
public function getMapType()
{
return self::MAP_TYPE_FIXED_LEN;
}
/**
* Returns an integer which specifies how many more bytes to read.
*
* A positive integer indicates the number of more bytes to fetch before invoking
* this method again.
*
* A value of zero means this is already a valid character.
* A value of -1 means this cannot possibly be a valid character.
*
* @param string $bytes
* @param int $size
*
* @return int
*/
public function validateByteSequence($bytes, $size)
{
$needed = $this->_width - $size;
return ($needed > -1) ? $needed : -1;
}
/**
* Returns the number of bytes which should be read to start each character.
*
* @return int
*/
public function getInitialByteSize()
{
return $this->_width;
}
}

View file

@ -1,83 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Analyzes US-ASCII characters.
*
* @author Chris Corbyn
*/
class Swift_CharacterReader_UsAsciiReader implements Swift_CharacterReader
{
/**
* Returns the complete character map.
*
* @param string $string
* @param int $startOffset
* @param array $currentMap
* @param string $ignoredChars
*
* @return int
*/
public function getCharPositions($string, $startOffset, &$currentMap, &$ignoredChars)
{
$strlen=strlen($string);
$ignoredChars='';
for ($i = 0; $i < $strlen; ++$i) {
if ($string[$i]>"\x07F") { // Invalid char
$currentMap[$i+$startOffset]=$string[$i];
}
}
return $strlen;
}
/**
* Returns mapType
*
* @return int mapType
*/
public function getMapType()
{
return self::MAP_TYPE_INVALID;
}
/**
* Returns an integer which specifies how many more bytes to read.
*
* A positive integer indicates the number of more bytes to fetch before invoking
* this method again.
* A value of zero means this is already a valid character.
* A value of -1 means this cannot possibly be a valid character.
*
* @param string $bytes
* @param int $size
*
* @return int
*/
public function validateByteSequence($bytes, $size)
{
$byte = reset($bytes);
if (1 == count($bytes) && $byte >= 0x00 && $byte <= 0x7F) {
return 0;
} else {
return -1;
}
}
/**
* Returns the number of bytes which should be read to start each character.
*
* @return int
*/
public function getInitialByteSize()
{
return 1;
}
}

View file

@ -1,179 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Analyzes UTF-8 characters.
*
* @author Chris Corbyn
* @author Xavier De Cock <xdecock@gmail.com>
*/
class Swift_CharacterReader_Utf8Reader implements Swift_CharacterReader
{
/** Pre-computed for optimization */
private static $length_map=array(
// N=0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x0N
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x1N
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x2N
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x3N
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x4N
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x5N
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x6N
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x7N
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0x8N
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0x9N
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0xAN
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0xBN
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // 0xCN
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // 0xDN
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, // 0xEN
4,4,4,4,4,4,4,4,5,5,5,5,6,6,0,0 // 0xFN
);
private static $s_length_map=array(
"\x00"=>1, "\x01"=>1, "\x02"=>1, "\x03"=>1, "\x04"=>1, "\x05"=>1, "\x06"=>1, "\x07"=>1,
"\x08"=>1, "\x09"=>1, "\x0a"=>1, "\x0b"=>1, "\x0c"=>1, "\x0d"=>1, "\x0e"=>1, "\x0f"=>1,
"\x10"=>1, "\x11"=>1, "\x12"=>1, "\x13"=>1, "\x14"=>1, "\x15"=>1, "\x16"=>1, "\x17"=>1,
"\x18"=>1, "\x19"=>1, "\x1a"=>1, "\x1b"=>1, "\x1c"=>1, "\x1d"=>1, "\x1e"=>1, "\x1f"=>1,
"\x20"=>1, "\x21"=>1, "\x22"=>1, "\x23"=>1, "\x24"=>1, "\x25"=>1, "\x26"=>1, "\x27"=>1,
"\x28"=>1, "\x29"=>1, "\x2a"=>1, "\x2b"=>1, "\x2c"=>1, "\x2d"=>1, "\x2e"=>1, "\x2f"=>1,
"\x30"=>1, "\x31"=>1, "\x32"=>1, "\x33"=>1, "\x34"=>1, "\x35"=>1, "\x36"=>1, "\x37"=>1,
"\x38"=>1, "\x39"=>1, "\x3a"=>1, "\x3b"=>1, "\x3c"=>1, "\x3d"=>1, "\x3e"=>1, "\x3f"=>1,
"\x40"=>1, "\x41"=>1, "\x42"=>1, "\x43"=>1, "\x44"=>1, "\x45"=>1, "\x46"=>1, "\x47"=>1,
"\x48"=>1, "\x49"=>1, "\x4a"=>1, "\x4b"=>1, "\x4c"=>1, "\x4d"=>1, "\x4e"=>1, "\x4f"=>1,
"\x50"=>1, "\x51"=>1, "\x52"=>1, "\x53"=>1, "\x54"=>1, "\x55"=>1, "\x56"=>1, "\x57"=>1,
"\x58"=>1, "\x59"=>1, "\x5a"=>1, "\x5b"=>1, "\x5c"=>1, "\x5d"=>1, "\x5e"=>1, "\x5f"=>1,
"\x60"=>1, "\x61"=>1, "\x62"=>1, "\x63"=>1, "\x64"=>1, "\x65"=>1, "\x66"=>1, "\x67"=>1,
"\x68"=>1, "\x69"=>1, "\x6a"=>1, "\x6b"=>1, "\x6c"=>1, "\x6d"=>1, "\x6e"=>1, "\x6f"=>1,
"\x70"=>1, "\x71"=>1, "\x72"=>1, "\x73"=>1, "\x74"=>1, "\x75"=>1, "\x76"=>1, "\x77"=>1,
"\x78"=>1, "\x79"=>1, "\x7a"=>1, "\x7b"=>1, "\x7c"=>1, "\x7d"=>1, "\x7e"=>1, "\x7f"=>1,
"\x80"=>0, "\x81"=>0, "\x82"=>0, "\x83"=>0, "\x84"=>0, "\x85"=>0, "\x86"=>0, "\x87"=>0,
"\x88"=>0, "\x89"=>0, "\x8a"=>0, "\x8b"=>0, "\x8c"=>0, "\x8d"=>0, "\x8e"=>0, "\x8f"=>0,
"\x90"=>0, "\x91"=>0, "\x92"=>0, "\x93"=>0, "\x94"=>0, "\x95"=>0, "\x96"=>0, "\x97"=>0,
"\x98"=>0, "\x99"=>0, "\x9a"=>0, "\x9b"=>0, "\x9c"=>0, "\x9d"=>0, "\x9e"=>0, "\x9f"=>0,
"\xa0"=>0, "\xa1"=>0, "\xa2"=>0, "\xa3"=>0, "\xa4"=>0, "\xa5"=>0, "\xa6"=>0, "\xa7"=>0,
"\xa8"=>0, "\xa9"=>0, "\xaa"=>0, "\xab"=>0, "\xac"=>0, "\xad"=>0, "\xae"=>0, "\xaf"=>0,
"\xb0"=>0, "\xb1"=>0, "\xb2"=>0, "\xb3"=>0, "\xb4"=>0, "\xb5"=>0, "\xb6"=>0, "\xb7"=>0,
"\xb8"=>0, "\xb9"=>0, "\xba"=>0, "\xbb"=>0, "\xbc"=>0, "\xbd"=>0, "\xbe"=>0, "\xbf"=>0,
"\xc0"=>2, "\xc1"=>2, "\xc2"=>2, "\xc3"=>2, "\xc4"=>2, "\xc5"=>2, "\xc6"=>2, "\xc7"=>2,
"\xc8"=>2, "\xc9"=>2, "\xca"=>2, "\xcb"=>2, "\xcc"=>2, "\xcd"=>2, "\xce"=>2, "\xcf"=>2,
"\xd0"=>2, "\xd1"=>2, "\xd2"=>2, "\xd3"=>2, "\xd4"=>2, "\xd5"=>2, "\xd6"=>2, "\xd7"=>2,
"\xd8"=>2, "\xd9"=>2, "\xda"=>2, "\xdb"=>2, "\xdc"=>2, "\xdd"=>2, "\xde"=>2, "\xdf"=>2,
"\xe0"=>3, "\xe1"=>3, "\xe2"=>3, "\xe3"=>3, "\xe4"=>3, "\xe5"=>3, "\xe6"=>3, "\xe7"=>3,
"\xe8"=>3, "\xe9"=>3, "\xea"=>3, "\xeb"=>3, "\xec"=>3, "\xed"=>3, "\xee"=>3, "\xef"=>3,
"\xf0"=>4, "\xf1"=>4, "\xf2"=>4, "\xf3"=>4, "\xf4"=>4, "\xf5"=>4, "\xf6"=>4, "\xf7"=>4,
"\xf8"=>5, "\xf9"=>5, "\xfa"=>5, "\xfb"=>5, "\xfc"=>6, "\xfd"=>6, "\xfe"=>0, "\xff"=>0,
);
/**
* Returns the complete character map.
*
* @param string $string
* @param int $startOffset
* @param array $currentMap
* @param mixed $ignoredChars
*
* @return int
*/
public function getCharPositions($string, $startOffset, &$currentMap, &$ignoredChars)
{
if (!isset($currentMap['i']) || ! isset($currentMap['p'])) {
$currentMap['p'] = $currentMap['i'] = array();
}
$strlen=strlen($string);
$charPos=count($currentMap['p']);
$foundChars=0;
$invalid=false;
for ($i = 0; $i < $strlen; ++$i) {
$char = $string[$i];
$size = self::$s_length_map[$char];
if ($size == 0) {
/* char is invalid, we must wait for a resync */
$invalid = true;
continue;
} else {
if ($invalid == true) {
/* We mark the chars as invalid and start a new char */
$currentMap['p'][$charPos + $foundChars] = $startOffset + $i;
$currentMap['i'][$charPos + $foundChars] = true;
++$foundChars;
$invalid = false;
}
if (($i + $size) > $strlen) {
$ignoredChars = substr($string, $i);
break;
}
for ($j = 1; $j < $size; ++$j) {
$char = $string[$i + $j];
if ($char > "\x7F" && $char < "\xC0") {
// Valid - continue parsing
} else {
/* char is invalid, we must wait for a resync */
$invalid = true;
continue 2;
}
}
/* Ok we got a complete char here */
$currentMap['p'][$charPos + $foundChars] = $startOffset + $i + $size;
$i += $j - 1;
++$foundChars;
}
}
return $foundChars;
}
/**
* Returns mapType.
*
* @return int mapType
*/
public function getMapType()
{
return self::MAP_TYPE_POSITIONS;
}
/**
* Returns an integer which specifies how many more bytes to read.
*
* A positive integer indicates the number of more bytes to fetch before invoking
* this method again.
* A value of zero means this is already a valid character.
* A value of -1 means this cannot possibly be a valid character.
*
* @param string $bytes
* @param int $size
*
* @return int
*/
public function validateByteSequence($bytes, $size)
{
if ($size<1) {
return -1;
}
$needed = self::$length_map[$bytes[0]] - $size;
return ($needed > -1)
? $needed
: -1
;
}
/**
* Returns the number of bytes which should be read to start each character.
*
* @return int
*/
public function getInitialByteSize()
{
return 1;
}
}

View file

@ -1,26 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* A factory for creating CharacterReaders.
*
* @author Chris Corbyn
*/
interface Swift_CharacterReaderFactory
{
/**
* Returns a CharacterReader suitable for the charset applied.
*
* @param string $charset
*
* @return Swift_CharacterReader
*/
public function getReaderFor($charset);
}

View file

@ -1,124 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Standard factory for creating CharacterReaders.
*
* @author Chris Corbyn
*/
class Swift_CharacterReaderFactory_SimpleCharacterReaderFactory implements Swift_CharacterReaderFactory
{
/**
* A map of charset patterns to their implementation classes.
*
* @var array
*/
private static $_map = array();
/**
* Factories which have already been loaded.
*
* @var Swift_CharacterReaderFactory[]
*/
private static $_loaded = array();
/**
* Creates a new CharacterReaderFactory.
*/
public function __construct()
{
$this->init();
}
public function __wakeup()
{
$this->init();
}
public function init()
{
if (count(self::$_map) > 0) {
return;
}
$prefix = 'Swift_CharacterReader_';
$singleByte = array(
'class' => $prefix . 'GenericFixedWidthReader',
'constructor' => array(1)
);
$doubleByte = array(
'class' => $prefix . 'GenericFixedWidthReader',
'constructor' => array(2)
);
$fourBytes = array(
'class' => $prefix . 'GenericFixedWidthReader',
'constructor' => array(4)
);
// Utf-8
self::$_map['utf-?8'] = array(
'class' => $prefix . 'Utf8Reader',
'constructor' => array()
);
//7-8 bit charsets
self::$_map['(us-)?ascii'] = $singleByte;
self::$_map['(iso|iec)-?8859-?[0-9]+'] = $singleByte;
self::$_map['windows-?125[0-9]'] = $singleByte;
self::$_map['cp-?[0-9]+'] = $singleByte;
self::$_map['ansi'] = $singleByte;
self::$_map['macintosh'] = $singleByte;
self::$_map['koi-?7'] = $singleByte;
self::$_map['koi-?8-?.+'] = $singleByte;
self::$_map['mik'] = $singleByte;
self::$_map['(cork|t1)'] = $singleByte;
self::$_map['v?iscii'] = $singleByte;
//16 bits
self::$_map['(ucs-?2|utf-?16)'] = $doubleByte;
//32 bits
self::$_map['(ucs-?4|utf-?32)'] = $fourBytes;
// Fallback
self::$_map['.*'] = $singleByte;
}
/**
* Returns a CharacterReader suitable for the charset applied.
*
* @param string $charset
*
* @return Swift_CharacterReader
*/
public function getReaderFor($charset)
{
$charset = trim(strtolower($charset));
foreach (self::$_map as $pattern => $spec) {
$re = '/^' . $pattern . '$/D';
if (preg_match($re, $charset)) {
if (!array_key_exists($pattern, self::$_loaded)) {
$reflector = new ReflectionClass($spec['class']);
if ($reflector->getConstructor()) {
$reader = $reflector->newInstanceArgs($spec['constructor']);
} else {
$reader = $reflector->newInstance();
}
self::$_loaded[$pattern] = $reader;
}
return self::$_loaded[$pattern];
}
}
}
}

View file

@ -1,89 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* An abstract means of reading and writing data in terms of characters as opposed
* to bytes.
*
* Classes implementing this interface may use a subsystem which requires less
* memory than working with large strings of data.
*
* @author Chris Corbyn
*/
interface Swift_CharacterStream
{
/**
* Set the character set used in this CharacterStream.
*
* @param string $charset
*/
public function setCharacterSet($charset);
/**
* Set the CharacterReaderFactory for multi charset support.
*
* @param Swift_CharacterReaderFactory $factory
*/
public function setCharacterReaderFactory(Swift_CharacterReaderFactory $factory);
/**
* Overwrite this character stream using the byte sequence in the byte stream.
*
* @param Swift_OutputByteStream $os output stream to read from
*/
public function importByteStream(Swift_OutputByteStream $os);
/**
* Import a string a bytes into this CharacterStream, overwriting any existing
* data in the stream.
*
* @param string $string
*/
public function importString($string);
/**
* Read $length characters from the stream and move the internal pointer
* $length further into the stream.
*
* @param int $length
*
* @return string
*/
public function read($length);
/**
* Read $length characters from the stream and return a 1-dimensional array
* containing there octet values.
*
* @param int $length
*
* @return int[]
*/
public function readBytes($length);
/**
* Write $chars to the end of the stream.
*
* @param string $chars
*/
public function write($chars);
/**
* Move the internal pointer to $charOffset in the stream.
*
* @param int $charOffset
*/
public function setPointer($charOffset);
/**
* Empty the stream and reset the internal pointer.
*/
public function flushContents();
}

View file

@ -1,294 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* A CharacterStream implementation which stores characters in an internal array.
*
* @author Chris Corbyn
*/
class Swift_CharacterStream_ArrayCharacterStream implements Swift_CharacterStream
{
/** A map of byte values and their respective characters */
private static $_charMap;
/** A map of characters and their derivative byte values */
private static $_byteMap;
/** The char reader (lazy-loaded) for the current charset */
private $_charReader;
/** A factory for creating CharacterReader instances */
private $_charReaderFactory;
/** The character set this stream is using */
private $_charset;
/** Array of characters */
private $_array = array();
/** Size of the array of character */
private $_array_size = array();
/** The current character offset in the stream */
private $_offset = 0;
/**
* Create a new CharacterStream with the given $chars, if set.
*
* @param Swift_CharacterReaderFactory $factory for loading validators
* @param string $charset used in the stream
*/
public function __construct(Swift_CharacterReaderFactory $factory, $charset)
{
self::_initializeMaps();
$this->setCharacterReaderFactory($factory);
$this->setCharacterSet($charset);
}
/**
* Set the character set used in this CharacterStream.
*
* @param string $charset
*/
public function setCharacterSet($charset)
{
$this->_charset = $charset;
$this->_charReader = null;
}
/**
* Set the CharacterReaderFactory for multi charset support.
*
* @param Swift_CharacterReaderFactory $factory
*/
public function setCharacterReaderFactory(Swift_CharacterReaderFactory $factory)
{
$this->_charReaderFactory = $factory;
}
/**
* Overwrite this character stream using the byte sequence in the byte stream.
*
* @param Swift_OutputByteStream $os output stream to read from
*/
public function importByteStream(Swift_OutputByteStream $os)
{
if (!isset($this->_charReader)) {
$this->_charReader = $this->_charReaderFactory
->getReaderFor($this->_charset);
}
$startLength = $this->_charReader->getInitialByteSize();
while (false !== $bytes = $os->read($startLength)) {
$c = array();
for ($i = 0, $len = strlen($bytes); $i < $len; ++$i) {
$c[] = self::$_byteMap[$bytes[$i]];
}
$size = count($c);
$need = $this->_charReader
->validateByteSequence($c, $size);
if ($need > 0 &&
false !== $bytes = $os->read($need))
{
for ($i = 0, $len = strlen($bytes); $i < $len; ++$i) {
$c[] = self::$_byteMap[$bytes[$i]];
}
}
$this->_array[] = $c;
++$this->_array_size;
}
}
/**
* Import a string a bytes into this CharacterStream, overwriting any existing
* data in the stream.
*
* @param string $string
*/
public function importString($string)
{
$this->flushContents();
$this->write($string);
}
/**
* Read $length characters from the stream and move the internal pointer
* $length further into the stream.
*
* @param int $length
*
* @return string
*/
public function read($length)
{
if ($this->_offset == $this->_array_size) {
return false;
}
// Don't use array slice
$arrays = array();
$end = $length + $this->_offset;
for ($i = $this->_offset; $i < $end; ++$i) {
if (!isset($this->_array[$i])) {
break;
}
$arrays[] = $this->_array[$i];
}
$this->_offset += $i - $this->_offset; // Limit function calls
$chars = false;
foreach ($arrays as $array) {
$chars .= implode('', array_map('chr', $array));
}
return $chars;
}
/**
* Read $length characters from the stream and return a 1-dimensional array
* containing there octet values.
*
* @param int $length
*
* @return integer[]
*/
public function readBytes($length)
{
if ($this->_offset == $this->_array_size) {
return false;
}
$arrays = array();
$end = $length + $this->_offset;
for ($i = $this->_offset; $i < $end; ++$i) {
if (!isset($this->_array[$i])) {
break;
}
$arrays[] = $this->_array[$i];
}
$this->_offset += ($i - $this->_offset); // Limit function calls
return call_user_func_array('array_merge', $arrays);
}
/**
* Write $chars to the end of the stream.
*
* @param string $chars
*/
public function write($chars)
{
if (!isset($this->_charReader)) {
$this->_charReader = $this->_charReaderFactory->getReaderFor(
$this->_charset);
}
$startLength = $this->_charReader->getInitialByteSize();
$fp = fopen('php://memory', 'w+b');
fwrite($fp, $chars);
unset($chars);
fseek($fp, 0, SEEK_SET);
$buffer = array(0);
$buf_pos = 1;
$buf_len = 1;
$has_datas = true;
do {
$bytes = array();
// Buffer Filing
if ($buf_len - $buf_pos < $startLength) {
$buf = array_splice($buffer, $buf_pos);
$new = $this->_reloadBuffer($fp, 100);
if ($new) {
$buffer = array_merge($buf, $new);
$buf_len = count($buffer);
$buf_pos = 0;
} else {
$has_datas = false;
}
}
if ($buf_len - $buf_pos > 0) {
$size = 0;
for ($i = 0; $i < $startLength && isset($buffer[$buf_pos]); ++$i) {
++$size;
$bytes[] = $buffer[$buf_pos++];
}
$need = $this->_charReader->validateByteSequence(
$bytes, $size);
if ($need > 0) {
if ($buf_len - $buf_pos < $need) {
$new = $this->_reloadBuffer($fp, $need);
if ($new) {
$buffer = array_merge($buffer, $new);
$buf_len = count($buffer);
}
}
for ($i = 0; $i < $need && isset($buffer[$buf_pos]); ++$i) {
$bytes[] = $buffer[$buf_pos++];
}
}
$this->_array[] = $bytes;
++$this->_array_size;
}
} while ($has_datas);
fclose($fp);
}
/**
* Move the internal pointer to $charOffset in the stream.
*
* @param int $charOffset
*/
public function setPointer($charOffset)
{
if ($charOffset > $this->_array_size) {
$charOffset = $this->_array_size;
} elseif ($charOffset < 0) {
$charOffset = 0;
}
$this->_offset = $charOffset;
}
/**
* Empty the stream and reset the internal pointer.
*/
public function flushContents()
{
$this->_offset = 0;
$this->_array = array();
$this->_array_size = 0;
}
private function _reloadBuffer($fp, $len)
{
if (!feof($fp) && ($bytes = fread($fp, $len)) !== false) {
$buf = array();
for ($i = 0, $len = strlen($bytes); $i < $len; ++$i) {
$buf[] = self::$_byteMap[$bytes[$i]];
}
return $buf;
}
return false;
}
private static function _initializeMaps()
{
if (!isset(self::$_charMap)) {
self::$_charMap = array();
for ($byte = 0; $byte < 256; ++$byte) {
self::$_charMap[$byte] = chr($byte);
}
self::$_byteMap = array_flip(self::$_charMap);
}
}
}

View file

@ -1,275 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* A CharacterStream implementation which stores characters in an internal array.
*
* @author Xavier De Cock <xdecock@gmail.com>
*/
class Swift_CharacterStream_NgCharacterStream implements Swift_CharacterStream
{
/**
* The char reader (lazy-loaded) for the current charset.
*
* @var Swift_CharacterReader
*/
private $_charReader;
/**
* A factory for creating CharacterReader instances.
*
* @var Swift_CharacterReaderFactory
*/
private $_charReaderFactory;
/**
* The character set this stream is using.
*
* @var string
*/
private $_charset;
/**
* The data's stored as-is.
*
* @var string
*/
private $_datas = '';
/**
* Number of bytes in the stream
*
* @var int
*/
private $_datasSize = 0;
/**
* Map.
*
* @var mixed
*/
private $_map;
/**
* Map Type.
*
* @var int
*/
private $_mapType = 0;
/**
* Number of characters in the stream.
*
* @var int
*/
private $_charCount = 0;
/**
* Position in the stream.
*
* @var int
*/
private $_currentPos = 0;
/**
* Constructor.
*
* @param Swift_CharacterReaderFactory $factory
* @param string $charset
*/
public function __construct(Swift_CharacterReaderFactory $factory, $charset)
{
$this->setCharacterReaderFactory($factory);
$this->setCharacterSet($charset);
}
/* -- Changing parameters of the stream -- */
/**
* Set the character set used in this CharacterStream.
*
* @param string $charset
*/
public function setCharacterSet($charset)
{
$this->_charset = $charset;
$this->_charReader = null;
$this->_mapType = 0;
}
/**
* Set the CharacterReaderFactory for multi charset support.
*
* @param Swift_CharacterReaderFactory $factory
*/
public function setCharacterReaderFactory(Swift_CharacterReaderFactory $factory)
{
$this->_charReaderFactory = $factory;
}
/**
* @see Swift_CharacterStream::flushContents()
*/
public function flushContents()
{
$this->_datas = null;
$this->_map = null;
$this->_charCount = 0;
$this->_currentPos = 0;
$this->_datasSize = 0;
}
/**
* @see Swift_CharacterStream::importByteStream()
*
* @param Swift_OutputByteStream $os
*/
public function importByteStream(Swift_OutputByteStream $os)
{
$this->flushContents();
$blocks=512;
$os->setReadPointer(0);
while(false!==($read = $os->read($blocks)))
$this->write($read);
}
/**
* @see Swift_CharacterStream::importString()
*
* @param string $string
*/
public function importString($string)
{
$this->flushContents();
$this->write($string);
}
/**
* @see Swift_CharacterStream::read()
*
* @param int $length
*
* @return string
*/
public function read($length)
{
if ($this->_currentPos>=$this->_charCount) {
return false;
}
$ret=false;
$length = ($this->_currentPos+$length > $this->_charCount)
? $this->_charCount - $this->_currentPos
: $length;
switch ($this->_mapType) {
case Swift_CharacterReader::MAP_TYPE_FIXED_LEN:
$len = $length*$this->_map;
$ret = substr($this->_datas,
$this->_currentPos * $this->_map,
$len);
$this->_currentPos += $length;
break;
case Swift_CharacterReader::MAP_TYPE_INVALID:
$end = $this->_currentPos + $length;
$end = $end > $this->_charCount
?$this->_charCount
:$end;
$ret = '';
for (; $this->_currentPos < $length; ++$this->_currentPos) {
if (isset ($this->_map[$this->_currentPos])) {
$ret .= '?';
} else {
$ret .= $this->_datas[$this->_currentPos];
}
}
break;
case Swift_CharacterReader::MAP_TYPE_POSITIONS:
$end = $this->_currentPos + $length;
$end = $end > $this->_charCount
?$this->_charCount
:$end;
$ret = '';
$start = 0;
if ($this->_currentPos>0) {
$start = $this->_map['p'][$this->_currentPos-1];
}
$to = $start;
for (; $this->_currentPos < $end; ++$this->_currentPos) {
if (isset($this->_map['i'][$this->_currentPos])) {
$ret .= substr($this->_datas, $start, $to - $start).'?';
$start = $this->_map['p'][$this->_currentPos];
} else {
$to = $this->_map['p'][$this->_currentPos];
}
}
$ret .= substr($this->_datas, $start, $to - $start);
break;
}
return $ret;
}
/**
* @see Swift_CharacterStream::readBytes()
*
* @param int $length
*
* @return integer[]
*/
public function readBytes($length)
{
$read=$this->read($length);
if ($read!==false) {
$ret = array_map('ord', str_split($read, 1));
return $ret;
}
return false;
}
/**
* @see Swift_CharacterStream::setPointer()
*
* @param int $charOffset
*/
public function setPointer($charOffset)
{
if ($this->_charCount<$charOffset) {
$charOffset=$this->_charCount;
}
$this->_currentPos = $charOffset;
}
/**
* @see Swift_CharacterStream::write()
*
* @param string $chars
*/
public function write($chars)
{
if (!isset($this->_charReader)) {
$this->_charReader = $this->_charReaderFactory->getReaderFor(
$this->_charset);
$this->_map = array();
$this->_mapType = $this->_charReader->getMapType();
}
$ignored='';
$this->_datas .= $chars;
$this->_charCount += $this->_charReader->getCharPositions(substr($this->_datas, $this->_datasSize), $this->_datasSize, $this->_map, $ignored);
if ($ignored!==false) {
$this->_datasSize=strlen($this->_datas)-strlen($ignored);
} else {
$this->_datasSize=strlen($this->_datas);
}
}
}

View file

@ -1,63 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2009 Fabien Potencier <fabien.potencier@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Base class for Spools (implements time and message limits).
*
* @author Fabien Potencier
*/
abstract class Swift_ConfigurableSpool implements Swift_Spool
{
/** The maximum number of messages to send per flush */
private $_message_limit;
/** The time limit per flush */
private $_time_limit;
/**
* Sets the maximum number of messages to send per flush.
*
* @param int $limit
*/
public function setMessageLimit($limit)
{
$this->_message_limit = (int) $limit;
}
/**
* Gets the maximum number of messages to send per flush.
*
* @return int The limit
*/
public function getMessageLimit()
{
return $this->_message_limit;
}
/**
* Sets the time limit (in seconds) per flush.
*
* @param int $limit The limit
*/
public function setTimeLimit($limit)
{
$this->_time_limit = (int) $limit;
}
/**
* Gets the time limit (in seconds) per flush.
*
* @return int The limit
*/
public function getTimeLimit()
{
return $this->_time_limit;
}
}

View file

@ -1,370 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Dependency Injection container.
*
* @author Chris Corbyn
*/
class Swift_DependencyContainer
{
/** Constant for literal value types */
const TYPE_VALUE = 0x0001;
/** Constant for new instance types */
const TYPE_INSTANCE = 0x0010;
/** Constant for shared instance types */
const TYPE_SHARED = 0x0100;
/** Constant for aliases */
const TYPE_ALIAS = 0x1000;
/** Singleton instance */
private static $_instance = null;
/** The data container */
private $_store = array();
/** The current endpoint in the data container */
private $_endPoint;
/**
* Constructor should not be used.
*
* Use {@link getInstance()} instead.
*/
public function __construct() { }
/**
* Returns a singleton of the DependencyContainer.
*
* @return Swift_DependencyContainer
*/
public static function getInstance()
{
if (!isset(self::$_instance)) {
self::$_instance = new self();
}
return self::$_instance;
}
/**
* List the names of all items stored in the Container.
*
* @return array
*/
public function listItems()
{
return array_keys($this->_store);
}
/**
* Test if an item is registered in this container with the given name.
*
* @see register()
*
* @param string $itemName
*
* @return bool
*/
public function has($itemName)
{
return array_key_exists($itemName, $this->_store)
&& isset($this->_store[$itemName]['lookupType']);
}
/**
* Lookup the item with the given $itemName.
*
* @see register()
*
* @param string $itemName
*
* @return mixed
*
* @throws Swift_DependencyException If the dependency is not found
*/
public function lookup($itemName)
{
if (!$this->has($itemName)) {
throw new Swift_DependencyException(
'Cannot lookup dependency "' . $itemName . '" since it is not registered.'
);
}
switch ($this->_store[$itemName]['lookupType']) {
case self::TYPE_ALIAS:
return $this->_createAlias($itemName);
case self::TYPE_VALUE:
return $this->_getValue($itemName);
case self::TYPE_INSTANCE:
return $this->_createNewInstance($itemName);
case self::TYPE_SHARED:
return $this->_createSharedInstance($itemName);
}
}
/**
* Create an array of arguments passed to the constructor of $itemName.
*
* @param string $itemName
*
* @return array
*/
public function createDependenciesFor($itemName)
{
$args = array();
if (isset($this->_store[$itemName]['args'])) {
$args = $this->_resolveArgs($this->_store[$itemName]['args']);
}
return $args;
}
/**
* Register a new dependency with $itemName.
*
* This method returns the current DependencyContainer instance because it
* requires the use of the fluid interface to set the specific details for the
* dependency.
* @see asNewInstanceOf(), asSharedInstanceOf(), asValue()
*
* @param string $itemName
*
* @return Swift_DependencyContainer
*/
public function register($itemName)
{
$this->_store[$itemName] = array();
$this->_endPoint =& $this->_store[$itemName];
return $this;
}
/**
* Specify the previously registered item as a literal value.
*
* {@link register()} must be called before this will work.
*
* @param mixed $value
*
* @return Swift_DependencyContainer
*/
public function asValue($value)
{
$endPoint =& $this->_getEndPoint();
$endPoint['lookupType'] = self::TYPE_VALUE;
$endPoint['value'] = $value;
return $this;
}
/**
* Specify the previously registered item as an alias of another item.
*
* @param string $lookup
*
* @return Swift_DependencyContainer
*/
public function asAliasOf($lookup)
{
$endPoint =& $this->_getEndPoint();
$endPoint['lookupType'] = self::TYPE_ALIAS;
$endPoint['ref'] = $lookup;
return $this;
}
/**
* Specify the previously registered item as a new instance of $className.
*
* {@link register()} must be called before this will work.
* Any arguments can be set with {@link withDependencies()},
* {@link addConstructorValue()} or {@link addConstructorLookup()}.
*
* @see withDependencies(), addConstructorValue(), addConstructorLookup()
*
* @param string $className
*
* @return Swift_DependencyContainer
*/
public function asNewInstanceOf($className)
{
$endPoint =& $this->_getEndPoint();
$endPoint['lookupType'] = self::TYPE_INSTANCE;
$endPoint['className'] = $className;
return $this;
}
/**
* Specify the previously registered item as a shared instance of $className.
*
* {@link register()} must be called before this will work.
*
* @param string $className
*
* @return Swift_DependencyContainer
*/
public function asSharedInstanceOf($className)
{
$endPoint =& $this->_getEndPoint();
$endPoint['lookupType'] = self::TYPE_SHARED;
$endPoint['className'] = $className;
return $this;
}
/**
* Specify a list of injected dependencies for the previously registered item.
*
* This method takes an array of lookup names.
*
* @see addConstructorValue(), addConstructorLookup()
*
* @param array $lookups
*
* @return Swift_DependencyContainer
*/
public function withDependencies(array $lookups)
{
$endPoint =& $this->_getEndPoint();
$endPoint['args'] = array();
foreach ($lookups as $lookup) {
$this->addConstructorLookup($lookup);
}
return $this;
}
/**
* Specify a literal (non looked up) value for the constructor of the
* previously registered item.
*
* @see withDependencies(), addConstructorLookup()
*
* @param mixed $value
*
* @return Swift_DependencyContainer
*/
public function addConstructorValue($value)
{
$endPoint =& $this->_getEndPoint();
if (!isset($endPoint['args'])) {
$endPoint['args'] = array();
}
$endPoint['args'][] = array('type' => 'value', 'item' => $value);
return $this;
}
/**
* Specify a dependency lookup for the constructor of the previously
* registered item.
*
* @see withDependencies(), addConstructorValue()
*
* @param string $lookup
*
* @return Swift_DependencyContainer
*/
public function addConstructorLookup($lookup)
{
$endPoint =& $this->_getEndPoint();
if (!isset($this->_endPoint['args'])) {
$endPoint['args'] = array();
}
$endPoint['args'][] = array('type' => 'lookup', 'item' => $lookup);
return $this;
}
/** Get the literal value with $itemName */
private function _getValue($itemName)
{
return $this->_store[$itemName]['value'];
}
/** Resolve an alias to another item */
private function _createAlias($itemName)
{
return $this->lookup($this->_store[$itemName]['ref']);
}
/** Create a fresh instance of $itemName */
private function _createNewInstance($itemName)
{
$reflector = new ReflectionClass($this->_store[$itemName]['className']);
if ($reflector->getConstructor()) {
return $reflector->newInstanceArgs(
$this->createDependenciesFor($itemName)
);
} else {
return $reflector->newInstance();
}
}
/** Create and register a shared instance of $itemName */
private function _createSharedInstance($itemName)
{
if (!isset($this->_store[$itemName]['instance'])) {
$this->_store[$itemName]['instance'] = $this->_createNewInstance($itemName);
}
return $this->_store[$itemName]['instance'];
}
/** Get the current endpoint in the store */
private function &_getEndPoint()
{
if (!isset($this->_endPoint)) {
throw new BadMethodCallException(
'Component must first be registered by calling register()'
);
}
return $this->_endPoint;
}
/** Get an argument list with dependencies resolved */
private function _resolveArgs(array $args)
{
$resolved = array();
foreach ($args as $argDefinition) {
switch ($argDefinition['type']) {
case 'lookup':
$resolved[] = $this->_lookupRecursive($argDefinition['item']);
break;
case 'value':
$resolved[] = $argDefinition['item'];
break;
}
}
return $resolved;
}
/** Resolve a single dependency with an collections */
private function _lookupRecursive($item)
{
if (is_array($item)) {
$collection = array();
foreach ($item as $k => $v) {
$collection[$k] = $this->_lookupRecursive($v);
}
return $collection;
} else {
return $this->lookup($item);
}
}
}

View file

@ -1,27 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* DependencyException gets thrown when a requested dependency is missing.
*
* @author Chris Corbyn
*/
class Swift_DependencyException extends Swift_SwiftException
{
/**
* Create a new DependencyException with $message.
*
* @param string $message
*/
public function __construct($message)
{
parent::__construct($message);
}
}

View file

@ -1,69 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* An embedded file, in a multipart message.
*
* @author Chris Corbyn
*/
class Swift_EmbeddedFile extends Swift_Mime_EmbeddedFile
{
/**
* Create a new EmbeddedFile.
*
* Details may be optionally provided to the constructor.
*
* @param string|Swift_OutputByteStream $data
* @param string $filename
* @param string $contentType
*/
public function __construct($data = null, $filename = null, $contentType = null)
{
call_user_func_array(
array($this, 'Swift_Mime_EmbeddedFile::__construct'),
Swift_DependencyContainer::getInstance()
->createDependenciesFor('mime.embeddedfile')
);
$this->setBody($data);
$this->setFilename($filename);
if ($contentType) {
$this->setContentType($contentType);
}
}
/**
* Create a new EmbeddedFile.
*
* @param string|Swift_OutputByteStream $data
* @param string $filename
* @param string $contentType
*
* @return Swift_Mime_EmbeddedFile
*/
public static function newInstance($data = null, $filename = null, $contentType = null)
{
return new self($data, $filename, $contentType);
}
/**
* Create a new EmbeddedFile from a filesystem path.
*
* @param string $path
*
* @return Swift_Mime_EmbeddedFile
*/
public static function fromPath($path)
{
return self::newInstance()->setFile(
new Swift_ByteStream_FileByteStream($path)
);
}
}

View file

@ -1,27 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Interface for all Encoder schemes.
* @author Chris Corbyn
*/
interface Swift_Encoder extends Swift_Mime_CharsetObserver
{
/**
* Encode a given string to produce an encoded string.
*
* @param string $string
* @param int $firstLineOffset if first line needs to be shorter
* @param int $maxLineLength - 0 indicates the default length for this encoding
*
* @return string
*/
public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0);
}

View file

@ -1,58 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Handles Base 64 Encoding in Swift Mailer.
*
* @author Chris Corbyn
*/
class Swift_Encoder_Base64Encoder implements Swift_Encoder
{
/**
* Takes an unencoded string and produces a Base64 encoded string from it.
*
* Base64 encoded strings have a maximum line length of 76 characters.
* If the first line needs to be shorter, indicate the difference with
* $firstLineOffset.
*
* @param string $string to encode
* @param int $firstLineOffset
* @param int $maxLineLength optional, 0 indicates the default of 76 bytes
*
* @return string
*/
public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0)
{
if (0 >= $maxLineLength || 76 < $maxLineLength) {
$maxLineLength = 76;
}
$encodedString = base64_encode($string);
$firstLine = '';
if (0 != $firstLineOffset) {
$firstLine = substr(
$encodedString, 0, $maxLineLength - $firstLineOffset
) . "\r\n";
$encodedString = substr(
$encodedString, $maxLineLength - $firstLineOffset
);
}
return $firstLine . trim(chunk_split($encodedString, $maxLineLength, "\r\n"));
}
/**
* Does nothing.
*/
public function charsetChanged($charset)
{
}
}

View file

@ -1,282 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Handles Quoted Printable (QP) Encoding in Swift Mailer.
*
* Possibly the most accurate RFC 2045 QP implementation found in PHP.
*
* @author Chris Corbyn
*/
class Swift_Encoder_QpEncoder implements Swift_Encoder
{
/**
* The CharacterStream used for reading characters (as opposed to bytes).
*
* @var Swift_CharacterStream
*/
protected $_charStream;
/**
* A filter used if input should be canonicalized.
*
* @var Swift_StreamFilter
*/
protected $_filter;
/**
* Pre-computed QP for HUGE optimization.
*
* @var string[]
*/
protected static $_qpMap = array(
0 => '=00', 1 => '=01', 2 => '=02', 3 => '=03', 4 => '=04',
5 => '=05', 6 => '=06', 7 => '=07', 8 => '=08', 9 => '=09',
10 => '=0A', 11 => '=0B', 12 => '=0C', 13 => '=0D', 14 => '=0E',
15 => '=0F', 16 => '=10', 17 => '=11', 18 => '=12', 19 => '=13',
20 => '=14', 21 => '=15', 22 => '=16', 23 => '=17', 24 => '=18',
25 => '=19', 26 => '=1A', 27 => '=1B', 28 => '=1C', 29 => '=1D',
30 => '=1E', 31 => '=1F', 32 => '=20', 33 => '=21', 34 => '=22',
35 => '=23', 36 => '=24', 37 => '=25', 38 => '=26', 39 => '=27',
40 => '=28', 41 => '=29', 42 => '=2A', 43 => '=2B', 44 => '=2C',
45 => '=2D', 46 => '=2E', 47 => '=2F', 48 => '=30', 49 => '=31',
50 => '=32', 51 => '=33', 52 => '=34', 53 => '=35', 54 => '=36',
55 => '=37', 56 => '=38', 57 => '=39', 58 => '=3A', 59 => '=3B',
60 => '=3C', 61 => '=3D', 62 => '=3E', 63 => '=3F', 64 => '=40',
65 => '=41', 66 => '=42', 67 => '=43', 68 => '=44', 69 => '=45',
70 => '=46', 71 => '=47', 72 => '=48', 73 => '=49', 74 => '=4A',
75 => '=4B', 76 => '=4C', 77 => '=4D', 78 => '=4E', 79 => '=4F',
80 => '=50', 81 => '=51', 82 => '=52', 83 => '=53', 84 => '=54',
85 => '=55', 86 => '=56', 87 => '=57', 88 => '=58', 89 => '=59',
90 => '=5A', 91 => '=5B', 92 => '=5C', 93 => '=5D', 94 => '=5E',
95 => '=5F', 96 => '=60', 97 => '=61', 98 => '=62', 99 => '=63',
100 => '=64', 101 => '=65', 102 => '=66', 103 => '=67', 104 => '=68',
105 => '=69', 106 => '=6A', 107 => '=6B', 108 => '=6C', 109 => '=6D',
110 => '=6E', 111 => '=6F', 112 => '=70', 113 => '=71', 114 => '=72',
115 => '=73', 116 => '=74', 117 => '=75', 118 => '=76', 119 => '=77',
120 => '=78', 121 => '=79', 122 => '=7A', 123 => '=7B', 124 => '=7C',
125 => '=7D', 126 => '=7E', 127 => '=7F', 128 => '=80', 129 => '=81',
130 => '=82', 131 => '=83', 132 => '=84', 133 => '=85', 134 => '=86',
135 => '=87', 136 => '=88', 137 => '=89', 138 => '=8A', 139 => '=8B',
140 => '=8C', 141 => '=8D', 142 => '=8E', 143 => '=8F', 144 => '=90',
145 => '=91', 146 => '=92', 147 => '=93', 148 => '=94', 149 => '=95',
150 => '=96', 151 => '=97', 152 => '=98', 153 => '=99', 154 => '=9A',
155 => '=9B', 156 => '=9C', 157 => '=9D', 158 => '=9E', 159 => '=9F',
160 => '=A0', 161 => '=A1', 162 => '=A2', 163 => '=A3', 164 => '=A4',
165 => '=A5', 166 => '=A6', 167 => '=A7', 168 => '=A8', 169 => '=A9',
170 => '=AA', 171 => '=AB', 172 => '=AC', 173 => '=AD', 174 => '=AE',
175 => '=AF', 176 => '=B0', 177 => '=B1', 178 => '=B2', 179 => '=B3',
180 => '=B4', 181 => '=B5', 182 => '=B6', 183 => '=B7', 184 => '=B8',
185 => '=B9', 186 => '=BA', 187 => '=BB', 188 => '=BC', 189 => '=BD',
190 => '=BE', 191 => '=BF', 192 => '=C0', 193 => '=C1', 194 => '=C2',
195 => '=C3', 196 => '=C4', 197 => '=C5', 198 => '=C6', 199 => '=C7',
200 => '=C8', 201 => '=C9', 202 => '=CA', 203 => '=CB', 204 => '=CC',
205 => '=CD', 206 => '=CE', 207 => '=CF', 208 => '=D0', 209 => '=D1',
210 => '=D2', 211 => '=D3', 212 => '=D4', 213 => '=D5', 214 => '=D6',
215 => '=D7', 216 => '=D8', 217 => '=D9', 218 => '=DA', 219 => '=DB',
220 => '=DC', 221 => '=DD', 222 => '=DE', 223 => '=DF', 224 => '=E0',
225 => '=E1', 226 => '=E2', 227 => '=E3', 228 => '=E4', 229 => '=E5',
230 => '=E6', 231 => '=E7', 232 => '=E8', 233 => '=E9', 234 => '=EA',
235 => '=EB', 236 => '=EC', 237 => '=ED', 238 => '=EE', 239 => '=EF',
240 => '=F0', 241 => '=F1', 242 => '=F2', 243 => '=F3', 244 => '=F4',
245 => '=F5', 246 => '=F6', 247 => '=F7', 248 => '=F8', 249 => '=F9',
250 => '=FA', 251 => '=FB', 252 => '=FC', 253 => '=FD', 254 => '=FE',
255 => '=FF'
);
protected static $_safeMapShare = array();
/**
* A map of non-encoded ascii characters.
*
* @var string[]
*/
protected $_safeMap = array();
/**
* Creates a new QpEncoder for the given CharacterStream.
*
* @param Swift_CharacterStream $charStream to use for reading characters
* @param Swift_StreamFilter $filter if input should be canonicalized
*/
public function __construct(Swift_CharacterStream $charStream, Swift_StreamFilter $filter = null)
{
$this->_charStream = $charStream;
if (!isset(self::$_safeMapShare[$this->getSafeMapShareId()])) {
$this->initSafeMap();
self::$_safeMapShare[$this->getSafeMapShareId()] = $this->_safeMap;
} else {
$this->_safeMap = self::$_safeMapShare[$this->getSafeMapShareId()];
}
$this->_filter = $filter;
}
public function __sleep()
{
return array('_charStream', '_filter');
}
public function __wakeup()
{
if (!isset(self::$_safeMapShare[$this->getSafeMapShareId()])) {
$this->initSafeMap();
self::$_safeMapShare[$this->getSafeMapShareId()] = $this->_safeMap;
} else {
$this->_safeMap = self::$_safeMapShare[$this->getSafeMapShareId()];
}
}
protected function getSafeMapShareId()
{
return get_class($this);
}
protected function initSafeMap()
{
foreach (array_merge(
array(0x09, 0x20), range(0x21, 0x3C), range(0x3E, 0x7E)) as $byte)
{
$this->_safeMap[$byte] = chr($byte);
}
}
/**
* Takes an unencoded string and produces a QP encoded string from it.
*
* QP encoded strings have a maximum line length of 76 characters.
* If the first line needs to be shorter, indicate the difference with
* $firstLineOffset.
*
* @param string $string to encode
* @param int $firstLineOffset, optional
* @param int $maxLineLength, optional 0 indicates the default of 76 chars
*
* @return string
*/
public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0)
{
if ($maxLineLength > 76 || $maxLineLength <= 0) {
$maxLineLength = 76;
}
$thisLineLength = $maxLineLength - $firstLineOffset;
$lines = array();
$lNo = 0;
$lines[$lNo] = '';
$currentLine =& $lines[$lNo++];
$size=$lineLen=0;
$this->_charStream->flushContents();
$this->_charStream->importString($string);
// Fetching more than 4 chars at one is slower, as is fetching fewer bytes
// Conveniently 4 chars is the UTF-8 safe number since UTF-8 has up to 6
// bytes per char and (6 * 4 * 3 = 72 chars per line) * =NN is 3 bytes
while (false !== $bytes = $this->_nextSequence()) {
// If we're filtering the input
if (isset($this->_filter)) {
// If we can't filter because we need more bytes
while ($this->_filter->shouldBuffer($bytes)) {
// Then collect bytes into the buffer
if (false === $moreBytes = $this->_nextSequence(1)) {
break;
}
foreach ($moreBytes as $b) {
$bytes[] = $b;
}
}
// And filter them
$bytes = $this->_filter->filter($bytes);
}
$enc = $this->_encodeByteSequence($bytes, $size);
if ($currentLine && $lineLen+$size >= $thisLineLength) {
$lines[$lNo] = '';
$currentLine =& $lines[$lNo++];
$thisLineLength = $maxLineLength;
$lineLen=0;
}
$lineLen+=$size;
$currentLine .= $enc;
}
return $this->_standardize(implode("=\r\n", $lines));
}
/**
* Updates the charset used.
*
* @param string $charset
*/
public function charsetChanged($charset)
{
$this->_charStream->setCharacterSet($charset);
}
/**
* Encode the given byte array into a verbatim QP form.
*
* @param integer[] $bytes
* @param int $size
*
* @return string
*/
protected function _encodeByteSequence(array $bytes, &$size)
{
$ret = '';
$size=0;
foreach ($bytes as $b) {
if (isset($this->_safeMap[$b])) {
$ret .= $this->_safeMap[$b];
++$size;
} else {
$ret .= self::$_qpMap[$b];
$size+=3;
}
}
return $ret;
}
/**
* Get the next sequence of bytes to read from the char stream.
*
* @param int $size number of bytes to read
*
* @return integer[]
*/
protected function _nextSequence($size = 4)
{
return $this->_charStream->readBytes($size);
}
/**
* Make sure CRLF is correct and HT/SPACE are in valid places.
*
* @param string $string
*
* @return string
*/
protected function _standardize($string)
{
$string = str_replace(array("\t=0D=0A", " =0D=0A", "=0D=0A"),
array("=09\r\n", "=20\r\n", "\r\n"), $string
);
switch ($end = ord(substr($string, -1))) {
case 0x09:
case 0x20:
$string = substr_replace($string, self::$_qpMap[$end], -1);
}
return $string;
}
}

View file

@ -1,84 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Handles RFC 2231 specified Encoding in Swift Mailer.
*
* @author Chris Corbyn
*/
class Swift_Encoder_Rfc2231Encoder implements Swift_Encoder
{
/**
* A character stream to use when reading a string as characters instead of bytes.
*
* @var Swift_CharacterStream
*/
private $_charStream;
/**
* Creates a new Rfc2231Encoder using the given character stream instance.
*
* @param Swift_CharacterStream
*/
public function __construct(Swift_CharacterStream $charStream)
{
$this->_charStream = $charStream;
}
/**
* Takes an unencoded string and produces a string encoded according to
* RFC 2231 from it.
*
* @param string $string
* @param int $firstLineOffset
* @param int $maxLineLength optional, 0 indicates the default of 75 bytes
*
* @return string
*/
public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0)
{
$lines = array(); $lineCount = 0;
$lines[] = '';
$currentLine =& $lines[$lineCount++];
if (0 >= $maxLineLength) {
$maxLineLength = 75;
}
$this->_charStream->flushContents();
$this->_charStream->importString($string);
$thisLineLength = $maxLineLength - $firstLineOffset;
while (false !== $char = $this->_charStream->read(4)) {
$encodedChar = rawurlencode($char);
if (0 != strlen($currentLine)
&& strlen($currentLine . $encodedChar) > $thisLineLength)
{
$lines[] = '';
$currentLine =& $lines[$lineCount++];
$thisLineLength = $maxLineLength;
}
$currentLine .= $encodedChar;
}
return implode("\r\n", $lines);
}
/**
* Updates the charset used.
*
* @param string $charset
*/
public function charsetChanged($charset)
{
$this->_charStream->setCharacterSet($charset);
}
}

View file

@ -1,64 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Provides quick access to each encoding type.
*
* @author Chris Corbyn
*/
class Swift_Encoding
{
/**
* Get the Encoder that provides 7-bit encoding.
*
* @return Swift_Mime_ContentEncoder
*/
public static function get7BitEncoding()
{
return self::_lookup('mime.7bitcontentencoder');
}
/**
* Get the Encoder that provides 8-bit encoding.
*
* @return Swift_Mime_ContentEncoder
*/
public static function get8BitEncoding()
{
return self::_lookup('mime.8bitcontentencoder');
}
/**
* Get the Encoder that provides Quoted-Printable (QP) encoding.
*
* @return Swift_Mime_ContentEncoder
*/
public static function getQpEncoding()
{
return self::_lookup('mime.qpcontentencoder');
}
/**
* Get the Encoder that provides Base64 encoding.
*
* @return Swift_Mime_ContentEncoder
*/
public static function getBase64Encoding()
{
return self::_lookup('mime.base64contentencoder');
}
// -- Private Static Methods
private static function _lookup($key)
{
return Swift_DependencyContainer::getInstance()->lookup($key);
}
}

Some files were not shown because too many files have changed in this diff Show more