From 066f38d622a07fe44c10027913e85129391dd8e8 Mon Sep 17 00:00:00 2001 From: mruz Date: Fri, 25 Jul 2014 11:46:37 +0200 Subject: [PATCH] Library, Sitemap generator --- app/common/library/Sitemap.php | 196 ++++++++++++ app/common/library/Sitemap/Code.php | 139 ++++++++ app/common/library/Sitemap/Geo.php | 50 +++ app/common/library/Sitemap/Mobile.php | 25 ++ app/common/library/Sitemap/News.php | 192 +++++++++++ .../library/Sitemap/SitemapInterface.php | 11 + app/common/library/Sitemap/URL.php | 148 +++++++++ app/common/library/Sitemap/Video.php | 299 ++++++++++++++++++ 8 files changed, 1060 insertions(+) create mode 100644 app/common/library/Sitemap.php create mode 100644 app/common/library/Sitemap/Code.php create mode 100644 app/common/library/Sitemap/Geo.php create mode 100644 app/common/library/Sitemap/Mobile.php create mode 100644 app/common/library/Sitemap/News.php create mode 100644 app/common/library/Sitemap/SitemapInterface.php create mode 100644 app/common/library/Sitemap/URL.php create mode 100644 app/common/library/Sitemap/Video.php diff --git a/app/common/library/Sitemap.php b/app/common/library/Sitemap.php new file mode 100644 index 0000000..15f6252 --- /dev/null +++ b/app/common/library/Sitemap.php @@ -0,0 +1,196 @@ +getShared('config')->sitemap) && $config = \Phalcon\DI::getDefault()->getShared('config')->sitemap) { + foreach ($config as $key => $value) { + $this->$key = $value; + } + } + + // XML document + $this->_xml = new \DOMDocument('1.0', 'UTF-8'); + + // Attributes + $this->_xml->formatOutput = true; + + // Root element + $this->_root = $this->_xml->createElement('urlset'); + + // Append to XML document + $this->_xml->appendChild($this->_root); + } + + /** + * @param Sitemap_URL $object + */ + public function add(Sitemap\URL $object) + { + $url = $object->create(); + + // Decorate the urlset + $object->root($this->_root); + + // Append URL to root element + $this->_root->appendChild($this->_xml->importNode($url, true)); + } + + /** + * Ping web services + * + * @param string $sitemap Full website path to sitemap + * @return array Service key with the HTTP response code as the value. + */ + public static function ping($sitemap) + { + if (!isset(\Phalcon\DI::getDefault()->getShared('config')->sitemap->ping)) { + return null; + } + + // URLs to ping + $ping = \Phalcon\DI::getDefault()->getShared('config')->sitemap->ping; + + // Main handle + $master = curl_multi_init(); + + $handles = array(); + + // Create handles for each URL and add them to the main handle. + foreach ($ping as $key => $val) { + $handles[$key] = curl_init(sprintf($val, $sitemap)); + + curl_setopt($handles[$key], CURLOPT_FOLLOWLOCATION, true); + curl_setopt($handles[$key], CURLOPT_RETURNTRANSFER, true); + curl_setopt($handles[$key], CURLOPT_USERAGENT, 'Mozilla/5.0 (X11; U; Linux x86_64; en-GB; rv:1.9.2.3) Gecko/20100423 Ubuntu/10.04 (lucid) Firefox/3.6.3'); + + curl_multi_add_handle($master, $handles[$key]); + } + + do { + curl_multi_exec($master, $still_running); + } while ($still_running > 0); + + $info = array(); + + // Build an array of the execution information. + foreach (array_keys($ping) as $key) { + $info[$key] = curl_getinfo($handles[$key], CURLINFO_HTTP_CODE); + + // Close the handles while we're here. + curl_multi_remove_handle($master, $handles[$key]); + } + + // and finally close the master handle. + curl_multi_close($master); + + return $info; + } + + /** + * UTF8 encode a string + * + * @access public + * @param string $string + * @return string + */ + public static function encode($string) + { + $string = htmlspecialchars($string, ENT_QUOTES, 'UTF-8'); + + // This is a rather ugly hack. Basically urlencode and rawurlencode use RFC 1738 + // encoding. This brings it up to date (RFC 3986); The newer RFC has a different + // set of reserved characters. Credit goes to davis dot peixoto at gmail dot com + // God bless PHP comments. + $entities = array('%21', '%2A', '%27', '%28', '%29', '%3B', '%3A', '%40', + '%26', '%3D', '%2B', '%24', '%2C', '%2F', '%3F', '%23', '%5B', '%5D'); + + $replacements = array('!', '*', "'", "(", ")", ";", ":", "@", "&", "=", "+", + "$", ",", "/", "?", "#", "[", "]"); + + $string = str_replace($entities, $replacements, rawurlencode($string)); + + return str_replace(''', ''', $string); + } + + /** + * Format a unix timestamp into W3C Datetime + * + * @access public + * @see http://www.w3.org/TR/NOTE-datetime + * @param string $unix Unixtimestamp + * @return string W3C Datetime + */ + public static function date_format($unix) + { + if (is_numeric($unix) AND $unix <= PHP_INT_MAX) { + return date('Y-m-d\TH:i:sP', $unix); + } + + throw new \InvalidArgumentException('Must be a unix timestamp'); + } + + /** + * @return string Either an XML document or a gzipped file + */ + public function render() + { + // Default uncompressed + $response = $this->_xml->saveXML(); + + if ($this->gzip) { + // Try and gzip the file before we send it off. + try { + $response = gzencode($response, $this->compression); + } catch (ErrorException $e) { + \Baseapp\Bootstrap::exception($e); + } + } + + return $response; + } + + /** + * @return string XML output. + */ + public function __toString() + { + return $this->render(); + } + +} diff --git a/app/common/library/Sitemap/Code.php b/app/common/library/Sitemap/Code.php new file mode 100644 index 0000000..b3961e3 --- /dev/null +++ b/app/common/library/Sitemap/Code.php @@ -0,0 +1,139 @@ + null, + 'license' => null, + 'filename' => null, + 'packageurl' => null, + 'packagemap' => null + ); + protected $_licenses = array( + 'aladdin', 'artistic', 'apache', 'apple', 'bsd', 'cpl', 'gpl', 'lgpl', 'disclaimer', + 'ibm', 'lucent', 'mit', 'mozilla', 'nasa', 'python', 'qpl', 'sleepycat', 'zope' + ); + protected $_archives = array( + '.tar', '.tar.z', '.tar.gz', '.tgz', '.tar.bz2', '.tbz', '.tbz2', '.zip' + ); + + /** + * @param string $type Case-insensitive. The value "archive" indicates that + * the file is an archive file. For source code files, the value defines the + * the source code language. Examples include "C", "Python", "C#", "Java", "Vim". + * For source code language, the Short Name, as specified in the list of supported + * languages, must be used. The value must be printable ASCII characters, and + * no white space is allowed. + * + * @see http://www.google.com/support/webmasters/bin/answer.py?answer=75252 + */ + public function set_file_type($type) + { + $type = (string) $type; + + if (!preg_match('/^[a-z][a-z0-9+#]*$/i', $type)) { + throw new \InvalidArgumentException('Type must only contain a-z, 0-9, + and #'); + } + + $this->_attributes['filetype'] = $type; + + return $this; + } + + /** + * @param string $license Case-insensitive. The name of the software license. + * For archive files, this indicates the default license for files in the archive. + * Examples include "GPL", "BSD", "Python", "disclaimer". You must use the Short + * Name, as specified in the list of supported licenses. + * + * @see http://www.google.com/support/webmasters/bin/answer.py?answer=75256 + */ + public function set_license($license) + { + $license = (string) $license; + + if (!in_array($license, $this->_licenses)) { + throw new \InvalidArgumentException('Invalid license type. See http://www.google.com/support/webmasters/bin/answer.py?answer=75256 for details'); + } + + $this->_attributes['license'] = $license; + + return $this; + } + + /** + * @param string $file_name The name of the actual file. This is useful if the + * URL ends in something like download.php?id=1234 instead of the actual filename. + * The name can contain any character except "/". If the file is an archive file, + * it will be indexed only if it has one of the supported archive suffixes. + * + * @see http://www.google.com/support/webmasters/bin/answer.py?answer=75259 + */ + public function set_file_name($file_name) + { + $file_name = (string) $file_name; + + if ($this->_attributes['filetype'] === 'archive') { + if (!in_array(pathinfo($file_name, PATHINFO_EXTENSION), $this->_archives)) { + throw new \InvalidArgumentException('Not a valid archive type'); + } + } + + $this->_attributes['filename'] = basename($file_name); + + return $this; + } + + /** + * @param $package_type For use only when the value of codesearch:filetype + * is not "archive". The URL truncated at the top-level directory for the package. + * For example, the file http://path/Foo/1.23/bar/file.c could have the package URL + * http://path/Foo/1.23. All files in a package should have the same packageurl. + * This tells us which files belong together. + */ + public function set_package_url($package_type) + { + $this->_attributes['packageurl'] = $package_type; + } + + /** + * @param string $package_map Case-sensitive. For use only when codesearch:filetype + * is "archive". The name of the packagemap file inside the archive. Just like a + * Sitemap is a list of files on a web site, a packagemap is a list of files in + * a package. + * + * @see http://www.google.com/help/codesearch_packagemap.html + */ + public function set_package_map($package_map) + { + $this->_attributes['packagemap'] = $package_map; + } + + public function create() + { + // Here we need to create a new DOMDocument. This is so we can re-import the + // DOMElement at the other end. + $document = new \DOMDocument; + + // Mobile element + $code = $document->createElement('codesearch:codesearch'); + + // Append attributes + foreach ($this->_attributes as $name => $value) { + if (null !== $value) { + $code->appendChild($document->createElement('codesearch:' . $name, $value)); + } + } + + return $code; + } + + public function root(\DOMElement & $root) + { + $root->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:codesearch', 'http://www.google.com/codesearch/schemas/sitemap/1.0'); + } + +} diff --git a/app/common/library/Sitemap/Geo.php b/app/common/library/Sitemap/Geo.php new file mode 100644 index 0000000..6b670fe --- /dev/null +++ b/app/common/library/Sitemap/Geo.php @@ -0,0 +1,50 @@ +_allowed_formats)) { + $this->_format = $format; + + return $this; + } + + throw new \InvalidArgumentException('The format must either be kml, kmlz or georss'); + } + + public function create() + { + // Here we need to create a new DOMDocument. This is so we can re-import the + // DOMElement at the other end. + $document = new \DOMDocument; + + // Mobile element + $geo = $document->createElement('geo:geo'); + + // Add format + $geo->appendChild($document->createElement('geo:format', $this->_format)); + + return $geo; + } + + public function root(\DOMElement & $root) + { + $root->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:geo', 'http://www.google.com/geo/schemas/sitemap/1.0'); + } + +} diff --git a/app/common/library/Sitemap/Mobile.php b/app/common/library/Sitemap/Mobile.php new file mode 100644 index 0000000..353da0a --- /dev/null +++ b/app/common/library/Sitemap/Mobile.php @@ -0,0 +1,25 @@ +createElement('mobile:mobile'); + + return $mobile; + } + + public function root(\DOMElement & $root) + { + $root->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:mobile', 'http://www.google.com/schemas/sitemap-mobile/1.0'); + } + +} diff --git a/app/common/library/Sitemap/News.php b/app/common/library/Sitemap/News.php new file mode 100644 index 0000000..5646e34 --- /dev/null +++ b/app/common/library/Sitemap/News.php @@ -0,0 +1,192 @@ + null, + 'lang' => null + ); + + /** + * @var array article attributes + */ + protected $_attributes = array( + 'access' => null, + 'genres' => null, + 'publication_date' => null, + 'title' => null, + 'keywords' => null, + 'stock_tickers' => null, + ); + + /** + * @param string $publication name of the news publication. It must exactly + * match the name as it appears on your articles in news.google.com, omitting + * any trailing parentheticals. + */ + public function set_publication($publication) + { + $this->_publication['publication'] = $publication; + + return $this; + } + + /** + * @param string $lang hould be an ISO 639 Language Code (either 2 or 3 letters). + * Exception: For Chinese, please use zh-cn for Simplified Chinese or zh-tw for + * Traditional Chinese. + * + * @see http://www.loc.gov/standards/iso639-2/php/code_list.php + */ + public function set_lang($lang) + { + if ('zh-cn' !== $lang AND 'zh-tw' !== $lang) { + if (!preg_match('/^[a-z]{2,3}$/', $lang)) { + throw new \InvalidArgumentException('Invalid language code'); + } + } + + $this->_publication['lang'] = $lang; + + return $this; + } + + /** + * @param string $access Possible values include "Subscription" or "Registration", + * describing the accessibility of the article. If the article is accessible to + * Google News readers without a registration or subscription, this tag should + * be omitted. + */ + public function set_access($access) + { + if ('subscription' !== strtolower($access) AND 'registration' !== strtolower($access)) { + throw new \InvalidArgumentException('Invalid access string'); + } + + $this->_attributes['access'] = $access; + + return $this; + } + + /** + * @param string $genres A comma-separated list of properties characterizing the + * content of the article, such as "PressRelease" or "UserGenerated." See Google + * News content properties for a list of possible values. Your content must be + * labeled accurately, in order to provide a consistent experience for our users. + * + * @see http://www.google.com/support/webmasters/bin/answer.py?answer=93992 + */ + public function set_genres(array $genres) + { + $allowed = array('PressRelease', 'Satire', 'Blog', 'OpEd', 'Opinion', 'UserGenerated'); + + $difference = array_diff($genres, $allowed); + + if (count($difference) > 0) { + throw new \InvalidArgumentException('Invalid genre passed'); + } + + $this->_attributes['genres'] = implode(',', $genres); + + return $this; + } + + /** + * @param integer $date Article publication date in unixtimestamp format + */ + public function set_publication_date($date) + { + $this->_attributes['publication_date'] = \Baseapp\Library\Sitemap::date_format($date); + + return $this; + } + + /** + * @param string $title The title of the news article. Note: The title may be + * truncated for space reasons when shown on Google News. + */ + public function set_title($title) + { + $this->_attributes['title'] = $title; + + return $this; + } + + /** + * @param string $keywords A comma-separated list of keywords describing the + * topic of the article. Keywords may be drawn from, but are not limited to, + * the list of existing Google News keywords. + * + * @see http://www.google.com/support/webmasters/bin/answer.py?answer=116037 + */ + public function set_keywords(array $keywords) + { + $this->_attributes['keywords'] = implode(',', $keywords); + + return $this; + } + + /** + * @param string $tickers A comma-separated list of up to 5 stock tickers of + * the companies, mutual funds, or other financial entities that are the main + * subject of the article. + * + * @see http://finance.google.com/ + */ + public function set_stock_tickers(array $tickers) + { + if (count($tickers) > 5) { + throw new \OutOfRangeException('You can\'t provide more than 5 tickers'); + } + + // Check ticker values. + foreach ($tickers as $ticker) { + if (strpos($ticker, ':') === false) { + throw new \InvalidArgumentException('The ticker ' . $ticker . ' is in the wrong format'); + } + } + + $this->_attributes['stock_tickers'] = implode(', ', $tickers); + + return $this; + } + + public function create() + { + // Here we need to create a new DOMDocument. This is so we can re-import the + // DOMElement at the other end. + $document = new \DOMDocument; + + $news = $document->createElement('news:news'); + + // Publication + $publication = $document->createElement('news:publication'); + + $news->appendChild($publication); + + // Publication attributes + $publication->appendChild($document->createElement('news:name', $this->_publication['publication'])); + $publication->appendChild($document->createElement('news:language', $this->_publication['lang'])); + + // Append attributes + foreach ($this->_attributes as $name => $value) { + if (null !== $value) { + $news->appendChild($document->createElement('news:' . $name, $value)); + } + } + + return $news; + } + + public function root(\DOMElement & $root) + { + $root->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:news', 'http://www.google.com/schemas/sitemap-news/0.9'); + } + +} diff --git a/app/common/library/Sitemap/SitemapInterface.php b/app/common/library/Sitemap/SitemapInterface.php new file mode 100644 index 0000000..fe4cefc --- /dev/null +++ b/app/common/library/Sitemap/SitemapInterface.php @@ -0,0 +1,11 @@ + null, + 'lastmod' => null, + 'changefreq' => null, + 'priority' => null, + ); + + /** + * URL of the page. This URL must begin with the protocol (such as http) and end + * with a trailing slash, if your web server requires it. This value must be + * less than 2,048 characters. + * @see http://www.sitemaps.org/protocol.php + * @param string $location + */ + public function set_loc($location) + { + if (strlen($location) > 2048) { + throw new \LengthException('The location was too long, maximum length of 2,048 characters.'); + } + + $location = \Baseapp\Library\Sitemap::encode($location); + + if (!filter_var($location, FILTER_VALIDATE_URL)) { + throw new \InvalidArgumentException('The location was not a valid URL'); + } + + $this->attributes['loc'] = $location; + + return $this; + } + + /** + * The date of last modification of the file. + * @param integer $lastmod Unix timestamp + */ + public function set_last_mod($lastmod) + { + $this->attributes['lastmod'] = \Baseapp\Library\Sitemap::date_format($lastmod); + + return $this; + } + + /** + * How frequently the page is likely to change + * @param string $change_frequency + */ + public function set_change_frequency($change_frequency) + { + $change_frequency = (string) $change_frequency; + + $frequencies = array('always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never'); + + if (!in_array($change_frequency, $frequencies)) { + throw new \InvalidArgumentException('Invalid change frequency'); + } + + $this->attributes['changefreq'] = $change_frequency; + + return $this; + } + + /** + * The priority of this URL relative to other URLs on your site. Ranges from + * 0 to 1, the default is 0.5 + * @param integer $priority + */ + public function set_priority($priority) + { + if (!is_numeric($priority)) { + throw new \InvalidArgumentException('The priority was not a numeric value.'); + } + + if ($priority > 1 OR $priority < 0) { + throw new \RangeException('Priority must be between 0 and 1 (inclusive).'); + } + + /* + * @TODO: Deal with locales that don't use a period as their decimal point. + */ + + $this->attributes['priority'] = $priority; + + return $this; + } + + /** + * @var Sitemap Interface + */ + private $driver = null; + + /** + * + * @param $driver + */ + public function __construct(\Baseapp\Library\Sitemap\SitemapInterface $driver = null) + { + $this->driver = $driver; + } + + /** + * Creates the URL node and decorates it with additional sitemap information. + */ + public function create() + { + $document = new \DOMDocument; + + $url_node = $document->createElement('url'); + + foreach ($this->attributes as $name => $value) { + // The loc attribute is required. + if (null === $this->attributes['loc']) { + throw new \RuntimeException('loc is required'); + } + + // Add attributes that aren't empty. + if (null !== $value) { + $url_node->appendChild(new \DOMElement($name, $value)); + } + } + + // If a specialised sitemap was used, import it's data here. + if (null !== $this->driver) { + $url_node->appendChild($document->importNode($this->driver->create(), true)); + } + + return $url_node; + } + + public function root(\DOMElement & $root) + { + // Add urlset namespace. + $root->setAttribute('xmlns', 'http://www.sitemaps.org/schemas/sitemap/0.9'); + $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); + $root->setAttribute('xsi:schemaLocation', 'http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd'); + + if (null !== $this->driver) { + $this->driver->root($root); + } + } + +} diff --git a/app/common/library/Sitemap/Video.php b/app/common/library/Sitemap/Video.php new file mode 100644 index 0000000..6cffa42 --- /dev/null +++ b/app/common/library/Sitemap/Video.php @@ -0,0 +1,299 @@ + null, + 'title' => null, + 'description' => null, + 'content_loc' => null, + 'expiration_date' => null, + 'rating' => null, + 'view_count' => null, + 'publication_date' => null, + 'category' => null, + 'family_friendly' => null, + 'requires_subscription' => null + ); + protected $_player_loc = array(); + + /** + * @param string $thumbnail_loc A URL pointing to the URL for the video + * thumbnail image file. We can accept most image sizes/types but recommend + * your thumbs are at least 160x120 in .jpg, .png, or. gif formats. + */ + public function set_thumbnail_loc($thumbnail_loc) + { + $this->_attributes['thumbnail_loc'] = $thumbnail_loc; + } + + /** + * @param string $title The title of the video. Limited to 100 characters. + */ + public function set_title($title) + { + if (strlen($title) > 100) { + throw new \InvalidArgumentException('Your title can be no longer than 100 characters'); + } + + $this->_attributes['title'] = $title; + } + + /** + * @param string $description The description of the video. Descriptions + * longer than 2048 characters will be truncated. + */ + public function set_description($description) + { + if (strlen($description) > 2048) { + throw new \InvalidArgumentException('Your description can be no longer than 2048 characters'); + } + + $this->_attributes['description'] = $description; + } + + /** + * @param string $loc + * @see http://www.google.com/support/webmasters/bin/answer.py?answer=80472 + */ + public function set_content_loc($loc) + { + if (!filter_var($loc, FILTER_VALIDATE_URL)) { + throw new \InvalidArgumentException($loc . ' is not a valid URL'); + } + + $this->_attributes['content_loc'] = $loc; + } + + /** + * @param string $loc + * @param boolean $allow_embed + * @param string $autostart + * @see http://www.google.com/support/webmasters/bin/answer.py?answer=80472 + */ + public function set_player_loc($loc, $allow_embed, $autostart) + { + if (!filter_var($loc, FILTER_VALIDATE_URL)) { + throw new \InvalidArgumentException($loc . ' is not a valid URL'); + } + + $allow_embed = $allow_embed ? 'yes' : 'no'; + + $this->_player_loc['loc'] = $loc; + $this->_player_loc['allow_embed'] = $allow_embed; + $this->_player_loc['autostart'] = $autostart; + } + + /** + * @param integer $duration The duration of the video in seconds. Value must + * be between 0 and 28800 (8 hours). Non-digit characters are disallowed. + */ + public function set_duration($duration) + { + $eight_hours = 28800; + + $duration = (int) $duration; + + if ($duration > $eight_hours OR $duration < 0) { + throw new \InvalidArgumentException('Duration must be between 0 and 8 hours'); + } + + $this->_attributes['duration'] = $duration; + } + + /** + * @param integer $expiration_date The date after which the video will no + * longer be available, in unixtimestamp format. + */ + public function set_expiration_date($expiration_date) + { + $this->_attributes['expiration_date'] = \Baseapp\Library\Sitemap::date_format($expiration_date); + } + + /** + * @param float $rating The rating of the video. The value must be float + * number in the range 0.0-5.0. + */ + public function set_rating($rating) + { + if ($rating < 0 OR $rating > 5) { + throw new \OutOfRangeException('Rating must be in the range of 0.0 to 5.0'); + } + + $this->_attributes['rating'] = $rating; + } + + /** + * + * @param type $loc + * @todo finish. + */ + public function set_content_segment_loc($loc) + { + + } + + /** + * @param string $view_count The number of times the video has been viewed + */ + public function set_view_count($view_count) + { + $this->_attributes['view_count'] = (int) $view_count; + } + + /** + * @param integer $publication_date The date the video was first published, + * in unixtimestamp format. + */ + public function set_publication_date($publication_date) + { + $this->_attributes['publication_date'] = \Baseapp\Library\Sitemap::date_format($publication_date); + } + + /** + * @param array $tag Array of tags, a maximum of 32 tags is permitted + */ + public function set_tag($tag) + { + if (is_array($tag)) { + foreach ($tag as $row) { + $this->set_tag($row); + } + } + + if (count($this->_attributes['tags']) > 32) { + throw new \OverflowException('A maximum of 32 tags are permitted'); + } + + $this->_attributes['tags'][] = array('tag' => $tag); + } + + /** + * @param string $category The video's category. For example, cooking. The + * value should be a string no longer than 256 characters. In general, + * categories are broad groupings of content by subject. Usually a video will + * belong to a single category. For example, a site about cooking could have + * categories for Broiling, Baking, and Grilling + */ + public function set_category($category) + { + if (strlen($category) > 256) { + throw new \InvalidArgumentException('The category should be no longer than 256 characters long.'); + } + + $this->_attributes['category'] = $category; + } + + /** + * @param string $family_friendly "No" if the video should be available only + * to users with SafeSearch turned off. + */ + public function set_family_friendly($family_friendly) + { + $family_friendly = $family_friendly ? 'yes' : 'no'; + + $this->_attributes['family_friendly'] = $family_friendly; + } + + /** + * Accepts an array in the form of + * + * $array('country' => true|false); + * + * @param type $countries + * @todo finish + */ + public function set_restriction($countries) + { + + } + + /** + * + * @param type $loc + * @todo finish + */ + public function set_gallery_loc($loc) + { + $this->_attributes['gallery_loc'] = $loc; + } + + /** + * @param float $price The price to download or view the video. The required + * attribute currency specifies the currency in ISO 4217 format. More than one + * element can be listed (for example, in order to specify various + * currencies). + * @todo finish + */ + public function set_price($price) + { + + } + + /** + * @param bool $subscription Indicates whether a subscription (either paid or free) + * is required to view the video. Allowed values are yes or no. + */ + public function set_requires_subscription($requires_subscription) + { + $requires_subscription = $requires_subscription ? 'yes' : 'no'; + + $this->_attributes['requires_subscription'] = $requires_subscription; + } + + /** + * @param string $uploader A name or handle of the video’s uploader. Only one + * is allowed per video. The optional attribute info specifies + * the URL of a webpage with additional information about this uploader. This + * URL must be on the same domain as the tag. + * @todo add info attribute. + */ + public function set_uploader($uploader) + { + $this->_attributes['uploader'] = $uploader; + } + + public function create() + { + // Here we need to create a new DOMDocument. This is so we can re-import the + // DOMElement at the other end. + $document = new \DOMDocument; + + // Video element + $video = $document->createElement('video:video'); + + $document->appendChild($video); + + /** + * Small recursive function to add attributes to the document. + */ + $append_attributes = function($attributes) use ($video, $document) { + foreach ($attributes as $name => $value) { + if (null !== $value) { + if (is_array($value)) { + $append_attributes($value); + } + + $element = $document->createElement('video:' . $name); + $element->appendChild($document->createTextNode($value)); + $video->appendChild($element); + } + } + }; + + $append_attributes($this->_attributes); + + // @todo append: uploader, restriction and player_loc. + + return $video; + } + + public function root(\DOMElement & $root) + { + $root->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:video', 'http://www.google.com/schemas/sitemap-video/1.1'); + } + +}