diff --git a/controller/Controller.php b/controller/Controller.php index e971ee42e..f1e939217 100644 --- a/controller/Controller.php +++ b/controller/Controller.php @@ -7,7 +7,7 @@ class Controller { /** * The controller has to know the model to access the data stored there. - * @param $model contains the Model object. + * @var Model $model contains the Model object. */ public $model; @@ -151,7 +151,12 @@ protected function sendNotModifiedHeader($modifiedDate): bool */ protected function getIfModifiedSince() { - return isset($_SERVER["HTTP_IF_MODIFIED_SINCE"]) ? strtotime($_SERVER["HTTP_IF_MODIFIED_SINCE"]) : null; + $ifModifiedSince = null; + if (isset($_SERVER["HTTP_IF_MODIFIED_SINCE"])) { + // example value set by a browser: "2019-04-13 08:28:23" + $ifModifiedSince = DateTime::createFromFormat("Y-m-d H:i:s", $_SERVER["HTTP_IF_MODIFIED_SINCE"]); + } + return $ifModifiedSince; } /** diff --git a/controller/WebController.php b/controller/WebController.php index aa96e148a..73f8182e9 100644 --- a/controller/WebController.php +++ b/controller/WebController.php @@ -11,6 +11,11 @@ */ class WebController extends Controller { + /** + * How long to store retrieved disk configuration for HTTP 304 header + * from git information. + */ + const GIT_MODIFIED_CONFIG_TTL = 600; // 10 minutes /** * Provides access to the templating engine. * @property object $twig the twig templating engine. @@ -260,6 +265,30 @@ public function invokeVocabularyConcept(Request $request) * @return DateTime|null */ protected function getModifiedDate(Concept $concept, Vocabulary $vocab) + { + $modifiedDate = null; + + $conceptModifiedDate = $this->getConceptModifiedDate($concept, $vocab); + $gitModifiedDate = $this->getGitModifiedDate(); + $configModifiedDate = $this->getConfigModifiedDate(); + + // max with an empty list raises an error and returns bool + if ($conceptModifiedDate || $gitModifiedDate || $configModifiedDate) { + $modifiedDate = max($conceptModifiedDate, $gitModifiedDate, $configModifiedDate); + } + return $modifiedDate; + } + + /** + * Get the concept modified date. Returns the concept modified date if available. Otherwise, + * reverts to the modified date of the default concept scheme if available. Lastly, if it could + * not get the modified date from the concept nor from the concept scheme, it returns null. + * + * @param Concept $concept concept used to retrieve modified date + * @param Vocabulary $vocab vocabulary used to retrieve modified date + * @return DateTime|null|string + */ + protected function getConceptModifiedDate(Concept $concept, Vocabulary $vocab) { $modifiedDate = $concept->getModifiedDate(); if (!$modifiedDate) { @@ -274,9 +303,72 @@ protected function getModifiedDate(Concept $concept, Vocabulary $vocab) } } } + return $modifiedDate; } + /** + * Return the datetime of the latest commit, or null if git is not available or if the command failed + * to execute. + * + * @see https://stackoverflow.com/a/33986403 + * @return DateTime|null + */ + protected function getGitModifiedDate() + { + $commitDate = null; + $cache = $this->model->getConfig()->getCache(); + $cacheKey = "git:modified_date"; + $gitCommand = 'git log -1 --date=iso --pretty=format:%cd'; + if ($cache->isAvailable()) { + $commitDate = $cache->fetch($cacheKey); + if (!$commitDate) { + $commitDate = $this->executeGitModifiedDateCommand($gitCommand); + if ($commitDate) { + $cache->store($cacheKey, $commitDate, static::GIT_MODIFIED_CONFIG_TTL); + } + } + } else { + $commitDate = $this->executeGitModifiedDateCommand($gitCommand); + } + return $commitDate; + } + + /** + * Execute the git command and return a parsed date time, or null if the command failed. + * + * @param string $gitCommand git command line that returns a formatted date time + * @return DateTime|null + */ + protected function executeGitModifiedDateCommand($gitCommand) + { + $commitDate = null; + $commandOutput = @exec($gitCommand); + if ($commandOutput) { + $commitDate = new \DateTime(trim($commandOutput)); + $commitDate->setTimezone(new \DateTimeZone('UTC')); + } + return $commitDate; + } + + /** + * Return the datetime of the modified time of the config file. This value is read in the GlobalConfig + * for every request, so we simply access that value and if not null, we will return a datetime. Otherwise, + * we return a null value. + * + * @see http://php.net/manual/en/function.filemtime.php + * @return DateTime|null + */ + protected function getConfigModifiedDate() + { + $dateTime = null; + $configModifiedTime = $this->model->getConfig()->getConfigModifiedTime(); + if (!is_null($configModifiedTime)) { + $dateTime = (new DateTime())->setTimestamp($configModifiedTime); + } + return $dateTime; + } + /** * Invokes the feedback page with information of the users current vocabulary. */ diff --git a/model/Cache.php b/model/Cache.php index 06a452ae5..08f296584 100644 --- a/model/Cache.php +++ b/model/Cache.php @@ -23,6 +23,7 @@ public function store($key, $value, $ttl=3600) { if (function_exists('apcu_store')) { return apcu_store($key, $value, $ttl); } + return false; } public function isAvailable() { diff --git a/model/Concept.php b/model/Concept.php index b84a5bcb4..2630b4c6e 100644 --- a/model/Concept.php +++ b/model/Concept.php @@ -599,7 +599,7 @@ public function getProperties() // checking if the property value is not in the current vocabulary $exvoc = $this->model->guessVocabularyFromURI($val->getUri(), $this->vocab->getId()); if ($exvoc && $exvoc->getId() !== $this->vocab->getId()) { - $ret[$prop]->addValue(new ConceptMappingPropertyValue($this->model, $this->vocab, $val, $prop, $this->clang), $this->clang); + $ret[$prop]->addValue(new ConceptMappingPropertyValue($this->model, $this->vocab, $val, $this->resource, $prop, $this->clang), $this->clang); continue; } $ret[$prop]->addValue(new ConceptPropertyValue($this->model, $this->vocab, $val, $prop, $this->clang), $this->clang); @@ -682,7 +682,6 @@ public function getDate() { $ret = ''; $created = ''; - $modified = ''; try { // finding the created properties if ($this->resource->get('dc:created')) { diff --git a/model/ConceptMappingPropertyValue.php b/model/ConceptMappingPropertyValue.php index 133dbb477..58051c835 100644 --- a/model/ConceptMappingPropertyValue.php +++ b/model/ConceptMappingPropertyValue.php @@ -210,7 +210,7 @@ public function asJskos($queryExVocabs = true) $ret['to']['memberSet'][0]['notation'] = (string) $notation; } - $label = $this->getLabel($queryExVocabs); + $label = $this->getLabel(null, $queryExVocabs); if (isset($label)) { if (is_string($label)) { list($labelLang, $labelValue) = ['-', $label]; diff --git a/model/DataObject.php b/model/DataObject.php index 91f7be171..eded68410 100644 --- a/model/DataObject.php +++ b/model/DataObject.php @@ -22,6 +22,7 @@ class DataObject * Initializes the DataObject * @param Model $model * @param EasyRdf\Resource $resource + * @throws Exception */ public function __construct($model, $resource) { diff --git a/model/GlobalConfig.php b/model/GlobalConfig.php index c39a1f177..05d9de18b 100644 --- a/model/GlobalConfig.php +++ b/model/GlobalConfig.php @@ -23,6 +23,10 @@ class GlobalConfig extends BaseConfig { private $namespaces; /** EasyRdf\Graph graph */ private $graph; + /** + * @var int the time the config file was last modified + */ + private $configModifiedTime = null; public function __construct($config_name='/../config.ttl') { @@ -44,6 +48,14 @@ public function getCache() return $this->cache; } + /** + * @return int the time the config file was last modified + */ + public function getConfigModifiedTime() + { + return $this->configModifiedTime; + } + /** * Initialize configuration, reading the configuration file from the disk, * and creating the graph and resources objects. Uses a cache if available, @@ -52,10 +64,15 @@ public function getCache() private function initializeConfig() { try { + // retrieve last modified time for config file (filemtime returns int|bool!) + $configModifiedTime = filemtime($this->filePath); + if (!is_bool($configModifiedTime)) { + $this->configModifiedTime = $configModifiedTime; + } // use APC user cache to store parsed config.ttl configuration - if ($this->cache->isAvailable()) { + if ($this->cache->isAvailable() && !is_null($this->configModifiedTime)) { // @codeCoverageIgnoreStart - $key = realpath($this->filePath) . ", " . filemtime($this->filePath); + $key = realpath($this->filePath) . ", " . $this->configModifiedTime; $nskey = "namespaces of " . $key; $this->graph = $this->cache->fetch($key); $this->namespaces = $this->cache->fetch($nskey); diff --git a/model/Model.php b/model/Model.php index ef9980847..fd1503aae 100644 --- a/model/Model.php +++ b/model/Model.php @@ -201,6 +201,7 @@ public function getRDF($vocid, $uri, $format) if (!$result->isEmpty()) { return $serialiser->serialise($result, $retform); } + return ""; } /** diff --git a/model/VocabularyConfig.php b/model/VocabularyConfig.php index fb44367d7..48269e2b9 100644 --- a/model/VocabularyConfig.php +++ b/model/VocabularyConfig.php @@ -6,6 +6,7 @@ class VocabularyConfig extends BaseConfig { private $plugins; + private $languageOrderCache = array(); public function __construct($resource, $globalPlugins=array()) { @@ -433,6 +434,9 @@ public function getTypes($lang = null) */ public function getLanguageOrder($clang) { + if (array_key_exists($clang, $this->languageOrderCache)) { + return $this->languageOrderCache[$clang]; + } $ret = array($clang); $fallbacks = !empty($this->resource->get('skosmos:fallbackLanguages')) ? $this->resource->get('skosmos:fallbackLanguages') : array(); foreach ($fallbacks as $lang) { @@ -448,6 +452,8 @@ public function getLanguageOrder($clang) $ret[] = $lang; } } + // store in cache so this doesn't have to be computed again + $this->languageOrderCache[$clang] = $ret; return $ret; } diff --git a/resource/fonts/eot/FiraMono-Bold.eot b/resource/fonts/eot/FiraMono-Bold.eot index f4a15ff39..87013fad7 100755 Binary files a/resource/fonts/eot/FiraMono-Bold.eot and b/resource/fonts/eot/FiraMono-Bold.eot differ diff --git a/resource/fonts/eot/FiraMono-Medium.eot b/resource/fonts/eot/FiraMono-Medium.eot new file mode 100644 index 000000000..41a3d1006 Binary files /dev/null and b/resource/fonts/eot/FiraMono-Medium.eot differ diff --git a/resource/fonts/eot/FiraMono-Regular.eot b/resource/fonts/eot/FiraMono-Regular.eot index 7a0b52f04..4c5eba2e6 100755 Binary files a/resource/fonts/eot/FiraMono-Regular.eot and b/resource/fonts/eot/FiraMono-Regular.eot differ diff --git a/resource/fonts/eot/FiraSans-Bold.eot b/resource/fonts/eot/FiraSans-Bold.eot index 58ca9daf5..f9228cbe7 100755 Binary files a/resource/fonts/eot/FiraSans-Bold.eot and b/resource/fonts/eot/FiraSans-Bold.eot differ diff --git a/resource/fonts/eot/FiraSans-BoldItalic.eot b/resource/fonts/eot/FiraSans-BoldItalic.eot index 62856adcf..f91569a3e 100755 Binary files a/resource/fonts/eot/FiraSans-BoldItalic.eot and b/resource/fonts/eot/FiraSans-BoldItalic.eot differ diff --git a/resource/fonts/eot/FiraSans-Book.eot b/resource/fonts/eot/FiraSans-Book.eot new file mode 100644 index 000000000..007bf7f03 Binary files /dev/null and b/resource/fonts/eot/FiraSans-Book.eot differ diff --git a/resource/fonts/eot/FiraSans-BookItalic.eot b/resource/fonts/eot/FiraSans-BookItalic.eot new file mode 100644 index 000000000..bcb5291de Binary files /dev/null and b/resource/fonts/eot/FiraSans-BookItalic.eot differ diff --git a/resource/fonts/eot/FiraSans-Eight.eot b/resource/fonts/eot/FiraSans-Eight.eot new file mode 100644 index 000000000..485ff3aa1 Binary files /dev/null and b/resource/fonts/eot/FiraSans-Eight.eot differ diff --git a/resource/fonts/eot/FiraSans-EightItalic.eot b/resource/fonts/eot/FiraSans-EightItalic.eot new file mode 100644 index 000000000..73ec42a00 Binary files /dev/null and b/resource/fonts/eot/FiraSans-EightItalic.eot differ diff --git a/resource/fonts/eot/FiraSans-ExtraBold.eot b/resource/fonts/eot/FiraSans-ExtraBold.eot new file mode 100644 index 000000000..3729e9378 Binary files /dev/null and b/resource/fonts/eot/FiraSans-ExtraBold.eot differ diff --git a/resource/fonts/eot/FiraSans-ExtraBoldItalic.eot b/resource/fonts/eot/FiraSans-ExtraBoldItalic.eot new file mode 100644 index 000000000..dc812bf39 Binary files /dev/null and b/resource/fonts/eot/FiraSans-ExtraBoldItalic.eot differ diff --git a/resource/fonts/eot/FiraSans-ExtraLight.eot b/resource/fonts/eot/FiraSans-ExtraLight.eot new file mode 100644 index 000000000..c8776ef8b Binary files /dev/null and b/resource/fonts/eot/FiraSans-ExtraLight.eot differ diff --git a/resource/fonts/eot/FiraSans-ExtraLightItalic.eot b/resource/fonts/eot/FiraSans-ExtraLightItalic.eot new file mode 100644 index 000000000..211c8fcb7 Binary files /dev/null and b/resource/fonts/eot/FiraSans-ExtraLightItalic.eot differ diff --git a/resource/fonts/eot/FiraSans-Four.eot b/resource/fonts/eot/FiraSans-Four.eot new file mode 100644 index 000000000..2c21641c2 Binary files /dev/null and b/resource/fonts/eot/FiraSans-Four.eot differ diff --git a/resource/fonts/eot/FiraSans-FourItalic.eot b/resource/fonts/eot/FiraSans-FourItalic.eot new file mode 100644 index 000000000..aa16cc88d Binary files /dev/null and b/resource/fonts/eot/FiraSans-FourItalic.eot differ diff --git a/resource/fonts/eot/FiraSans-Hair.eot b/resource/fonts/eot/FiraSans-Hair.eot new file mode 100644 index 000000000..a1b1f0d41 Binary files /dev/null and b/resource/fonts/eot/FiraSans-Hair.eot differ diff --git a/resource/fonts/eot/FiraSans-HairItalic.eot b/resource/fonts/eot/FiraSans-HairItalic.eot new file mode 100644 index 000000000..45b9d9d3f Binary files /dev/null and b/resource/fonts/eot/FiraSans-HairItalic.eot differ diff --git a/resource/fonts/eot/FiraSans-Heavy.eot b/resource/fonts/eot/FiraSans-Heavy.eot new file mode 100644 index 000000000..6323009fe Binary files /dev/null and b/resource/fonts/eot/FiraSans-Heavy.eot differ diff --git a/resource/fonts/eot/FiraSans-HeavyItalic.eot b/resource/fonts/eot/FiraSans-HeavyItalic.eot new file mode 100644 index 000000000..c13ae438c Binary files /dev/null and b/resource/fonts/eot/FiraSans-HeavyItalic.eot differ diff --git a/resource/fonts/eot/FiraSans-Italic.eot b/resource/fonts/eot/FiraSans-Italic.eot new file mode 100644 index 000000000..0519f1b5b Binary files /dev/null and b/resource/fonts/eot/FiraSans-Italic.eot differ diff --git a/resource/fonts/eot/FiraSans-Light.eot b/resource/fonts/eot/FiraSans-Light.eot index 154271d5c..3f4c7f67d 100755 Binary files a/resource/fonts/eot/FiraSans-Light.eot and b/resource/fonts/eot/FiraSans-Light.eot differ diff --git a/resource/fonts/eot/FiraSans-LightItalic.eot b/resource/fonts/eot/FiraSans-LightItalic.eot index 67d94787d..b4c103c77 100755 Binary files a/resource/fonts/eot/FiraSans-LightItalic.eot and b/resource/fonts/eot/FiraSans-LightItalic.eot differ diff --git a/resource/fonts/eot/FiraSans-Medium.eot b/resource/fonts/eot/FiraSans-Medium.eot index 2ae0a766a..6e0666a0a 100755 Binary files a/resource/fonts/eot/FiraSans-Medium.eot and b/resource/fonts/eot/FiraSans-Medium.eot differ diff --git a/resource/fonts/eot/FiraSans-MediumItalic.eot b/resource/fonts/eot/FiraSans-MediumItalic.eot index a5ceae851..50691ee28 100755 Binary files a/resource/fonts/eot/FiraSans-MediumItalic.eot and b/resource/fonts/eot/FiraSans-MediumItalic.eot differ diff --git a/resource/fonts/eot/FiraSans-Regular.eot b/resource/fonts/eot/FiraSans-Regular.eot index a52c3ed49..5605f2ab7 100755 Binary files a/resource/fonts/eot/FiraSans-Regular.eot and b/resource/fonts/eot/FiraSans-Regular.eot differ diff --git a/resource/fonts/eot/FiraSans-SemiBold.eot b/resource/fonts/eot/FiraSans-SemiBold.eot new file mode 100644 index 000000000..fa865f958 Binary files /dev/null and b/resource/fonts/eot/FiraSans-SemiBold.eot differ diff --git a/resource/fonts/eot/FiraSans-SemiBoldItalic.eot b/resource/fonts/eot/FiraSans-SemiBoldItalic.eot new file mode 100644 index 000000000..79ebb0d66 Binary files /dev/null and b/resource/fonts/eot/FiraSans-SemiBoldItalic.eot differ diff --git a/resource/fonts/eot/FiraSans-Thin.eot b/resource/fonts/eot/FiraSans-Thin.eot new file mode 100644 index 000000000..ce47de2a1 Binary files /dev/null and b/resource/fonts/eot/FiraSans-Thin.eot differ diff --git a/resource/fonts/eot/FiraSans-ThinItalic.eot b/resource/fonts/eot/FiraSans-ThinItalic.eot new file mode 100644 index 000000000..68c56c038 Binary files /dev/null and b/resource/fonts/eot/FiraSans-ThinItalic.eot differ diff --git a/resource/fonts/eot/FiraSans-Two.eot b/resource/fonts/eot/FiraSans-Two.eot new file mode 100644 index 000000000..99030c474 Binary files /dev/null and b/resource/fonts/eot/FiraSans-Two.eot differ diff --git a/resource/fonts/eot/FiraSans-TwoItalic.eot b/resource/fonts/eot/FiraSans-TwoItalic.eot new file mode 100644 index 000000000..a7d47feb1 Binary files /dev/null and b/resource/fonts/eot/FiraSans-TwoItalic.eot differ diff --git a/resource/fonts/eot/FiraSans-Ultra.eot b/resource/fonts/eot/FiraSans-Ultra.eot new file mode 100644 index 000000000..9e5a1941f Binary files /dev/null and b/resource/fonts/eot/FiraSans-Ultra.eot differ diff --git a/resource/fonts/eot/FiraSans-UltraItalic.eot b/resource/fonts/eot/FiraSans-UltraItalic.eot new file mode 100644 index 000000000..7cc4ac5ec Binary files /dev/null and b/resource/fonts/eot/FiraSans-UltraItalic.eot differ diff --git a/resource/fonts/eot/FiraSans-UltraLight.eot b/resource/fonts/eot/FiraSans-UltraLight.eot new file mode 100644 index 000000000..6321f6fc6 Binary files /dev/null and b/resource/fonts/eot/FiraSans-UltraLight.eot differ diff --git a/resource/fonts/eot/FiraSans-UltraLightItalic.eot b/resource/fonts/eot/FiraSans-UltraLightItalic.eot new file mode 100644 index 000000000..8f7678154 Binary files /dev/null and b/resource/fonts/eot/FiraSans-UltraLightItalic.eot differ diff --git a/resource/fonts/ttf/FiraMono-Bold.ttf b/resource/fonts/ttf/FiraMono-Bold.ttf index 4b8b1cfbc..6d4ffb06c 100755 Binary files a/resource/fonts/ttf/FiraMono-Bold.ttf and b/resource/fonts/ttf/FiraMono-Bold.ttf differ diff --git a/resource/fonts/ttf/FiraMono-Medium.ttf b/resource/fonts/ttf/FiraMono-Medium.ttf new file mode 100644 index 000000000..1e95ced4c Binary files /dev/null and b/resource/fonts/ttf/FiraMono-Medium.ttf differ diff --git a/resource/fonts/ttf/FiraMono-Regular.ttf b/resource/fonts/ttf/FiraMono-Regular.ttf index 5238c09ed..59e1e1a1d 100755 Binary files a/resource/fonts/ttf/FiraMono-Regular.ttf and b/resource/fonts/ttf/FiraMono-Regular.ttf differ diff --git a/resource/fonts/ttf/FiraSans-Bold.ttf b/resource/fonts/ttf/FiraSans-Bold.ttf index c45687f80..95e166024 100755 Binary files a/resource/fonts/ttf/FiraSans-Bold.ttf and b/resource/fonts/ttf/FiraSans-Bold.ttf differ diff --git a/resource/fonts/ttf/FiraSans-BoldItalic.ttf b/resource/fonts/ttf/FiraSans-BoldItalic.ttf index 43f8bade3..97b52ef3c 100755 Binary files a/resource/fonts/ttf/FiraSans-BoldItalic.ttf and b/resource/fonts/ttf/FiraSans-BoldItalic.ttf differ diff --git a/resource/fonts/ttf/FiraSans-Book.ttf b/resource/fonts/ttf/FiraSans-Book.ttf new file mode 100644 index 000000000..4834eede3 Binary files /dev/null and b/resource/fonts/ttf/FiraSans-Book.ttf differ diff --git a/resource/fonts/ttf/FiraSans-BookItalic.ttf b/resource/fonts/ttf/FiraSans-BookItalic.ttf new file mode 100644 index 000000000..4d19d5224 Binary files /dev/null and b/resource/fonts/ttf/FiraSans-BookItalic.ttf differ diff --git a/resource/fonts/ttf/FiraSans-Eight.ttf b/resource/fonts/ttf/FiraSans-Eight.ttf new file mode 100644 index 000000000..2aa2ff005 Binary files /dev/null and b/resource/fonts/ttf/FiraSans-Eight.ttf differ diff --git a/resource/fonts/ttf/FiraSans-EightItalic.ttf b/resource/fonts/ttf/FiraSans-EightItalic.ttf new file mode 100644 index 000000000..84c669736 Binary files /dev/null and b/resource/fonts/ttf/FiraSans-EightItalic.ttf differ diff --git a/resource/fonts/ttf/FiraSans-ExtraBold.ttf b/resource/fonts/ttf/FiraSans-ExtraBold.ttf new file mode 100644 index 000000000..136788386 Binary files /dev/null and b/resource/fonts/ttf/FiraSans-ExtraBold.ttf differ diff --git a/resource/fonts/ttf/FiraSans-ExtraBoldItalic.ttf b/resource/fonts/ttf/FiraSans-ExtraBoldItalic.ttf new file mode 100644 index 000000000..edb8bbaae Binary files /dev/null and b/resource/fonts/ttf/FiraSans-ExtraBoldItalic.ttf differ diff --git a/resource/fonts/ttf/FiraSans-ExtraLight.ttf b/resource/fonts/ttf/FiraSans-ExtraLight.ttf new file mode 100644 index 000000000..24024222f Binary files /dev/null and b/resource/fonts/ttf/FiraSans-ExtraLight.ttf differ diff --git a/resource/fonts/ttf/FiraSans-ExtraLightItalic.ttf b/resource/fonts/ttf/FiraSans-ExtraLightItalic.ttf new file mode 100644 index 000000000..b199fed93 Binary files /dev/null and b/resource/fonts/ttf/FiraSans-ExtraLightItalic.ttf differ diff --git a/resource/fonts/ttf/FiraSans-Four.ttf b/resource/fonts/ttf/FiraSans-Four.ttf new file mode 100644 index 000000000..aed0f89bb Binary files /dev/null and b/resource/fonts/ttf/FiraSans-Four.ttf differ diff --git a/resource/fonts/ttf/FiraSans-FourItalic.ttf b/resource/fonts/ttf/FiraSans-FourItalic.ttf new file mode 100644 index 000000000..af669aacc Binary files /dev/null and b/resource/fonts/ttf/FiraSans-FourItalic.ttf differ diff --git a/resource/fonts/ttf/FiraSans-Hair.ttf b/resource/fonts/ttf/FiraSans-Hair.ttf new file mode 100644 index 000000000..d108ba086 Binary files /dev/null and b/resource/fonts/ttf/FiraSans-Hair.ttf differ diff --git a/resource/fonts/ttf/FiraSans-HairItalic.ttf b/resource/fonts/ttf/FiraSans-HairItalic.ttf new file mode 100644 index 000000000..c5242ad55 Binary files /dev/null and b/resource/fonts/ttf/FiraSans-HairItalic.ttf differ diff --git a/resource/fonts/ttf/FiraSans-Heavy.ttf b/resource/fonts/ttf/FiraSans-Heavy.ttf new file mode 100644 index 000000000..d26f38864 Binary files /dev/null and b/resource/fonts/ttf/FiraSans-Heavy.ttf differ diff --git a/resource/fonts/ttf/FiraSans-HeavyItalic.ttf b/resource/fonts/ttf/FiraSans-HeavyItalic.ttf new file mode 100644 index 000000000..7ae7493dd Binary files /dev/null and b/resource/fonts/ttf/FiraSans-HeavyItalic.ttf differ diff --git a/resource/fonts/ttf/FiraSans-Italic.ttf b/resource/fonts/ttf/FiraSans-Italic.ttf new file mode 100644 index 000000000..f5e49144f Binary files /dev/null and b/resource/fonts/ttf/FiraSans-Italic.ttf differ diff --git a/resource/fonts/ttf/FiraSans-Light.ttf b/resource/fonts/ttf/FiraSans-Light.ttf index 9dc6a161e..2ad0374b4 100755 Binary files a/resource/fonts/ttf/FiraSans-Light.ttf and b/resource/fonts/ttf/FiraSans-Light.ttf differ diff --git a/resource/fonts/ttf/FiraSans-LightItalic.ttf b/resource/fonts/ttf/FiraSans-LightItalic.ttf index 5d2ee19f1..4b4c3a225 100755 Binary files a/resource/fonts/ttf/FiraSans-LightItalic.ttf and b/resource/fonts/ttf/FiraSans-LightItalic.ttf differ diff --git a/resource/fonts/ttf/FiraSans-Medium.ttf b/resource/fonts/ttf/FiraSans-Medium.ttf index f7592fbc1..c454f7393 100755 Binary files a/resource/fonts/ttf/FiraSans-Medium.ttf and b/resource/fonts/ttf/FiraSans-Medium.ttf differ diff --git a/resource/fonts/ttf/FiraSans-MediumItalic.ttf b/resource/fonts/ttf/FiraSans-MediumItalic.ttf index 9ea5a3b2b..5416cfa75 100755 Binary files a/resource/fonts/ttf/FiraSans-MediumItalic.ttf and b/resource/fonts/ttf/FiraSans-MediumItalic.ttf differ diff --git a/resource/fonts/ttf/FiraSans-Regular.ttf b/resource/fonts/ttf/FiraSans-Regular.ttf index 93561ae5a..d9fdc0e92 100755 Binary files a/resource/fonts/ttf/FiraSans-Regular.ttf and b/resource/fonts/ttf/FiraSans-Regular.ttf differ diff --git a/resource/fonts/ttf/FiraSans-SemiBold.ttf b/resource/fonts/ttf/FiraSans-SemiBold.ttf new file mode 100644 index 000000000..821a43d7f Binary files /dev/null and b/resource/fonts/ttf/FiraSans-SemiBold.ttf differ diff --git a/resource/fonts/ttf/FiraSans-SemiBoldItalic.ttf b/resource/fonts/ttf/FiraSans-SemiBoldItalic.ttf new file mode 100644 index 000000000..48c346f65 Binary files /dev/null and b/resource/fonts/ttf/FiraSans-SemiBoldItalic.ttf differ diff --git a/resource/fonts/ttf/FiraSans-Thin.ttf b/resource/fonts/ttf/FiraSans-Thin.ttf new file mode 100644 index 000000000..69bad634d Binary files /dev/null and b/resource/fonts/ttf/FiraSans-Thin.ttf differ diff --git a/resource/fonts/ttf/FiraSans-ThinItalic.ttf b/resource/fonts/ttf/FiraSans-ThinItalic.ttf new file mode 100644 index 000000000..f44307f59 Binary files /dev/null and b/resource/fonts/ttf/FiraSans-ThinItalic.ttf differ diff --git a/resource/fonts/ttf/FiraSans-Two.ttf b/resource/fonts/ttf/FiraSans-Two.ttf new file mode 100644 index 000000000..551672f2c Binary files /dev/null and b/resource/fonts/ttf/FiraSans-Two.ttf differ diff --git a/resource/fonts/ttf/FiraSans-TwoItalic.ttf b/resource/fonts/ttf/FiraSans-TwoItalic.ttf new file mode 100644 index 000000000..1af78f27b Binary files /dev/null and b/resource/fonts/ttf/FiraSans-TwoItalic.ttf differ diff --git a/resource/fonts/ttf/FiraSans-Ultra.ttf b/resource/fonts/ttf/FiraSans-Ultra.ttf new file mode 100644 index 000000000..ecd797465 Binary files /dev/null and b/resource/fonts/ttf/FiraSans-Ultra.ttf differ diff --git a/resource/fonts/ttf/FiraSans-UltraItalic.ttf b/resource/fonts/ttf/FiraSans-UltraItalic.ttf new file mode 100644 index 000000000..07902d5e0 Binary files /dev/null and b/resource/fonts/ttf/FiraSans-UltraItalic.ttf differ diff --git a/resource/fonts/ttf/FiraSans-UltraLight.ttf b/resource/fonts/ttf/FiraSans-UltraLight.ttf new file mode 100644 index 000000000..6d2b2e01b Binary files /dev/null and b/resource/fonts/ttf/FiraSans-UltraLight.ttf differ diff --git a/resource/fonts/ttf/FiraSans-UltraLightItalic.ttf b/resource/fonts/ttf/FiraSans-UltraLightItalic.ttf new file mode 100644 index 000000000..037bdbcef Binary files /dev/null and b/resource/fonts/ttf/FiraSans-UltraLightItalic.ttf differ diff --git a/resource/fonts/woff/FiraMono-Bold.woff b/resource/fonts/woff/FiraMono-Bold.woff index e81f4ae5b..735352fb9 100755 Binary files a/resource/fonts/woff/FiraMono-Bold.woff and b/resource/fonts/woff/FiraMono-Bold.woff differ diff --git a/resource/fonts/woff/FiraMono-Medium.woff b/resource/fonts/woff/FiraMono-Medium.woff new file mode 100644 index 000000000..a33c7245c Binary files /dev/null and b/resource/fonts/woff/FiraMono-Medium.woff differ diff --git a/resource/fonts/woff/FiraMono-Regular.woff b/resource/fonts/woff/FiraMono-Regular.woff index 0a04a4f1a..b38ee144d 100755 Binary files a/resource/fonts/woff/FiraMono-Regular.woff and b/resource/fonts/woff/FiraMono-Regular.woff differ diff --git a/resource/fonts/woff/FiraSans-Bold.woff b/resource/fonts/woff/FiraSans-Bold.woff index 415071c2b..a8dba6487 100755 Binary files a/resource/fonts/woff/FiraSans-Bold.woff and b/resource/fonts/woff/FiraSans-Bold.woff differ diff --git a/resource/fonts/woff/FiraSans-BoldItalic.woff b/resource/fonts/woff/FiraSans-BoldItalic.woff index a4129c610..54895de69 100755 Binary files a/resource/fonts/woff/FiraSans-BoldItalic.woff and b/resource/fonts/woff/FiraSans-BoldItalic.woff differ diff --git a/resource/fonts/woff/FiraSans-Book.woff b/resource/fonts/woff/FiraSans-Book.woff new file mode 100644 index 000000000..3d39706df Binary files /dev/null and b/resource/fonts/woff/FiraSans-Book.woff differ diff --git a/resource/fonts/woff/FiraSans-BookItalic.woff b/resource/fonts/woff/FiraSans-BookItalic.woff new file mode 100644 index 000000000..ae9a33ec4 Binary files /dev/null and b/resource/fonts/woff/FiraSans-BookItalic.woff differ diff --git a/resource/fonts/woff/FiraSans-Eight.woff b/resource/fonts/woff/FiraSans-Eight.woff new file mode 100644 index 000000000..4fcce277a Binary files /dev/null and b/resource/fonts/woff/FiraSans-Eight.woff differ diff --git a/resource/fonts/woff/FiraSans-EightItalic.woff b/resource/fonts/woff/FiraSans-EightItalic.woff new file mode 100644 index 000000000..451026259 Binary files /dev/null and b/resource/fonts/woff/FiraSans-EightItalic.woff differ diff --git a/resource/fonts/woff/FiraSans-ExtraBold.woff b/resource/fonts/woff/FiraSans-ExtraBold.woff new file mode 100644 index 000000000..716035728 Binary files /dev/null and b/resource/fonts/woff/FiraSans-ExtraBold.woff differ diff --git a/resource/fonts/woff/FiraSans-ExtraBoldItalic.woff b/resource/fonts/woff/FiraSans-ExtraBoldItalic.woff new file mode 100644 index 000000000..7914f0eb8 Binary files /dev/null and b/resource/fonts/woff/FiraSans-ExtraBoldItalic.woff differ diff --git a/resource/fonts/woff/FiraSans-ExtraLight.woff b/resource/fonts/woff/FiraSans-ExtraLight.woff new file mode 100644 index 000000000..005d0b7c0 Binary files /dev/null and b/resource/fonts/woff/FiraSans-ExtraLight.woff differ diff --git a/resource/fonts/woff/FiraSans-ExtraLightItalic.woff b/resource/fonts/woff/FiraSans-ExtraLightItalic.woff new file mode 100644 index 000000000..fef3e389b Binary files /dev/null and b/resource/fonts/woff/FiraSans-ExtraLightItalic.woff differ diff --git a/resource/fonts/woff/FiraSans-Four.woff b/resource/fonts/woff/FiraSans-Four.woff new file mode 100644 index 000000000..4da1db7e3 Binary files /dev/null and b/resource/fonts/woff/FiraSans-Four.woff differ diff --git a/resource/fonts/woff/FiraSans-FourItalic.woff b/resource/fonts/woff/FiraSans-FourItalic.woff new file mode 100644 index 000000000..a3f84683f Binary files /dev/null and b/resource/fonts/woff/FiraSans-FourItalic.woff differ diff --git a/resource/fonts/woff/FiraSans-Hair.woff b/resource/fonts/woff/FiraSans-Hair.woff new file mode 100644 index 000000000..5fbacb7d6 Binary files /dev/null and b/resource/fonts/woff/FiraSans-Hair.woff differ diff --git a/resource/fonts/woff/FiraSans-HairItalic.woff b/resource/fonts/woff/FiraSans-HairItalic.woff new file mode 100644 index 000000000..f2d7f9801 Binary files /dev/null and b/resource/fonts/woff/FiraSans-HairItalic.woff differ diff --git a/resource/fonts/woff/FiraSans-Heavy.woff b/resource/fonts/woff/FiraSans-Heavy.woff new file mode 100644 index 000000000..ad5de4b6a Binary files /dev/null and b/resource/fonts/woff/FiraSans-Heavy.woff differ diff --git a/resource/fonts/woff/FiraSans-HeavyItalic.woff b/resource/fonts/woff/FiraSans-HeavyItalic.woff new file mode 100644 index 000000000..7914da911 Binary files /dev/null and b/resource/fonts/woff/FiraSans-HeavyItalic.woff differ diff --git a/resource/fonts/woff/FiraSans-Italic.woff b/resource/fonts/woff/FiraSans-Italic.woff new file mode 100644 index 000000000..298019498 Binary files /dev/null and b/resource/fonts/woff/FiraSans-Italic.woff differ diff --git a/resource/fonts/woff/FiraSans-Light.woff b/resource/fonts/woff/FiraSans-Light.woff index f05e73a75..747071e9a 100755 Binary files a/resource/fonts/woff/FiraSans-Light.woff and b/resource/fonts/woff/FiraSans-Light.woff differ diff --git a/resource/fonts/woff/FiraSans-LightItalic.woff b/resource/fonts/woff/FiraSans-LightItalic.woff index 43a73e887..e19720ace 100755 Binary files a/resource/fonts/woff/FiraSans-LightItalic.woff and b/resource/fonts/woff/FiraSans-LightItalic.woff differ diff --git a/resource/fonts/woff/FiraSans-Medium.woff b/resource/fonts/woff/FiraSans-Medium.woff index 562722774..7d742c5fb 100755 Binary files a/resource/fonts/woff/FiraSans-Medium.woff and b/resource/fonts/woff/FiraSans-Medium.woff differ diff --git a/resource/fonts/woff/FiraSans-MediumItalic.woff b/resource/fonts/woff/FiraSans-MediumItalic.woff index f3814873b..dd5bbe6f3 100755 Binary files a/resource/fonts/woff/FiraSans-MediumItalic.woff and b/resource/fonts/woff/FiraSans-MediumItalic.woff differ diff --git a/resource/fonts/woff/FiraSans-Regular.woff b/resource/fonts/woff/FiraSans-Regular.woff index 9ff40445b..d8e0363f4 100755 Binary files a/resource/fonts/woff/FiraSans-Regular.woff and b/resource/fonts/woff/FiraSans-Regular.woff differ diff --git a/resource/fonts/woff/FiraSans-SemiBold.woff b/resource/fonts/woff/FiraSans-SemiBold.woff new file mode 100644 index 000000000..8b408d4d0 Binary files /dev/null and b/resource/fonts/woff/FiraSans-SemiBold.woff differ diff --git a/resource/fonts/woff/FiraSans-SemiBoldItalic.woff b/resource/fonts/woff/FiraSans-SemiBoldItalic.woff new file mode 100644 index 000000000..2592e4fcf Binary files /dev/null and b/resource/fonts/woff/FiraSans-SemiBoldItalic.woff differ diff --git a/resource/fonts/woff/FiraSans-Thin.woff b/resource/fonts/woff/FiraSans-Thin.woff new file mode 100644 index 000000000..f986fb583 Binary files /dev/null and b/resource/fonts/woff/FiraSans-Thin.woff differ diff --git a/resource/fonts/woff/FiraSans-ThinItalic.woff b/resource/fonts/woff/FiraSans-ThinItalic.woff new file mode 100644 index 000000000..c90247e82 Binary files /dev/null and b/resource/fonts/woff/FiraSans-ThinItalic.woff differ diff --git a/resource/fonts/woff/FiraSans-Two.woff b/resource/fonts/woff/FiraSans-Two.woff new file mode 100644 index 000000000..f1db0fb4f Binary files /dev/null and b/resource/fonts/woff/FiraSans-Two.woff differ diff --git a/resource/fonts/woff/FiraSans-TwoItalic.woff b/resource/fonts/woff/FiraSans-TwoItalic.woff new file mode 100644 index 000000000..f5ddbf607 Binary files /dev/null and b/resource/fonts/woff/FiraSans-TwoItalic.woff differ diff --git a/resource/fonts/woff/FiraSans-Ultra.woff b/resource/fonts/woff/FiraSans-Ultra.woff new file mode 100644 index 000000000..b42f714be Binary files /dev/null and b/resource/fonts/woff/FiraSans-Ultra.woff differ diff --git a/resource/fonts/woff/FiraSans-UltraItalic.woff b/resource/fonts/woff/FiraSans-UltraItalic.woff new file mode 100644 index 000000000..34863071c Binary files /dev/null and b/resource/fonts/woff/FiraSans-UltraItalic.woff differ diff --git a/resource/fonts/woff/FiraSans-UltraLight.woff b/resource/fonts/woff/FiraSans-UltraLight.woff new file mode 100644 index 000000000..2c22e9465 Binary files /dev/null and b/resource/fonts/woff/FiraSans-UltraLight.woff differ diff --git a/resource/fonts/woff/FiraSans-UltraLightItalic.woff b/resource/fonts/woff/FiraSans-UltraLightItalic.woff new file mode 100644 index 000000000..421f93373 Binary files /dev/null and b/resource/fonts/woff/FiraSans-UltraLightItalic.woff differ diff --git a/resource/js/docready.js b/resource/js/docready.js index 16c6b921c..9f394c9a2 100644 --- a/resource/js/docready.js +++ b/resource/js/docready.js @@ -755,6 +755,16 @@ $(function() { // DOCUMENT READY $('.clear-search').addClass('clear-search-dark'); } }); + + // monkey-patching TypeAhead's Dropdown object for: https://github.com/NatLibFi/Skosmos/issues/773 + // Updating typeahead.js to 0.11 requires a few changes that are not really complicated. + // However, our dropdown style is broken, and it appears hard to be fixed. typeahead.js + // Also does not appear to be maintained, so this temporary fix will prevent + // accidental selection of values. TODO: we must fix this in a future release, possibly + // using another library. + var typeaheadInstance = $typeahead.data("ttTypeahead"); + typeaheadInstance.dropdown.$menu.off("mouseenter.tt", ".tt-suggestion"); + typeaheadInstance.dropdown.$menu.off("mouseleave.tt", ".tt-suggestion"); } // storing the search input before autocompletion changes it diff --git a/tests/WebControllerTest.php b/tests/WebControllerTest.php index cde32d907..abbb6738f 100644 --- a/tests/WebControllerTest.php +++ b/tests/WebControllerTest.php @@ -6,10 +6,10 @@ class WebControllerTest extends TestCase { /** - * Data for testGetModifiedDate. + * Data for testConceptGetModifiedDate. * @return array */ - public function modifiedDateDataProvider() + public function conceptModifiedDateDataProvider() { return [ # when there is no modified date for a concept, and there is no modified date for the main concept scheme, @@ -59,6 +59,85 @@ public function modifiedDateDataProvider() ]; } + /** + * Data for testGetGitModifiedDateCacheEnabled and for testGetConfigModifiedDate. We are able to use the + * same data provider in two methods, as they both have similar interface and behaviour. Only difference + * being that one retrieves the information from git, and the other from the file system, but as the + * methods that perform the action are abstracted away, this data provider works for both. + * @return array + */ + public function gitAndConfigModifiedDateDataProvider() + { + return [ + # cache disabled + [ + false, # cache enabled + null, # cached value + $this->datetime('01-Feb-2009') # modified date time + ], # set #0 + # cache enabled, but nothing fetched + [ + true, # cache enabled + null, # cached value + $this->datetime('01-Feb-2009') # modified date time + ], # set #1 + # cache enabled, cached value returned + [ + true, # cache enabled + $this->datetime('01-Feb-2009'), # cached value + null # modified date time + ], # set #2 + ]; + } + + public function modifiedDateDataProvider() + { + return [ + # concept has the most recent date time + [ + $this->datetime('01-Feb-2011'), # concept + $this->datetime('01-Feb-2002'), # git + $this->datetime('01-Feb-2003'), # config + $this->datetime('01-Feb-2011') # returned + ], # set #0 + # concept has the most recent date time + [ + $this->datetime('01-Feb-2011'), # concept + null, # git + $this->datetime('01-Feb-2003'), # config + $this->datetime('01-Feb-2011') # returned + ], # set #1 + # concept has the most recent date time + [ + $this->datetime('01-Feb-2011'), # concept + null, # git + null, # config + $this->datetime('01-Feb-2011') # returned + ], # set #2 + # git has the most recent date time + [ + $this->datetime('01-Feb-2001'), # concept + $this->datetime('01-Feb-2012'), # git + $this->datetime('01-Feb-2003'), # config + $this->datetime('01-Feb-2012') # returned + ], # set #3 + # config has the most recent date time + [ + $this->datetime('01-Feb-2001'), # concept + $this->datetime('01-Feb-2002'), # git + $this->datetime('01-Feb-2013'), # config + $this->datetime('01-Feb-2013') # returned + ], # set #4 + # no date time found + [ + null, # concept + null, # git + null, # config + null # returned + ], # set #4 + ]; + } + /** * Utility method to create a datetime for data provider. * @param string $string @@ -70,13 +149,118 @@ private function datetime(string $string) } /** - * Test that the behaviour of getModifiedDate works as expected. If there is a concept with a modified + * @param bool $cacheAvailable + * @param DateTime|null $cachedValue + * @param DateTime|null $modifiedDate + * @dataProvider gitAndConfigModifiedDateDataProvider + */ + public function testGetGitModifiedDate($cacheAvailable, $cachedValue, $modifiedDate) + { + $cache = Mockery::spy('Cache'); + $cache->shouldReceive('isAvailable') + ->andReturn($cacheAvailable); + if ($cacheAvailable) { + $cache->shouldReceive('fetch') + ->andReturn($cachedValue); + if ($modifiedDate) { + $cache->shouldReceive('store') + ->andReturn(true); + } + } + $globalConfig = Mockery::mock('GlobalConfig'); + $globalConfig->shouldReceive('getCache') + ->andReturn($cache); + $model = Mockery::mock('Model'); + $model->shouldReceive('getConfig') + ->andReturn($globalConfig); + $controller = Mockery::mock('WebController') + ->shouldAllowMockingProtectedMethods() + ->makePartial(); + $controller->model = $model; + if ($modifiedDate) { + $controller->shouldReceive('executeGitModifiedDateCommand') + ->andReturn($modifiedDate); + } + $gitModifiedDate = $controller->getGitModifiedDate(); + + $this->assertNotNull($gitModifiedDate); + $this->assertTrue($gitModifiedDate > (new DateTime())->setTimeStamp(1)); + } + + /** + * Execute the git command and test that it returns a valid date time. It should be safe to execute this, as + * Travis-CI and developer environments should have git installed. + */ + public function testExecuteGitModifiedDateCommand() + { + $controller = Mockery::mock('WebController') + ->shouldAllowMockingProtectedMethods() + ->makePartial(); + $gitModifiedDate = $controller->executeGitModifiedDateCommand('git log -1 --date=iso --pretty=format:%cd'); + $this->assertInstanceOf('DateTime', $gitModifiedDate); + $this->assertTrue($gitModifiedDate > (new DateTime())->setTimeStamp(1)); + } + + /** + * @param bool $cacheAvailable + * @param DateTime|null $cachedValue + * @param DateTime|null $modifiedDate + * @dataProvider gitAndConfigModifiedDateDataProvider + */ + public function testGetConfigModifiedDate($cacheAvailable, $cachedValue, $modifiedDate) + { + $globalConfig = Mockery::mock('GlobalConfig'); + $globalConfig->shouldReceive('getConfigModifiedTime') + ->andReturn(1); + $model = Mockery::mock('Model'); + $model->shouldReceive('getConfig') + ->andReturn($globalConfig); + $controller = Mockery::mock('WebController') + ->shouldAllowMockingProtectedMethods() + ->makePartial(); + $controller->model = $model; + if ($modifiedDate) { + $controller->shouldReceive('retrieveConfigModifiedDate') + ->andReturn($modifiedDate); + } + $configModifiedDate = $controller->getConfigModifiedDate(); + + $this->assertNotNull($configModifiedDate); + $this->assertEquals((new DateTime())->setTimeStamp(1), $configModifiedDate); + } + + /** + * @param DateTime|null $concept + * @param DateTime|null $git + * @param DateTime|null $config + * @param DateTime|null $modifiedDate + * @dataProvider modifiedDateDataProvider + */ + public function testGetModifiedDate($concept, $git, $config, $modifiedDate) + { + $controller = Mockery::mock('WebController') + ->shouldAllowMockingProtectedMethods() + ->makePartial(); + $controller->shouldReceive('getConceptModifiedDate') + ->andReturn($concept); + $controller->shouldReceive('getGitModifiedDate') + ->andReturn($git); + $controller->shouldReceive('getConfigModifiedDate') + ->andReturn($config); + $concept = Mockery::mock('Concept'); + $vocabulary = Mockery::mock('Vocabulary'); + $returnedValue = $controller->getModifiedDate($concept, $vocabulary); + $this->assertEquals($modifiedDate, $returnedValue); + } + + /** + * Test that the behaviour of getConceptModifiedDate works as expected. If there is a concept with a modified * date, then it will return that value. If there is no modified date in the concept, but the main * concept scheme contains a date, then the main concept scheme's modified date will be returned instead. * Finally, if neither of the previous scenarios occur, then it returns null. - * @dataProvider modifiedDateDataProvider + * @dataProvider conceptModifiedDateDataProvider */ - public function testGetModifiedDate($conceptDate, $schemeDate, $isSchemeEmpty, $isLiteralNull, $expected) + public function testConceptGetModifiedDate($conceptDate, $schemeDate, $isSchemeEmpty, $isLiteralNull, $expected) { $concept = Mockery::mock("Concept"); $concept @@ -113,7 +297,7 @@ public function testGetModifiedDate($conceptDate, $schemeDate, $isSchemeEmpty, $ $controller = Mockery::mock('WebController') ->shouldAllowMockingProtectedMethods() ->makePartial(); - $date = $controller->getModifiedDate($concept, $vocab); + $date = $controller->getConceptModifiedDate($concept, $vocab); $this->assertEquals($expected, $date); } }