1<?php
2/**
3 * @package     Joomla.Administrator
4 * @subpackage  com_templates
5 *
6 * @copyright   Copyright (C) 2005 - 2020 Open Source Matters, Inc. All rights reserved.
7 * @license     GNU General Public License version 2 or later; see LICENSE.txt
8 */
9
10defined('_JEXEC') or die;
11
12/**
13 * Template model class.
14 *
15 * @since  1.6
16 */
17class TemplatesModelTemplate extends JModelForm
18{
19	/**
20	 * The information in a template
21	 *
22	 * @var    stdClass
23	 * @since  1.6
24	 */
25	protected $template = null;
26
27	/**
28	 * The path to the template
29	 *
30	 * @var    stdClass
31	 * @since  3.2
32	 */
33	protected $element = null;
34
35	/**
36	 * Internal method to get file properties.
37	 *
38	 * @param   string  $path  The base path.
39	 * @param   string  $name  The file name.
40	 *
41	 * @return  object
42	 *
43	 * @since   1.6
44	 */
45	protected function getFile($path, $name)
46	{
47		$temp = new stdClass;
48
49		if ($template = $this->getTemplate())
50		{
51			$temp->name = $name;
52			$temp->id = urlencode(base64_encode($path . $name));
53
54			return $temp;
55		}
56	}
57
58	/**
59	 * Method to get a list of all the files to edit in a template.
60	 *
61	 * @return  array  A nested array of relevant files.
62	 *
63	 * @since   1.6
64	 */
65	public function getFiles()
66	{
67		$result = array();
68
69		if ($template = $this->getTemplate())
70		{
71			jimport('joomla.filesystem.folder');
72			$app    = JFactory::getApplication();
73			$client = JApplicationHelper::getClientInfo($template->client_id);
74			$path   = JPath::clean($client->path . '/templates/' . $template->element . '/');
75			$lang   = JFactory::getLanguage();
76
77			// Load the core and/or local language file(s).
78			$lang->load('tpl_' . $template->element, $client->path, null, false, true) ||
79			$lang->load('tpl_' . $template->element, $client->path . '/templates/' . $template->element, null, false, true);
80			$this->element = $path;
81
82			if (!is_writable($path))
83			{
84				$app->enqueueMessage(JText::_('COM_TEMPLATES_DIRECTORY_NOT_WRITABLE'), 'error');
85			}
86
87			if (is_dir($path))
88			{
89				$result = $this->getDirectoryTree($path);
90			}
91			else
92			{
93				$app->enqueueMessage(JText::_('COM_TEMPLATES_ERROR_TEMPLATE_FOLDER_NOT_FOUND'), 'error');
94
95				return false;
96			}
97		}
98
99		return $result;
100	}
101
102	/**
103	 * Get the directory tree.
104	 *
105	 * @param   string  $dir  The path of the directory to scan
106	 *
107	 * @return  array
108	 *
109	 * @since   3.2
110	 */
111	public function getDirectoryTree($dir)
112	{
113		$result = array();
114
115		$dirFiles = scandir($dir);
116
117		foreach ($dirFiles as $key => $value)
118		{
119			if (!in_array($value, array('.', '..')))
120			{
121				if (is_dir($dir . $value))
122				{
123					$relativePath = str_replace($this->element, '', $dir . $value);
124					$result['/' . $relativePath] = $this->getDirectoryTree($dir . $value . '/');
125				}
126				else
127				{
128					$ext           = pathinfo($dir . $value, PATHINFO_EXTENSION);
129					$allowedFormat = $this->checkFormat($ext);
130
131					if ($allowedFormat == true)
132					{
133						$relativePath = str_replace($this->element, '', $dir);
134						$info = $this->getFile('/' . $relativePath, $value);
135						$result[] = $info;
136					}
137				}
138			}
139		}
140
141		return $result;
142	}
143
144	/**
145	 * Method to auto-populate the model state.
146	 *
147	 * Note. Calling getState in this method will result in recursion.
148	 *
149	 * @return  void
150	 *
151	 * @since   1.6
152	 */
153	protected function populateState()
154	{
155		jimport('joomla.filesystem.file');
156		$app = JFactory::getApplication('administrator');
157
158		// Load the User state.
159		$pk = $app->input->getInt('id');
160		$this->setState('extension.id', $pk);
161
162		// Load the parameters.
163		$params = JComponentHelper::getParams('com_templates');
164		$this->setState('params', $params);
165	}
166
167	/**
168	 * Method to get the template information.
169	 *
170	 * @return  mixed  Object if successful, false if not and internal error is set.
171	 *
172	 * @since   1.6
173	 */
174	public function &getTemplate()
175	{
176		if (empty($this->template))
177		{
178			$pk  = $this->getState('extension.id');
179			$db  = $this->getDbo();
180			$app = JFactory::getApplication();
181
182			// Get the template information.
183			$query = $db->getQuery(true)
184				->select('extension_id, client_id, element, name, manifest_cache')
185				->from('#__extensions')
186				->where($db->quoteName('extension_id') . ' = ' . (int) $pk)
187				->where($db->quoteName('type') . ' = ' . $db->quote('template'));
188			$db->setQuery($query);
189
190			try
191			{
192				$result = $db->loadObject();
193			}
194			catch (RuntimeException $e)
195			{
196				$app->enqueueMessage($e->getMessage(), 'warning');
197				$this->template = false;
198
199				return false;
200			}
201
202			if (empty($result))
203			{
204				$app->enqueueMessage(JText::_('COM_TEMPLATES_ERROR_EXTENSION_RECORD_NOT_FOUND'), 'error');
205				$this->template = false;
206			}
207			else
208			{
209				$this->template = $result;
210			}
211		}
212
213		return $this->template;
214	}
215
216	/**
217	 * Method to check if new template name already exists
218	 *
219	 * @return  boolean   true if name is not used, false otherwise
220	 *
221	 * @since	2.5
222	 */
223	public function checkNewName()
224	{
225		$db = $this->getDbo();
226		$query = $db->getQuery(true)
227			->select('COUNT(*)')
228			->from('#__extensions')
229			->where('name = ' . $db->quote($this->getState('new_name')));
230		$db->setQuery($query);
231
232		return ($db->loadResult() == 0);
233	}
234
235	/**
236	 * Method to check if new template name already exists
237	 *
238	 * @return  string     name of current template
239	 *
240	 * @since	2.5
241	 */
242	public function getFromName()
243	{
244		return $this->getTemplate()->element;
245	}
246
247	/**
248	 * Method to check if new template name already exists
249	 *
250	 * @return  boolean   true if name is not used, false otherwise
251	 *
252	 * @since	2.5
253	 */
254	public function copy()
255	{
256		$app = JFactory::getApplication();
257
258		if ($template = $this->getTemplate())
259		{
260			jimport('joomla.filesystem.folder');
261			$client = JApplicationHelper::getClientInfo($template->client_id);
262			$fromPath = JPath::clean($client->path . '/templates/' . $template->element . '/');
263
264			// Delete new folder if it exists
265			$toPath = $this->getState('to_path');
266
267			if (JFolder::exists($toPath))
268			{
269				if (!JFolder::delete($toPath))
270				{
271					$app->enqueueMessage(JText::_('COM_TEMPLATES_ERROR_COULD_NOT_WRITE'), 'error');
272
273					return false;
274				}
275			}
276
277			// Copy all files from $fromName template to $newName folder
278			if (!JFolder::copy($fromPath, $toPath) || !$this->fixTemplateName())
279			{
280				return false;
281			}
282
283			return true;
284		}
285		else
286		{
287			$app->enqueueMessage(JText::_('COM_TEMPLATES_ERROR_INVALID_FROM_NAME'), 'error');
288
289			return false;
290		}
291	}
292
293	/**
294	 * Method to delete tmp folder
295	 *
296	 * @return  boolean   true if delete successful, false otherwise
297	 *
298	 * @since	2.5
299	 */
300	public function cleanup()
301	{
302		// Clear installation messages
303		$app = JFactory::getApplication();
304		$app->setUserState('com_installer.message', '');
305		$app->setUserState('com_installer.extension_message', '');
306
307		// Delete temporary directory
308		return JFolder::delete($this->getState('to_path'));
309	}
310
311	/**
312	 * Method to rename the template in the XML files and rename the language files
313	 *
314	 * @return  boolean  true if successful, false otherwise
315	 *
316	 * @since	2.5
317	 */
318	protected function fixTemplateName()
319	{
320		// Rename Language files
321		// Get list of language files
322		$result   = true;
323		$files    = JFolder::files($this->getState('to_path'), '.ini', true, true);
324		$newName  = strtolower($this->getState('new_name'));
325		$template = $this->getTemplate();
326		$oldName  = $template->element;
327		$manifest = json_decode($template->manifest_cache);
328
329		jimport('joomla.filesystem.file');
330
331		foreach ($files as $file)
332		{
333			$newFile = '/' . str_replace($oldName, $newName, basename($file));
334			$result  = JFile::move($file, dirname($file) . $newFile) && $result;
335		}
336
337		// Edit XML file
338		$xmlFile = $this->getState('to_path') . '/templateDetails.xml';
339
340		if (JFile::exists($xmlFile))
341		{
342			$contents = file_get_contents($xmlFile);
343			$pattern[] = '#<name>\s*' . $manifest->name . '\s*</name>#i';
344			$replace[] = '<name>' . $newName . '</name>';
345			$pattern[] = '#<language(.*)' . $oldName . '(.*)</language>#';
346			$replace[] = '<language${1}' . $newName . '${2}</language>';
347			$contents = preg_replace($pattern, $replace, $contents);
348			$result = JFile::write($xmlFile, $contents) && $result;
349		}
350
351		return $result;
352	}
353
354	/**
355	 * Method to get the record form.
356	 *
357	 * @param   array    $data      Data for the form.
358	 * @param   boolean  $loadData  True if the form is to load its own data (default case), false if not.
359	 *
360	 * @return  JForm    A JForm object on success, false on failure
361	 *
362	 * @since   1.6
363	 */
364	public function getForm($data = array(), $loadData = true)
365	{
366		$app = JFactory::getApplication();
367
368		// Codemirror or Editor None should be enabled
369		$db = $this->getDbo();
370		$query = $db->getQuery(true)
371			->select('COUNT(*)')
372			->from('#__extensions as a')
373			->where(
374				'(a.name =' . $db->quote('plg_editors_codemirror') .
375				' AND a.enabled = 1) OR (a.name =' .
376				$db->quote('plg_editors_none') .
377				' AND a.enabled = 1)'
378			);
379		$db->setQuery($query);
380		$state = $db->loadResult();
381
382		if ((int) $state < 1)
383		{
384			$app->enqueueMessage(JText::_('COM_TEMPLATES_ERROR_EDITOR_DISABLED'), 'warning');
385		}
386
387		// Get the form.
388		$form = $this->loadForm('com_templates.source', 'source', array('control' => 'jform', 'load_data' => $loadData));
389
390		if (empty($form))
391		{
392			return false;
393		}
394
395		return $form;
396	}
397
398	/**
399	 * Method to get the data that should be injected in the form.
400	 *
401	 * @return  mixed  The data for the form.
402	 *
403	 * @since   1.6
404	 */
405	protected function loadFormData()
406	{
407		$data = $this->getSource();
408
409		$this->preprocessData('com_templates.source', $data);
410
411		return $data;
412	}
413
414	/**
415	 * Method to get a single record.
416	 *
417	 * @return  mixed  Object on success, false on failure.
418	 *
419	 * @since   1.6
420	 */
421	public function &getSource()
422	{
423		$app = JFactory::getApplication();
424		$item = new stdClass;
425
426		if (!$this->template)
427		{
428			$this->getTemplate();
429		}
430
431		if ($this->template)
432		{
433			$input    = JFactory::getApplication()->input;
434			$fileName = base64_decode($input->get('file'));
435			$client   = JApplicationHelper::getClientInfo($this->template->client_id);
436
437			try
438			{
439				$filePath = JPath::check($client->path . '/templates/' . $this->template->element . '/' . $fileName);
440			}
441			catch (Exception $e)
442			{
443				$app->enqueueMessage(JText::_('COM_TEMPLATES_ERROR_SOURCE_FILE_NOT_FOUND'), 'error');
444
445				return;
446			}
447
448			if (file_exists($filePath))
449			{
450				$item->extension_id = $this->getState('extension.id');
451				$item->filename = $fileName;
452				$item->source = file_get_contents($filePath);
453			}
454			else
455			{
456				$app->enqueueMessage(JText::_('COM_TEMPLATES_ERROR_SOURCE_FILE_NOT_FOUND'), 'error');
457			}
458		}
459
460		return $item;
461	}
462
463	/**
464	 * Method to store the source file contents.
465	 *
466	 * @param   array  $data  The source data to save.
467	 *
468	 * @return  boolean  True on success, false otherwise and internal error set.
469	 *
470	 * @since   1.6
471	 */
472	public function save($data)
473	{
474		jimport('joomla.filesystem.file');
475
476		// Get the template.
477		$template = $this->getTemplate();
478
479		if (empty($template))
480		{
481			return false;
482		}
483
484		$app = JFactory::getApplication();
485		$fileName = base64_decode($app->input->get('file'));
486		$client = JApplicationHelper::getClientInfo($template->client_id);
487		$filePath = JPath::clean($client->path . '/templates/' . $template->element . '/' . $fileName);
488
489		// Include the extension plugins for the save events.
490		JPluginHelper::importPlugin('extension');
491
492		$user = get_current_user();
493		chown($filePath, $user);
494		JPath::setPermissions($filePath, '0644');
495
496		// Try to make the template file writable.
497		if (!is_writable($filePath))
498		{
499			$app->enqueueMessage(JText::_('COM_TEMPLATES_ERROR_SOURCE_FILE_NOT_WRITABLE'), 'warning');
500			$app->enqueueMessage(JText::sprintf('COM_TEMPLATES_FILE_PERMISSIONS', JPath::getPermissions($filePath)), 'warning');
501
502			if (!JPath::isOwner($filePath))
503			{
504				$app->enqueueMessage(JText::_('COM_TEMPLATES_CHECK_FILE_OWNERSHIP'), 'warning');
505			}
506
507			return false;
508		}
509
510		// Make sure EOL is Unix
511		$data['source'] = str_replace(array("\r\n", "\r"), "\n", $data['source']);
512
513		$return = JFile::write($filePath, $data['source']);
514
515		if (!$return)
516		{
517			$app->enqueueMessage(JText::sprintf('COM_TEMPLATES_ERROR_FAILED_TO_SAVE_FILENAME', $fileName), 'error');
518
519			return false;
520		}
521
522		// Get the extension of the changed file.
523		$explodeArray = explode('.', $fileName);
524		$ext = end($explodeArray);
525
526		if ($ext == 'less')
527		{
528			$app->enqueueMessage(JText::sprintf('COM_TEMPLATES_COMPILE_LESS', $fileName));
529		}
530
531		return true;
532	}
533
534	/**
535	 * Get overrides folder.
536	 *
537	 * @param   string  $name  The name of override.
538	 * @param   string  $path  Location of override.
539	 *
540	 * @return  object  containing override name and path.
541	 *
542	 * @since   3.2
543	 */
544	public function getOverridesFolder($name,$path)
545	{
546		$folder = new stdClass;
547		$folder->name = $name;
548		$folder->path = base64_encode($path . $name);
549
550		return $folder;
551	}
552
553	/**
554	 * Get a list of overrides.
555	 *
556	 * @return  array containing overrides.
557	 *
558	 * @since   3.2
559	 */
560	public function getOverridesList()
561	{
562		if ($template = $this->getTemplate())
563		{
564			$client        = JApplicationHelper::getClientInfo($template->client_id);
565			$componentPath = JPath::clean($client->path . '/components/');
566			$modulePath    = JPath::clean($client->path . '/modules/');
567			$pluginPath    = JPath::clean(JPATH_ROOT . '/plugins/');
568			$layoutPath    = JPath::clean(JPATH_ROOT . '/layouts/');
569			$components    = JFolder::folders($componentPath);
570
571			foreach ($components as $component)
572			{
573				if (file_exists($componentPath . '/' . $component . '/views/'))
574				{
575					$viewPath = JPath::clean($componentPath . '/' . $component . '/views/');
576				}
577				elseif (file_exists($componentPath . '/' . $component . '/view/'))
578				{
579					$viewPath = JPath::clean($componentPath . '/' . $component . '/view/');
580				}
581				else
582				{
583					$viewPath = '';
584				}
585
586				if ($viewPath)
587				{
588					$views = JFolder::folders($viewPath);
589
590					foreach ($views as $view)
591					{
592						// Only show the view has layout inside it
593						if (file_exists($viewPath . $view . '/tmpl'))
594						{
595							$result['components'][$component][] = $this->getOverridesFolder($view, $viewPath);
596						}
597					}
598				}
599			}
600
601			foreach (JFolder::folders($pluginPath) as $pluginGroup)
602			{
603				foreach (JFolder::folders($pluginPath . '/' . $pluginGroup) as $plugin)
604				{
605					if (file_exists($pluginPath . '/' . $pluginGroup . '/' . $plugin . '/tmpl/'))
606					{
607						$pluginLayoutPath = JPath::clean($pluginPath . '/' . $pluginGroup . '/');
608						$result['plugins'][$pluginGroup][] = $this->getOverridesFolder($plugin, $pluginLayoutPath);
609					}
610				}
611			}
612
613			$modules = JFolder::folders($modulePath);
614
615			foreach ($modules as $module)
616			{
617				$result['modules'][] = $this->getOverridesFolder($module, $modulePath);
618			}
619
620			$layoutFolders = JFolder::folders($layoutPath);
621
622			foreach ($layoutFolders as $layoutFolder)
623			{
624				$layoutFolderPath = JPath::clean($layoutPath . '/' . $layoutFolder . '/');
625				$layouts = JFolder::folders($layoutFolderPath);
626
627				foreach ($layouts as $layout)
628				{
629					$result['layouts'][$layoutFolder][] = $this->getOverridesFolder($layout, $layoutFolderPath);
630				}
631			}
632
633			// Check for layouts in component folders
634			foreach ($components as $component)
635			{
636				if (file_exists($componentPath . '/' . $component . '/layouts/'))
637				{
638					$componentLayoutPath = JPath::clean($componentPath . '/' . $component . '/layouts/');
639
640					if ($componentLayoutPath)
641					{
642						$layouts = JFolder::folders($componentLayoutPath);
643
644						foreach ($layouts as $layout)
645						{
646							$result['layouts'][$component][] = $this->getOverridesFolder($layout, $componentLayoutPath);
647						}
648					}
649				}
650			}
651		}
652
653		if (!empty($result))
654		{
655			return $result;
656		}
657	}
658
659	/**
660	 * Create overrides.
661	 *
662	 * @param   string  $override  The override location.
663	 *
664	 * @return   boolean  true if override creation is successful, false otherwise
665	 *
666	 * @since   3.2
667	 */
668	public function createOverride($override)
669	{
670		jimport('joomla.filesystem.folder');
671
672		if ($template = $this->getTemplate())
673		{
674			$app          = JFactory::getApplication();
675			$explodeArray = explode(DIRECTORY_SEPARATOR, $override);
676			$name         = end($explodeArray);
677			$client       = JApplicationHelper::getClientInfo($template->client_id);
678
679			if (stristr($name, 'mod_') != false)
680			{
681				$htmlPath   = JPath::clean($client->path . '/templates/' . $template->element . '/html/' . $name);
682			}
683			elseif (stristr($override, 'com_') != false)
684			{
685				$size = count($explodeArray);
686
687				$url = JPath::clean($explodeArray[$size - 3] . '/' . $explodeArray[$size - 1]);
688
689				if ($explodeArray[$size - 2] == 'layouts')
690				{
691					$htmlPath = JPath::clean($client->path . '/templates/' . $template->element . '/html/layouts/' . $url);
692				}
693				else
694				{
695					$htmlPath = JPath::clean($client->path . '/templates/' . $template->element . '/html/' . $url);
696				}
697			}
698			elseif (stripos($override, JPath::clean(JPATH_ROOT . '/plugins/')) === 0)
699			{
700				$size       = count($explodeArray);
701				$layoutPath = JPath::clean('plg_' . $explodeArray[$size - 2] . '_' . $explodeArray[$size - 1]);
702				$htmlPath   = JPath::clean($client->path . '/templates/' . $template->element . '/html/' . $layoutPath);
703			}
704			else
705			{
706				$layoutPath = implode('/', array_slice($explodeArray, -2));
707				$htmlPath   = JPath::clean($client->path . '/templates/' . $template->element . '/html/layouts/' . $layoutPath);
708			}
709
710			// Check Html folder, create if not exist
711			if (!JFolder::exists($htmlPath))
712			{
713				if (!JFolder::create($htmlPath))
714				{
715					$app->enqueueMessage(JText::_('COM_TEMPLATES_FOLDER_ERROR'), 'error');
716
717					return false;
718				}
719			}
720
721			if (stristr($name, 'mod_') != false)
722			{
723				$return = $this->createTemplateOverride(JPath::clean($override . '/tmpl'), $htmlPath);
724			}
725			elseif (stristr($override, 'com_') != false && stristr($override, 'layouts') == false)
726			{
727				$return = $this->createTemplateOverride(JPath::clean($override . '/tmpl'), $htmlPath);
728			}
729			elseif (stripos($override, JPath::clean(JPATH_ROOT . '/plugins/')) === 0)
730			{
731				$return = $this->createTemplateOverride(JPath::clean($override . '/tmpl'), $htmlPath);
732			}
733			else
734			{
735				$return = $this->createTemplateOverride($override, $htmlPath);
736			}
737
738			if ($return)
739			{
740				$app->enqueueMessage(JText::_('COM_TEMPLATES_OVERRIDE_CREATED') . str_replace(JPATH_ROOT, '', $htmlPath));
741
742				return true;
743			}
744			else
745			{
746				$app->enqueueMessage(JText::_('COM_TEMPLATES_OVERRIDE_FAILED'), 'error');
747
748				return false;
749			}
750		}
751	}
752
753	/**
754	 * Create override folder & file
755	 *
756	 * @param   string  $overridePath  The override location
757	 * @param   string  $htmlPath      The html location
758	 *
759	 * @return  boolean                True on success. False otherwise.
760	 */
761	public function createTemplateOverride($overridePath, $htmlPath)
762	{
763		$return = false;
764
765		if (empty($overridePath) || empty($htmlPath))
766		{
767			return $return;
768		}
769
770		// Get list of template folders
771		$folders = JFolder::folders($overridePath, null, true, true);
772
773		if (!empty($folders))
774		{
775			foreach ($folders as $folder)
776			{
777				$htmlFolder = $htmlPath . str_replace($overridePath, '', $folder);
778
779				if (!JFolder::exists($htmlFolder))
780				{
781					JFolder::create($htmlFolder);
782				}
783			}
784		}
785
786		// Get list of template files (Only get *.php file for template file)
787		$files = JFolder::files($overridePath, '.php', true, true);
788
789		if (empty($files))
790		{
791			return true;
792		}
793
794		foreach ($files as $file)
795		{
796			$overrideFilePath = str_replace($overridePath, '', $file);
797			$htmlFilePath = $htmlPath . $overrideFilePath;
798
799			if (JFile::exists($htmlFilePath))
800			{
801				// Generate new unique file name base on current time
802				$today = JFactory::getDate();
803				$htmlFilePath = JFile::stripExt($htmlFilePath) . '-' . $today->format('Ymd-His') . '.' . JFile::getExt($htmlFilePath);
804			}
805
806			$return = JFile::copy($file, $htmlFilePath, '', true);
807		}
808
809		return $return;
810	}
811
812	/**
813	 * Compile less using the less compiler under /build.
814	 *
815	 * @param   string  $input  The relative location of the less file.
816	 *
817	 * @return  boolean  true if compilation is successful, false otherwise
818	 *
819	 * @since   3.2
820	 */
821	public function compileLess($input)
822	{
823		if ($template = $this->getTemplate())
824		{
825			$app          = JFactory::getApplication();
826			$client       = JApplicationHelper::getClientInfo($template->client_id);
827			$path         = JPath::clean($client->path . '/templates/' . $template->element . '/');
828			$inFile       = urldecode(base64_decode($input));
829			$explodeArray = explode('/', $inFile);
830			$fileName     = end($explodeArray);
831			$outFile      = current(explode('.', $fileName));
832
833			$less = new JLess;
834			$less->setFormatter(new JLessFormatterJoomla);
835
836			try
837			{
838				$less->compileFile($path . $inFile, $path . 'css/' . $outFile . '.css');
839
840				return true;
841			}
842			catch (Exception $e)
843			{
844				$app->enqueueMessage($e->getMessage(), 'error');
845			}
846		}
847	}
848
849	/**
850	 * Delete a particular file.
851	 *
852	 * @param   string  $file  The relative location of the file.
853	 *
854	 * @return   boolean  True if file deletion is successful, false otherwise
855	 *
856	 * @since   3.2
857	 */
858	public function deleteFile($file)
859	{
860		if ($template = $this->getTemplate())
861		{
862			$app      = JFactory::getApplication();
863			$client   = JApplicationHelper::getClientInfo($template->client_id);
864			$path     = JPath::clean($client->path . '/templates/' . $template->element . '/');
865			$filePath = $path . urldecode(base64_decode($file));
866
867			$return = JFile::delete($filePath);
868
869			if (!$return)
870			{
871				$app->enqueueMessage(JText::_('COM_TEMPLATES_FILE_DELETE_FAIL'), 'error');
872
873				return false;
874			}
875
876			return true;
877		}
878	}
879
880	/**
881	 * Create new file.
882	 *
883	 * @param   string  $name      The name of file.
884	 * @param   string  $type      The extension of the file.
885	 * @param   string  $location  Location for the new file.
886	 *
887	 * @return  boolean  true if file created successfully, false otherwise
888	 *
889	 * @since   3.2
890	 */
891	public function createFile($name, $type, $location)
892	{
893		if ($template = $this->getTemplate())
894		{
895			$app    = JFactory::getApplication();
896			$client = JApplicationHelper::getClientInfo($template->client_id);
897			$path   = JPath::clean($client->path . '/templates/' . $template->element . '/');
898
899			if (file_exists(JPath::clean($path . '/' . $location . '/' . $name . '.' . $type)))
900			{
901				$app->enqueueMessage(JText::_('COM_TEMPLATES_FILE_EXISTS'), 'error');
902
903				return false;
904			}
905
906			if (!fopen(JPath::clean($path . '/' . $location . '/' . $name . '.' . $type), 'x'))
907			{
908				$app->enqueueMessage(JText::_('COM_TEMPLATES_FILE_CREATE_ERROR'), 'error');
909
910				return false;
911			}
912
913			// Check if the format is allowed and will be showed in the backend
914			$check = $this->checkFormat($type);
915
916			// Add a message if we are not allowed to show this file in the backend.
917			if (!$check)
918			{
919				$app->enqueueMessage(JText::sprintf('COM_TEMPLATES_WARNING_FORMAT_WILL_NOT_BE_VISIBLE', $type), 'warning');
920			}
921
922			return true;
923		}
924	}
925
926	/**
927	 * Upload new file.
928	 *
929	 * @param   string  $file      The name of the file.
930	 * @param   string  $location  Location for the new file.
931	 *
932	 * @return   boolean  True if file uploaded successfully, false otherwise
933	 *
934	 * @since   3.2
935	 */
936	public function uploadFile($file, $location)
937	{
938		jimport('joomla.filesystem.folder');
939
940		if ($template = $this->getTemplate())
941		{
942			$app      = JFactory::getApplication();
943			$client   = JApplicationHelper::getClientInfo($template->client_id);
944			$path     = JPath::clean($client->path . '/templates/' . $template->element . '/');
945			$fileName = JFile::makeSafe($file['name']);
946
947			$err = null;
948			JLoader::register('TemplateHelper', JPATH_ADMINISTRATOR . '/components/com_templates/helpers/template.php');
949
950			if (!TemplateHelper::canUpload($file, $err))
951			{
952				// Can't upload the file
953				return false;
954			}
955
956			if (file_exists(JPath::clean($path . '/' . $location . '/' . $file['name'])))
957			{
958				$app->enqueueMessage(JText::_('COM_TEMPLATES_FILE_EXISTS'), 'error');
959
960				return false;
961			}
962
963			if (!JFile::upload($file['tmp_name'], JPath::clean($path . '/' . $location . '/' . $fileName)))
964			{
965				$app->enqueueMessage(JText::_('COM_TEMPLATES_FILE_UPLOAD_ERROR'), 'error');
966
967				return false;
968			}
969
970			$url = JPath::clean($location . '/' . $fileName);
971
972			return $url;
973		}
974	}
975
976	/**
977	 * Create new folder.
978	 *
979	 * @param   string  $name      The name of the new folder.
980	 * @param   string  $location  Location for the new folder.
981	 *
982	 * @return   boolean  True if override folder is created successfully, false otherwise
983	 *
984	 * @since   3.2
985	 */
986	public function createFolder($name, $location)
987	{
988		jimport('joomla.filesystem.folder');
989
990		if ($template = $this->getTemplate())
991		{
992			$app    = JFactory::getApplication();
993			$client = JApplicationHelper::getClientInfo($template->client_id);
994			$path   = JPath::clean($client->path . '/templates/' . $template->element . '/');
995
996			if (file_exists(JPath::clean($path . '/' . $location . '/' . $name . '/')))
997			{
998				$app->enqueueMessage(JText::_('COM_TEMPLATES_FOLDER_EXISTS'), 'error');
999
1000				return false;
1001			}
1002
1003			if (!JFolder::create(JPath::clean($path . '/' . $location . '/' . $name)))
1004			{
1005				$app->enqueueMessage(JText::_('COM_TEMPLATES_FOLDER_CREATE_ERROR'), 'error');
1006
1007				return false;
1008			}
1009
1010			return true;
1011		}
1012	}
1013
1014	/**
1015	 * Delete a folder.
1016	 *
1017	 * @param   string  $location  The name and location of the folder.
1018	 *
1019	 * @return  boolean  True if override folder is deleted successfully, false otherwise
1020	 *
1021	 * @since   3.2
1022	 */
1023	public function deleteFolder($location)
1024	{
1025		jimport('joomla.filesystem.folder');
1026
1027		if ($template = $this->getTemplate())
1028		{
1029			$app    = JFactory::getApplication();
1030			$client = JApplicationHelper::getClientInfo($template->client_id);
1031			$path   = JPath::clean($client->path . '/templates/' . $template->element . '/' . $location);
1032
1033			if (!file_exists($path))
1034			{
1035				$app->enqueueMessage(JText::_('COM_TEMPLATES_FOLDER_NOT_EXISTS'), 'error');
1036
1037				return false;
1038			}
1039
1040			$return = JFolder::delete($path);
1041
1042			if (!$return)
1043			{
1044				$app->enqueueMessage(JText::_('COM_TEMPLATES_FILE_DELETE_ERROR'), 'error');
1045
1046				return false;
1047			}
1048
1049			return true;
1050		}
1051	}
1052
1053	/**
1054	 * Rename a file.
1055	 *
1056	 * @param   string  $file  The name and location of the old file
1057	 * @param   string  $name  The new name of the file.
1058	 *
1059	 * @return  string  Encoded string containing the new file location.
1060	 *
1061	 * @since   3.2
1062	 */
1063	public function renameFile($file, $name)
1064	{
1065		if ($template = $this->getTemplate())
1066		{
1067			$app          = JFactory::getApplication();
1068			$client       = JApplicationHelper::getClientInfo($template->client_id);
1069			$path         = JPath::clean($client->path . '/templates/' . $template->element . '/');
1070			$fileName     = base64_decode($file);
1071			$explodeArray = explode('.', $fileName);
1072			$type         = end($explodeArray);
1073			$explodeArray = explode('/', $fileName);
1074			$newName      = str_replace(end($explodeArray), $name . '.' . $type, $fileName);
1075
1076			if (file_exists($path . $newName))
1077			{
1078				$app->enqueueMessage(JText::_('COM_TEMPLATES_FILE_EXISTS'), 'error');
1079
1080				return false;
1081			}
1082
1083			if (!rename($path . $fileName, $path . $newName))
1084			{
1085				$app->enqueueMessage(JText::_('COM_TEMPLATES_FILE_RENAME_ERROR'), 'error');
1086
1087				return false;
1088			}
1089
1090			return base64_encode($newName);
1091		}
1092	}
1093
1094	/**
1095	 * Get an image address, height and width.
1096	 *
1097	 * @return  array an associative array containing image address, height and width.
1098	 *
1099	 * @since   3.2
1100	 */
1101	public function getImage()
1102	{
1103		if ($template = $this->getTemplate())
1104		{
1105			$app      = JFactory::getApplication();
1106			$client   = JApplicationHelper::getClientInfo($template->client_id);
1107			$fileName = base64_decode($app->input->get('file'));
1108			$path     = JPath::clean($client->path . '/templates/' . $template->element . '/');
1109
1110			if (stristr($client->path, 'administrator') == false)
1111			{
1112				$folder = '/templates/';
1113			}
1114			else
1115			{
1116				$folder = '/administrator/templates/';
1117			}
1118
1119			$uri = JUri::root(true) . $folder . $template->element;
1120
1121			if (file_exists(JPath::clean($path . $fileName)))
1122			{
1123				$JImage = new JImage(JPath::clean($path . $fileName));
1124				$image['address'] = $uri . $fileName;
1125				$image['path']    = $fileName;
1126				$image['height']  = $JImage->getHeight();
1127				$image['width']   = $JImage->getWidth();
1128			}
1129
1130			else
1131			{
1132				$app->enqueueMessage(JText::_('COM_TEMPLATES_ERROR_IMAGE_FILE_NOT_FOUND'), 'error');
1133
1134				return false;
1135			}
1136
1137			return $image;
1138		}
1139	}
1140
1141	/**
1142	 * Crop an image.
1143	 *
1144	 * @param   string  $file  The name and location of the file
1145	 * @param   string  $w     width.
1146	 * @param   string  $h     height.
1147	 * @param   string  $x     x-coordinate.
1148	 * @param   string  $y     y-coordinate.
1149	 *
1150	 * @return  boolean     true if image cropped successfully, false otherwise.
1151	 *
1152	 * @since   3.2
1153	 */
1154	public function cropImage($file, $w, $h, $x, $y)
1155	{
1156		if ($template = $this->getTemplate())
1157		{
1158			$app      = JFactory::getApplication();
1159			$client   = JApplicationHelper::getClientInfo($template->client_id);
1160			$relPath  = base64_decode($file);
1161			$path     = JPath::clean($client->path . '/templates/' . $template->element . '/' . $relPath);
1162
1163			try
1164			{
1165				$image      = new \JImage($path);
1166				$properties = $image->getImageFileProperties($path);
1167
1168				switch ($properties->mime)
1169				{
1170					case 'image/png':
1171						$imageType = \IMAGETYPE_PNG;
1172						break;
1173					case 'image/gif':
1174						$imageType = \IMAGETYPE_GIF;
1175						break;
1176					default:
1177						$imageType = \IMAGETYPE_JPEG;
1178				}
1179
1180				$image->crop($w, $h, $x, $y, false);
1181				$image->toFile($path, $imageType);
1182
1183				return true;
1184			}
1185			catch (Exception $e)
1186			{
1187				$app->enqueueMessage($e->getMessage(), 'error');
1188			}
1189		}
1190	}
1191
1192	/**
1193	 * Resize an image.
1194	 *
1195	 * @param   string  $file    The name and location of the file
1196	 * @param   string  $width   The new width of the image.
1197	 * @param   string  $height  The new height of the image.
1198	 *
1199	 * @return   boolean  true if image resize successful, false otherwise.
1200	 *
1201	 * @since   3.2
1202	 */
1203	public function resizeImage($file, $width, $height)
1204	{
1205		if ($template = $this->getTemplate())
1206		{
1207			$app     = JFactory::getApplication();
1208			$client  = JApplicationHelper::getClientInfo($template->client_id);
1209			$relPath = base64_decode($file);
1210			$path    = JPath::clean($client->path . '/templates/' . $template->element . '/' . $relPath);
1211
1212			try
1213			{
1214				$image      = new \JImage($path);
1215				$properties = $image->getImageFileProperties($path);
1216
1217				switch ($properties->mime)
1218				{
1219					case 'image/png':
1220						$imageType = \IMAGETYPE_PNG;
1221						break;
1222					case 'image/gif':
1223						$imageType = \IMAGETYPE_GIF;
1224						break;
1225					default:
1226						$imageType = \IMAGETYPE_JPEG;
1227				}
1228
1229				$image->resize($width, $height, false, \JImage::SCALE_FILL);
1230				$image->toFile($path, $imageType);
1231
1232				return true;
1233			}
1234			catch (Exception $e)
1235			{
1236				$app->enqueueMessage($e->getMessage(), 'error');
1237			}
1238		}
1239	}
1240
1241	/**
1242	 * Template preview.
1243	 *
1244	 * @return  object  object containing the id of the template.
1245	 *
1246	 * @since   3.2
1247	 */
1248	public function getPreview()
1249	{
1250		$app = JFactory::getApplication();
1251		$db = $this->getDbo();
1252		$query = $db->getQuery(true);
1253
1254		$query->select('id, client_id');
1255		$query->from('#__template_styles');
1256		$query->where($db->quoteName('template') . ' = ' . $db->quote($this->template->element));
1257
1258		$db->setQuery($query);
1259
1260		try
1261		{
1262			$result = $db->loadObject();
1263		}
1264		catch (RuntimeException $e)
1265		{
1266			$app->enqueueMessage($e->getMessage(), 'warning');
1267		}
1268
1269		if (empty($result))
1270		{
1271			$app->enqueueMessage(JText::_('COM_TEMPLATES_ERROR_EXTENSION_RECORD_NOT_FOUND'), 'warning');
1272		}
1273		else
1274		{
1275			return $result;
1276		}
1277	}
1278
1279	/**
1280	 * Rename a file.
1281	 *
1282	 * @return  mixed  array on success, false on failure
1283	 *
1284	 * @since   3.2
1285	 */
1286	public function getFont()
1287	{
1288		if ($template = $this->getTemplate())
1289		{
1290			$app          = JFactory::getApplication();
1291			$client       = JApplicationHelper::getClientInfo($template->client_id);
1292			$relPath      = base64_decode($app->input->get('file'));
1293			$explodeArray = explode('/', $relPath);
1294			$fileName     = end($explodeArray);
1295			$path         = JPath::clean($client->path . '/templates/' . $template->element . '/' . $relPath);
1296
1297			if (stristr($client->path, 'administrator') == false)
1298			{
1299				$folder = '/templates/';
1300			}
1301			else
1302			{
1303				$folder = '/administrator/templates/';
1304			}
1305
1306			$uri = JUri::root(true) . $folder . $template->element;
1307
1308			if (file_exists(JPath::clean($path)))
1309			{
1310				$font['address'] = $uri . $relPath;
1311
1312				$font['rel_path'] = $relPath;
1313
1314				$font['name'] = $fileName;
1315			}
1316
1317			else
1318			{
1319				$app->enqueueMessage(JText::_('COM_TEMPLATES_ERROR_FONT_FILE_NOT_FOUND'), 'error');
1320
1321				return false;
1322			}
1323
1324			return $font;
1325		}
1326	}
1327
1328	/**
1329	 * Copy a file.
1330	 *
1331	 * @param   string  $newName   The name of the copied file
1332	 * @param   string  $location  The final location where the file is to be copied
1333	 * @param   string  $file      The name and location of the file
1334	 *
1335	 * @return   boolean  true if image resize successful, false otherwise.
1336	 *
1337	 * @since   3.2
1338	 */
1339	public function copyFile($newName, $location, $file)
1340	{
1341		if ($template = $this->getTemplate())
1342		{
1343			$app          = JFactory::getApplication();
1344			$client       = JApplicationHelper::getClientInfo($template->client_id);
1345			$relPath      = base64_decode($file);
1346			$explodeArray = explode('.', $relPath);
1347			$ext          = end($explodeArray);
1348			$path         = JPath::clean($client->path . '/templates/' . $template->element . '/');
1349			$newPath      = JPath::clean($path . '/' . $location . '/' . $newName . '.' . $ext);
1350
1351			if (file_exists($newPath))
1352			{
1353				$app->enqueueMessage(JText::_('COM_TEMPLATES_FILE_EXISTS'), 'error');
1354
1355				return false;
1356			}
1357
1358			if (JFile::copy($path . $relPath, $newPath))
1359			{
1360				$app->enqueueMessage(JText::sprintf('COM_TEMPLATES_FILE_COPY_SUCCESS', $newName . '.' . $ext));
1361
1362				return true;
1363			}
1364			else
1365			{
1366				return false;
1367			}
1368		}
1369	}
1370
1371	/**
1372	 * Get the compressed files.
1373	 *
1374	 * @return   array if file exists, false otherwise
1375	 *
1376	 * @since   3.2
1377	 */
1378	public function getArchive()
1379	{
1380		if ($template = $this->getTemplate())
1381		{
1382			$app     = JFactory::getApplication();
1383			$client  = JApplicationHelper::getClientInfo($template->client_id);
1384			$relPath = base64_decode($app->input->get('file'));
1385			$path    = JPath::clean($client->path . '/templates/' . $template->element . '/' . $relPath);
1386
1387			if (file_exists(JPath::clean($path)))
1388			{
1389				$files = array();
1390				$zip = new ZipArchive;
1391
1392				if ($zip->open($path) === true)
1393				{
1394					for ($i = 0; $i < $zip->numFiles; $i++)
1395					{
1396						$entry = $zip->getNameIndex($i);
1397						$files[] = $entry;
1398					}
1399				}
1400				else
1401				{
1402					$app->enqueueMessage(JText::_('COM_TEMPLATES_FILE_ARCHIVE_OPEN_FAIL'), 'error');
1403
1404					return false;
1405				}
1406			}
1407			else
1408			{
1409				$app->enqueueMessage(JText::_('COM_TEMPLATES_ERROR_FONT_FILE_NOT_FOUND'), 'error');
1410
1411				return false;
1412			}
1413
1414			return $files;
1415		}
1416	}
1417
1418	/**
1419	 * Extract contents of an archive file.
1420	 *
1421	 * @param   string  $file  The name and location of the file
1422	 *
1423	 * @return  boolean  true if image extraction is successful, false otherwise.
1424	 *
1425	 * @since   3.2
1426	 */
1427	public function extractArchive($file)
1428	{
1429		if ($template = $this->getTemplate())
1430		{
1431			$app          = JFactory::getApplication();
1432			$client       = JApplicationHelper::getClientInfo($template->client_id);
1433			$relPath      = base64_decode($file);
1434			$explodeArray = explode('/', $relPath);
1435			$fileName     = end($explodeArray);
1436			$folderPath   = stristr($relPath, $fileName, true);
1437			$path         = JPath::clean($client->path . '/templates/' . $template->element . '/' . $folderPath . '/');
1438
1439			if (file_exists(JPath::clean($path . '/' . $fileName)))
1440			{
1441				$zip = new ZipArchive;
1442
1443				if ($zip->open(JPath::clean($path . '/' . $fileName)) === true)
1444				{
1445					for ($i = 0; $i < $zip->numFiles; $i++)
1446					{
1447						$entry = $zip->getNameIndex($i);
1448
1449						if (file_exists(JPath::clean($path . '/' . $entry)))
1450						{
1451							$app->enqueueMessage(JText::_('COM_TEMPLATES_FILE_ARCHIVE_EXISTS'), 'error');
1452
1453							return false;
1454						}
1455					}
1456
1457					$zip->extractTo($path);
1458
1459					return true;
1460				}
1461				else
1462				{
1463					$app->enqueueMessage(JText::_('COM_TEMPLATES_FILE_ARCHIVE_OPEN_FAIL'), 'error');
1464
1465					return false;
1466				}
1467			}
1468			else
1469			{
1470				$app->enqueueMessage(JText::_('COM_TEMPLATES_FILE_ARCHIVE_NOT_FOUND'), 'error');
1471
1472				return false;
1473			}
1474		}
1475	}
1476
1477	/**
1478	 * Check if the extension is allowed and will be shown in the template manager
1479	 *
1480	 * @param   string  $ext  The extension to check if it is allowed
1481	 *
1482	 * @return  boolean  true if the extension is allowed false otherwise
1483	 *
1484	 * @since   3.6.0
1485	 */
1486	protected function checkFormat($ext)
1487	{
1488		if (!isset($this->allowedFormats))
1489		{
1490			$params       = JComponentHelper::getParams('com_templates');
1491			$imageTypes   = explode(',', $params->get('image_formats'));
1492			$sourceTypes  = explode(',', $params->get('source_formats'));
1493			$fontTypes    = explode(',', $params->get('font_formats'));
1494			$archiveTypes = explode(',', $params->get('compressed_formats'));
1495
1496			$this->allowedFormats = array_merge($imageTypes, $sourceTypes, $fontTypes, $archiveTypes);
1497		}
1498
1499		return in_array($ext, $this->allowedFormats);
1500	}
1501}
1502