360, 'padding-height' => 0, 'style' => [], 'width' => 640 ]; /** * Build the template representing the embedded content * * @return string */ abstract protected function getContentTemplate(); /** * Build a template based on a list of attributes * * @param array $attributes * @return string */ public function getTemplate(array $attributes) { $this->attributes = $attributes + $this->defaultAttributes; return ($this->needsWrapper()) ? $this->getWrappedTemplate() : $this->getUnwrappedTemplate(); } /** * Format an attribute value to be used in an XPath expression * * @param string $expr Original value * @return string Formatted value */ protected function expr($expr) { $expr = trim($expr, '{}'); return (preg_match('(^[@$]?[-\\w]+$)D', $expr)) ? $expr : "($expr)"; } /** * Generate xsl:attributes elements from an array * * @param array $attributes Array of [name => value] where value can be XSL code * @return string XSL source */ protected function generateAttributes(array $attributes) { if (isset($attributes['style']) && is_array($attributes['style'])) { $attributes['style'] = $this->generateStyle($attributes['style']); } ksort($attributes); $xsl = ''; foreach ($attributes as $attrName => $attrValue) { $innerXML = (strpos($attrValue, '' . $innerXML . ''; } return $xsl; } /** * Generate a CSS declaration based on an array of CSS properties * * @param array $properties Property name => property value * @return string */ protected function generateStyle(array $properties) { ksort($properties); $style = ''; foreach ($properties as $name => $value) { $style .= $name . ':' . $value . ';'; } return trim($style, ';'); } /** * Generate and return the padding declaration used in the responsive wrapper * * @return string */ protected function getResponsivePadding() { $height = $this->expr($this->attributes['height']); $paddingHeight = $this->expr($this->attributes['padding-height']); $width = $this->expr($this->attributes['width']); // Create the padding declaration for the fixed ratio $css = 'padding-bottom:%'; // Add the padding declaration for the computed ratio if applicable if (!empty($this->attributes['padding-height'])) { // NOTE: there needs to be whitespace around tokens in calc() $css .= ';padding-bottom:calc(% + ' . $paddingHeight . 'px)'; } // If the width is dynamic, use a conditional to protect against divisions by zero if (strpos($width, '@') !== false) { $css = '' . $css . ''; } return $css; } /** * Generate and return a responsive template for the embedded content * * @return string */ protected function getUnwrappedTemplate() { $this->attributes['style']['width'] = '100%'; $this->attributes['style']['height'] = $this->attributes['height'] . 'px'; $this->attributes['style']['max-width'] = '100%'; if (isset($this->attributes['max-width'])) { $this->attributes['style']['max-width'] = $this->attributes['max-width'] . 'px'; } elseif ($this->attributes['width'] !== '100%') { $property = ($this->hasDynamicWidth()) ? 'width' : 'max-width'; $this->attributes['style'][$property] = $this->attributes['width'] . 'px'; } if ($this->attributes['style']['width'] === $this->attributes['style']['max-width']) { unset($this->attributes['style']['max-width']); } return $this->getContentTemplate(); } /** * Generate and return a template for the embedded content, complete with a responsive wrapper * * @return string */ protected function getWrappedTemplate() { $this->attributes['style']['width'] = '100%'; $this->attributes['style']['height'] = '100%'; $this->attributes['style']['position'] = 'absolute'; $this->attributes['style']['left'] = '0'; $outerStyle = 'display:inline-block;width:100%;max-width:' . $this->attributes['width'] . 'px'; $innerStyle = 'display:block;overflow:hidden;position:relative;' . $this->getResponsivePadding(); $template = '' . $this->generateAttributes(['style' => $outerStyle]); $template .= '' . $this->generateAttributes(['style' => $innerStyle]); $template .= $this->getContentTemplate(); $template .= ''; return $template; } /** * Test whether current template has a dynamic height * * @return bool */ protected function hasDynamicHeight() { return (isset($this->attributes['onload']) && strpos($this->attributes['onload'], '.height') !== false); } /** * Test whether current template has a dynamic width * * @return bool */ protected function hasDynamicWidth() { return (isset($this->attributes['onload']) && strpos($this->attributes['onload'], '.width') !== false); } /** * Merge two array of attributes * * @param array $defaultAttributes * @param array $newAttributes * @return array */ protected function mergeAttributes(array $defaultAttributes, array $newAttributes) { $attributes = array_merge($defaultAttributes, $newAttributes); if (isset($defaultAttributes['style'], $newAttributes['style'])) { // Re-add the default attributes that were lost (but not replaced) in the merge $attributes['style'] += $defaultAttributes['style']; } return $attributes; } /** * Test whether current template needs a wrapper to be responsive * * @return bool */ protected function needsWrapper() { return ($this->attributes['width'] !== '100%' && !$this->hasDynamicHeight()); } }