Initial commit - Typo3 11.5.41

This commit is contained in:
Matteo Gallo
2026-07-03 17:53:31 +02:00
commit 5ca4743197
6811 changed files with 568848 additions and 0 deletions

View File

@@ -0,0 +1,63 @@
<?php
namespace SJBR\SrFreecap\Configuration;
/*
* Copyright notice
*
* (c) 2013-2020 Stanislas Rolland <typo32020(arobas)sjbr.ca>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
* A copy is found in the textfile GPL.txt and important notices to the license
* from the author is found in LICENSE.txt distributed with these scripts.
*
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
*/
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper;
/**
* Class providing configuration help for extension SrFreecap
*/
class ConfigurationHelper
{
/**
* Renders a select element that allows to choose the encryption algoritm to be used by the extension
*
* @param array $params: Field information to be rendered
* @param AbstractTagBasedViewHelper $pObj: The calling parent object.
* @return string The HTML select field
*/
public function buildEncryptionAlgorithmSelector(array $params, AbstractTagBasedViewHelper $pObj)
{
if (in_array('openssl', get_loaded_extensions())) {
$encryptionAlgorithms = openssl_get_cipher_methods(true);
if (!empty($encryptionAlgorithms)) {
$field = '<br /><select id="' . $params['propertyName'] . '" name="' . $params['fieldName'] . '" >' . LF;
foreach ($encryptionAlgorithms as $encryptionAlgorithm) {
$selected = $params['fieldValue'] == $encryptionAlgorithm ? 'selected="selected"' : '';
$field .= '<option name="' . $encryptionAlgorithm . '" value="' . $encryptionAlgorithm . '" ' . $selected . '>' . $encryptionAlgorithm . '</option>' . LF;
}
$field .= '</select><br /><br />' . LF;
} else {
$field = '<br />Available encryption algorithms could not be found. Algorithm AES-256-CBC will be used.<br />';
}
} else {
$field = '<br />PHP openssl extension is not available.<br />';
}
return $field;
}
}

View File

@@ -0,0 +1,80 @@
<?php
namespace SJBR\SrFreecap\Controller;
/*
* Copyright notice
*
* (c) 2012-2021 Stanislas Rolland <typo3AAAA(arobas)sjbr.ca>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
* A copy is found in the textfile GPL.txt and important notices to the license
* from the author is found in LICENSE.txt distributed with these scripts.
*
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
*/
use SJBR\SrFreecap\Domain\Repository\WordRepository;
use SJBR\SrFreecap\View\AudioPlayer\PlayMp3;
use SJBR\SrFreecap\View\AudioPlayer\PlayWav;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
/**
* Renders a wav audio version of the CAPTCHA
*/
class AudioPlayerController extends ActionController
{
/**
* @var string Name of the extension this controller belongs to
*/
protected $extensionName = 'SrFreecap';
/**
* @var WordRepository
*/
protected $wordRepository;
/**
* Dependency injection of the Word Repository
*
* @param WordRepository $wordRepository
*/
public function injectWordRepository(WordRepository $wordRepository)
{
$this->wordRepository = $wordRepository;
}
/**
* Play the audio catcha
*
* @return string Audio content to be sent to the client
*/
public function playAction()
{
$word = $this->wordRepository->getWord();
$format = $this->request->getFormat();
if ($format === 'mp3') {
$this->view = GeneralUtility::makeInstance(PlayMp3::class);
} else if ($format === 'wav') {
$this->view = GeneralUtility::makeInstance(PlayWav::class);
} else {
throw new \Exception('Unknow audio format ' . $format);
}
$this->view->assign('word', $word);
$this->view->render();
}
}

View File

@@ -0,0 +1,74 @@
<?php
namespace SJBR\SrFreecap\Controller;
/***************************************************************
* Copyright notice
*
* (c) 2012-2021 Stanislas Rolland <typo3AAAA(arobas)sjbr.ca>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
* A copy is found in the textfile GPL.txt and important notices to the license
* from the author is found in LICENSE.txt distributed with these scripts.
*
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
use SJBR\SrFreecap\Domain\Model\Font;
use SJBR\SrFreecap\Domain\Repository\FontRepository;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
/**
* Font Maker controller
*/
class FontMakerController extends ActionController
{
/**
* @var string Name of the extension this controller belongs to
*/
protected $extensionName = 'SrFreecap';
/**
* Display the font maker form
*
* @param Font $font
* @return string An HTML form for creating a new font
*/
public function newAction(Font $font = null)
{
if (!is_object($font)) {
$font = new Font();
}
$this->view->assign('font', $font);
}
/**
* Create the font file and display the result
*
* @param Font $font
* @return string HTML presenting the new font that was created
*/
public function createAction(Font $font)
{
// Create the font data
$font->createGdFontFile();
// Store the GD font file
$fontRepository = GeneralUtility::makeInstance(FontRepository::class);
$fontRepository->writeFontFile($font);
$this->view->assign('font', $font);
}
}

View File

@@ -0,0 +1,234 @@
<?php
namespace SJBR\SrFreecap\Controller;
/*
* Copyright notice
*
* (c) 2012-2022 Stanislas Rolland <typo3AAAA(arobas)sjbr.ca>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
* A copy is found in the textfile GPL.txt and important notices to the license
* from the author is found in LICENSE.txt distributed with these scripts.
*
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
*/
use SJBR\SrFreecap\Domain\Repository\WordRepository;
use SJBR\SrFreecap\View\ImageGenerator\ShowPng;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
/**
* Renders the CAPTCHA image
*/
class ImageGeneratorController extends ActionController
{
/**
* @var string Name of the extension this controller belongs to
*/
protected $extensionName = 'SrFreecap';
/**
* @var string Name of the extension this controller belongs to
*/
protected $extensionKey = 'sr_freecap';
/**
* @var WordRepository
*/
protected $wordRepository;
/**
* Dependency injection of the Word Repository
*
* @param WordRepository $wordRepository
*/
public function injectWordRepository(WordRepository $wordRepository)
{
$this->wordRepository = $wordRepository;
}
/**
* Show the CAPTCHA image
*/
public function showAction()
{
// Get session data
$word = $this->wordRepository->getWord();
// Which type of hash to use
// Store in session so can validate in form processor
$word->setHashFunction('md5');
$this->view = GeneralUtility::makeInstance(ShowPng::class);
$this->view->assign('word', $word);
// Adjust settings
$this->processSettings();
$this->view->assign('settings', $this->settings);
// Render the captcha image
$this->view->render();
// Store the session data
$this->wordRepository->setWord($word);
}
/**
* Reviews and adjusts plugin settings
*/
protected function processSettings()
{
// Image type:
// possible values: "jpg", "png", "gif"
// jpg doesn't support transparency (transparent bg option ends up white)
// png isn't supported by old browsers (see http://www.libpng.org/pub/png/pngstatus.html)
// gif may not be supported by your GD Lib.
$this->settings['imageFormat'] = (isset($this->settings['imageFormat']) && $this->settings['imageFormat']) ? $this->settings['imageFormat'] : 'png';
// true = generate pseudo-random string, false = use dictionary
// dictionary is easier to recognise
// - both for humans and computers, so use random string if you're paranoid.
$this->settings['useWordsList'] = (isset($this->settings['useWordsList']) && $this->settings['useWordsList']) ? true : false;
// if your server is NOT set up to deny web access to files beginning ".ht"
// then you should ensure the dictionary file is kept outside the web directory
// eg: if www.foo.com/index.html points to c:\website\www\index.html
// then the dictionary should be placed in c:\website\dict.txt
// test your server's config by trying to access the dictionary through a web browser
// you should NOT be able to view the contents.
// can leave this blank if not using dictionary
$this->settings['wordsListLocation'] = \SJBR\SrFreecap\Utility\LocalizationUtility::getWordsListLocation($this->settings['defaultWordsList'] ?? '');
// Used for non-dictionary word generation and to calculate image width
$this->settings['maxWordLength'] = (isset($this->settings['maxWordLength']) && $this->settings['maxWordLength']) ? $this->settings['maxWordLength'] : 6;
// Maximum times a user can refresh the image
// on a 6500 word dictionary, I think 15-50 is enough to not annoy users and make BF unfeasble.
// further notes re: BF attacks in "avoid brute force attacks" section, below
// on the other hand, those attempting OCR will find the ability to request new images
// very useful; if they can't crack one, just grab an easier target...
// for the ultra-paranoid, setting it to <5 will still work for most users
$this->settings['maxAttempts'] = (isset($this->settings['maxAttempts']) && $this->settings['maxAttempts']) ? $this->settings['maxAttempts'] : 50;
// List of fonts to use
// font size should be around 35 pixels wide for each character.
// you can use my GD fontmaker script at www.puremango.co.uk to create your own fonts
// There are other programs to can create GD fonts, but my script allows a greater
// degree of control over exactly how wide each character is, and is therefore
// recommended for 'special' uses. For normal use of GD fonts,
// the GDFontGenerator @ http://www.philiplb.de is excellent for convering ttf to GD
// the fonts included with freeCap *only* include lowercase alphabetic characters
// so are not suitable for most other uses
// to increase security, you really should add other fonts
if ($this->settings['generateNumbers'] ?? false) {
$this->settings['fontLocations'] = ['EXT:' . $this->extensionKey . '/Resources/Private/Captcha/Fonts/anonymous.gdf'];
} else {
$this->settings['fontLocations'] = [
'EXT:' . $this->extensionKey . '/Resources/Private/Captcha/Fonts/freecap_font1.gdf',
'EXT:' . $this->extensionKey . '/Resources/Private/Captcha/Fonts/freecap_font2.gdf',
'EXT:' . $this->extensionKey . '/Resources/Private/Captcha/Fonts/freecap_font3.gdf',
'EXT:' . $this->extensionKey . '/Resources/Private/Captcha/Fonts/freecap_font4.gdf',
'EXT:' . $this->extensionKey . '/Resources/Private/Captcha/Fonts/freecap_font5.gdf'
];
}
if ($this->settings['fontFiles'] ?? false) {
$this->settings['fontLocations'] = GeneralUtility::trimExplode(',', $this->settings['fontFiles'], 1);
}
for ($i = 0; $i < sizeof($this->settings['fontLocations']); $i++) {
if (substr($this->settings['fontLocations'][$i],0,4) == 'EXT:') {
$this->settings['fontLocations'][$i] = GeneralUtility::getFileAbsFileName($this->settings['fontLocations'][$i]);
} else {
$this->settings['fontLocations'][$i] = Environment::getPublicPath() . '/uploads/' . ExtensionManagementUtility::getCN($this->extensionKey) . '/' . $this->settings['fontLocations'][$i];
}
}
// Text color
// 0 = one random color for all letters
// 1 = different random color for each letter
if ($this->settings['textColor'] ?? false) {
$this->settings['textColor'] = 1;
} else {
$this->settings['textColor'] = 0;
}
// Text position
$this->settings['textPosition'] = [];
$this->settings['textPosition']['horizontal'] = isset($this->settings['textHorizontalPosition']) ? (int)$this->settings['textHorizontalPosition'] : 32;
$this->settings['textPosition']['vertical'] = isset($this->settings['textVerticalPosition']) ? (int)$this->settings['textVerticalPosition'] : 15;
// Text morphing factor
$this->settings['morphFactor'] = (isset($this->settings['morphFactor']) && $this->settings['morphFactor']) ? (int)$this->settings['morphFactor'] : 0;
// Limits for text color
$this->settings['colorMaximum'] = [];
if (isset($this->settings['colorMaximumDarkness'])) {
$this->settings['colorMaximum']['darkness'] = (int)$this->settings['colorMaximumDarkness'];
}
if (isset($this->settings['colorMaximumLightness'])) {
$this->settings['colorMaximum']['lightness'] = (int)$this->settings['colorMaximumLightness'];
}
// Background
// Many thanks to http://ocr-research.org.ua and http://sam.zoy.org/pwntcha/ for testing
// for jpgs, 'transparent' is white
if (!isset($this->settings['backgroundType']) || !in_array($this->settings['backgroundType'], ['Transparent', 'White with grid', 'White with squiggles', 'Morphed image blocks'])) {
$this->settings['backgroundType'] = 'White with grid';
}
// Should we blur the background? (looks nicer, makes text easier to read, takes longer)
$this->settings['backgroundBlur'] = ((isset($this->settings['backgroundBlur']) && $this->settings['backgroundBlur']) || !isset($this->settings['backgroundBlur'])) ? true : false;
// For background type 'Morphed image blocks', which images should we use?
// If you add your own, make sure they're fairly 'busy' images (ie a lot of shapes in them)
$this->settings['backgroundImages'] = [
'EXT:' . $this->extensionKey . '/Resources/Private/Captcha/Images/freecap_im1.jpg',
'EXT:' . $this->extensionKey . '/Resources/Private/Captcha/Images/freecap_im2.jpg',
'EXT:' . $this->extensionKey . '/Resources/Private/Captcha/Images/freecap_im3.jpg',
'EXT:' . $this->extensionKey . '/Resources/Private/Captcha/Images/freecap_im4.jpg',
'EXT:' . $this->extensionKey . '/Resources/Private/Captcha/Images/freecap_im5.jpg'
];
// For non-transparent backgrounds only:
// if 0, merges CAPTCHA with background
// if 1, write CAPTCHA over background
$this->settings['mergeWithBackground'] = (isset($this->settings['mergeWithBackground']) && $this->settings['mergeWithBackground']) ? 0 : 1;
// Should we morph the background? (recommend yes, but takes a little longer to compute)
$this->settings['backgroundMorph'] = (isset($this->settings['backgroundMorph']) && $this->settings['backgroundMorph']) ? true : false;
// Read each font and get font character widths
$this->settings['fontWidths'] = [];
for ($i=0 ; $i < sizeof($this->settings['fontLocations']); $i++) {
$handle = fopen($this->settings['fontLocations'][$i],"r");
// Read header of GD font, up to char width
$c_wid = fread($handle,12);
$this->settings['fontWidths'][$i] = ord($c_wid[8])+ord($c_wid[9])+ord($c_wid[10])+ord($c_wid[11]);
fclose($handle);
}
// Modify image width depending on maximum possible length of word
// you shouldn't need to use words > 6 chars in length really.
$this->settings['imageWidth'] = ($this->settings['maxWordLength'] * (array_sum($this->settings['fontWidths'])/sizeof($this->settings['fontWidths']))) + (isset($this->settings['imageAdditionalWidth']) ? (int)$this->settings['imageAdditionalWidth'] : 40);
$this->settings['imageHeight'] = (isset($this->settings['imageHeight']) && $this->settings['imageHeight']) ? (int)$this->settings['imageHeight'] : 90;
// Try to avoid the 'free p*rn' method of CAPTCHA circumvention
// see www.wikipedia.com/captcha for more info
// "To avoid spam, please do NOT enter the text if this site is not example.org";
// or more simply:
// "for use only on example.org";
// reword or add lines as you please
$this->settings['siteTag'] = (isset($this->settings['siteTag']) && $this->settings['siteTag']) ? explode('|', LocalizationUtility::translate('site_tag', $this->extensionName, [(isset($this->settings['siteTagDomain']) && $this->settings['siteTagDomain']) ? $this->settings['siteTagDomain'] : 'example.org'])) : [];
// where to write the above:
// 0=top
// 1=bottom
// 2=both
$this->settings['siteTagPosition'] = isset($this->settings['siteTagPosition']) ? (int)$this->settings['siteTagPosition'] : 1;
}
}

View File

@@ -0,0 +1,230 @@
<?php
namespace SJBR\SrFreecap\Domain\Model;
/*
* Copyright notice
*
* (c) 2012-2022 Stanislas Rolland <typo3AAAA(arobas)sjbr.ca>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
* A copy is found in the textfile GPL.txt and important notices to the license
* from the author is found in LICENSE.txt distributed with these scripts.
*
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
*/
use SJBR\SrFreecap\Utility\FontMakingUtility;
use SJBR\SrFreecap\Validation\Validator\TtfFileValidator;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Extbase\Annotation\Validate;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
/**
* Font object
*
* This file must be iso-8859-2-encoded!
*
*/
class Font extends AbstractEntity
{
/**
* @var int
*/
protected $charactersIncludedInFont;
/**
* @var int
* @Validate("NumberRange", options={"minimum": 5, "maximum": 255})
*/
protected $characterWidth;
/**
* @var int
* @Validate("NumberRange", options={"minimum": 5, "maximum": 255})
*/
protected $characterHeight;
/**
* @var int
**/
protected $endianness;
/**
* @var string
* @Validate("NotEmpty")
* @Validate("StringLength", options={"minimum": 1, "maximum": 255})
* @Validate("\SJBR\SrFreecap\Validation\Validator\TtfFileValidator")
**/
protected $ttfFontFileName = '';
/**
* @var string
**/
protected $gdFontFilePrefix = 'font';
/**
* @var string
**/
protected $pngImageFileName = '';
/**
* @var string
**/
protected $gdFontData = '';
/**
* @var string
**/
protected $gdFontFileName = '';
public function __construct(
$charactersIncludedInFont = 0,
$characterWidth = 34,
$characterHeight = 50,
$endianness = 0,
$ttfFontFileName = '',
$gdFontFilePrefix = '',
$pngImageFileName = '',
$gdFontData = '',
$gdFontFileName = ''
) {
$this->setCharactersIncludedInFont($charactersIncludedInFont);
$this->setCharacterWidth($characterWidth);
$this->setCharacterHeight($characterHeight);
$this->setEndianness($endianness);
$this->setTtfFontFileName($ttfFontFileName);
$this->setGdFontFilePrefix($gdFontFilePrefix);
$this->setPngImageFileName($pngImageFileName);
$this->setGdFontData($gdFontData);
$this->setGdFontFileName($gdFontFileName);
}
public function setCharactersIncludedInFont($charactersIncludedInFont) {
$this->charactersIncludedInFont = (int)$charactersIncludedInFont;
}
public function getCharactersIncludedInFont() {
return $this->charactersIncludedInFont;
}
public function setCharacterWidth($characterWidth) {
$this->characterWidth = (int)$characterWidth;
}
public function getCharacterWidth() {
return $this->characterWidth;
}
public function setCharacterHeight($characterHeight) {
$this->characterHeight = (int)$characterHeight;
}
public function getCharacterHeight() {
return $this->characterHeight;
}
public function setEndianness($endianness) {
$this->endianness = (int)$endianness;
}
public function getEndianness() {
return $this->endianness;
}
public function setTtfFontFileName($ttfFontFileName) {
$this->ttfFontFileName = (string)$ttfFontFileName;
}
public function getTtfFontFileName() {
return $this->ttfFontFileName;
}
public function setGdFontFilePrefix($gdFontFilePrefix = 'font') {
$this->gdFontFilePrefix = (string)$gdFontFilePrefix;
}
public function getGdFontFilePrefix() {
return $this->gdFontFilePrefix;
}
public function setPngImageFileName($pngImageFileName) {
$this->pngImageFileName = (string)$pngImageFileName;
}
public function getPngImageFileName() {
return $this->pngImageFileName;
}
public function setGdFontData($gdFontData) {
$this->gdFontData = (string)$gdFontData;
}
public function getGdFontdata() {
return $this->gdFontData;
}
public function setGdFontFileName($gdFontFileName) {
$this->gdFontFileName = (string)$gdFontFileName;
}
public function getGdFontFileName() {
return $this->gdFontFileName;
}
/**
* Creates teh GD font file
*/
public function createGdFontFile()
{
switch ($this->charactersIncludedInFont) {
case 1:
$characters = 'a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z';
$startCharacter = 'a';
break;
case 2:
$characters = 'a,b,c,d,e,f,g,h,i,j,k,l,m,n,o'
.',p,q,r,s,t,u,v,w,x,y,z,-,-,-,-,-'
.',-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-'
.',-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-'
.',-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-'
.',-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-'
.',-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-'
.',-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,ß'
.',à,á,â,ă,ä,Í,ć,ç,č,é,ę,ë,ě,Í,î,ď'
.',đ,ń,ň,ó,ô,ő,ö,-,ř,ů,ú,ű,ü,ý,ţ,-';
$startCharacter = 'a';
break;
case 0:
default:
$characters = '0,1,2,3,4,5,6,7,8,9';
$startCharacter = '0';
break;
}
$numberOfCharacters = count(explode(',', $characters));
$this->setPngImageFileName(FontMakingUtility::makeFontImage($characters, $this->ttfFontFileName, $this->characterWidth, $this->characterHeight));
if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib_png'] ?? false) {
$image = @ImageCreateFromPNG(Environment::getPublicPath() . '/' . $this->pngImageFileName);
} else {
$image = @ImageCreateFromGIF(Environment::getPublicPath() . '/' . $this->pngImageFileName);
}
if ($image !== false) {
$this->setGdFontdata(FontMakingUtility::makeFont($image, $numberOfCharacters, $startCharacter, $this->characterWidth, $this->characterHeight, $this->endianness));
ImageDestroy($image);
}
}
}

View File

@@ -0,0 +1,109 @@
<?php
namespace SJBR\SrFreecap\Domain\Model;
/***************************************************************
* Copyright notice
*
* (c) 2012-2020 Stanislas Rolland <typo32020(arobas)sjbr.ca>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
* A copy is found in the textfile GPL.txt and important notices to the license
* from the author is found in LICENSE.txt distributed with these scripts.
*
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
/**
* Word object
*/
class Word extends AbstractEntity
{
/**
* @var string
*/
protected $wordHash;
/**
* @var string
*/
protected $hashFunction;
/**
* @var array
*/
protected $wordCypher;
/**
* @var int
*/
protected $attempts;
public function __construct(
$wordHash = '',
$hashFunction = 'md5',
$wordCypher = array(),
$attempts = 0
)
{
$this->setWordHash($wordHash);
$this->setHashFunction($hashFunction);
$this->setWordCypher($wordCypher);
$this->setAttempts($attempts);
}
public function setWordHash($wordHash)
{
$this->wordHash = (string)$wordHash;
}
public function getWordHash()
{
return $this->wordHash;
}
public function setHashFunction($hashFunction)
{
$this->hashFunction = (string)$hashFunction;
}
public function getHashFunction()
{
return $this->hashFunction;
}
public function setWordCypher($wordCypher)
{
$this->wordCypher = (array)$wordCypher;
}
public function getWordCypher()
{
return $this->wordCypher;
}
public function setAttempts($attempts)
{
$this->attempts = (int)$attempts;
}
public function getAttempts()
{
return $this->attempts;
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace SJBR\SrFreecap\Domain\Repository;
/*
* Copyright notice
*
* (c) 2012-2018 Stanislas Rolland <typo3@sjbr.ca>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
* A copy is found in the textfile GPL.txt and important notices to the license
* from the author is found in LICENSE.txt distributed with these scripts.
*
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
*/
use SJBR\SrFreecap\Domain\Model\Font;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Persistence\Repository;
/**
* Font repository
*/
class FontRepository extends Repository
{
/**
* @var string Name of the extension this controller belongs to
*/
protected $extensionKey = 'sr_freecap';
/**
* Writes the GD font file
*
* @param Font the object to be stored
* @return \SJBR\SrFreecap\Domain\Repository\FontRepository $this
*/
public function writeFontFile(Font $font)
{
$directory = Environment::getPublicPath() . '/' . 'uploads/' . ExtensionManagementUtility::getCN($this->extensionKey) . '/';
GeneralUtility::mkdir_deep($directory);
$fileName = $directory . $font->getGdFontFilePrefix() . '_' . md5($font->getGdFontData()) . '.gdf';
if (GeneralUtility::writeFile($fileName, $font->getGdFontData())) {
$font->setGdFontFileName($fileName);
}
return $this;
}
}

View File

@@ -0,0 +1,92 @@
<?php
namespace SJBR\SrFreecap\Domain\Repository;
/*
* Copyright notice
*
* (c) 2012-2021 Stanislas Rolland <typo3AAAA(arobas)sjbr.ca>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
* A copy is found in the textfile GPL.txt and important notices to the license
* from the author is found in LICENSE.txt distributed with these scripts.
*
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
*/
use SJBR\SrFreecap\Domain\Model\Word;
use SJBR\SrFreecap\Domain\Session\SessionStorage;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Persistence\Repository;
/**
* Word repository in session storage
*/
class WordRepository extends Repository
{
/**
* The session sorage handler
* @var SessionStorage
*/
protected $sessionStorage;
/**
* Constructor
*/
public function __construct(SessionStorage $sessionStorage)
{
// Get an instance of the session storage handler
$this->sessionStorage = $sessionStorage;
}
/**
* Returns the object stored in the user's session
*
* @return Word the stored object
*/
public function getWord()
{
$word = $this->sessionStorage->restoreFromSession();
// If no Word object is found in session data, initialize a new one
if (!is_object($word)) {
$word = new Word();
}
return $word;
}
/**
* Writes the object into the user's session
*
* @param Word the object to be stored
* @return WordRepository
*/
public function setWord(Word $object)
{
$this->sessionStorage->writeToSession($object);
return $this;
}
/**
* Cleans up the session: removes the stored object from the user's session
*
* @return WordRepository
*/
public function cleanUpWord()
{
$this->sessionStorage->cleanUpSession();
return $this;
}
}

View File

@@ -0,0 +1,93 @@
<?php
namespace SJBR\SrFreecap\Domain\Session;
/*
* Copyright notice
*
* (c) 2012-2021 Stanislas Rolland <typo3AAAA(arobas)sjbr.ca>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
* A copy is found in the textfile GPL.txt and important notices to the license
* from the author is found in LICENSE.txt distributed with these scripts.
*
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
*/
use TYPO3\CMS\Core\Session\UserSession;
use TYPO3\CMS\Core\Session\UserSessionManager;
use TYPO3\CMS\Core\Session\Backend\Exception\SessionNotFoundException;
use TYPO3\CMS\Core\SingletonInterface;
use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication;
/**
* Session storage
*/
class SessionStorage implements SingletonInterface
{
const SESSIONNAMESPACE = 'tx_srfreecap';
/**
* Returns the object stored in the user's PHP session
*
* @return Object the stored object
*/
public function restoreFromSession()
{
$sessionData = $this->getUser()->getSessionData(self::SESSIONNAMESPACE);
if ($sessionData === null) {
return null;
}
return unserialize($sessionData);
}
/**
* Writes an object into the PHP session
*
* @param $object any serializable object to store into the session
* @return SessionStorage
*/
public function writeToSession($object)
{
$sessionData = serialize($object);
$this->getUser()->setAndSaveSessionData(self::SESSIONNAMESPACE, $sessionData);
return $this;
}
/**
* Cleans up the session: removes the stored object from the PHP session
*
* @return SessionStorage
*/
public function cleanUpSession()
{
$this->getUser()->setAndSaveSessionData(self::SESSIONNAMESPACE, null);
return $this;
}
/**
* Gets a frontend user session
*
* @return User The current frontend user object
*/
protected function getUser() : FrontendUserAuthentication
{
if (!isset($GLOBALS['TSFE']) || !$GLOBALS['TSFE']->fe_user) {
throw new SessionNotFoundException('No frontend user found in session!');
}
return $GLOBALS['TSFE']->fe_user;
}
}

View File

@@ -0,0 +1,275 @@
<?php
namespace SJBR\SrFreecap\Http;
/*
* Copyright notice
*
* 2010 Daniel Lienert <daniel@lienert.cc>, Michael Knoll <mimi@kaktusteam.de>
* 2012-2022 Stanislas Rolland <typo3AAAA(arobas)sjbr.ca>
* All rights reserved
*
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
*/
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Error\Http\BadRequestException;
use TYPO3\CMS\Core\Http\NullResponse;
use TYPO3\CMS\Core\Localization\Locales;
use TYPO3\CMS\Core\Utility\ArrayUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\RootlineUtility;
use TYPO3\CMS\Extbase\Core\Bootstrap;
use TYPO3\CMS\Extbase\Mvc\Dispatcher;
use TYPO3\CMS\Extbase\Mvc\Request;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
/**
* Dispatch the eid request
*/
class EidDispatcher
{
/**
* Array of all request Arguments
*
* @var array
*/
protected $requestArguments = [];
/**
* @var string
*/
protected $vendorName = 'SJBR';
/**
* @var string
*/
protected $extensionName = 'SrFreecap';
/**
* @var string
*/
protected $pluginName;
/**
* @var string
*/
protected $controllerName;
/**
* @var string
*/
protected $actionName;
/**
* @var string
*/
protected $formatName;
/**
* @var array
*/
protected $arguments = [];
/**
* Initializes and dispatches actions
* Call this function if you want to use this dispatcher "standalone"
* @param ServerRequestInterface $request
* @return Response
*/
public function initAndDispatch($request)
{
return $this->initTypoScriptConfiguration($request)
->initLanguage($request)
->initCallArguments()
->dispatch();
}
/**
* Builds an extbase context and returns the response
*
* @return ResponseInterface
*/
protected function dispatch()
{
$bootstrap = GeneralUtility::makeInstance(Bootstrap::class);
$configuration['vendorName'] = $this->vendorName;
$configuration['extensionName'] = $this->extensionName;
$configuration['pluginName'] = $this->pluginName;
$bootstrap->initialize($configuration);
$request = $this->buildRequest();
try {
$response = GeneralUtility::makeInstance(Dispatcher::class)->dispatch($request);
} catch (\Exception $e) {
throw new BadRequestException('An argument is missing or invalid', 1394587024);
}
return $response;
}
/**
* Get the TypoScript configuration
*
* @param ServerRequestInterface $request
* @return EidDispatcher
*/
protected function initTypoScriptConfiguration($request)
{
$controller = $request->getAttribute('frontend.controller');
$controller->type = 0;
$context = $controller->getContext();
$controller->rootLine = GeneralUtility::makeInstance(RootlineUtility::class, $controller->id, $controller->MP, $context)->get();
$controller->getConfigArray();
return $this;
}
/**
* Set language and locale
*
* @param ServerRequestInterface $request
* @return EidDispatcher
*/
protected function initLanguage($request)
{
$controller = $request->getAttribute('frontend.controller');
$siteLanguage = $controller->getLanguage();
$locales = GeneralUtility::makeInstance(Locales::class);
$locales->setSystemLocaleFromSiteLanguage($siteLanguage);
return $this;
}
/**
* Build a request object
*
* @return Request $request
*/
protected function buildRequest()
{
$controllerClassName = $this->vendorName . '\\' . $this->extensionName . '\\' . 'Controller' . '\\' . $this->pluginName . 'Controller';
$request = GeneralUtility::makeInstance(Request::class, $controllerClassName);
$request->setControllerObjectName($controllerClassName);
$request->setPluginName($this->pluginName);
$request->setControllerActionName($this->actionName);
$request->setFormat($this->formatName);
$request->setArguments($this->arguments);
return $request;
}
/**
* Prepare the call arguments
*
* @return EidDispatcher
*/
public function initCallArguments() {
$request = GeneralUtility::_GP('request');
if ($request) {
$this->setRequestArgumentsFromJSON($request);
} else {
$this->setRequestArgumentsFromGetPost();
}
return $this->setPluginName($this->requestArguments['pluginName'])
->setControllerName()
->setActionName($this->requestArguments['actionName'])
->setFormatName($this->requestArguments['formatName'])
->setArguments($this->requestArguments['arguments'] ?? []);
}
/**
* Set the request array from JSON
*
* @param string $request
*/
protected function setRequestArgumentsFromJSON($request)
{
$requestArray = json_decode($request, true);
if (is_array($requestArray)) {
ArrayUtility::mergeRecursiveWithOverrule($this->requestArguments, $requestArray);
}
}
/**
* Set the request array from the getPost array
*/
protected function setRequestArgumentsFromGetPost()
{
$validArguments = ['pluginName', 'actionName', 'formatName', 'arguments'];
foreach ($validArguments as $argument) {
if (GeneralUtility::_GP($argument) ?? false) {
$this->requestArguments[$argument] = GeneralUtility::_GP($argument);
} else if (GeneralUtility::_GP('amp;' . $argument) ?? false) {
// Something went wrong...
$this->requestArguments[$argument] = GeneralUtility::_GP('amp;' . $argument);
} else if ($argument !== 'arguments') {
throw new BadRequestException('An argument is missing', 1394587023);
}
}
}
/**
* @param string $pluginName
* @return EidDispatcher
*/
protected function setPluginName($pluginName = 'ImageGenerator')
{
$this->pluginName = htmlspecialchars((string)$pluginName);
return $this;
}
/**
* @return EidDispatcher
*/
protected function setControllerName()
{
$this->controllerName = $this->pluginName;
return $this;
}
/**
* @param string $actionName
* @return EidDispatcher
*/
protected function setActionName($actionName = 'show')
{
$this->actionName = htmlspecialchars((string)$actionName);
return $this;
}
/**
* @param string $formatName
* @return EidDispatcher
*/
protected function setFormatName($formatName = 'txt')
{
$this->formatName = htmlspecialchars((string)$formatName);
return $this;
}
/**
* @param array $arguments
* @return EidDispatcher
*/
protected function setArguments($arguments)
{
if (!is_array($arguments)) {
$this->arguments = [];
} else {
$this->arguments = $arguments;
}
return $this;
}
}

View File

@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace SJBR\SrFreecap\Middleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use TYPO3\CMS\Core\Http\DispatcherInterface;
use TYPO3\CMS\Core\Http\NullResponse;
use TYPO3\CMS\Core\Http\Response;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Lightweight alternative to regular frontend requests; used when $_GET[eID] is set.
* In the future, logic from the EidUtility will be moved to this class, however in most cases
* a custom PSR-15 middleware will be better suited for whatever job the eID functionality does currently.
*
* @internal
*/
class EidHandler implements MiddlewareInterface
{
/**
* @var DispatcherInterface
*/
protected $dispatcher;
/**
* @param DispatcherInterface $dispatcher
*/
public function __construct(DispatcherInterface $dispatcher)
{
$this->dispatcher = $dispatcher;
}
/**
* Dispatches the request to the corresponding eID class or eID script
*
* @param ServerRequestInterface $request
* @param RequestHandlerInterface $handler
* @return ResponseInterface
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$eID = $request->getParsedBody()['eIDSR'] ?? $request->getQueryParams()['eIDSR'] ?? null;
if ($eID === null) {
return $handler->handle($request);
}
// Remove any output produced until now
ob_clean();
/** @var Response $response */
$response = new Response();
if (empty($eID) || !isset($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['sr_freecap']['eIDSR_include'][$eID])) {
$response = $response->withStatus(404, 'eIDSR not registered');
} else {
$configuration = $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['sr_freecap']['eIDSR_include'][$eID];
$response = new NullResponse();
// Simple check to make sure that it's not an absolute file (to use the fallback)
if (strpos($configuration, '::') !== false || is_callable($configuration)) {
$frontendUser = $request->getAttribute('frontend.user');
$request = $request->withAttribute('target', $configuration);
$response = $this->dispatcher->dispatch($request);
}
}
return $response;
}
}

View File

@@ -0,0 +1,88 @@
<?php
namespace SJBR\SrFreecap;
/*
* Copyright notice
*
* (c) 2005-2021 Stanislas Rolland <typo3AAAA(arobas)sjbr.ca>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* A copy is found in the textfile GPL.txt and important notices to the license
* from the author is found in LICENSE.txt distributed with these scripts.
*
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
*/
use SJBR\SrFreecap\Validation\Validator\CaptchaValidator;
use SJBR\SrFreecap\ViewHelpers\AudioViewHelper;
use SJBR\SrFreecap\ViewHelpers\ImageViewHelper;
use SJBR\SrFreecap\ViewHelpers\TranslateViewHelper;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
class PiBaseApi
{
/**
* @var string The extension key
*/
public $extKey = 'sr_freecap';
/**
* This function generates an array of markers used to render the captcha element
*
* @return array marker array containing the captcha markers to be sustituted in the html template
*/
public function makeCaptcha()
{
// Get the configuration manager
$configurationManager = GeneralUtility::makeInstance(ConfigurationManagerInterface::class);
// Get translation view helper
$translator = GeneralUtility::makeInstance(TranslateViewHelper::class);
$translator->injectConfigurationManager($configurationManager);
$markerArray = [];
$markerArray['###'. strtoupper($this->extKey) . '_NOTICE###'] = $translator->render('notice') . ' ' . $translator->render('explain');
// Get the captcha image view helper
$imageViewHelper = GeneralUtility::makeInstance(ImageViewHelper::class);
$imageViewHelper->injectConfigurationManager($configurationManager);
$markerArray['###'. strtoupper($this->extKey) . '_IMAGE###'] = $imageViewHelper->render('pi1');
$markerArray['###'. strtoupper($this->extKey) . '_CANT_READ###'] = '';
// Get the audio icon view helper
$audioViewHelper = GeneralUtility::makeInstance(AudioViewHelper::class);
$audioViewHelper->injectConfigurationManager($configurationManager);
$markerArray['###'. strtoupper($this->extKey) . '_ACCESSIBLE###'] = $audioViewHelper->render('pi1');
return $markerArray;
}
/**
* Check the word that was entered against the hashed value
*
* @param string $word: hte word that was entered
* @return bool true, if the word entered matches the hashes value
*/
public function checkWord($word)
{
// Get the validator
$validator = GeneralUtility::makeInstance(CaptchaValidator::class);
// Check word
return !$validator->validate($word)->hasErrors();
}
}
class_alias('SJBR\\SrFreecap\\PiBaseApi', 'tx_srfreecap_pi2');

View File

@@ -0,0 +1,128 @@
<?php
namespace SJBR\SrFreecap\Utility;
/*
* Copyright notice
*
* (c) 2012-2020 Stanislas Rolland <typo32020(arobas)sjbr.ca>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
* A copy is found in the textfile GPL.txt and important notices to the license
* from the author is found in LICENSE.txt distributed with these scripts.
*
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
*/
/**
* Utility dealing with audio content
*/
class AudioContentUtility
{
/**
* Joins multiple audio files
*
* @param array $files: the array of audio files
* @param string $format: the audio format
* @return string the contents of joined audio file
*/
public static function joinAudioFiles($files, $format = 'wav') {
switch ($format) {
case 'mp3':
return self::joinMp3Files($files);
break;
case 'wav':
default:
return self::joinWavFiles($files);
break;
}
}
/**
* Joins multiple wav files
*
* All wave files need to have the same format and need to be uncompressed.
* The headers of the last file will be used (with recalculated datasize
* of course)
*
* @link http://ccrma.stanford.edu/CCRMA/Courses/422/projects/WaveFormat/
* @link http://www.thescripts.com/forum/thread3770.html
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
* @author Andreas Gohr <gohr@cosmocode.de>
*
* @param array $wavs: the array of wav files
* @return string the contents of joined wav file
*/
protected static function joinWavFiles($wavs)
{
$fields = join('/', array(
'H8Format',
'H8Subchunk1ID',
'VSubchunk1Size',
'vAudioFormat',
'vNumChannels',
'VSampleRate',
'VByteRate',
'vBlockAlign',
'vBitsPerSample'
));
$data = '';
foreach ($wavs as $wav){
$fp = fopen($wav, 'rb');
// Read ChunkID
$headerPart1 = fread($fp, 4);
// Read ChunkSize
$headerPart2 = fread($fp, 4);
// Read following fields
$headerPart3 = fread($fp, 28);
$info = unpack($fields, $headerPart3);
// Read optional extra stuff
// We will not use this since AudioFormat of all our sound files is PCM
if ($info['Subchunk1Size'] > 16) {
$headerPart3 .= fread($fp, ($info['Subchunk1Size']-16));
}
// Read SubChunk2ID
$headerPart3 .= fread($fp, 4);
// Read Subchunk2Size
$size = unpack('VSubChunk2Size', fread($fp, 4));
$size = $size['SubChunk2Size'];
// Read data
$data .= fread($fp, $size);
fclose($fp);
}
return $headerPart1 . pack('V', 36 + strlen($data)) . $headerPart3 . pack('V', strlen($data)) . $data;
}
/**
* Joins multiple mp3 files
*
* All mp3 files need to have the same format and need to be uncompressed.
* The headers of the last file will be used (with recalculated datasize
* of course)
*
* @link http://www.theblog.ca/merge-mp3s-php
* @param array $files: the array of mp3 files
* @return string the contents of joined mp3 file
*/
protected static function joinMp3Files($files)
{
$data = '';
foreach ($files as $file) {
$mp3 = new Mp3ContentUtility($file);
$data .= $mp3->striptags();
}
return $data;
}
}

View File

@@ -0,0 +1,91 @@
<?php
namespace SJBR\SrFreecap\Utility;
/*
* Copyright notice
*
* (c) 2012-2022 Stanislas Rolland <typo3AAAA(arobas)sjbr.ca>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
* A copy is found in the textfile GPL.txt and important notices to the license
* from the author is found in LICENSE.txt distributed with these scripts.
*
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
*/
class EncryptionUtility
{
/**
* Salt
*/
const SALT = 'cH!swe!retReGu7W6bEDRup7usuDUh9THeD2CHeGE*ewr4n39=E@rAsp7c-Ph@pH';
/**
* Encrypts a string
*
* @param array $string: the string to be encrypted
* @return array an array with the string as the first element and the initialization vector as the second element
*/
public static function encrypt($string)
{
if (in_array('openssl', get_loaded_extensions())) {
$encryptionAlgorithm = strtolower($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['sr_freecap']['encryptionAlgorithm'] ?? 'aes-256-cbc');
$availableAlgorithms = openssl_get_cipher_methods(true);
if (in_array($encryptionAlgorithm, $availableAlgorithms)) {
$key = md5($GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'] ?? 0, true);
$iv_size = openssl_cipher_iv_length($encryptionAlgorithm);
$salt = (isset($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['sr_freecap']['salt']) && trim($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['sr_freecap']['salt'])) ? trim($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['sr_freecap']['salt']) : self::SALT;
$hash = hash('sha256', $salt . $key . $salt);
$iv = substr($hash, strlen($hash) - $iv_size);
$key = substr($hash, 0, 32);
$string = openssl_encrypt($string, $encryptionAlgorithm, $key, OPENSSL_RAW_DATA, $iv);
$cypher = [base64_encode($string), base64_encode($iv)];
} else {
$cypher = [base64_encode($string)];
}
} else {
$cypher = [base64_encode($string)];
}
return $cypher;
}
/**
* Decrypts a string
*
* @param array $cypher: an array as returned by encrypt()
* @return string the decrypted string
*/
public static function decrypt($cypher)
{
if (in_array('openssl', get_loaded_extensions())) {
$encryptionAlgorithm = strtolower($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['sr_freecap']['encryptionAlgorithm'] ?? 'aes-256-cbc');
$availableAlgorithms = openssl_get_cipher_methods(true);
if (in_array($encryptionAlgorithm, $availableAlgorithms)) {
$key = md5($GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'] ?? 0, true);
$salt = isset($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['sr_freecap']['salt']) ? trim($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['sr_freecap']['salt']) : self::SALT;
$hash = hash('sha256', $salt . $key . $salt);
$key = substr($hash, 0, 32);
$string = trim(openssl_decrypt(base64_decode($cypher[0]), $encryptionAlgorithm, $key, OPENSSL_RAW_DATA, base64_decode($cypher[1])));
} else {
$string = base64_decode($cypher[0]);
}
} else {
$string = base64_decode($cypher[0]);
}
return $string;
}
}

View File

@@ -0,0 +1,146 @@
<?php
namespace SJBR\SrFreecap\Utility;
/*
* Copyright notice
*
* (c) 2012-2022 Stanislas Rolland <typo3AAAA(arobas)sjbr.ca>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
* A copy is found in the textfile GPL.txt and important notices to the license
* from the author is found in LICENSE.txt distributed with these scripts.
*
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
*/
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Utility for making GD fonts
*/
class FontMakingUtility
{
/**
* Returns an image displaying a list of characters with specified font file and character size
*
* @param string $characters: text to display on the image
* @param string $font: specified True Type font file name
* @param integer $width: width of each character
* @param string $height: height of the image
* @return array image file info array
*/
public static function makeFontImage($characters, $font, $width = 34, $height = 50)
{
$size = intval($height * .8);
$vertOffset = intval($height * .7);
$color = '#000000';
$bgColor = 'white';
$align = 'left';
$charactersArray = explode(',', $characters);
$charactersCount = count($charactersArray);
$gifObjArray = array();
$gifObjArray['backColor'] = $bgColor;
$gifObjArray['transparentBackground'] = 0;
$gifObjArray['reduceColors'] = '';
$gifObjArray['maxWidth'] = ($charactersCount * $width) + 1;
$gifObjArray['XY'] = ($charactersCount * $width) . ',' . $height;
for ($ic = 1; $ic < $charactersCount+1; $ic++) {
$gifObjArray[$ic . '0'] = 'TEXT';
$gifObjArray[$ic . '0.']['text'] = $charactersArray[$ic-1];
$bbox = imagettfbbox($size, 0, GeneralUtility::getFileAbsFileName($font), $charactersArray[$ic-1]);
$hOffset = intval(($width - ($bbox[4] - $bbox[6]))/2);
$vOffset = intval(($width - ($bbox[7] - $bbox[1]))/2);
$gifObjArray[$ic . '0.']['niceText'] = 0;
$gifObjArray[$ic . '0.']['antiAlias'] = 1;
$gifObjArray[$ic . '0.']['align'] = $align;
$gifObjArray[$ic . '0.']['fontSize'] = $size;
$gifObjArray[$ic . '0.']['fontFile'] = '../' . $font;
$gifObjArray[$ic . '0.']['fontColor'] = $color;
$gifObjArray[$ic . '0.']['maxWidth'] = $width;
$gifObjArray[$ic . '0.']['offset'] = (($ic-1) * $width + $hOffset) . ',' . $vertOffset;
}
$gifCreator = GeneralUtility::makeInstance(GifBuilderUtility::class);
if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib'] ?? false) {
$gifCreator->start($gifObjArray, []);
return $gifCreator->gifBuild();
} else {
return false;
}
}
/************************************************************\
*
* GD Fontmaker Copyright 2005 Howard Yeend
* www.puremango.co.uk
*
* This file is part of GD Fontmaker.
*
* GD Fontmaker is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* GD Fontmaker is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GD Fontmaker; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*
\************************************************************/
public static function makeFont($image, $numchars, $startchar, $pixelwidth, $pixelheight, $endianness = 0)
{
$startchar = ord($startchar);
// encode this at start of font
if ($endianness) {
// big-endian
$fontdata = chr(0).chr(0).chr(0).chr($numchars).chr(0).chr(0).chr(0).chr($startchar).chr(0).chr(0).chr(0).chr($pixelwidth).chr(0).chr(0).chr(0).chr($pixelheight);
} else {
// little-endian
$fontdata = chr($numchars).chr(0).chr(0).chr(0).chr($startchar).chr(0).chr(0).chr(0).chr($pixelwidth).chr(0).chr(0).chr(0).chr($pixelheight).chr(0).chr(0).chr(0);
}
// loop through each pixel of each character of the PNG
// (we know the dimensions of the characters because the user told us what they were)
$y = 0;
$x = 0;
$start_x = 0;
for ($c = 0; $c < $numchars*$pixelwidth; $c += $pixelwidth) {
for ($y = 0; $y < $pixelheight; $y++) {
for ($x = $c; $x < $c+$pixelwidth; $x++) {
// get colour of this pixel
$rgb = ImageColorAt($image, $x, $y);
if ($rgb == 0) {
// it's black; font data
$fontdata .= chr(255);
} else {
// it's not black; background
$fontdata .= chr(0);
}
$i++;
}
}
}
return $fontdata;
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace SJBR\SrFreecap\Utility;
/*
* Copyright notice
*
* (c) 2012-2018 Stanislas Rolland <typo3(arobas)sjbr.ca>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
* A copy is found in the textfile GPL.txt and important notices to the license
* from the author is found in LICENSE.txt distributed with these scripts.
*
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
*/
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
use TYPO3\CMS\Frontend\Imaging\GifBuilder;
/**
* Utility extending Gif builder
*/
class GifBuilderUtility extends GifBuilder
{
/**
* Returns the reference to a "resource" in TypoScript.
*
* @param string The resource value.
* @return string Returns the relative filepath
*/
public function checkFile($file)
{
$file = GeneralUtility::getFileAbsFileName(Environment::getPublicPath() . '/' . $file);
$file = PathUtility::stripPathSitePrefix($file);
return $file;
}
/**
* Writes the input GDlib image pointer to file
*
* @param resource The GDlib image resource pointer
* @param string The filename to write to
* @param int $quality The image quality (for JPEGs)
* @return mixed The output of either imageGif, imagePng or imageJpeg based on the filename to write
* @see maskImageOntoImage(), scale(), output()
*/
public function ImageWrite($destImg, $theImage, $quality = 0)
{
return parent::ImageWrite($destImg, Environment::getPublicPath() . '/' . $theImage, $quality);
}
}

View File

@@ -0,0 +1,446 @@
<?php
namespace SJBR\SrFreecap\Utility;
/*
* Copyright notice
*
* (c) 2012-2022 Stanislas Rolland <typo3AAAA(arobas)sjbr.ca>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
* A copy is found in the textfile GPL.txt and important notices to the license
* from the author is found in LICENSE.txt distributed with these scripts.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
*/
/************************************************************\
*
* freeCap v1.4.1 Copyright 2005 Howard Yeend
* www.puremango.co.uk
*
* This file is part of freeCap.
*
* freeCap is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* freeCap is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with freeCap; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*
\************************************************************/
use SJBR\SrFreecap\Utility\RandomContentUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Utility dealing with image content
*/
class ImageContentUtility {
const BACKGROUND_TYPE_TRANSPARENT = 'Transparent';
const BACKGROUND_TYPE_WHITE_WITH_GRID = 'White with grid';
const BACKGROUND_TYPE_WHITE_WITH_SQUIGGLES = 'White with squiggles';
const BACKGROUND_TYPE_MORPHED_IMAGE_BLOCKS = 'Morphed image blocks';
const MERGE_BACKGROUND_OVER_CAPTCHA = 0;
const MERGE_CAPTCHA_OVER_BACKGROUND = 1;
const TEXT_COLOR_UNIFORM = 0;
const TEXT_COLOR_ONE_PER_CHARACTER = 1;
const SITE_TAG_POSITION_TOP = 0;
const SITE_TAG_POSITION_BOTTOM = 1;
const SITE_TAG_POSITION_BOTH = 2;
/**
* Write word on image
*
* @param int $width: width of the image in pixels
* @param int $height: width of the image in pixels
* @param string $word: the captcha word
* @param int $textColorType (see constants)
* @param array $textPosition: 'horizontal' => horizontal starting position of text, 'vertical' => vertical starting position of text
* @param int $colorMaximum: 'darkness' => maximum color darkness along any dimension, 'lightness' => maximum color lightness along any dimension
* @param string $backgroundType (see constants)
* @param array $fontLocations: array of font files locations
* @param array $fontWidths: array of font widths
* @param int $morphfactor: text morphing factor
* @return string GD image identifier of noisy background
*/
public static function writeWordOnImage($width, $height, $word, $textColorType, $textPosition, $colorMaximum, $backgroundType, $fontLocations, $fontWidths, $morphFactor = 1)
{
$image = ImageCreate($width, $height);
$image2 = ImageCreate($width, $height);
// Set background colour (can change to any colour not in possible $text_col range)
// it doesn't matter as it'll be transparent or coloured over.
// if you're using background type 'Morphed image blocks', you might want to try to ensure that the color chosen
// below doesn't appear too much in any of your background images.
$background = ImageColorAllocate($image, 254, 254, 254);
$background2 = ImageColorAllocate($image2, 254, 254, 254);
// Set transparencies
ImageColorTransparent($image, $background);
// $image2 transparent to allow characters to overlap slightly while morphing
ImageColorTransparent($image2, $background2);
// Fill backgrounds
ImageFill($image, 0, 0, $background);
ImageFill($image2, 0, 0, $background2);
// Write word in random starting X position
$word_start_x = RandomContentUtility::getRandomNumberInRange(5, $textPosition['horizontal']);
// Y positions jiggled about later
$word_start_y = $textPosition['vertical'];
// Get uniform color
if ($textColorType == self::TEXT_COLOR_UNIFORM) {
$textColor = RandomContentUtility::getRandomColor($colorMaximum['darkness'], $colorMaximum['lightness'], $backgroundType == self::BACKGROUND_TYPE_MORPHED_IMAGE_BLOCKS);
$textColor2 = ImageColorAllocate($image2, $textColor[0], $textColor[1], $textColor[2]);
}
// Write each character in different font
$textFontWidths = array();
$x_pos = $word_start_x;
for ($i = 0; $i < strlen($word); $i++) {
// Get changing color
if ($textColorType == self::TEXT_COLOR_ONE_PER_CHARACTER) {
$textColor = RandomContentUtility::getRandomColor($colorMaximum['darkness'], $colorMaximum['lightness'], $backgroundType == self::BACKGROUND_TYPE_MORPHED_IMAGE_BLOCKS);
$textColor2 = ImageColorAllocate($image2, $textColor[0], $textColor[1], $textColor[2]);
}
$fontIndex = RandomContentUtility::getRandomNumberInRange(0, sizeof($fontLocations)-1);
$font = ImageLoadFont($fontLocations[$fontIndex]);
ImageString($image2, $font, $x_pos, $word_start_y, $word[$i], $textColor2);
$textFontWidths[$i] = $fontWidths[$fontIndex];
$x_pos += $textFontWidths[$i];
}
// Morph Image
// Firstly move each character up or down a bit:
$x_pos = $word_start_x;
$y_pos = 0;
for ($i = 0; $i < strlen($word); $i++) {
// Move on Y axis
// Deviate at least 4 pixels between each letter
$prev_y = $y_pos;
do {
$y_pos = RandomContentUtility::getRandomNumberInRange(-5, 5);
} while ($y_pos < $prev_y + 2 && $y_pos > $prev_y - 2);
ImageCopy($image, $image2, $x_pos, $y_pos, $x_pos, 0, $textFontWidths[$i], $height);
$x_pos += $textFontWidths[$i];
}
ImageFilledRectangle($image2, 0, 0, $width, $height, $background2);
// Randomly morph each character individually on x-axis
// This is where the main distortion happens
$y_chunk = 1;
$morph_x = 0;
$orig_x = $word_start_x - $textFontWidths[0];
for ($j = 0; $j < strlen($word); $j++) {
$orig_x += $textFontWidths[$j];
$y_pos = 0;
for ($i = 0; $i <= $height; $i += $y_chunk) {
// morph x += so that instead of deviating from orig x each time, we deviate from where we last deviated to
// get it? instead of a zig zag, we get more of a sine wave.
// I wish we could deviate more but it looks crap if we do.
$morph_x += RandomContentUtility::getRandomNumberInRange(-$morphFactor, $morphFactor);
// had to change this to ImageCopyMerge when starting using ImageCreateTrueColor
// according to the manual; "when (pct is) 100 this function behaves identically to imagecopy()"
// but this is NOT true when dealing with transparencies...
ImageCopyMerge($image2, $image, $orig_x + $morph_x, $i + $y_pos, $orig_x, $i, $textFontWidths[$j], $y_chunk, 100);
}
}
ImageFilledRectangle($image, 0, 0, $width, $height, $background);
// Now do the same on the y-axis
// (much easier because we can just do it across the whole image, don't have to do it char-by-char)
$y_pos = 0;
$x_chunk = 1;
for ($i = 0; $i <= $width ; $i += $x_chunk) {
// Can result in image going too far off on Y-axis;
// not much I can do about that, apart from make image bigger
// again, I wish I could do 1.5 pixels
$y_pos += RandomContentUtility::getRandomNumberInRange(-1, 1);
ImageCopy($image, $image2, $i, $y_pos, $i, 0, $x_chunk, $height);
}
// Cleanup
ImageDestroy($image2);
return $image;
}
/**
* Generate noisy background
*
* @param int $width: width of the image in pixels
* @param int $height: width of the image in pixels
* @param string $word: the captcha word
* @param string $backgroundType (see constants)
* @param array $backgroundImages: array of background image file names
* @param boolean $morphBackground: if TRUE, the background will be morphed
* @param boolean $blurBackground: if TRUE, the background will be blurred
* @return string GD image identifier of noisy background
*/
public static function generateNoisyBackground($width, $height, $word, $backgroundType, $backgroundImages = array(), $morphBackground = true, $blurBackground = true)
{
$image = ImageCreateTrueColor($width, $height);
if ($backgroundType != self::BACKGROUND_TYPE_TRANSPARENT) {
// Generate noisy background, to be merged with CAPTCHA later
// any suggestions on how best to do this much appreciated
// sample code would be even better!
// I'm not an OCR expert (hell, I'm not even an image expert; puremango.co.uk was designed in MsPaint)
// so the noise models are based around my -guesswork- as to what would make it hard for an OCR prog
// ideally, the character obfuscation would be strong enough not to need additional background noise
// in any case, I hope at least one of the options given here provide some extra security!
$tempBackground = ImageCreateTrueColor($width*1.5, $height*1.5);
$background = ImageColorAllocate($image, 255, 255, 255);
ImageFill($image, 0, 0, $background);
$tempBackgroundColor = ImageColorAllocate($tempBackground, 255, 255, 255);
ImageFill($tempBackground, 0, 0, $tempBackgroundColor);
// We draw all noise onto tempBackground
// then if we're morphing, merge from tempBackground to image
// or if not, just copy a $width x $height portion of $tempBackground to $image
// tempBackground is much larger so that when morphing, the edges retain the noise.
switch ($backgroundType) {
case self::BACKGROUND_TYPE_WHITE_WITH_GRID:
// Draw grid on x
for ($i = RandomContentUtility::getRandomNumberInRange(6, 20); $i < $width*2; $i += RandomContentUtility::getRandomNumberInRange(10, 25)) {
ImageSetThickness($tempBackground, RandomContentUtility::getRandomNumberInRange(2, 6));
$textColor = RandomContentUtility::getRandomColor(100, 150);
$textColor2 = ImageColorAllocate($tempBackground, $textColor[0], $textColor[1], $textColor[2]);
ImageLine($tempBackground, $i, 0, $i, $height*2, $textColor2);
}
// Draw grid on y
for ($i = RandomContentUtility::getRandomNumberInRange(6, 20); $i < $height*2 ; $i += RandomContentUtility::getRandomNumberInRange(10, 25)) {
ImageSetThickness($tempBackground, RandomContentUtility::getRandomNumberInRange(2, 6));
$textColor = RandomContentUtility::getRandomColor(100, 150);
$textColor2 = ImageColorAllocate($tempBackground, $textColor[0], $textColor[1], $textColor[2]);
ImageLine($tempBackground, 0, $i, $width*2, $i , $textColor2);
}
break;
case self::BACKGROUND_TYPE_WHITE_WITH_SQUIGGLES:
ImageSetThickness($tempBackground, 4);
for ($i = 0; $i < strlen($word)+1; $i++) {
$textColor = RandomContentUtility::getRandomColor(100, 150);
$textColor2 = ImageColorAllocate($tempBackground, $textColor[0], $textColor[1], $textColor[2]);
$points = Array();
// Draw random squiggle for each character
// the longer the loop, the more complex the squiggle
// keep random so OCR can't say "if found shape has 10 points, ignore it"
// each squiggle will, however, be a closed shape, so OCR could try to find
// line terminations and start from there. (I don't think they're that advanced yet..)
for ($j = 1; $j < RandomContentUtility::getRandomNumberInRange(5, 10); $j++) {
$points[] = RandomContentUtility::getRandomNumberInRange(1*(20*($i+1)), 1*(50*($i+1)));
$points[] = RandomContentUtility::getRandomNumberInRange(30, $height+30);
}
ImagePolygon($tempBackground, $points, intval(sizeof($points)/2), $textColor2);
}
break;
case self::BACKGROUND_TYPE_MORPHED_IMAGE_BLOCKS:
// Take random chunks of $backgroundImages and paste them onto the background
for ($i = 0; $i < sizeof($backgroundImages); $i++) {
// Read each image and its size
$tempImages[$i] = ImageCreateFromJPEG(GeneralUtility::getFileAbsFileName($backgroundImages[$i]));
$tempWidths[$i] = imagesx($tempImages[$i]);
$tempHeights[$i] = imagesy($tempImages[$i]);
}
$blocksize = RandomContentUtility::getRandomNumberInRange(20, 60);
for ($i = 0; $i < $width*2; $i += $blocksize) {
// Could randomise blocksize here... hardly matters
for ($j = 0; $j < $height*2; $j += $blocksize) {
$imageIndex = RandomContentUtility::getRandomNumberInRange(0, sizeof($tempImages)-1);
$cut_x = RandomContentUtility::getRandomNumberInRange(0, $tempWidths[$imageIndex]-$blocksize);
$cut_y = RandomContentUtility::getRandomNumberInRange(0, $tempHeights[$imageIndex]-$blocksize);
ImageCopy($tempBackground, $tempImages[$imageIndex], $i, $j, $cut_x, $cut_y, $blocksize, $blocksize);
}
}
// Cleanup
for ($i = 0; $i < sizeof($tempImages); $i++) {
ImageDestroy($tempImages[$i]);
}
break;
}
if ($morphBackground) {
// Morph background
// We do this separately to the main text morph because:
// a) the main text morph is done char-by-char, this is done across whole image
// b) if an attacker could un-morph the background, it would un-morph the CAPTCHA
// hence background is morphed differently to text
// why do we morph it at all? it might make it harder for an attacker to remove the background
// morph_chunk 1 looks better but takes longer
// this is a different and less perfect morph than the one we do on the CAPTCHA
// occasonally you get some dark background showing through around the edges
// it doesn't need to be perfect as it's only the background.
$morph_chunk = RandomContentUtility::getRandomNumberInRange(1, 5);
$morph_y = 0;
for ($x = 0; $x < $width; $x += $morph_chunk) {
$morph_chunk = RandomContentUtility::getRandomNumberInRange(1, 5);
$morph_y += RandomContentUtility::getRandomNumberInRange(-1, 1);
ImageCopy($image, $tempBackground, $x, 0, $x+30, 30+$morph_y, $morph_chunk, $height*2);
}
ImageCopy($tempBackground, $image, 0, 0, 0, 0, $width, $height);
$morph_x = 0;
for ($y = 0; $y <= $height; $y += $morph_chunk) {
$morph_chunk = RandomContentUtility::getRandomNumberInRange(1, 5);
$morph_x += RandomContentUtility::getRandomNumberInRange(-1, 1);
ImageCopy($image, $tempBackground, $morph_x, $y, 0, $y, $width, $morph_chunk);
}
} else {
// Just copy tempBackground onto $image
ImageCopy($image, $tempBackground, 0, 0, 30, 30, $width, $height);
}
// Cleanup
ImageDestroy($tempBackground);
if ($blurBackground) {
$image = self::blurImage($image);
}
}
return $image;
}
/**
* Merge captcha image with background
*
* @param int $width: width of the image in pixels
* @param int $height: width of the image in pixels
* @param string $captchaImage: a GD image identifier
* @param string $backgroundImage: a GD image identifier
* @param string $backgroundType (see constants)
* @param int $mergeType: 0 - Background over captcha, 1 - Captcha over background (see constants)
* @param int $backgroundFadePercentage: fading factor for background
* @return string GD image identifier of merged image
*/
public static function mergeCaptchaWithBackground($width, $height, $captchaImage, $backgroundImage, $backgroundType, $mergeType)
{
if ($backgroundType != self::BACKGROUND_TYPE_TRANSPARENT) {
// How faded should the background be? (100=totally gone, 0=bright as the day)
// to test how much protection the bg noise gives, take a screenshot of the freeCap image
// and take it into a photo editor. play with contrast and brightness.
// If you can remove most of the background, then it's not a good enough percentage
switch ($backgroundType) {
case self::BACKGROUND_TYPE_WHITE_WITH_GRID:
case self::BACKGROUND_TYPE_WHITE_WITH_SQUIGGLES:
$backgroundFadePercentage = 65;
break;
case self::BACKGROUND_TYPE_MORPHED_IMAGE_BLOCKS:
$backgroundFadePercentage = 50;
break;
}
// Slightly randomize the background fade
$backgroundFadePercentage += RandomContentUtility::getRandomNumberInRange(-2, 2);
// Fade background
if ($backgroundType != self::BACKGROUND_TYPE_MORPHED_IMAGE_BLOCKS) {
$tempImage = ImageCreateTrueColor($width, $height);
$white = ImageColorAllocate($tempImage, 255, 255, 255);
ImageFill($tempImage, 0, 0, $white);
ImageCopyMerge($backgroundImage, $tempImage, 0, 0, 0, 0, $width, $height, $backgroundFadePercentage);
ImageDestroy($tempImage);
$colorFadePercentage = 50;
} else {
$colorFadePercentage = $backgroundFadePercentage;
}
// Merge background image with CAPTCHA image to create smooth background
if ($mergeType == self::MERGE_CAPTCHA_OVER_BACKGROUND) {
// Might want to not blur if using this method, otherwise leaves white-ish border around each letter
ImageCopyMerge($backgroundImage, $captchaImage, 0, 0, 0, 0, $width, $height, 100);
ImageCopy($captchaImage, $backgroundImage, 0, 0, 0, 0, $width, $height);
} else {
// Background over captcha
ImageCopyMerge($captchaImage, $backgroundImage, 0, 0, 0, 0, $width, $height, $colorFadePercentage);
}
}
return $captchaImage;
}
/**
* Blurs an image
*
* @param string $image: a GD image identifier
* @return string GD image identifier of blurred image
*/
public static function blurImage($image)
{
// w00t. my very own blur function
// in GD2, there's a gaussian blur function. bunch of bloody show-offs... :-)
$width = imagesx($image);
$height = imagesy($image);
$temp_im = ImageCreateTrueColor($width, $height);
$bg = ImageColorAllocate($temp_im, 150, 150, 150);
// preserves transparency if in orig image
ImageColorTransparent($temp_im, $bg);
// fill bg
ImageFill($temp_im, 0, 0, $bg);
// anything higher than 3 makes it totally unreadable
// might be useful in a 'real' blur function, though (ie blurring pictures not text)
$distance = 1;
// use $distance=30 to have multiple copies of the word. not sure if this is useful.
// blur by merging with itself at different x/y offsets:
ImageCopyMerge($temp_im, $image, 0, 0, 0, $distance, $width, $height-$distance, 70);
ImageCopyMerge($image, $temp_im, 0, 0, $distance, 0, $width-$distance, $height, 70);
ImageCopyMerge($temp_im, $image, 0, $distance, 0, 0, $width, $height, 70);
ImageCopyMerge($image, $temp_im, $distance, 0, 0, 0, $width, $height, 70);
// remove temp image
ImageDestroy($temp_im);
return $image;
}
/**
* Outputs the image with appropriate headers
*
* @param string $image: a GD image identifier
* @param string $imageType: type of image (jpg, gif or png)
* @return void
*/
public static function sendImage($image, $imageType)
{
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Pragma: no-cache');
header('Cache-Control: no-cache, no-store, must-revalidate');
// Setting privacy policy header for IE in popup window
header('P3P:CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"');
switch ($imageType) {
case 'jpg':
header('Content-Type: image/jpeg');
ImageJPEG($image);
break;
case 'gif':
header('Content-Type: image/gif');
ImageGIF($image);
break;
case 'png':
default:
header('Content-Type: image/png');
ImagePNG($image);
break;
}
}
}

View File

@@ -0,0 +1,159 @@
<?php
namespace SJBR\SrFreecap\Utility;
/*
* Copyright notice
*
* (c) 2009 Sebastian Kurfürst <sebastian@typo3.org>
* (c) 2013-2021 Stanislas Rolland <typo3AAAA(arobas)sjbr.ca>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
*/
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Http\ApplicationType;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Localization\Locales;
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
use TYPO3\CMS\Core\Site\SiteFinder;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
/**
* Localization helper which should be used to fetch appropriate words list or voice rendering language
*
*/
class LocalizationUtility
{
/**
* Key of the extension to which this class belongs
*
* @var string
*/
protected static $extensionKey = 'sr_freecap';
/**
* Gets the location of the words list based on configured language
*
* @param string $defaultWordsList: location of the default words list
* @return string the location of the words list to be used
*/
public static function getWordsListLocation($defaultWordsList = '')
{
$languageKeys = static::getLanguageKeys();
$initialWordsList = $defaultWordsList;
if (!trim($initialWordsList)) {
$initialWordsList = 'EXT:' . self::$extensionKey . '/Resources/Private/Captcha/Words/default_freecap_words';
}
$path = dirname(GeneralUtility::getFileAbsFileName($initialWordsList)) . '/';
$wordsListLocation = $path . $languageKeys['languageKey'] . '_freecap_words';
if (!is_file($wordsListLocation)) {
foreach ($languageKeys['alternativeLanguageKeys'] as $language) {
$wordsListLocation = $path . $language . '_freecap_words';
if (is_file($wordsListLocation)) {
break;
}
}
}
if (!is_file($wordsListLocation)) {
$wordsListLocation = $path . 'default_freecap_words';
if (!is_file($wordsListLocation)) {
$wordsListLocation = '';
}
}
return $wordsListLocation;
}
/**
* Gets the directory of wav files based on configured language
*
* @return string name of the directory containing the wav files to be used
*/
public static function getVoicesDirectory()
{
$languageKeys = static::getLanguageKeys();
$path = ExtensionManagementUtility::extPath(self::$extensionKey) . 'Resources/Private/Captcha/Voices/';
$voicesDirectory = $path . $languageKeys['languageKey'] . '/';
if (!is_dir($voicesDirectory)) {
foreach ($languageKeys['alternativeLanguageKeys'] as $language) {
$voicesDirectory = $path . $language . '/';
if (is_dir($voicesDirectory)) {
break;
}
}
}
if (!is_dir($voicesDirectory)) {
$voicesDirectory = $path . 'default/';
}
return $voicesDirectory;
}
/**
* Sets the currently active language/language_alt keys.
* Default values are "default" for language key and an empty array for language_alt key.
*
* @return array
*/
protected static function getLanguageKeys(): array
{
$languageKeys = [
'languageKey' => 'default',
'alternativeLanguageKeys' => [],
];
if (($GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface
&& ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isFrontend()
) {
$tsfe = static::getTypoScriptFrontendController();
$pageId = $tsfe->id;
$siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
$currentSite = $siteFinder->getSiteByPageId($pageId);
$languageAspect = GeneralUtility::makeInstance(Context::class)->getAspect('language');
// Current language
$currentSiteLanguageId = $languageAspect->getId();
$currentSiteLanguage = $currentSite->getLanguageById((int)$currentSiteLanguageId);
$languageKeys['languageKey'] = $currentSiteLanguage->getTypo3Language();
// Alternative languages
$alternativeLanguageIds = $languageAspect->getFallbackChain();
foreach ($alternativeLanguageIds as $alternativeLanguageId) {
$alternativeLanguage = $currentSite->getLanguageById((int)$alternativeLanguageId);
$languageKeys['alternativeLanguageKeys'][] = $alternativeLanguage->getTypo3Language();
}
if (empty($languageKeys['alternativeLanguageKeys'])) {
$locales = GeneralUtility::makeInstance(Locales::class);
if (in_array($languageKeys['languageKey'], $locales->getLocales())) {
foreach ($locales->getLocaleDependencies($languageKeys['languageKey']) as $language) {
$languageKeys['alternativeLanguageKeys'][] = $language;
}
}
}
} else {
$languageKeys['languageKey'] = $GLOBALS['BE_USER']->uc['lang'];
}
return $languageKeys;
}
/**
* @return TypoScriptFrontendController
*/
protected static function getTypoScriptFrontendController()
{
return $GLOBALS['TSFE'];
}
}

View File

@@ -0,0 +1,94 @@
<?php
namespace SJBR\SrFreecap\Utility;
/*
* Copyright notice
*
* (c) 2012-2020 Stanislas Rolland <typo32020(arobas)sjbr.ca>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
* A copy is found in the textfile GPL.txt and important notices to the license
* from the author is found in LICENSE.txt distributed with these scripts.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
*/
/**
* Utility dealing with mp3 audio content
* See http://www.theblog.ca/merge-mp3s-php
*/
class Mp3ContentUtility
{
var $mp3Content;
// Create a new mp3
public function __construct($file = '')
{
if ($file != '') {
$this->mp3Content = file_get_contents($file);
}
}
// Get the mp3 content
public function getContent()
{
return $this->mp3Content;
}
// Calculate where's the beginning of the sound file
protected function getStart()
{
$strlen = strlen($this->mp3Content);
for ($i=0; $i < $strlen; $i++) {
$v = substr($this->mp3Content, $i, 1);
$value = ord($v);
if ($value == 255) {
return $i;
}
}
}
// Calculate where's the end of the sound file
protected function getIdvEnd()
{
$strlen = strlen($this->mp3Content);
$str = substr($this->mp3Content, ($strlen - 128));
$str1 = substr($str, 0, 3);
if (strtolower($str1) == strtolower('TAG')) {
return $str;
} else {
return false;
}
}
// Remove the ID3 tags
public function striptags()
{
// Remove start stuff...
$newStr = '';
$s = $start = $this->getStart();
if ($s === false) {
return false;
} else {
$this->mp3Content = substr($this->mp3Content, $start);
}
//Remove end tag stuff
$end = $this->getIdvEnd();
if ($end !== false) {
$this->mp3Content = substr($this->mp3Content, 0, (strlen($this->mp3Content)-129));
}
return $this->mp3Content;
}
}

View File

@@ -0,0 +1,126 @@
<?php
namespace SJBR\SrFreecap\Utility;
/*
* Copyright notice
*
* (c) 2012-2020 Stanislas Rolland <typo32020(arobas)sjbr.ca>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
* A copy is found in the textfile GPL.txt and important notices to the license
* from the author is found in LICENSE.txt distributed with these scripts.
*
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
*/
/**
* Random content utility
*/
class RandomContentUtility
{
/**
* Returns a random number within the given range
*
* @param int $min: the lower boundary of the range
* @param int $max: the upper boundary of the range
* @return int the generated number
*/
public static function getRandomNumberInRange($min, $max)
{
if ($min > $max) {
$newMin = $max;
$newMax = $min;
} else {
$newMin = $min;
$newMax = $max;
}
return mt_rand($newMin, $newMax);
}
/**
* Returns a random color in a the form of an array of 3 numbers
*
* @param int $colorMaximumDarkness: maximum darkness along any dimension
* @param int $colorMaximumLightness: maximum lightness along any dimension
* @param boolean $darker: if true, produce a possibly darker image by default
* @return array the random color
*/
public static function getRandomColor ($colorMaximumDarkness, $colorMaximumLightness, $darker = false)
{
$color = [];
if ($darker) {
// Needs darker colour..
$minimum = isset($colorMaximumDarkness) ? $colorMaximumDarkness : 10;
$maximum = isset($colorMaximumLightness) ? $colorMaximumLightness : 100;
} else {
$minimum = isset($colorMaximumDarkness) ? $colorMaximumDarkness : 30;
$maximum = isset($colorMaximumLightness) ? $colorMaximumLightness : 140;
}
for ($i = 0; $i < 3 ; $i++) {
$color[] = self::getRandomNumberInRange($minimum, $maximum);
}
return $color;
}
/**
* Returns a random word
*
* @param boolean $useWordsList: if true, a word dictionary is used
* @param string $wordsList: dictionary file location
* @param boolean $numbersOnly: if true, use only numbers
*
* @return string random word
*/
public static function getRandomWord($useWordsList = false, $wordsList = '', $numbersOnly = false, $maxWordLength = 6)
{
if ($useWordsList && is_file($wordsList)) {
// Load dictionary and choose random word
// Keep dictionary in non-web accessible folder, or htaccess it
// or modify so word comes from a database; SELECT word FROM words ORDER BY rand() LIMIT 1
$words = @file($wordsList);
$word = strtolower($words[self::getRandomNumberInRange(0, sizeof($words)-1)]);
// Cut off line breaks
$word = preg_replace('/['.preg_quote(chr(10).chr(13)).']+/', '', $word);
unset($words);
} else {
// Based on code originally by breakzero at hotmail dot com
// (http://uk.php.net/manual/en/function.rand.php)
// generate pseudo-random string
// doesn't use ijtf as are easily mistaken
// I'm not using numbers because the custom fonts I've created don't support anything other than
// lowercase or space (but you can download new fonts or create your own using my GD fontmaker script)
if ($numbersOnly) {
$consonants = '123456789';
$vowels = '123456789';
} else {
$consonants = 'bcdghklmnpqrsvwxyz';
$vowels = 'aeuo';
}
$word = '';
$wordlen = self::getRandomNumberInRange(5, $maxWordLength);
for ($i = 0; $i < $wordlen; $i++) {
// Don't allow to start with 'vowel'
if (self::getRandomNumberInRange(0, 20) >= 10 && $i != 0) {
$word .= $vowels[self::getRandomNumberInRange(0, strlen($vowels)-1)];
} else {
$word .= $consonants[self::getRandomNumberInRange(0, strlen($consonants)-1)];
}
}
}
return $word;
}
}

View File

@@ -0,0 +1,97 @@
<?php
namespace SJBR\SrFreecap\Validation\Validator;
/*
* Copyright notice
*
* (c) 2012-2021 Stanislas Rolland <typo3AAAA(arobas)sjbr.ca>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
*/
use SJBR\SrFreecap\Domain\Repository\WordRepository;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Validation\Validator\AbstractValidator;
/**
* Captcha validator
*/
class CaptchaValidator extends AbstractValidator
{
/**
* Specifies whether this validator accepts empty values.
*
* If this is true, the validators isValid() method is not called in case of an empty value
* Note: A value is considered empty if it is null or an empty string!
* By default all validators except for NotEmpty and the Composite Validators accept empty values
*
* @var bool
*/
protected $acceptsEmptyValues = false;
/**
* @var string Name of the extension this controller belongs to
*/
protected $extensionName = 'SrFreecap';
/**
* Check the word that was entered against the hashed value
* Returns true, if the given property ($word) matches the session captcha value.
*
* @param string $word: the word that was entered and should be validated
* @return boolean true, if the word entered matches the hash value, false if an error occured
*/
public function isValid($word)
{
$isValid = false;
// This validator needs a frontend user session
if (is_object($GLOBALS ['TSFE']) && isset($GLOBALS ['TSFE']->fe_user)) {
// Get session data
$wordRepository = GeneralUtility::makeInstance(WordRepository::class);
$wordObject = $wordRepository->getWord();
$wordHash = $wordObject->getWordHash();
// Check the word hash against the stored hash value
if (!empty($wordHash) && !empty($word)) {
if ($wordObject->getHashFunction() == 'md5') {
// All freeCap words are lowercase.
// font #4 looks uppercase, but trust me, it's not...
if (md5(strtolower(utf8_decode($word))) == $wordHash) {
// Reset freeCap session vars
// Cannot stress enough how important it is to do this
// Defeats re-use of known image with spoofed session id
$wordRepository->cleanUpWord();
$isValid = true;
}
}
}
} else {
$isValid = empty($word);
}
if (!$isValid) {
// Please enter the word or number as it appears in the image. The entered value was incorrect.
$this->addError(
$this->translateErrorMessage(
'9221561048',
'SrFreecap'
),
9221561048
);
}
return $isValid;
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace SJBR\SrFreecap\Validation\Validator;
/***************************************************************
* Copyright notice
*
* (c) 2012-2018 Stanislas Rolland <typo3@sjbr.ca>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Validation\Validator\AbstractValidator;
/**
* Validator for True Type Font file existence
*
*/
class TtfFileValidator extends AbstractValidator
{
/**
* Returns true, if the given property ($propertyValue) is a valid number in the given range.
*
* If at least one error occurred, the result is false.
*
* @param mixed $value The value that should be validated
* @return boolean true if the value is within the range, otherwise false
*/
protected function isValid($value)
{
$isValid = true;
$absoluteFileName = GeneralUtility::getFileAbsFileName($value);
// Check file existence
if (!is_file($absoluteFileName)) {
// A file with the given name could not be found.
$this->addError(
$this->translateErrorMessage(
'9221561046',
'SrFreecap'
),
9221561046
);
$isValid = false;
} else {
// Check file extension
$pathInfo = pathinfo($absoluteFileName);
if (strtolower($pathInfo['extension']) !== 'ttf') {
// The specified file is not a True Type Font file.
$this->addError(
$this->translateErrorMessage(
'9221561047',
'SrFreecap'
),
9221561046
);
$isValid = false;
}
}
return $isValid;
}
}

View File

@@ -0,0 +1,199 @@
<?php
namespace SJBR\SrFreecap\View\AudioPlayer;
/*
* Copyright notice
*
* (c) 2013-2022 Stanislas Rolland <typo3AAAA(arobas)sjbr.ca>
* All rights reserved
*
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
* A copy is found in the textfile GPL.txt and important notices to the license
* from the author is found in LICENSE.txt distributed with these scripts.
*
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
*/
use SJBR\SrFreecap\Utility\AudioContentUtility;
use SJBR\SrFreecap\Utility\EncryptionUtility;
use SJBR\SrFreecap\Utility\LocalizationUtility;
use TYPO3Fluid\Fluid\View\ViewInterface;
/**
* Abstract class for rendering an audio version of the CAPTCHA
*/
class AbstractPlayFormat implements ViewInterface
{
/**
* @var string Name of the extension this view helper belongs to
*/
protected $extensionName = 'SrFreecap';
/**
* @var string Key of the extension this view helper belongs to
*/
protected $extensionKey = 'sr_freecap';
/**
* @var \TYPO3\CMS\Core\Domain\Model\Word
*/
protected $word;
/**
* Add a variable to the view data collection.
* Can be chained, so $this->view->assign(..., ...)->assign(..., ...); is possible
*
* @param string $key Key of variable
* @param mixed $value Value of object
* @return ViewInterface an instance of $this, to enable chaining
*/
public function assign($key, $value)
{
switch ($key) {
case 'word':
$this->word = $value;
break;
}
return $this;
}
/**
* Add multiple variables to the view data collection
*
* @param array $values array in the format array(key1 => value1, key2 => value2)
* @return ViewInterface an instance of $this, to enable chaining
*/
public function assignMultiple(array $values)
{
return $this;
}
/**
* Renders the audio version of captcha
*
* @return string The audio output to play
*/
public function render()
{
// Get the catcha word
$word = $this->getWord();
// Get the letter rensering files
$letterRenderingFiles = $this->getLetterRenderingFiles($word);
// Join the files
$audioContent = AudioContentUtility::joinAudioFiles($letterRenderingFiles);
// Output proper headers and echo audio content
$this->sendHeaders($audioContent);
// Send audio content
$this->sendAudioContent($audioContent);
// Return the audio content
return $audioContent;
}
/**
* Gets the word that was stored in session data
* @return string the retrieved and decoded word
*/
protected function getWord()
{
// Get cypher from session data
$cypher = $this->word->getWordCypher();
// Decrypt the word
$decryptedString = EncryptionUtility::decrypt($cypher);
return implode('-', preg_split('//', $decryptedString, -1, PREG_SPLIT_NO_EMPTY));
}
/**
* Builds an array of letter rendering files in the specified format
*
* @param string $word: the word to be spelled and played
* @param string $extension: the audio file extension being used
* @return array array of file names
*/
protected function getLetterRenderingFiles($word, $extension = 'wav')
{
$letterRenderingFiles = [];
// Split the word
$letters = preg_split('//', $word, -1, PREG_SPLIT_NO_EMPTY);
// Get the directory containing the wav files
$voicesDirectory = LocalizationUtility::getVoicesDirectory();
// Assemble the file names
foreach ($letters as $letter){
// Word lists are encoded in ISO-8859-1 (possibly in ISO-8859-2?)
$file = $voicesDirectory . ((isset($GLOBALS['TYPO3_CONF_VARS']['SYS']['UTF8filesystem']) && $GLOBALS['TYPO3_CONF_VARS']['SYS']['UTF8filesystem']) ? utf8_encode($letter) : $letter) . '.' . $extension;
if (is_file($file)) {
$letterRenderingFiles[] = $file;
}
}
return $letterRenderingFiles;
}
/**
* Sends headers appropriate for audio content
*
* @param string $audioContent: the audio content that will be sent
* @param string $mimeType: the audio mime type being used
* @return void
*/
protected function sendHeaders($audioContent, $mimeType = 'x-wav')
{
header('Content-Type: audio/' . $mimeType);
header('Content-Transfer-Encoding: binary');
header('Content-Length: ' . strlen($audioContent));
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Pragma: no-cache');
header('Cache-Control: no-cache, no-store, must-revalidate');
// Setting privacy policy header for IE in popup window
header('P3P:CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"');
}
/**
* Sends audio content
*
* @param string $audioContent: the audio content that will be sent
* @return void
*/
protected function sendAudioContent($audioContent)
{
echo $audioContent;
}
/**
* Renders a given section.
*
* @param string $sectionName Name of section to render
* @param array $variables The variables to use
* @param boolean $ignoreUnknown Ignore an unknown section and just return an empty string
* @return string rendered template for the section
* @throws Exception\InvalidSectionException
*/
public function renderSection($sectionName, array $variables = [], $ignoreUnknown = false)
{
return '';
}
/**
* Renders a partial.
*
* @param string $partialName
* @param string $sectionName
* @param array $variables
* @param boolean $ignoreUnknown Ignore an unknown section and just return an empty string
* @return string
*/
public function renderPartial($partialName, $sectionName, array $variables, $ignoreUnknown = false)
{
return '';
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace SJBR\SrFreecap\View\AudioPlayer;
/*
* Copyright notice
*
* (c) 2013-2018 Stanislas Rolland <typo3(arobas)sjbr.ca>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
* A copy is found in the textfile GPL.txt and important notices to the license
* from the author is found in LICENSE.txt distributed with these scripts.
*
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
*/
use SJBR\SrFreecap\Utility\AudioContentUtility;
/**
* Renders a mp3 audio version of the CAPTCHA
*/
class PlayMp3 extends AbstractPlayFormat
{
/**
* Renders the audio version of captcha
*
* @return string The audio output to play
*/
public function render()
{
// Get the catcha word
$word = $this->getWord();
// Get the letter rensering files
$letterRenderingFiles = $this->getLetterRenderingFiles($word, 'mp3');
// Join the files
$audioContent = AudioContentUtility::joinAudioFiles($letterRenderingFiles, 'mp3');
// Output proper headers
$this->sendHeaders($audioContent, 'mpeg');
// Send audio content
$this->sendAudioContent($audioContent);
// Return the audio content
return $audioContent;
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace SJBR\SrFreecap\View\AudioPlayer;
/***************************************************************
* Copyright notice
*
* (c) 2012-2013 Stanislas Rolland <typo3(arobas)sjbr.ca>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
* A copy is found in the textfile GPL.txt and important notices to the license
* from the author is found in LICENSE.txt distributed with these scripts.
*
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
/**
* Renders a wav audio version of the CAPTCHA
*
* @author Stanislas Rolland <typo3(arobas)sjbr.ca>
*/
class PlayWav extends AbstractPlayFormat {
}

View File

@@ -0,0 +1,264 @@
<?php
namespace SJBR\SrFreecap\View\ImageGenerator;
/*
* Copyright notice
*
* (c) 2005-2022 Stanislas Rolland <typo3AAAA(arobas)sjbr.ca>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
* A copy is found in the textfile GPL.txt and important notices to the license
* from the author is found in LICENSE.txt distributed with these scripts.
*
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
*/
/*
* Integrates freeCap v1.4.1 into TYPO3 and generates the freeCap CAPTCHA image.
*/
/*
*
* freeCap v1.4.1 Copyright 2005 Howard Yeend
* www.puremango.co.uk
*
* This file is part of freeCap.
*
* freeCap is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* freeCap is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with freeCap; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
use SJBR\SrFreecap\Domain\Model\Word;
use SJBR\SrFreecap\Domain\Repository\WordRepository;
use SJBR\SrFreecap\Utility\EncryptionUtility;
use SJBR\SrFreecap\Utility\ImageContentUtility;
use SJBR\SrFreecap\Utility\RandomContentUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
use TYPO3Fluid\Fluid\View\ViewInterface;
/**
* Renders a png image of the CAPTCHA
*/
class ShowPng implements ViewInterface
{
/**
* @var string Name of the extension this view helper belongs to
*/
protected $extensionName = 'SrFreecap';
/**
* @var string Name of the plugin this view helper belongs to
*/
protected $pluginName = 'ImageGenerator';
/**
* @var string Key of the extension this view helper belongs to
*/
protected $extensionKey = 'sr_freecap';
/**
* @var Word
*/
protected $word;
/**
* @var array Configuration of this view
*/
protected $settings;
/**
* Add a variable to the view data collection.
* Can be chained, so $this->view->assign(..., ...)->assign(..., ...); is possible
*
* @param string $key Key of variable
* @param mixed $value Value of object
* @return SJBR\SrFreecap\View\ImageGenerator\ShowPng an instance of $this, to enable chaining
* @api
*/
public function assign($key, $value)
{
switch ($key) {
case 'word':
$this->word = $value;
break;
case 'settings':
$this->settings = $value;
break;
}
return $this;
}
/**
* Add multiple variables to the view data collection
*
* @param array $values array in the format array(key1 => value1, key2 => value2)
* @return SJBR\SrFreecap\View\ImageGenerator\ShowPng an instance of $this, to enable chaining
* @api
*/
public function assignMultiple(array $values)
{
return $this;
}
/**
* Renders the captcha image
*
* @return string empty string (the image is sent here)
*/
public function render()
{
// Avoid Brute Force Attacks:
if (!$this->word->getAttempts()) {
$this->word->setAttempts(1);
} else {
$this->word->setAttempts($this->word->getAttempts() + 1);
// if more than ($this->settings['maxAttempts']) refreshes, block further refreshes
// can be negated by connecting with new session id
// could get round this by storing num attempts in database against IP
// could get round that by connecting with different IP (eg, using proxy servers)
// in short, there's little point trying to avoid brute forcing
// the best way to protect against BF attacks is to ensure the dictionary is not
// accessible via the web or use random string option
if ($this->word->getAttempts() > $this->settings['maxAttempts']) {
$this->word->setWordHash('');
$this->word->setWordCypher([]);
$this->word->setHashFunction('');
// Get an instance of the word repository
$wordRepository = GeneralUtility::makeInstance(WordRepository::class);
// Reset the word
$wordRepository->setWord($this->word);
$string = LocalizationUtility::translate('max_attempts', $this->extensionName);
$font = 5;
$width = imagefontwidth($font) * strlen($string);
$height = imagefontheight($font);
$image = ImageCreate($width+2, $height+20);
$background = ImageColorAllocate($image, 255,255,255);
ImageColorTransparent($image, $background);
$red = ImageColorAllocate($image, 255, 0, 0);
ImageString($image, $font, 1, 10, $string, $red);
ImageContentUtility::sendImage($image, $this->settings['imageFormat']);
ImageDestroy($image);
// Return an empty string
return '';
}
}
// Get random word
$word = RandomContentUtility::getRandomWord($this->settings['useWordsList'], $this->settings['wordsListLocation'], $this->settings['generateNumbers'] ?? 0, $this->settings['maxWordLength']);
// Save hash of word for comparison
// using hash so that if there's an insecurity elsewhere (eg on the form processor),
// an attacker could only get the hash
// also, shared servers usually give all users access to the session files
// echo `ls /tmp`; and echo `more /tmp/someone_elses_session_file`; usually work
// so even if your site is 100% secure, someone else's site on your server might not be
// hence, even if attackers can read the session file, they can't get the freeCap word
// (though most hashes are easy to brute force for simple strings)
$this->word->setWordHash(md5($word));
// We use a simple encrypt to prevent the session from being exposed
if ($this->settings['accessibleOutput'] ?? false) {
$this->word->setWordCypher(EncryptionUtility::encrypt($word));
}
// Build the image
$image = $this->buildImage($word, $this->settings['imageWidth'], $this->settings['imageHeight'], $this->settings['backgroundType']);
// Send the image
ImageContentUtility::sendImage($image, $this->settings['imageFormat']);
// Cleanup
ImageDestroy($image);
// Return an empty string
return '';
}
/**
* Builds the CAPTCHA image
*
* @param string $word
* @return string GD image identifier of image
*/
protected function buildImage($word, $width, $height, $backgroundType)
{
$image = ImageCreate($width, $height);
$background = ImageColorAllocate($image, 254, 254, 254);
// Write word on image
$image = ImageContentUtility::writeWordOnImage($width, $height, $word, $this->settings['textColor'], $this->settings['textPosition'], $this->settings['colorMaximum'], $backgroundType, $this->settings['fontLocations'], $this->settings['fontWidths'], $this->settings['morphFactor']);
// Blur edges
// Doesn't really add any security, but looks a lot nicer, and renders text a little easier to read
// for humans (hopefully not for OCRs, but if you know better, feel free to disable this function)
// (and if you do, let me know why)
$image = ImageContentUtility::blurImage($image);
if ($this->settings['imageFormat'] != 'jpg' && $backgroundType == ImageContentUtility::BACKGROUND_TYPE_TRANSPARENT) {
// Make background transparent
ImageColorTransparent($image, $background);
}
if ($backgroundType != ImageContentUtility::BACKGROUND_TYPE_TRANSPARENT) {
// Get noisy background
$image3 = ImageContentUtility::generateNoisyBackground($width, $height, $word, $backgroundType, $this->settings['backgroundImages'], $this->settings['backgroundMorph'], $this->settings['backgroundBlur']);
// Merge with obfuscated background
$image = ImageContentUtility::mergeCaptchaWithBackground($width, $height, $image, $image3, $backgroundType, $this->settings['mergeWithBackground']);
ImageDestroy($image3);
}
return $image;
}
/**
* Renders a given section.
*
* @param string $sectionName Name of section to render
* @param array $variables The variables to use
* @param boolean $ignoreUnknown Ignore an unknown section and just return an empty string
* @return string rendered template for the section
* @throws Exception\InvalidSectionException
*/
public function renderSection($sectionName, array $variables = [], $ignoreUnknown = false)
{
return '';
}
/**
* Renders a partial.
*
* @param string $partialName
* @param string $sectionName
* @param array $variables
* @param boolean $ignoreUnknown Ignore an unknown section and just return an empty string
* @return string
*/
public function renderPartial($partialName, $sectionName, array $variables, $ignoreUnknown = false)
{
return '';
}
}

View File

@@ -0,0 +1,154 @@
<?php
namespace SJBR\SrFreecap\ViewHelpers;
/*
* Copyright notice
*
* (c) 2013-2022 Stanislas Rolland <typo3AAAA(arobas)sjbr.ca>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
* A copy is found in the textfile GPL.txt and important notices to the license
* from the author is found in LICENSE.txt distributed with these scripts.
*
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
*/
use SJBR\SrFreecap\ViewHelpers\TranslateViewHelper;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Session\Backend\Exception\SessionNotFoundException;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper;
class AudioViewHelper extends AbstractTagBasedViewHelper
{
/**
* @var string Name of the extension this view helper belongs to
*/
protected $extensionName = 'SrFreecap';
/**
* @var string Name of the extension this view helper belongs to
*/
protected $pluginName = 'tx_srfreecap';
/**
* @var ConfigurationManagerInterface
*/
protected $configurationManager;
/**
* @param ConfigurationManagerInterface $configurationManager
* @return void
*/
public function injectConfigurationManager(ConfigurationManagerInterface $configurationManager)
{
$this->configurationManager = $configurationManager;
}
/**
* @var Context
*/
protected $context;
/**
* @param Context $context
*/
public function injectContext(Context $context)
{
$this->context = $context;
}
public function initializeArguments()
{
parent::initializeArguments();
$this->registerArgument('suffix', 'string', 'Suffix to be appended to the extenstion key when forming css class names', false, '');
}
/**
* Render the captcha audio rendering request icon
*
* @param string suffix to be appended to the extenstion key when forming css class names
* @return string The html used to render the captcha audio rendering request icon
*/
public function render($suffix = '')
{
// This viewhelper needs a frontend user session
if (!is_object($this->getTypoScriptFrontendController()) || !isset($this->getTypoScriptFrontendController()->fe_user)) {
throw new SessionNotFoundException('No frontend user found in session!');
}
$value = '';
// Get the plugin configuration
$settings = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_SETTINGS, $this->extensionName, $this->pluginName);
// Get the translation view helper
$translator = GeneralUtility::makeInstance(TranslateViewHelper::class);
// Generate the icon
if (($settings['accessibleOutput'] ?? false) && ($GLOBALS['TYPO3_CONF_VARS']['SYS']['UTF8filesystem'] ?? false)) {
$languageAspect = $this->context->getAspect('language');
$fakeId = substr(md5(uniqid(rand())), 0, 5);
$siteURL = GeneralUtility::getIndpEnv('TYPO3_SITE_URL');
$urlParams = [
'eIDSR' => 'sr_freecap_EidDispatcher',
'id' => $GLOBALS['TSFE']->id,
'pluginName' => 'AudioPlayer',
'actionName' => 'play',
'formatName' => 'wav',
'L' => $languageAspect->getId()
];
if ($this->getTypoScriptFrontendController()->MP) {
$urlParams['MP'] = $this->getTypoScriptFrontendController()->MP;
}
$audioURL = $siteURL . 'index.php?' . ltrim(GeneralUtility::implodeArrayForUrl('', $urlParams), '&');
if (isset($settings['accessibleOutputImage']) && $settings['accessibleOutputImage']) {
$value = '<input type="image" alt="' . $translator->render('click_here_accessible') . '"'
. ' title="' . $translator->render('click_here_accessible') . '"'
. ' src="' . $siteURL . PathUtility::stripPathSitePrefix(GeneralUtility::getFileAbsFileName($settings['accessibleOutputImage'])) . '"'
. ' onclick="' . $this->extensionName . '.playCaptcha(\'' . $fakeId . '\', \'' . $audioURL . '\', \'' . $translator->render('noPlayMessage') . '\');return false;" style="cursor: pointer;"'
. $this->getClassAttribute('image-accessible', $suffix) . ' />';
} else {
$value = '<span id="tx_srfreecap_captcha_playLink_' . $fakeId . '"'
. $this->getClassAttribute('accessible-link', $suffix) . '>' . $translator->render('click_here_accessible_before_link')
. '<a href="#" onClick="' . $this->extensionName . '.playCaptcha(\'' . $fakeId.'\', \'' . $audioURL . '\', \'' . $translator->render('noPlayMessage') . '\');return false;" style="cursor: pointer;" title="' . $translator->render('click_here_accessible') . '">'
. $translator->render('click_here_accessible_link') . '</a>'
. $translator->render('click_here_accessible_after_link') . '</span>';
}
$value .= '<span' . $this->getClassAttribute('accessible', $suffix) . ' id="tx_srfreecap_captcha_playAudio_' . $fakeId . '"></span>';
}
return $value;
}
/**
* Returns a class attribute with a class-name prefixed with $this->pluginName and with all underscores substituted to dashes (-)
*
* @param string $class The class name (or the END of it since it will be prefixed by $this->pluginName.'-')
* @param string suffix to be appended to the extenstion key when forming css class names
* @return string the class attribute with the combined class name (with the correct prefix)
*/
protected function getClassAttribute ($class, $suffix = '')
{
return ' class="' . trim(str_replace('_', '-', $this->pluginName) . ($suffix ? '-' . $suffix . '-' : '-') . $class) . '"';
}
/**
* @return TypoScriptFrontendController
*/
protected function getTypoScriptFrontendController()
{
return $GLOBALS['TSFE'];
}
}

View File

@@ -0,0 +1,174 @@
<?php
namespace SJBR\SrFreecap\ViewHelpers;
/*
* Copyright notice
*
* (c) 2013-2021 Stanislas Rolland <typo3AAAA(arobas)sjbr.ca>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
* A copy is found in the textfile GPL.txt and important notices to the license
* from the author is found in LICENSE.txt distributed with these scripts.
*
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
*/
use SJBR\SrFreecap\ViewHelpers\TranslateViewHelper;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Page\PageRenderer;
use TYPO3\CMS\Core\Session\Backend\Exception\SessionNotFoundException;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper;
class ImageViewHelper extends AbstractTagBasedViewHelper
{
/**
* @var string Name of the extension this view helper belongs to
*/
protected $extensionName = 'SrFreecap';
/**
* @var string Name of the extension this view helper belongs to
*/
protected $extensionKey = 'sr_freecap';
/**
* @var string Name of the plugin this view helper belongs to
*/
protected $pluginName = 'tx_srfreecap';
/**
* @var ConfigurationManagerInterface
*/
protected $configurationManager;
/**
* @param ConfigurationManagerInterface $configurationManager
*/
public function injectConfigurationManager(ConfigurationManagerInterface $configurationManager)
{
$this->configurationManager = $configurationManager;
}
/**
* @var PageRenderer
*/
protected $pageRenderer;
/**
* @param PageRenderer $pageRenderer
*/
public function injectPageRenderer(PageRenderer $pageRenderer)
{
$this->pageRenderer = $pageRenderer;
}
/**
* @var Context
*/
protected $context;
/**
* @param Context $context
*/
public function injectContext(Context $context)
{
$this->context = $context;
}
public function initializeArguments()
{
parent::initializeArguments();
$this->registerArgument('suffix', 'string', 'Suffix to be appended to the extenstion key when forming css class names', false, '');
}
/**
* Render the captcha image html
*
* @param string suffix to be appended to the extenstion key when forming css class names
* @return string The html used to render the captcha image
*/
public function render($suffix = '')
{
// This viewhelper needs a frontend user session
if (!is_object($this->getTypoScriptFrontendController()) || !isset($this->getTypoScriptFrontendController()->fe_user)) {
throw new SessionNotFoundException('No frontend user found in session!');
}
$value = '';
// Include the required JavaScript
$this->pageRenderer->addJsFooterFile(PathUtility::stripPathSitePrefix(ExtensionManagementUtility::extPath($this->extensionKey)) . 'Resources/Public/JavaScript/freeCap.js');
// Disable caching
$this->getTypoScriptFrontendController()->no_cache = true;
// Get the translation view helper
$translator = GeneralUtility::makeInstance(TranslateViewHelper::class);
// Generate the image url
$fakeId = substr(md5(uniqid(rand())), 0, 5);
$siteURL = GeneralUtility::getIndpEnv('TYPO3_SITE_URL');
$languageAspect = $this->context->getAspect('language');
$urlParams = [
'eIDSR' => 'sr_freecap_EidDispatcher',
'id' => $this->getTypoScriptFrontendController()->id,
'pluginName' => 'ImageGenerator',
'actionName' => 'show',
'formatName' => 'png',
'L' => $languageAspect->getId()
];
if ($this->getTypoScriptFrontendController()->MP) {
$urlParams['MP'] = $this->getTypoScriptFrontendController()->MP;
}
$urlParams['set'] = $fakeId;
$imageUrl = $siteURL . 'index.php?' . ltrim(GeneralUtility::implodeArrayForUrl('', $urlParams), '&');
// Generate the html text
$value = '<img' . $this->getClassAttribute('image', $suffix) . ' id="tx_srfreecap_captcha_image_' . $fakeId . '"'
. ' src="' . htmlspecialchars($imageUrl) . '"'
. ' alt="' . $translator->render('altText') . ' "/>'
. '<span' . $this->getClassAttribute('cant-read', $suffix) . '>' . $translator->render('cant_read1')
. ' <a href="#" onclick="this.blur();' . $this->extensionName . '.newImage(\'' . $fakeId . '\', \'' . $translator->render('noImageMessage').'\');return false;">'
. $translator->render('click_here') . '</a>'
. $translator->render('cant_read2') . '</span>';
return $value;
}
/**
* Returns a class attribute with a class-name prefixed with $this->pluginName and with all underscores substituted to dashes (-)
*
* @param string $class The class name (or the END of it since it will be prefixed by $this->pluginName.'-')
* @param string suffix to be appended to the extenstion key when forming css class names
* @return string the class attribute with the combined class name (with the correct prefix)
*/
protected function getClassAttribute($class, $suffix = '')
{
return ' class="' . trim(str_replace('_', '-', $this->pluginName) . ($suffix ? '-' . $suffix . '-' : '-') . $class) . '"';
}
/**
* @return TypoScriptFrontendController
*/
protected function getTypoScriptFrontendController()
{
return $GLOBALS['TSFE'];
}
}

View File

@@ -0,0 +1,147 @@
<?php
namespace SJBR\SrFreecap\ViewHelpers;
/*
* Copyright notice
*
* (c) 2013-2022 Stanislas Rolland <typo3AAAA@sjbr.ca>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
* A copy is found in the textfile GPL.txt and important notices to the license
* from the author is found in LICENSE.txt distributed with these scripts.
*
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
*/
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
/**
* Translate a key from locallang. The files are loaded from the folder
* "Resources/Private/Language/".
*
* Handle salutation suffixes
*
* == Examples ==
*
* <code title="Translate key">
* <f:translate key="key1" />
* </code>
* <output>
* // value of key "key1" in the current website language
* </output>
*
* <code title="Keep HTML tags">
* <f:translate key="htmlKey" htmlEscape="false" />
* </code>
* <output>
* // value of key "htmlKey" in the current website language, no htmlspecialchars applied
* </output>
*
* <code title="Translate key from custom locallang file">
* <f:translate key="LLL:EXT:myext/Resources/Private/Language/locallang.xml:key1" />
* </code>
* <output>
* // value of key "key1" in the current website language
* </output>
*
* <code title="Inline notation with arguments and default value">
* {f:translate(key: 'argumentsKey', arguments: {0: 'dog', 1: 'fox'}, default: 'default value')}
* </code>
* <output>
* // value of key "argumentsKey" in the current website language
* // with "%1" and "%2" are replaced by "dog" and "fox" (printf)
* // if the key is not found, the output is "default value"
* </output>
*/
class TranslateViewHelper extends AbstractViewHelper
{
/**
* @var string Name of the extension this view helper belongs to
*/
protected $extensionName = 'SrFreecap';
/**
* @var string Name of the extension this view helper belongs to
*/
protected $pluginName = 'tx_srfreecap';
/**
* @var array List of allowed suffixes
*/
protected $allowedSuffixes = ['formal', 'informal'];
/**
* @var ConfigurationManagerInterface
*/
protected $configurationManager;
/**
* @param ConfigurationManagerInterface $configurationManager
* @return void
*/
public function injectConfigurationManager(ConfigurationManagerInterface $configurationManager)
{
$this->configurationManager = $configurationManager;
}
public function initializeArguments()
{
$this->registerArgument('key', 'string', 'The language key to translate', true);
$this->registerArgument('default', 'string', 'Value to be used when the key is not found');
$this->registerArgument('htmlEscape', 'boolean', 'Whether to escape html', false, true);
$this->registerArgument('arguments', 'array', 'Arguments to be replaced in the string');
}
/**
* Translate a given key or use the tag body as default.
*
* @param string $key The locallang key
* @param string $default if the given locallang key could not be found, this value is used. . If this argument is not set, child nodes will be used to render the default
* @param boolean $htmlEscape true if the result should be htmlescaped. This won't have an effect for the default value
* @param array $arguments Arguments to be replaced in the resulting string
* @return string The translated key or tag body if key doesn't exist
* @author Christopher Hlubek <hlubek@networkteam.com>
* @author Bastian Waidelich <bastian@typo3.org>
*/
public function render($key = null)
{
if ($this->hasArgument('key')) {
$key = $this->arguments['key'];
}
$value = null;
$default = $this->hasArgument('default') ? $this->arguments['default'] : '';
$htmlEscape = $this->hasArgument('htmlEscape') ? $this->arguments['htmlEscape'] : false;
$arguments = $this->hasArgument('arguments') ? $this->arguments['arguments'] : [];
// If the suffix is allowed and we have a localized string for the desired salutation, we'll take that.
$settings = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_SETTINGS, $this->extensionName, $this->pluginName);
if (isset($settings['salutation']) && in_array($settings['salutation'], $this->allowedSuffixes, 1)) {
$expandedKey = $key . '_' . $settings['salutation'];
$value = LocalizationUtility::translate($expandedKey, $this->extensionName, $arguments);
}
if ($value === null) {
$value = LocalizationUtility::translate($key, $this->extensionName, $arguments);
}
if ($value === null) {
$value = $default !== null ? $default : $this->renderChildren();
} elseif ($htmlEscape) {
$value = htmlspecialchars($value);
}
return $value;
}
}