View file upload/library/XenForo/Model/EmailTemplate.php

File size: 19.33Kb
<?php

/**
 * Model for email template related data.
 *
 * @package XenForo_EmailTemplate
 */
class XenForo_Model_EmailTemplate extends XenForo_Model
{
	/**
	 * Gets the titles and IDs of the effective email templates that
	 * will be used. Customized templates will sit "on top" of master templates.
	 *
	 * Note that templates must exist in the master to be returned by this!
	 *
	 * @return array Format: [title] => info, not including subject or body text
	 */
	public function getAllEffectiveEmailTemplateTitles()
	{
		return $this->fetchAllKeyed('
			SELECT
				COALESCE(custom.template_id, master.template_id) AS template_id,
				master.title,
				COALESCE(custom.custom, master.custom) AS custom,
				master.addon_id,
				addon.title AS addonTitle
			FROM xf_email_template AS master
			LEFT JOIN xf_email_template AS custom ON
				(custom.title = master.title AND custom.custom = 1)
			LEFT JOIN xf_addon AS addon ON
				(addon.addon_id = master.addon_id)
			WHERE master.custom = 0
			ORDER BY CONVERT(master.title USING utf8)
		', 'title');
	}

	/**
	 * Gets the titles and IDs of all master email templates.
	 *
	 * @return array Format: [title] => info, not including subject or body text
	 */
	public function getAllMasterEmailTemplateTitles()
	{
		return $this->fetchAllKeyed('
			SELECT
				email_template.template_id, email_template.title, email_template.custom,
				addon.addon_id, addon.title AS addonTitle
			FROM xf_email_template AS email_template
			LEFT JOIN xf_addon AS addon ON
				(addon.addon_id = email_template.addon_id)
			WHERE custom = 0
			ORDER BY CONVERT(email_template.title USING utf8)
		', 'title');
	}

	public function getAllEmailTemplates()
	{
		return $this->fetchAllKeyed('
			SELECT *
			FROM xf_email_template
			ORDER BY CONVERT(title USING utf8)
		', 'template_id');
	}

	public function getAllMasterEmailTemplates()
	{
		return $this->fetchAllKeyed('
			SELECT *
			FROM xf_email_template
			WHERE custom = 0
			ORDER BY CONVERT(title USING utf8)
		', 'title');
	}

	/**
	 * Get the titles and IDs of all master email templates that belong to the
	 * specified add-on.
	 *
	 * @param string $addOnId
	 *
	 * @return array Format: [title] => info, not including subject or body text
	 */
	public function getMasterEmailTemplateTitlesByAddOn($addOnId)
	{
		return $this->fetchAllKeyed('
			SELECT template_id, title, custom, addon_id
			FROM xf_email_template
			WHERE addon_id = ?
				AND custom = 0
			ORDER BY CONVERT(title USING utf8)
		', 'title', $addOnId);
	}

	/**
	 * Get the master email templates that belong to the specified add-on.
	 *
	 * @param string $addOnId
	 *
	 * @return array Format: [title] => info
	 */
	public function getMasterEmailTemplatesByAddOn($addOnId)
	{
		return $this->fetchAllKeyed('
			SELECT *
			FROM xf_email_template
			WHERE addon_id = ?
				AND custom = 0
			ORDER BY CONVERT(title USING utf8)
		', 'title', $addOnId);
	}

	public function getMasterEmailTemplatesLikeTitle($title, $likeType = 'lr', $limit = 0)
	{
		return $this->fetchAllKeyed($this->limitQueryResults('
			SELECT *
			FROM xf_email_template
			WHERE CONVERT(title USING utf8) LIKE ' . XenForo_Db::quoteLike($title, $likeType) . '
			ORDER BY CONVERT(title USING utf8)
		', $limit), 'title');
	}

	/**
	 * Gets the master email templates by a collection of titles.
	 *
	 * @param array $titles
	 *
	 * @return array Format: [title] => info
	 */
	public function getMasterEmailTemplatesByTitles(array $titles)
	{
		if (!$titles)
		{
			return array();
		}

		return $this->fetchAllKeyed('
			SELECT template_id, title, custom, addon_id
			FROM xf_email_template
			WHERE title IN (' . $this->_getDb()->quote($titles) . ')
				AND custom = 0
			ORDER BY CONVERT(title USING utf8)
		', 'title');
	}

	/**
	 * Gets all effective email templates.
	 *
	 * @return array Format: [title] => info
	 */
	public function getAllEffectiveEmailTemplates()
	{
		// this relies on the ordering and later items overwriting
		return $this->fetchAllKeyed('
			SELECT *
			FROM xf_email_template
			ORDER BY custom, CONVERT(title USING utf8)
		', 'title');
	}

	/**
	 * Get the effective version of any templates that include the given phrase.
	 *
	 * @param string $phraseTitle

	 * @return array Format: [title] => info
	 */
	public function getEffectiveEmailTemplatesThatIncludePhrase($phraseTitle)
	{
		return $this->fetchAllKeyed('
			SELECT
				COALESCE(custom.template_id, master.template_id) AS template_id,
				COALESCE(custom.title, master.title) AS title,
				COALESCE(custom.custom, master.custom) AS custom,
				COALESCE(custom.subject, master.subject) AS subject,
				COALESCE(custom.subject_parsed, master.subject_parsed) AS subject_parsed,
				COALESCE(custom.body_text, master.body_text) AS body_text,
				COALESCE(custom.body_text_parsed, master.body_text_parsed) AS body_text_parsed,
				COALESCE(custom.body_html, master.body_html) AS body_html,
				COALESCE(custom.body_html_parsed, master.body_html_parsed) AS body_html_parsed,
				COALESCE(custom.addon_id, master.addon_id) AS addon_id
			FROM xf_email_template_phrase AS template_phrase
			INNER JOIN xf_email_template AS master ON
				(master.title = template_phrase.title AND master.custom = 0)
			LEFT JOIN xf_email_template AS custom ON
				(custom.title = master.title AND custom.custom = 1)
			WHERE template_phrase.phrase_title = ?
			ORDER BY CONVERT(master.title USING utf8)
		', 'title', $phraseTitle);
	}

	/**
	 * Gets email templates by titles
	 *
	 * @param array $titles

	 * @return array Format: [title] => info
	 */
	public function getEmailTemplatesByTitles(array $titles)
	{
		if (!$titles)
		{
			return array();
		}

		return $this->fetchAllKeyed('
			SELECT *
			FROM xf_email_template
			WHERE title IN (' . $this->_getDb()->quote($titles) . ')
		', 'template_id');
	}

	/**
	 * Gets the email template with the given ID.
	 *
	 * @param integer $id
	 *
	 * @return array|false
	 */
	public function getEmailTemplateById($id)
	{
		return $this->_getDb()->fetchRow('
			SELECT *
			FROM xf_email_template
			WHERE template_id = ?
		', $id);
	}

	/**
	 * Returns all the email templates specified by the array of IDs provided
	 *
	 * @param array $ids
	 *
	 * @return array Templates
	 */
	public function getEmailTemplatesByIds(array $ids)
	{
		if (empty($ids))
		{
			return array();
		}

		return $this->fetchAllKeyed('
			SELECT *
			FROM xf_email_template
			WHERE template_id IN (' . $this->_getDb()->quote($ids) . ')
			ORDER BY CONVERT(title USING utf8)
		', 'template_id');
	}

	/**
	 * Gets the effective email template for a given title. A customized
	 * version of a template will sit on top of the master version.
	 *
	 * @param string $title
	 *
	 * @return array|false
	 */
	public function getEffectiveEmailTemplateByTitle($title)
	{
		$db = $this->_getDb();

		return $db->fetchRow($db->limit(
			'
				SELECT *
				FROM xf_email_template
				WHERE title = ?
				ORDER BY custom DESC
			', 1
		), $title);
	}

	/**
	 * Gets the named template, based on its title and type (custom: 1/0).
	 *
	 * @param string $title
	 * @param integer $custom Custom, 1/0
	 *
	 * @return array|false
	 */
	public function getEmailTemplateByTitleAndType($title, $custom)
	{
		return $this->_getDb()->fetchRow('
			SELECT *
			FROM xf_email_template
			WHERE title = ?
				AND custom = ?
		', array($title, $custom));
	}

	public function reparseTemplate($templateId, $fullCompile = true)
	{
		$dw = XenForo_DataWriter::create('XenForo_DataWriter_EmailTemplate', XenForo_DataWriter::ERROR_SILENT);
		$dw->setExistingData($templateId);
		$dw->reparseTemplate();
		$dw->setOption(XenForo_DataWriter_EmailTemplate::OPTION_FULL_COMPILE, $fullCompile);
		$dw->save();
		return $dw;
	}

	/**
	 * Reparses all email templates
	 *
	 * @param integer $maxExecution The approx maximum length of time this function will run for
	 * @param integer $startTemplate The number of the template to start with in that style (not ID, just counter)
	 *
	 * @return boolean|int True if completed successfully, otherwise int of restart template counter
	 */
	public function reparseAllEmailTemplates($maxExecution = 0, $startTemplate = 0)
	{
		$db = $this->_getDb();

		$startTime = microtime(true);
		$complete = true;
		$lastTemplate = 0;

		XenForo_Db::beginTransaction($db);

		$templates = $this->getAllEmailTemplates();

		foreach ($templates AS $template)
		{
			$lastTemplate++;
			if ($lastTemplate < $startTemplate)
			{
				continue;
			}

			$this->reparseTemplate($template, false);

			if ($maxExecution && (microtime(true) - $startTime) > $maxExecution)
			{
				$complete = false;
				break;
			}
		}

		XenForo_Db::commit($db);

		if ($complete)
		{
			return true;
		}
		else
		{
			return $lastTemplate + 1;
		}
	}

	/**
	 * Compiles the named email template and inserts the compiled output.
	 *
	 * @param string $title
	 */
	public function compileAndInsertNamedEmailTemplate($title)
	{
		$template = $this->getEffectiveEmailTemplateByTitle($title);
		if (!$template)
		{
			return;
		}

		$this->compileAndInsertEmailTemplate($template);
	}

	/**
	 * Compiles all email templates.
	 */
	public function compileAllEmailTemplates()
	{
		$templates = $this->getAllEffectiveEmailTemplates();
		foreach ($templates AS $template)
		{
			$this->compileAndInsertEmailTemplate($template);
		}

		$db = $this->_getDb();

		$compiledRemove = $db->fetchAll("
			SELECT DISTINCT c.title
			FROM xf_email_template_compiled AS c
			LEFT JOIN xf_email_template AS m ON (c.title = m.title)
			WHERE m.title IS NULL
		");
		foreach ($compiledRemove AS $remove)
		{
			$db->delete('xf_email_template_compiled',
				"title = " . $db->quote($remove['title'])
			);
		}
	}

	/**
	 * Compiles all email templates that include the given phrase.
	 *
	 * @param string $phraseTitle
	 * @param boolean $deferred
	 */
	public function compileEmailTemplatesThatIncludePhrase($phraseTitle, $deferred = true)
	{
		$templates = $this->getEffectiveEmailTemplatesThatIncludePhrase($phraseTitle);
		if ($deferred)
		{
			XenForo_Application::defer('EmailTemplatePartialCompile', array(
				'recompileTemplateIds' => XenForo_Application::arrayColumn($templates, 'template_id')
			), null, true);
		}
		else
		{
			foreach ($templates AS $template)
			{
				$this->compileAndInsertEmailTemplate($template);
			}
		}
	}

	/**
	 * Compiles the given email template data and inserts the compiled output. This
	 * template is assumed to be the effective value for a given title.
	 *
	 * @param array $template Template info
	 */
	public function compileAndInsertEmailTemplate(array $template)
	{
		$languages = $this->getModelFromCache('XenForo_Model_Language')->getAllLanguages();
		$languages[0] = array('language_id' => 0);

		$db = $this->_getDb();
		$compiler = new XenForo_Template_Compiler_Email('');

		$templateId = $template['template_id'];
		$title = $template['title'];
		$subjectParsed = XenForo_Helper_Php::safeUnserialize($template['subject_parsed']);
		$bodyTextParsed = XenForo_Helper_Php::safeUnserialize($template['body_text_parsed']);
		$bodyHtmlParsed = XenForo_Helper_Php::safeUnserialize($template['body_html_parsed']);

		$phrases = array();
		$checkPhrases = true;

		foreach ($languages AS $language)
		{
			$compiler->setOutputVar('__subject');
			$compiledTemplate = $compiler->compileParsedPlainText($subjectParsed, $title, 0, $language['language_id']);
			if ($checkPhrases)
			{
				$phrases = array_merge($phrases, $compiler->getIncludedPhrases());
			}

			$compiler->setOutputVar('__bodyText');
			$compiledTemplate .= $compiler->compileParsedPlainText($bodyTextParsed, $title, 0, $language['language_id']);
			if ($checkPhrases)
			{
				$phrases = array_merge($phrases, $compiler->getIncludedPhrases());
			}

			$compiler->setOutputVar('__bodyHtml');
			$compiledTemplate .= $compiler->compileParsed($bodyHtmlParsed, $title, 0, $language['language_id']);
			if ($checkPhrases)
			{
				$phrases = array_merge($phrases, $compiler->getIncludedPhrases());
			}

			$db->query('
				INSERT INTO xf_email_template_compiled
					(language_id, title, template_compiled)
				VALUES
					(?, ?, ?)
				ON DUPLICATE KEY UPDATE template_compiled = VALUES(template_compiled)
			', array($language['language_id'], $title, $compiledTemplate));

			$checkPhrases = false;
		}

		$phrases = array_unique($phrases);

		$db->delete('xf_email_template_phrase', 'title = ' . $db->quote($title));
		foreach ($phrases AS $includedPhrase)
		{
			if (strlen($includedPhrase) > 75)
			{
				continue; // too long, can't be a valid phrase
			}

			$db->insert('xf_email_template_phrase', array(
				'title' => $title,
				'phrase_title' => $includedPhrase
			));
		}
	}

	/**
	 * Returns the path to the email template development directory, if it has been configured and exists
	 *
	 * @return string Path to email template directory
	 */
	public function getEmailTemplateDevelopmentDirectory()
	{
		$config = XenForo_Application::get('config');
		if (!$config->debug || !$config->development->directory)
		{
			return '';
		}

		return XenForo_Application::getInstance()->getRootDir()
			. '/' . $config->development->directory . '/file_output/email_templates';
	}

	/**
	 * Deletes the email templates that belong to the specified add-on.
	 *
	 * @param string $addOnId
	 */
	public function deleteEmailTemplatesForAddOn($addOnId)
	{
		$templates = $this->getMasterEmailTemplateTitlesByAddOn($addOnId);
		$titles = array_keys($templates);

		if ($titles)
		{
			$db = $this->_getDb();
			$quotedTitles = $db->quote($titles);

			$templateIds = array();
			foreach ($templates AS $template)
			{
				$templateIds[] = $template['template_id'];
			}
			$quotedIds = $db->quote($templateIds);

			$db->delete('xf_email_template', "title IN ($quotedTitles) AND custom = 0");
			$db->delete('xf_email_template_compiled', "title IN ($quotedTitles)");
			$db->delete('xf_email_template_phrase', "title IN ($quotedTitles)");
			$db->delete('xf_email_template_modification_log', "template_id IN ($quotedIds)");
		}

		XenForo_Template_Compiler_Email::resetTemplateCache();
	}

	/**
	 * Imports all email templates from the email templates directory into the database
	 */
	public function importEmailTemplatesFromDevelopment()
	{
		$db = $this->_getDb();

		$templateDir = $this->getEmailTemplateDevelopmentDirectory();
		if (!$templateDir && !is_dir($templateDir))
		{
			throw new XenForo_Exception("Email template development directory not enabled or doesn't exist");
		}

		$files = glob("$templateDir/*.txt");

		XenForo_Db::beginTransaction($db);
		$this->deleteEmailTemplatesForAddOn('XenForo');

		$titles = array();
		foreach ($files AS $templateFile)
		{
			if (!is_readable($templateFile))
			{
				throw new XenForo_Exception("Template file '$templateFile' not readable");
			}


			$filename = basename($templateFile);
			if (preg_match('/^(.+)\.(subject|text|html)\.txt$/', $filename, $match))
			{
				$titles[$match[1]][$match[2]] = $templateFile;
			}
		}

		$existingTemplates = $this->getMasterEmailTemplatesByTitles(array_keys($titles));

		foreach ($titles AS $title => $parts)
		{
			$data = array(
				'title' => $title,
				'custom' => 0,
				'subject' => file_get_contents($parts['subject']),
				'body_text' => file_get_contents($parts['text']),
				'body_html' => file_get_contents($parts['html']),
				'addon_id' => 'XenForo',
			);

			$dw = XenForo_DataWriter::create('XenForo_DataWriter_EmailTemplate');
			if (isset($existingTemplates[$title]))
			{
				$dw->setExistingData($existingTemplates[$title], true);
			}
			$dw->setOption(XenForo_DataWriter_EmailTemplate::OPTION_DEV_OUTPUT_DIR, '');
			$dw->setOption(XenForo_DataWriter_EmailTemplate::OPTION_FULL_COMPILE, false);
			$dw->setOption(XenForo_DataWriter_EmailTemplate::OPTION_TEST_COMPILE, false);
			$dw->bulkSet($data);
			$dw->save();
		}

		$this->compileAllEmailTemplates();

		XenForo_Db::commit($db);
	}

	/**
	 * Imports the add-on email templates XML.
	 *
	 * @param SimpleXMLElement $xml XML element pointing to the root of the data
	 * @param string $addOnId Add-on to import for
	 * @param boolean $fullCompile True to recompile all templates after importing
	 */
	public function importEmailTemplatesAddOnXml(SimpleXMLElement $xml, $addOnId, $fullCompile = true)
	{
		$db = $this->_getDb();

		XenForo_Db::beginTransaction($db);
		$this->deleteEmailTemplatesForAddOn($addOnId);

		$templates = XenForo_Helper_DevelopmentXml::fixPhpBug50670($xml->template);

		$titles = array();
		foreach ($templates AS $template)
		{
			$titles[] = (string)$template['title'];
		}

		$existingTemplates = $this->getMasterEmailTemplatesByTitles($titles);

		foreach ($templates AS $template)
		{
			$title = (string)$template['title'];

			$data = array(
				'title' => $title,
				'custom' => 0,
				'subject' => (string)$template->subject,
				'body_text' => (string)$template->body_text,
				'body_html' => (string)$template->body_html,
				'addon_id' => $addOnId,
			);

			$dw = XenForo_DataWriter::create('XenForo_DataWriter_EmailTemplate');
			if (isset($existingTemplates[$title]))
			{
				$dw->setExistingData($existingTemplates[$title], true);
			}
			$dw->setOption(XenForo_DataWriter_EmailTemplate::OPTION_DEV_OUTPUT_DIR, '');
			$dw->setOption(XenForo_DataWriter_EmailTemplate::OPTION_FULL_COMPILE, false);
			$dw->setOption(XenForo_DataWriter_EmailTemplate::OPTION_TEST_COMPILE, false);
			$dw->bulkSet($data);
			$dw->save();
		}

		if ($fullCompile)
		{
			$this->compileAllEmailTemplates();
		}

		XenForo_Db::commit($db);
	}

	/**
	 * Appends the add-on email template XML to a given DOM element.
	 *
	 * @param DOMElement $rootNode Node to append all elements to
	 * @param string $addOnId Add-on ID to be exported
	 */
	public function appendEmailTemplatesAddOnXml(DOMElement $rootNode, $addOnId)
	{
		$document = $rootNode->ownerDocument;

		$templates = $this->getMasterEmailTemplatesByAddOn($addOnId);
		foreach ($templates AS $template)
		{
			$templateNode = $document->createElement('template');
			$templateNode->setAttribute('title', $template['title']);

			$subjectNode = $document->createElement('subject');
			$subjectNode->appendChild($document->createCDATASection($template['subject']));
			$templateNode->appendChild($subjectNode);

			$bodyTextNode = $document->createElement('body_text');
			$bodyTextNode->appendChild($document->createCDATASection($template['body_text']));
			$templateNode->appendChild($bodyTextNode);

			$bodyHtmlNode = $document->createElement('body_html');
			$bodyHtmlNode->appendChild($document->createCDATASection($template['body_html']));
			$templateNode->appendChild($bodyHtmlNode);

			$rootNode->appendChild($templateNode);
		}
	}

	/**
	 * Gets the email templates development XML.
	 *
	 * @return DOMDocument
	 */
	public function getEmailTemplatesDevelopmentXml()
	{
		$document = new DOMDocument('1.0', 'utf-8');
		$document->formatOutput = true;
		$rootNode = $document->createElement('email_templates');
		$document->appendChild($rootNode);

		$this->appendEmailTemplatesAddOnXml($rootNode, 'XenForo');

		return $document;
	}
}