Initial commit

This commit is contained in:
2018-08-23 16:44:53 +02:00
commit 1f06564778
115 changed files with 13984 additions and 0 deletions

View File

@@ -0,0 +1,147 @@
<?php
/**
* The MIT License (MIT)
*
* Copyright (c) 2018 magdev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author magdev
* @copyright 2018 Marco Grätsch
* @package magdev/dossier
* @license http://opensource.org/licenses/MIT MIT License
*/
namespace Magdev\Dossier\Service;
use Magdev\Dossier\Analyzer\Base\AnalyzableInterface;
use Magdev\Dossier\Analyzer\Base\AnalyzerInterface;
use Magdev\Dossier\Analyzer\Base\AnalyzerException;
/**
* Service to analyze models
*
* @author magdev
*/
class AnalyzerService
{
/**
* Configuration service
* @var \Magdev\Dossier\Service\AnalyzerService
*/
protected $config = null;
/**
* Monolog service
* @var \Magdev\Dossier\Service\MonologService
*/
protected $logger = null;
/**
* Analyzer objects
* @var \ArrayObject
*/
protected $analyzers = null;
/**
* Constructor
*
* @param \Magdev\Dossier\Service\ConfigService $config
* @param \Magdev\Dossier\Service\MonologService $logger
*/
public function __construct(ConfigService $config, MonologService $logger)
{
$this->config = $config;
$this->logger = $logger;
$this->analyzers = new \ArrayObject();
}
/**
* Get all analyzers
*
* @return \ArrayObject
*/
public function getAnalyzers(): \ArrayObject
{
return $this->analyzers;
}
/**
* Add an analyzer
*
* @param \Magdev\Dossier\Analyzer\Base\AnalyzerInterface $analyzer
* @return \Magdev\Dossier\Service\AnalyzerService
* @throws \Magdev\Dossier\Analyzer\Base\AnalyzerException
*/
public function addAnalyzer(AnalyzerInterface $analyzer): AnalyzerService
{
if ($this->hasAnalyzer($analyzer->getName())) {
throw new AnalyzerException('Analyzer with name '.$name.' already exists');
}
$this->analyzers->append($analyzer);
$this->logger->debug('Analyzer '.get_class($analyzer).' added with name '.$analyzer->getName());
return $this;
}
/**
* Check if an analyzer name is already taken
*
* @param string $name
* @return bool
*/
public function hasAnalyzer(string $name): bool
{
return $this->getAnalyzers() instanceof AnalyzableInterface;
}
/**
* Get an analyzer by name
*
* @param string $name
* @return \Magdev\Dossier\Analyzer\Base\AnalyzerInterface
*/
public function getAnalyzer(string $name): ?AnalyzerInterface
{
foreach ($this->analyzers as $a) {
if ($a->getName() == $name) {
return $a;
}
}
return null;
}
/**
* Set analyzers
*
* @param \ArrayObject $analyzers
* @return AnalyzerService
*/
public function setAnalyzers(\ArrayObject $analyzers): AnalyzerService
{
$this->analyzers = $analyzers;
return $this;
}
}

View File

@@ -0,0 +1,464 @@
<?php
/**
* The MIT License (MIT)
*
* Copyright (c) 2018 magdev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author magdev
* @copyright 2018 Marco Grätsch
* @package magdev/dossier
* @license http://opensource.org/licenses/MIT MIT License
*/
namespace Magdev\Dossier\Service;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\Config\ConfigCache;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Yaml\Yaml;
use Symfony\Component\Dotenv\Dotenv;
use Symfony\Component\Finder\Finder;
use Magdev\Dossier\Config\ConfigLoader;
use Magdev\Dossier\Config\Config;
use Magdev\Dossier\Style\DossierStyle;
use Adbar\Dot;
/**
* Cnfiguration service
*
* @author magdev
*/
class ConfigService
{
const FORMAT_FLAT_ARRAY = 1;
const FORMAT_TABLE_ROWS = 2;
const FORMAT_PHP_ARRAY = 3;
const VALUE_SCALAR = 1;
const VALUE_INDEXED_ARRAY = 2;
/**
* The config object
* @var \Adbar\Dot
*/
protected $config = null;
/**
* The global config object
* @var \Adbar\Dot
*/
protected $global = null;
/**
* Constructor
*/
public function __construct()
{
if (file_exists(PROJECT_ROOT.'/.env')) {
$dotenv = new Dotenv();
$dotenv->load(PROJECT_ROOT.'/.env');
}
$this->loadConfig();
mb_internal_encoding(strtoupper($this->config->get('charset')));
}
/**
* Get the differences between current and global config
*
* @return array
*/
public function getDiff(array $current = null, array $global = null): array
{
if (is_null($current)) {
$current = $this->config->all();
}
if (is_null($global)) {
$global = $this->global->all();
}
$diff = array();
foreach ($current as $key => $value) {
if (is_array($value)) {
if (!isset($global[$key]) || !is_array($global[$key])) {
$diff[$key] = $value;
} else {
$newDiff = $this->getDiff($value, $global[$key]);
if (!empty($newDiff)) {
$diff[$key] = $newDiff;
}
}
} else if (!array_key_exists($key, $global) || $global[$key] !== $value) {
$diff[$key] = $value;
}
}
return $diff;
}
/**
* Get the Dot object
*
* @return \Adbar\Dot
*/
public function getConfig(): Dot
{
return $this->config;
}
/**
* Get the global Dot object
*
* @return \Adbar\Dot
*/
public function getGlobalConfig(): Dot
{
return $this->global;
}
/**
* Delegate method calls to Dot
*
* @param string $method
* @param array $args
* @throws \BadFunctionCallException
* @return mixed
*/
public function __call(string $method, array $args)
{
if (!method_exists($this->config, $method)) {
throw new \BadFunctionCallException('Unknown method: '.$method);
}
return call_user_func_array(array($this->config, $method), $args);
}
/**
* Get all configuration values
*
* @param int $format One of FORMAT_* constants
* @return array
*/
public function all(int $format = self::FORMAT_PHP_ARRAY): array
{
switch ($format) {
case self::FORMAT_FLAT_ARRAY:
$data = $this->allFlat();
break;
default:
case self::FORMAT_PHP_ARRAY:
$data = $this->config->all();
break;
}
ksort($data, SORT_NATURAL);
return $data;
}
/**
* Set a configuration value
*
* @param array|int|string $keys
* @param mixed $value
* @param bool $global
* @return \Magdev\Dossier\Service\ConfigService
*/
public function set($keys, $value, bool $global = false): ConfigService
{
$this->config->set($keys, $value);
if ($global) {
$this->global->set($keys, $value);
}
return $this;
}
/**
* Unset a configuration value
*
* @param unknown $keys
* @return \Magdev\Dossier\Service\ConfigService
*/
public function unset($keys): ConfigService
{
if ($this->config->has($keys)) {
$this->config->delete($keys);
}
if ($this->global->has($keys)) {
$this->global->delete($keys);
}
return $this;
}
/**
* Save the global configuration
*
* @return \Magdev\Dossier\Service\ConfigService
*/
public function saveGlobalConfig(): ConfigService
{
$yaml = '';
$config = $this->global->all();
if (!empty($config)) {
$yaml = Yaml::dump($config, 4, 3);
}
return $this->saveYaml(getenv('HOME').'/.dossier', $yaml);
}
/**
* Save the local configuration
*
* @return \Magdev\Dossier\Service\ConfigService
*/
public function saveProjectConfig(): ConfigService
{
$yaml = '';
$config = $this->getDiff();
if (!empty($config)) {
$yaml = Yaml::dump($config, 4, 3);
}
return $this->saveYaml(PROJECT_ROOT.'/.conf', $yaml);
}
/**
* Check if a value is stored in global configuration
*
* @param string $path
* @param mixed $value
* @return bool
*/
public function isGlobalConfig(string $path, $value): bool
{
if ($this->global->has($path) && $this->global->get($path) == $value) {
return true;
}
return false;
}
/**
* Save configuration
*
* @return \Magdev\Dossier\Service\ConfigService
*/
public function save(): ConfigService
{
return $this->saveGlobalConfig()
->saveProjectConfig()
->loadConfig();
}
/**
* Get all config values as flat array
*
* @return array
*/
public function allFlat(): array
{
$iterator = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($this->all()), \RecursiveIteratorIterator::SELF_FIRST);
$path = array();
$flatArray = array();
foreach ($iterator as $key => $value) {
$path[$iterator->getDepth()] = $key;
if (!is_array($value)) {
$flatArray[implode('.', array_slice($path, 0, $iterator->getDepth() + 1))] = $value;
}
}
return $flatArray;
}
/**
* Get all config values as table rows
*
* @param \Magdev\Dossier\Style\DossierStyle $io
* @param int $booleanWidth
* @return array
*/
public function allTable(DossierStyle $io, int $booleanWidth = null): array
{
$all = $this->allFlat();
$rows = array();
foreach ($all as $path => $value) {
$global = $this->isGlobalConfig($path, $value);
switch (gettype($value)) {
case 'string':
if ($value !== trim($value)) {
$value = "'$value'";
}
break;
case 'bool':
case 'boolean':
$value = $value ? 'true' : 'false';
break;
}
$rows[] = array($path, $value, $io->align($io->bool($global), $booleanWidth, DossierStyle::ALIGN_CENTER));
}
return $rows;
}
/**
* Find all themes in available directories
*
* @return array
*/
public function findThemes(bool $all = true): array
{
$folders = array(
PROJECT_ROOT.'/.conf/tpl',
getenv('HOME').'/.dossier/tpl',
DOSSIER_ROOT.'/app/tpl'
);
$finder = new Finder();
$finder->ignoreUnreadableDirs()
->directories()
->sortByName()
->depth('== 0');
foreach ($folders as $folder) {
if (file_exists($folder)) {
$finder->in($folder);
}
}
$themes = array();
foreach ($finder as $file) {
/* @var $file \SplFileInfo */
$path = str_replace(
array(DOSSIER_ROOT, PROJECT_ROOT, getenv('HOME')),
array('${DOSSIER}', '${PROJECT}', '${HOME}'),
$file->getPathInfo()->getRealpath()
);
$themes[$file->getFilename()] = $path;
}
ksort($themes);
if ($all) {
return $themes;
}
$themesSorted = array();
foreach ($themes as $name => $path) {
$index = array_search($path, $folders);
if (!array_key_exists($name, $themesSorted)) {
$themesSorted[$name] = $path;
} else {
$existingIndex = array_search($themesSorted[$name], $folders);
if ($index < $existingIndex) {
$themesSorted[$name] = $path;
}
}
}
return $themesSorted;
}
/**
* Save YAML to config file
*
* @param string $targetDir
* @param string $yaml
* @return \Magdev\Dossier\Service\ConfigService
*/
protected function saveYaml(string $targetDir, string $yaml): ConfigService
{
if (!is_dir($targetDir)) {
mkdir($targetDir, 0755, true);
}
file_put_contents($targetDir.'/dossier.yaml', $yaml);
return $this;
}
/**
* Load the configuration
*
* @return ConfigService
*/
protected function loadConfig(): ConfigService
{
clearstatcache();
$configDirectories = array(DOSSIER_ROOT.'/app/conf', getenv('HOME').'/.dossier', PROJECT_ROOT.'/.conf');
$env = getenv('APP_ENV') ?: 'prod';
$cachePath = DOSSIER_CACHE.'/src/'.strtolower($env).'DossierCachedConfig.php';
$fileLocator = new FileLocator($configDirectories);
$loader = new ConfigLoader($fileLocator);
$cache = new ConfigCache($cachePath, getenv('APP_DEBUG'));
if (!$cache->isFresh()) {
$resources = array();
$configFiles = $fileLocator->locate('dossier.yaml', null, false);
$envConfigFiles = $fileLocator->locate('dossier_'.strtolower($env).'.yaml', null, false);
if (!is_array($envConfigFiles)) {
$envConfigFiles = array($envConfigFiles);
}
$configFiles = array_merge($configFiles, $envConfigFiles);
foreach ($configFiles as $configFile) {
$loader->load($configFile);
$resources[] = new FileResource($configFile);
}
$code = '<?php'.PHP_EOL.'return '.(string) $loader.';'.PHP_EOL;
$cache->write($code, $resources);
}
$config = require $cachePath;
$this->config = new Dot($config['config']);
$this->global = new Dot($config['global']);
return $this;
}
/**
* Check if an array is an index flat array
*
* @param array $array
* @return bool
*/
protected function isIndexedFlatArray(array $array): bool
{
return array_key_exists(0, $array) !== false && is_scalar($array[0]) !== false;
}
}

View File

@@ -0,0 +1,39 @@
<?php
/**
* The MIT License (MIT)
*
* Copyright (c) 2018 magdev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author magdev
* @copyright 2018 Marco Grätsch
* @package magdev/dossier
* @license http://opensource.org/licenses/MIT MIT License
*/
namespace Magdev\Dossier\Service\Exceptions;
use Symfony\Component\Console\Exception\RuntimeException;
class ServiceExcepton extends RuntimeException
{
}

View File

@@ -0,0 +1,98 @@
<?php
/**
* The MIT License (MIT)
*
* Copyright (c) 2018 magdev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author magdev
* @copyright 2018 Marco Grätsch
* @package magdev/dossier
* @license http://opensource.org/licenses/MIT MIT License
*/
namespace Magdev\Dossier\Service;
/**
* Formatter Service
*
* @author magdev
*/
class FormatterService
{
const YEAR = 31536000;
const MONTH = 2635200;
const DAY = 86400;
/**
* Translator
* @var \Symfony\Component\Translation\Translator
*/
protected $translator = null;
/**
* Constructor
*
* @param \Magdev\Resume\Service\TranslatorService $translator
*/
public function __construct(TranslatorService $translator)
{
$this->translator = $translator->getTranslator();
}
/**
* Format seconds to years, months
*
* @TODO Implement translation
* @param int $seconds
* @param string $key
* @return string
*/
public function formatYearMonthDuration(int $seconds): string
{
$null = new \DateTime('@0');
$length = new \DateTime("@$seconds");
$years = floor($seconds / self::YEAR);
$months = floor($seconds - ($seconds * self::YEAR));
$msg = $this->translator->transChoice('date.format.year', $years);
if ($months) {
$msg .= ', '.$this->translator->transChoice('date.format.month', $months);
}
return $null->diff($length)->format($msg);
}
/**
* Wrapper for formatYearMonthDuration()
*
* @deprecated
* @param int $seconds
* @return string
*/
public function formatExperience(int $seconds): string
{
return $this->formatYearMonthDuration($seconds);
}
}

View File

@@ -0,0 +1,213 @@
<?php
/**
* The MIT License (MIT)
*
* Copyright (c) 2018 magdev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author magdev
* @copyright 2018 Marco Grätsch
* @package magdev/dossier
* @license http://opensource.org/licenses/MIT MIT License
*/
namespace Magdev\Dossier\Service;
use Mni\FrontYAML;
use Mni\FrontYAML\Bridge\Parsedown\ParsedownParser;
use Symfony\Component\Yaml\Yaml;
use Magdev\Dossier\Model\Base\ModelInterface;
use Magdev\Dossier\Model\Base\ModelExportableInterface;
use Magdev\Dossier\Model\CurriculumVitae;
use Magdev\Dossier\Model\Person;
use Magdev\Dossier\Model\Intro;
use Magdev\Dossier\Model\Project;
use Magdev\Dossier\Service\Exceptions\ServiceExcepton;
use Magdev\Dossier\Util\Base\DataCollectorInterface;
use Magdev\Dossier\Util\DataCollector;
/**
* Markdown service
*
* @author magdev
*/
class MarkdownService
{
/**
* Parser object
* @var \Mni\FrontYAML\Parser
*/
protected $parser = null;
/**
* Monolog service
* @var \Magdev\Dossier\Service\MonologService
*/
protected $logger = null;
/**
* Formatter service
* @var \Magdev\Dossier\Service\MonologService
*/
protected $formatter = null;
/**
* Constructor
*/
public function __construct(MonologService $logger, FormatterService $formatter)
{
$this->parser = new FrontYAML\Parser(null, new ParsedownParser(new \ParsedownExtra()));
$this->logger = $logger;
$this->formatter = $formatter;
}
/**
* Get the Markdown parser object
*
* @return \Mni\FrontYAML\Parser
*/
public function getParser(): FrontYAML\Parser
{
return $this->parser;
}
/**
* Get a dataCollector with models of markdown files
*
* @param string $sort
* @return \Magdev\Dossier\Util\Base\DataCollectorInterface
*/
public function getFileSet(string $sort = CurriculumVitae::SORT_DESC): DataCollectorInterface
{
$person = new Person($this->getDocument(PROJECT_ROOT.'/person.md'));
$intro = new Intro($this->getDocument(PROJECT_ROOT.'/intro.md'));
$cv = new CurriculumVitae($this->formatter);
$files = new \FilesystemIterator(PROJECT_ROOT.'/cv');
foreach ($files as $file) {
/* @var $file \SplFileInfo */
$document = $this->getDocument($file->getPathname());
$cv->append(new CurriculumVitae\Entry($document));
}
$cv->setSortDirection($sort)->sort();
$projects = new \ArrayObject();
$files = new \FilesystemIterator(PROJECT_ROOT.'/projects');
foreach ($files as $file) {
/* @var $file \SplFileInfo */
$document = $this->getDocument($file->getPathname());
$projects->append(new Project($document));
}
return new DataCollector(array(
'intro' => $intro,
'person' => $person,
'cv' => $cv,
'projects' => $projects,
));
}
/**
* Parse a markdown file
*
* @param $srcfile string
* @return \Mni\FrontYAML\Document
*/
public function getDocument(string $srcfile, bool $parseMarkdown = true): FrontYAML\Document
{
$content = '';
if (file_exists($srcfile)) {
$content = file_get_contents($srcfile);
$this->logger->debug(mb_strlen($content).' bytes loaded from file '.$srcfile);
}
return $this->getParser()->parse($content, $parseMarkdown);
}
/**
* Save Markdown data
*
* @param string $path
* @param array $data
* @param string $text
* @param bool $overwrite
* @return \Magdev\Dossier\Service\MarkdownService
*/
public function save(string $path, array $data, string $text, bool $overwrite = true): MarkdownService
{
$content = '';
if (sizeof($data)) {
$content .= '---'.PHP_EOL;
$content .= Yaml::dump($data, 1, 2);
$content .= '---'.PHP_EOL;
}
if ($text) {
$content .= $text.PHP_EOL;
}
return $this->saveFile($path, $content, $overwrite);
}
/**
* Save the contents to a file and creates backup copy
*
* @param string $path
* @param string $content
* @param bool $overwrite
* @return \Magdev\Dossier\Service\MarkdownService
*/
protected function saveFile(string $path, string $content, bool $overwrite = true): MarkdownService
{
if (!is_dir(dirname($path))) {
mkdir(dirname($path), 0755, true);
}
if ($content) {
if ($overwrite) {
if (file_exists($path.'~')) {
@unlink($path.'~');
}
@copy($path, $path.'~');
if (file_put_contents($path, $content) === false) {
throw new ServiceExcepton('Error while writing '.$path);
}
$this->logger->debug(mb_strlen($content).' bytes written to file '.$path);
return $this;
}
if (!file_exists($path)) {
if (file_put_contents($path, $content) === false) {
throw new ServiceExcepton('Error while writing '.$path);
}
$this->logger->debug(mb_strlen($content).' bytes written to file '.$path);
}
}
return $this;
}
}

View File

@@ -0,0 +1,77 @@
<?php
/**
* The MIT License (MIT)
*
* Copyright (c) 2018 magdev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author magdev
* @copyright 2018 Marco Grätsch
* @package magdev/dossier
* @license http://opensource.org/licenses/MIT MIT License
*/
namespace Magdev\Dossier\Service;
use zz\Html\HTMLMinify;
/**
* HTML Minifier Service
*
* @author magdev
*/
class MinifierService
{
/**
* Monolog service
* @var \Magdev\Dossier\Service\MonologService
*/
protected $logger = null;
/**
* Constructor
*
* @param \Magdev\Dossier\Service\MonologService $logger
*/
public function __construct(MonologService $logger)
{
$this->logger = $logger;
}
/**
* Minify HTML
*
* @param string $html
* @return string
*/
public function minify(string $html): string
{
if (!getenv('APP_DEBUG')) {
$html = HTMLMinify::minify($html, array(
'optimizationLevel' => HTMLMinify::OPTIMIZATION_ADVANCED,
'doctype' => HTMLMinify::DOCTYPE_HTML5,
));
}
return $html;
}
}

View File

@@ -0,0 +1,104 @@
<?php
/**
* The MIT License (MIT)
*
* Copyright (c) 2018 magdev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author magdev
* @copyright 2018 Marco Grätsch
* @package magdev/dossier
* @license http://opensource.org/licenses/MIT MIT License
*/
namespace Magdev\Dossier\Service;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Processor\IntrospectionProcessor;
/**
* Log service
*
* @author magdev
*/
class MonologService
{
/**
* Configuration service
* @var \Magdev\Dossier\Service\ConfigService
*/
protected $config = null;
/**
* Monolog logger
* @var \Monolog\Logger
*/
protected $logger = null;
/**
* Constructor
*
* @param \Magdev\Dossier\Service\ConfigService $config
*/
public function __construct(ConfigService $config)
{
$this->config = $config;
$logLevel = getenv('APP_DEBUG') ? Logger::DEBUG : $config->get('monolog.log_level', Logger::INFO);
$this->logger = new Logger('dossier');
$this->logger->pushHandler(new StreamHandler(PROJECT_ROOT.'/.dossier.log', $logLevel))
->pushProcessor(new IntrospectionProcessor(
$logLevel,
$config->get('monolog.skip_class_partials', array())
));
}
/**
* Get the Logger object
*
* @return \Monolog\Logger
*/
public function getLogger(): Logger
{
return $this->logger;
}
/**
* Delegate method calls to internal Logger
*
* @param string $method
* @param array $args
* @throws \BadFunctionCallException
* @return mixed
*/
public function __call(string $method, array $args)
{
if (!method_exists($this->logger, $method)) {
throw new \BadFunctionCallException('Unknown method: '.$method);
}
return call_user_func_array(array($this->logger, $method), $args);
}
}

View File

@@ -0,0 +1,178 @@
<?php
/**
* The MIT License (MIT)
*
* Copyright (c) 2018 magdev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author magdev
* @copyright 2018 Marco Grätsch
* @package magdev/dossier
* @license http://opensource.org/licenses/MIT MIT License
*/
namespace Magdev\Dossier\Service;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Magdev\Dossier\Style\DossierStyle;
/**
* Output helper
*
* @author magdev
* @deprecated
*/
class OutputHelperService
{
/**
* Output object
* @var \Symfony\Component\Console\Output\OutputInterface
*/
protected $output = null;
/**
* Translator service
* @var \Magdev\Dossier\Service\TranslatorService
*/
protected $translator = null;
/**
* Output style helper
* @var \Magdev\Dossier\Style\DossierStyle
*/
protected $ioStyle = null;
/**
* Constructor
*
* @param \Magdev\Dossier\Service\TranslatorService $translator
*/
public function __construct(TranslatorService $translator)
{
$this->translator = $translator;
}
/**
* Set the output object
*
* @param \Symfony\Component\Console\Output\OutputInterface $output
* @return \Magdev\Dossier\Service\OutputHelperService
*/
public function setOutput(OutputInterface $output): OutputHelperService
{
$this->output = $output;
return $this->addOutputStyles();
}
/**
* Get the style helper for dossier
*
* @param \Symfony\Component\Console\Input\InputInterface $input
* @param \Symfony\Component\Console\Output\OutputInterface $output
* @return \Magdev\Dossier\Style\DossierStyle
*/
public function getIoStyle(InputInterface $input, OutputInterface $output): DossierStyle
{
$this->setOutput($output);
if (!$this->ioStyle) {
$this->ioStyle = new DossierStyle($input, $output);
}
return $this->ioStyle;
}
/**
* Write the application header
*
* @return \Magdev\Dossier\Service\OutputHelperService
*/
public function writeApplicationHeader(): OutputHelperService
{
$this->output->write('<fg=magenta;options=bold>'.base64_decode(DOSSIER_LOGO).'</>');
$this->output->writeln('<fg=magenta;options=bold> '.$this->translator->trans('app.header').'</>');
$this->output->writeln('');
return $this;
}
/**
* Write a configuraton value
*
* @param string $key
* @param unknown $value
* @return OutputHelperService
*/
public function writeConfigValue(string $key, $value): OutputHelperService
{
$this->output->writeln('<fg=cyan;options=bold> '.$this->padRight($key).'</>: '.'<fg=yellow;options=bold> '.$value.'</>');
return $this;
}
/**
* Format a boolean value for output
*
* @param bool $status
* @param int $width
* @return string
*/
public function formatBoolean(bool $status, int $width = null): string
{
$string = $status ? '<fg=green;options=bold>*</>' : '<fg=red;options=bold>-</>';
if (is_int($width)) {
$indent = floor(($width - 1) / 2);
$string = str_repeat(' ', $indent).$string;
}
return $string;
}
/**
* Add some output styles to the output object
*
* @return \Magdev\Dossier\Service\OutputHelperService
*/
protected function addOutputStyles(): OutputHelperService
{
$formatter = $this->output->getFormatter();
$formatter->setStyle('cmd', new OutputFormatterStyle('white', 'blue', array('bold')));
$formatter->setStyle('header', new OutputFormatterStyle('magenta', null, array('bold')));
return $this;
}
/**
* Pad a string to the right
*
* @param string $text
* @param int $width
* @return string
*/
protected function padRight(string $text, int $width = 50): string
{
return str_pad($text, $width, ' ', STR_PAD_RIGHT);
}
}

176
src/Service/PdfService.php Normal file
View File

@@ -0,0 +1,176 @@
<?php
/**
* The MIT License (MIT)
*
* Copyright (c) 2018 magdev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author magdev
* @copyright 2018 Marco Grätsch
* @package magdev/dossier
* @license http://opensource.org/licenses/MIT MIT License
*/
namespace Magdev\Dossier\Service;
use PDFShift\PDFShift;
use PDFShift\Exceptions\PDFShiftException;
use Magdev\Dossier\Service\Exceptions\ServiceExcepton;
use Symfony\Component\Console\Exception\RuntimeException;
use Magdev\Dossier\Util\DataCollector;
/**
* Service to create PFs with PDFShift API
* @author magdev
*
*/
class PdfService
{
/**
* Configuration service
* @var \Magdev\Dossier\Service\ConfigService
*/
protected $config = null;
/**
* Monolog service
* @var \Magdev\Dossier\Service\MonologService
*/
protected $logger = null;
/**
* Template service
* @var \Magdev\Dossier\Service\TemplateService
*/
protected $tpl = null;
/**
* PDFShift API handler
* @var \PDFShift\PDFShift
*/
protected $pdf = null;
/**
* Constructor
*
* @param \Magdev\Dossier\Service\ConfigService $config
* @param \Magdev\Dossier\Service\MonologService $logger
* @param \Magdev\Dossier\Service\TemplateService $tpl
*/
public function __construct(ConfigService $config, MonologService $logger, TemplateService $tpl)
{
$this->config = $config;
$this->logger = $logger;
$this->tpl = $tpl;
if (!$this->config->get('pdfshift.apikey')) {
throw new ServiceException('PDFShift API-Key not set!');
}
try {
PDFShift::setApiKey($this->config->get('pdfshift.apikey'));
$this->pdf = new PDFShift(array(
'sandbox' => getenv('APP_DEBUG'),
'use_print' => $this->config->get('pdfshift.stylesheet.use_print'),
'format' => $this->config->get('pdfshift.page.format'),
'margin' => $this->config->get('pdfshift.page.margin'),
));
if ($userAgent = $this->config->get('pdfshift.http.user_agent')) {
$this->pdf->addHTTPHeader('user-agent', $userAgent);
}
} catch (PDFShiftException $e) {
throw new ServiceExcepton('Error initializing PDFShift client', -1, $e);
}
}
/**
* Get the internal PDFSift handler
* @return \PDFShift\PDFShift
*/
public function getPdfShift(): PDFShift
{
return $this->pdf;
}
/**
* Create the PDF
*
* @param string $htmlFile
* @return string
*/
public function createPdf(string $htmlFile, bool $addHeader = false, bool $addFooter = false, bool $addSecurity = false): string
{
$html = file_get_contents($htmlFile);
$outputDir = dirname($htmlFile);
$outputFilename = basename($htmlFile, '.html').'.pdf';
if ($addSecurity) {
$securityConfig = $this->config->get('pdfshift.security');
if (isset($securityConfig['userPassword']) && isset($securityConfig['ownerPassword'])) {
$this->pdf->protect($securityConfig);
}
}
if ($addHeader) {
$this->pdf->setHeader($this->createHeader(new DataCollector()), $this->config->get('pdfshift.header.spacing'));
}
if ($addFooter) {
$this->pdf->setFooter($this->createHeader(new DataCollector()), $this->config->get('pdfshift.footer.spacing'));
}
$this->pdf->convert($html);
$this->pdf->save($outputDir.'/'.$outputFilename);
if (!file_exists($outputDir.'/'.$outputFilename)) {
throw new RuntimeException('Failed to create PDF file at '.$outputDir.'/'.$outputFilename);
}
return $outputDir.'/'.$outputFilename;
}
/**
* Create the header HTML
*
* @param \Magdev\Dossier\Util\DataCollector $vars
* @return string
*/
public function createHeader(DataCollector $data): string
{
return $this->tpl->renderDocument('parts/pdf/header.html.twig', $data);
}
/**
* Create the footer HTML
*
* @param \Magdev\Dossier\Util\DataCollector $vars
* @return string
*/
public function createFooter(DataCollector $data): string
{
return $this->tpl->renderDocument('parts/pdf/footer.html.twig', $data);
}
}

View File

@@ -0,0 +1,186 @@
<?php
/**
* The MIT License (MIT)
*
* Copyright (c) 2018 magdev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author magdev
* @copyright 2018 Marco Grätsch
* @package magdev/dossier
* @license http://opensource.org/licenses/MIT MIT License
*/
namespace Magdev\Dossier\Service;
use Symfony\Component\Console\Exception\RuntimeException;
/**
* Phar helper
*
* @author magdev
*/
class PharHelperService
{
/**
* The Phar URL if inside of an archive
* @var string
*/
private $pharUrl = '';
/**
* Monolog service
* @var \Magdev\Dossier\Service\MonologService
*/
protected $logger = null;
/**
* Constructor
*/
public function __construct(MonologService $logger)
{
$this->pharUrl = \Phar::running(true);
$this->logger = $logger;
}
/**
* Check if running inside a phar-archive
*
* @return bool
*/
public function isInPhar(): bool
{
return $this->pharUrl != '';
}
/**
* Get the full URL for a file inside a phar-archive
*
* @param string $append
* @return string
*/
public function getPharUrl(string $append = ''): string
{
if ($this->isInPhar()) {
if (!$append) {
return $this->pharUrl;
}
return $this->pharUrl.'/'.$append;
}
if (!$append) {
return DOSSIER_ROOT;
}
return DOSSIER_ROOT.'/'.$append;
}
/**
* Read a file from phar-archive
*
* @param string $file
*/
public function read(string $file)
{
return file_get_contents($this->getPharUrl($file));
}
/**
* Get a local temp-path for files in phar-archives
*
* @param string $file
* @return string
*/
public function createLocalTempFile(string $file): string
{
$tmpfile = tempnam(sys_get_temp_dir(), 'dossier-');
$this->copy($file, $tmpfile);
return $tmpfile;
}
/**
* Copy a file from source to local fs
*
* @param string $source Relative path to DOSSIER_ROOT
* @param string $target Path on local fs
* @return bool
*/
public function copy(string $source, string $target): bool
{
return copy($this->getPharUrl($source), $target);
}
/**
* Copy a directory from phar-archive to local filesystem
*
* @TODO Fix recursive directory iteration
* @param string $source
* @param string $target
* @return bool
*/
public function copyDir(string $source, string $target): bool
{
$rit = new \RecursiveDirectoryIterator(DOSSIER_ROOT.'/'.$source);
foreach (new \RecursiveIteratorIterator($rit) as $filename => $current) {
if ($current->getFilename() != '.' && $current->getFilename() != '..') {
/* @var $current \SplFileInfo */
$relpath = str_replace(DOSSIER_ROOT.'/'.$source.'/', '', $current->getRealPath());
$targetDir = dirname($target.'/'.$relpath);
if (!is_dir($targetDir)) {
mkdir($targetDir, 0755, true);
}
copy($current->getRealPath(), $target.'/'.$relpath);
}
}
return true;
}
/**
* Extract the entire Phar archive
*
* @return \Magdev\Dossier\Service\PharHelperService
*/
public function extractArchive(string $targetDir): PharHelperService
{
if ($this->isInPhar()) {
if (!is_dir($targetDir)) {
mkdir($targetDir, 0755, true);
}
try {
$phar = new \Phar($_SERVER['SCRIPT_FILENAME']);
$phar->extractTo($targetDir);
} catch (\Exception $e) {
throw new RuntimeException('Error while extracting archive', -1, $e);
}
}
return $this;
}
}

View File

@@ -0,0 +1,301 @@
<?php
/**
* The MIT License (MIT)
*
* Copyright (c) 2018 magdev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author magdev
* @copyright 2018 Marco Grätsch
* @package magdev/dossier
* @license http://opensource.org/licenses/MIT MIT License
*/
namespace Magdev\Dossier\Service;
use Leafo\ScssPhp\Compiler;
/**
* LESS CSS processor service
*
* @author magdev
*/
class StylesheetProcessorService
{
const DELIM_MIXINS = 'MIXINS';
const DELIM_VARS = 'VARS';
const TYPE_LESS = 'less';
const TYPE_SCSS = 'scss';
/**
* LESS parser
* @var \Less_Parser
*/
protected $lessc = null;
/**
* SCSS compiler
* @var \Leafo\ScssPhp\Compiler
*/
protected $scssc = null;
/**
* Monolog service
* @var \Magdev\Dossier\Service\MonologService
*/
protected $logger = null;
/**
* Template service
* @var \Magdev\Dossier\Service\TemplateService
*/
protected $tpl = null;
/**
* Constructor
*
* @param string $formatter
*/
public function __construct(MonologService $logger, TemplateService $tpl, $formatter = '\\Leafo\\ScssPhp\\Formatter\\Compressed')
{
$this->logger = $logger;
$this->tpl = $tpl;
$this->lessc = new \Less_Parser(array(
'compress' => !getenv('APP_DEBUG')
));
$this->scssc = new Compiler();
if (!getenv('APP_DEBUG')) {
$this->scssc->setFormatter($formatter);
}
}
/**
* Get the LESS parser object
*
* @return \Less_Parser
*/
public function getLessc(): \Less_Parser
{
return $this->lessc;
}
/**
* Get the SASS parser object
*
* @return \Leafo\ScssPhp\Compiler
*/
public function getScssc(): Compiler
{
return $this->scssc;
}
/**
* Parse a file and get the CSS
*
* @param string $file
* @return string
*/
public function parse(string $file): string
{
if (file_exists($file)) {
$this->logger->debug('Pre-Processing stylesheet file: '.$file);
$type = explode('.', basename($file))[1];
if ($type == self::TYPE_LESS) {
return $this->lessc->parse(file_get_contents($file))->getCss();
}
if ($type == self::TYPE_SCSS) {
return $this->scssc->compile(file_get_contents($file));
}
}
return '';
}
/**
* Parse theme stylesheets
*
* @return string
*/
public function parseThemeStyles(): string
{
$css = '';
$lessFile = $this->tpl->getThemeFile('less/global.less');
if (file_exists($lessFile)) {
$css .= $this->parse($lessFile);
}
$scssFile = $this->tpl->getThemeFile('scss/global.scss');
if (file_exists($scssFile)) {
$css .= $this->parse($scssFile);
}
return $css;
}
/**
* Parse user stylesheet
*
* @return string
*/
public function parseUserstyles(): string
{
$css = '';
if (file_exists(PROJECT_ROOT.'/'.self::TYPE_LESS.'/userstyles.'.self::TYPE_LESS)) {
$this->logger->debug('Pre-Processing user-stylesheet file: '.PROJECT_ROOT.'/'.self::TYPE_LESS.'/userstyles.'.self::TYPE_LESS);
$css .= $this->parse(PROJECT_ROOT.'/'.self::TYPE_LESS.'/userstyles.'.self::TYPE_LESS);
}
if (file_exists(PROJECT_ROOT.'/'.self::TYPE_SCSS.'/userstyles.'.self::TYPE_SCSS)) {
$this->logger->debug('Pre-Processing user-stylesheet file: '.PROJECT_ROOT.'/'.self::TYPE_SCSS.'/userstyles.'.self::TYPE_SCSS);
$css .= $this->parse(PROJECT_ROOT.'/'.self::TYPE_SCSS.'/userstyles.'.self::TYPE_SCSS);
}
return $css;
}
/**
* Export LESS variables
*
* @param string $file
* @param string $targetDir
* @param string $type
* @return \Magdev\Dossier\Service\StylesheetProcessorService
*/
public function exportVariables(string $file, string $targetDir = PROJECT_ROOT, string $type = self::TYPE_LESS): StylesheetProcessorService
{
$code = $this->cutFile($file, self::DELIM_VARS);
if ($code) {
$target = $targetDir.'/'.$type.'/variables.'.$type;
file_put_contents($target, $code);
}
return $this;
}
/**
* Export LESS Mixins
*
* @param string $file
* @param string $targetDir
* @param string $type
* @return \Magdev\Dossier\Service\StylesheetProcessorService
*/
public function exportMixins(string $file, string $targetDir = PROJECT_ROOT, string $type = self::TYPE_LESS): StylesheetProcessorService
{
$code = $this->cutFile($file, self::DELIM_MIXINS);
if ($code) {
$target = $targetDir.'/'.$type.'/mixins.'.$type;
file_put_contents($target, $code);
}
return $this;
}
/**
* Write basic userstyles.less
*
* @param string $targetDir
* @param string $type
* @return \Magdev\Dossier\Service\StylesheetProcessorService
*/
public function writeBasicUserstylesheet(string $targetDir = PROJECT_ROOT, string $type = self::TYPE_LESS): StylesheetProcessorService
{
if ($type == self::TYPE_LESS) {
$code = <<<EOT
/**
* LESS userstyles for magdev/dossier
*/
EOT;
$path = $targetDir.'/less/userstyles.less';
} else if ($type == self::TYPE_SCSS) {
$code = <<<EOT
/**
* SCSS userstyles for magdev/dossier
*/
EOT;
$path = $targetDir.'/scss/userstyles.scss';
}
$code .= <<<EOT
@import "mixins";
@import "variables";
// Gobal styles
@media print {
// Print styles
}
@media screen {
// Screen styles
}
EOT;
if (!is_dir(dirname($path))) {
mkdir(dirname($path), 0755, true);
}
file_put_contents($path, $code);
return $this;
}
/**
* Cut parts off a stylesheet file
*
* @param string $file
* @param string $delimiter (MIXINS or VARS)
* @return string
*/
protected function cutFile(string $file, string $delimiter): string
{
if ($delimiter == self::DELIM_MIXINS || $delimiter == self::DELIM_VARS) {
$start = '// '.$delimiter.':START';
$end = '// '.$delimiter.':END';
$fp = fopen($file, 'r');
$lines = array();
$started = false;
while ($line = fgets($fp, 1024) !== false) {
if ($line == $start) {
$started = true;
}
if ($started) {
if ($line == $end) {
fclose($fp);
return implode(PHP_EOL, $lines);
} else {
$lines[] = $line;
}
}
}
fclose($fp);
}
return '';
}
}

View File

@@ -0,0 +1,328 @@
<?php
/**
* The MIT License (MIT)
*
* Copyright (c) 2018 magdev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author magdev
* @copyright 2018 Marco Grätsch
* @package magdev/dossier
* @license http://opensource.org/licenses/MIT MIT License
*/
namespace Magdev\Dossier\Service;
use Symfony\Component\Translation\Translator;
use Magdev\Dossier\Util\Base\DataCollectorInterface;
use Mni\FrontYAML\Parser;
/**
* Template service
*
* @author magdev
*/
class TemplateService
{
/**
* Twig object
* @var \Twig_Environment
*/
protected $twig = null;
/**
* Markdown service
* @var \Magdev\Dossier\Service\MarkdownService
*/
protected $markdown = null;
/**
* Translator service
* @var \Magdev\Dossier\Service\TranslatorService
*/
protected $translator = null;
/**
* HTML minifer service
* @var \Magdev\Dossier\Service\MinifierService
*/
protected $minifier = null;
/**
* Configuration service
* @var \Magdev\Dossier\Service\ConfigService
*/
protected $config = null;
/**
* Name of the current theme
* @var string
*/
protected $theme = '';
/**
* Name of the document
* @var string
*/
protected $docname = 'document';
/**
* Monolog service
* @var \Magdev\Dossier\Service\MonologService
*/
protected $logger = null;
/**
* Constructor
*
* @param \Magdev\Dossier\Service\MarkdownService $markdown
* @param \Magdev\Dossier\Service\TranslatorService $translator
* @param \Magdev\Dossier\Service\MinifierService $minifier
* @param \Magdev\Dossier\Service\ConfigService $config
* @param \Magdev\Dossier\Service\MonologService $logger
*/
public function __construct(MarkdownService $markdown, TranslatorService $translator, MinifierService $minifier, ConfigService $config, MonologService $logger)
{
$this->markdown = $markdown;
$this->translator = $translator;
$this->minifier = $minifier;
$this->config = $config;
$this->logger = $logger;
}
/**
* Get a file from a theme directory
*
* @param string $file
* @param string $theme
* @return string
*/
public function getThemeFile(string $file, string $theme = ''): string
{
$theme = !$theme ? $this->theme : $theme;
if (getenv('DOSSIER_THEME_DIR')) {
if (is_dir(getenv('DOSSIER_THEME_DIR').'/'.$theme) && file_exists(getenv('DOSSIER_THEME_DIR').'/'.$theme.'/'.$file)) {
return getenv('DOSSIER_THEME_DIR').'/'.$theme.'/'.$file;
}
}
if (is_dir(getenv('HOME').'/.dossier/tpl/'.$theme) && file_exists(getenv('HOME').'/.dossier/tpl/'.$theme.'/'.$file)) {
return getenv('HOME').'/.dossier/tpl/'.$theme.'/'.$file;
}
return DOSSIER_ROOT.'/app/tpl/'.$theme.'/'.$file;
}
/**
* Set the theme
*
* @param string $theme
* @return \Magdev\Dossier\Service\TemplateService
*/
public function setTheme(string $theme): TemplateService
{
$this->theme = $theme;
$loaders = array();
if (getenv('DOSSIER_THEME_DIR') && is_dir(getenv('DOSSIER_THEME_DIR').'/'.$theme)) {
$loaders[] = new \Twig_Loader_Filesystem(getenv('DOSSIER_THEME_DIR').'/'.$theme);
}
if (is_dir(getenv('HOME').'/.dossier/tpl/'.$theme)) {
$loaders[] = new \Twig_Loader_Filesystem(getenv('HOME').'/.dossier/tpl/'.$theme);
}
$loaders[] = new \Twig_Loader_Filesystem(DOSSIER_ROOT.'/app/tpl/'.$theme);
$this->twig = new \Twig_Environment(new \Twig_Loader_Chain($loaders), array(
'cache' => new \Twig_Cache_Filesystem(DOSSIER_CACHE, \Twig_Cache_Filesystem::FORCE_BYTECODE_INVALIDATION),
'debug' => getenv('APP_DEBUG'),
));
$this->addTwigExtensions($this->translator->getTranslator(), $this->config);
return $this;
}
/**
* Get the name of the current theme
*
* @return string
*/
public function getTheme(): string
{
return $this->theme;
}
/**
* Set the document name
*
* @param string $docname
* @return \Magdev\Dossier\Service\TemplateService
*/
public function setDocumentName(string $docname): TemplateService
{
$this->docname = $docname;
return $this;
}
/**
* Get the internal Twig object
*
* @return \Twig_Environment
*/
public function getTwig(): \Twig_Environment
{
return $this->twig;
}
/**
* Render the page
*
* @param string $template
* @param \Magdev\Dossier\Util\Base\DataCollectorInterface $data
* @param string $destDir
* @return string
*/
public function render(string $template, DataCollectorInterface $data, string $destDir): string
{
if (!is_dir($destDir)) {
mkdir($destDir, 0755, true);
}
$vars = $data->getData();
$name = isset($vars['name']) ? $vars['name'] : $this->docname;
$name .= isset($vars['theme']) ? '.'.$vars['theme'] : '';
$path = $destDir.'/'.$name.'.'.$this->translator->getTranslator()->getLocale().'.html';
$html = $this->twig->render($template, $vars);
$html = $this->minifier->minify($html);
if (!file_put_contents($path, $html)) {
throw new \RuntimeException('Error writing output file: '.$path);
}
return $path;
}
/**
* Render a template and return the resulted document
*
* @param string $template
* @param \Magdev\Dossier\Util\Base\DataCollectorInterface $data
* @return string
*/
public function renderDocument(string $template, DataCollectorInterface $data): string
{
$html = $this->twig->render($template, $data->getData());
$html = $this->minifier->minify($html);
return $html;
}
/**
* Add Twig extensions
*
* @param $translator \Symfony\Component\Translation\Translator
* @param $config \Magdev\Dossier\Service\ConfigService
* @return \Magdev\Dossier\Service\TemplateService
*/
private function addTwigExtensions(Translator $translator, ConfigService $config): TemplateService
{
$this->twig->addFilter(new \Twig_Filter('trans', function (string $id, array $parameters = array(), string $domain=null, string $locale=null) use ($translator) {
return $translator->trans($id, $parameters, $domain, $locale);
}));
$this->twig->addFilter(new \Twig_Filter('transChoice', function (string $id, int $number, array $parameters = array(), string $domain = null, string $locale = null) use ($translator) {
return $translator->transChoice($id, $number, $parameters, $domain, $locale);
}));
$this->twig->addFilter(new \Twig_Filter('md', function (string $string) {
return \ParsedownExtra::instance('twig')->parse($string);
}, array('is_safe' => array('html'))));
$this->twig->addFilter(new \Twig_Filter('inlinemd', function (string $string) {
return \ParsedownExtra::instance('twig')->line($string);
}, array('is_safe' => array('html'))));
$this->twig->addFilter(new \Twig_Filter('unixToDateTime', function (int $unixTime) {
return new \DateTime('@'.$unixTime);
}));
$this->twig->addFilter(new \Twig_Filter('filesize', function (int $filesize) {
if ($filesize > (1024*1024)) {
return round(($filesize/(1024*1024)), 2).' MiB';
}
if ($filesize > (1024)) {
return round(($filesize/1024), 2).' KiB';
}
return $filesize.' B';
}));
$this->twig->addFilter(new \Twig_Filter('split', function (string $string, string $split = ',') {
return explode($split, $string);
}));
$this->twig->addFilter(new \Twig_Filter('merge', function (array $strings, string $glue = ', ') {
return implode($glue, $strings);
}, array('is_safe' => array('html'))));
$this->twig->addFilter(new \Twig_Filter('splitmerge', function (string $string, string $split = ',', string $glue = '<br/>') {
$parts = explode($split, $string);
return $glue ? implode($glue, $parts) : $parts;
}, array('is_safe' => array('html'))));
$this->twig->addFilter(new \Twig_Filter('debug', function ($var) {
return getenv('APP_DEBUG') == true ? '<code>'.print_r($var, true).'</code>' : '';
}, array('is_safe' => array('html'))));
$this->twig->addFunction(new \Twig_Function('is_today', function (\DateTime $checkDate) {
return (new \DateTime())->diff($checkDate)->days == 0;
}));
$this->twig->addFunction(new \Twig_Function('is_disabled', function (bool $value) {
return $value == true;
}));
$this->twig->addFunction(new \Twig_Function('config', function (string $key) use ($config) {
if ($config->has($key)) {
$value = $config->get($key);
if (is_scalar($value)) {
return $value;
}
return json_encode($value, JSON_NUMERIC_CHECK);
}
return '';
}));
$this->twig->addFunction(new \Twig_Function('parseFilename', function(string $filename) {
$parts = explode('.', $filename);
return array(
'name' => ucfirst($parts[0]),
'theme' => ucfirst($parts[1]),
'locale' => strtoupper($parts[2]),
'type' => strtolower($parts[3]),
);
}));
return $this;
}
}

View File

@@ -0,0 +1,154 @@
<?php
/**
* The MIT License (MIT)
*
* Copyright (c) 2018 magdev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author magdev
* @copyright 2018 Marco Grätsch
* @package magdev/dossier
* @license http://opensource.org/licenses/MIT MIT License
*/
namespace Magdev\Dossier\Service;
use Symfony\Component\Translation\Translator;
use Symfony\Component\Translation\Loader\YamlFileLoader;
/**
* Translator Service
*
* @author magdev
*/
class TranslatorService
{
/**
* Translator Object
* @var \Symfony\Component\Translation\Translator
*/
protected $translator = null;
/**
* Fallback locales
* @var array
*/
protected $fallbackLocales = array();
/**
* Monolog service
* @var \Magdev\Dossier\Service\MonologService
*/
protected $logger = null;
/**
* Constructor
*
* @param string $locale
*/
public function __construct(ConfigService $config, MonologService $logger)
{
$this->logger = $logger;
$this->fallbackLocales = $config->get('translator.fallback_locales');
$this->loadLocale($config->get('translator.locale'));
}
/**
* Set the locale
*
* @param string $locale
* @return \Magdev\Dossier\Service\TranslatorService
*/
public function setLocale(string $locale): TranslatorService
{
return $this->loadLocale($locale);
}
/**
* Get the internal Translator object
*
* @return \Symfony\Component\Translation\Translator
*/
public function getTranslator(): Translator
{
return $this->translator;
}
/**
* Translate a string
*
* @param string $id
* @param array $parameters
* @param string $domain
* @param string $locale
* @see \Symfony\Component\Translation\Translator::trans()
* @return string
*/
public function trans(string $id, array $parameters = array(), string $domain = null, string $locale = null): string
{
return $this->getTranslator()->trans($id, $parameters, $domain, $locale);
}
/**
* Translate plural strings
*
* @param string $id
* @param int $number
* @param array $parameters
* @param string $domain
* @param string $locale
* @see \Symfony\Component\Translation\Translator::transChoice()
* @return string
*/
public function transChoice(string $id, int $number, array $parameters = array(), string $domain = null, string $locale = null): string
{
return $this->getTranslator()->transChoice($id, $number, $parameters, $domain, $locale);
}
/**
* Load a locale
*
* @param string $locale
* @return \Magdev\Dossier\Service\TranslatorService
*/
protected function loadLocale(string $locale): TranslatorService
{
$this->translator = new Translator($locale);
$this->translator->setFallbackLocales($this->fallbackLocales);
$this->translator->addLoader('yaml', new YamlFileLoader());
$this->translator->addResource('yaml', DOSSIER_ROOT.'/app/locale/messages.'.$locale.'.yaml', $locale);
if (file_exists(getenv('HOME').'/.dossier/locale/messages.'.$locale.'.yaml')) {
$this->translator->addResource('yaml', getenv('HOME').'/.dossier/locale/messages.'.$locale.'.yaml', $locale);
}
if (file_exists(PROJECT_ROOT.'/.conf/locale/messages.'.$locale.'.yaml')) {
$this->translator->addResource('yaml', PROJECT_ROOT.'/.conf/locale/messages.'.$locale.'.yaml', $locale);
}
return $this;
}
}

View File

@@ -0,0 +1,174 @@
<?php
/**
* The MIT License (MIT)
*
* Copyright (c) 2018 magdev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author magdev
* @copyright 2018 Marco Grätsch
* @package magdev/dossier
* @license http://opensource.org/licenses/MIT MIT License
*/
namespace Magdev\Dossier\Service;
use Symfony\Component\Console\Exception\RuntimeException;
/**
* URI helper
*
* @author magdev
*/
class UriHelperService
{
/**
* Root directory to strip to generate relative URLs
* @var string
*/
protected $rootDir = '';
/**
* Phar Helper service
* @var \Magdev\Dossier\Service\PharHelperService
*/
protected $pharHelper = null;
/**
* Monolog service
* @var \Magdev\Dossier\Service\MonologService
*/
protected $logger = null;
/**
* Constructor
*
* @param \Magdev\Dossier\Service\PharHelperService $helper
*/
public function __construct(MonologService $logger, PharHelperService $helper)
{
$this->logger = $logger;
$this->pharHelper = $helper;
$this->rootDir = PROJECT_ROOT.'/';
}
/**
* Set the root directory
*
* @param string $dir
* @return \Magdev\Dossier\Service\UrlHelperService
*/
public function setRootDirectory(string $dir): UrlHelperService
{
$this->rootDir = $dir;
return $this;
}
/**
* Get DataURI from file
*
* @param string $file
* @param string $mimetype
* @return string
*/
public function getDataUriFromFile(string $file, $mimetype = null): string
{
$tmpfile = null;
if ($this->pharHelper->isInPhar()) {
$tmpfile = $this->pharHelper->createLocalTempFile($file);
$f = new \SplFileObject($tmpfile);
} else {
$f = new \SplFileObject($file);
}
if (!$mimetype) {
$fi = new \finfo();
$mimetype = $fi->file($f->getRealPath(), FILEINFO_MIME);
}
$data = file_get_contents($f->getRealPath());
if ($tmpfile && file_exists($tmpfile)) {
@unlink($tmpfile);
}
return $this->toDataUri($data, $mimetype);
}
/**
* Get a DataURI from data
*
* @param mixed $data
* @param string $mimetype
* @return string
*/
public function getDataUriFromData($data, $mimetype = 'text/plain'): string
{
return $this->toDataUri($data, $mimetype);
}
/**
* Open a file in the default editor
*
* @param string $file
* @throws \Symfony\Component\Console\Exception\RuntimeException
* @return \Magdev\Dossier\Service\UriHelperService
*/
public function openFileInEditor(string $file): UriHelperService
{
if (getenv('XSESSION_IS_UP') == 'yes') {
system('xdg-open '.$file);
} else if ($editor = getenv('EDITOR')) {
system($editor.' '.$file);
} else {
throw new RuntimeException('Cannot find a responsible editor');
}
return $this;
}
/**
* Get the relative path for project files
*
* @param string $realpath
* @return string
*/
public function getRelativePath(string $realpath): string
{
return str_replace(PROJECT_ROOT.'/', '', $realpath);
}
/**
* Format the DataURI
*
* @param mixed $data
* @param string $mimetype
* @return string
*/
protected function toDataUri($data, string $mimetype): string
{
return 'data:'.$mimetype.'; base64,'.base64_encode($data);
}
}