PageRenderTime 56ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/concrete/src/Page/Theme/Theme.php

http://github.com/concrete5/concrete5
PHP | 976 lines | 791 code | 104 blank | 81 comment | 63 complexity | 44c7c757b9cf070f23393cf44c8cbaad MD5 | raw file
Possible License(s): MIT, LGPL-2.1, MPL-2.0-no-copyleft-exception, BSD-3-Clause
  1. <?php
  2. namespace Concrete\Core\Page\Theme;
  3. use Concrete\Core\Entity\Site\Site;
  4. use Concrete\Core\Http\ResponseAssetGroup;
  5. use Config;
  6. use Loader;
  7. use Page;
  8. use Environment;
  9. use Core;
  10. use Concrete\Core\Page\Theme\File as PageThemeFile;
  11. use Concrete\Core\Package\PackageList;
  12. use Concrete\Core\Foundation\ConcreteObject;
  13. use PageTemplate;
  14. use Concrete\Core\Page\Theme\GridFramework\GridFramework;
  15. use Concrete\Core\Page\Single as SinglePage;
  16. use Concrete\Core\StyleCustomizer\Preset;
  17. use Concrete\Core\Entity\StyleCustomizer\CustomCssRecord;
  18. use Localization;
  19. /**
  20. * A page's theme is a pointer to a directory containing templates, CSS files and optionally PHP includes, images and JavaScript files.
  21. * Themes inherit down the tree when a page is added, but can also be set at the site-wide level (thereby overriding any previous choices.).
  22. */
  23. class Theme extends ConcreteObject
  24. {
  25. const E_THEME_INSTALLED = 1;
  26. const THEME_EXTENSION = '.php';
  27. const THEME_CUSTOMIZABLE_STYLESHEET_EXTENSION = '.less';
  28. const FILENAME_TYPOGRAPHY_CSS = 'typography.css';
  29. protected $pThemeName;
  30. protected $pThemeID;
  31. protected $pThemeDescription;
  32. protected $pThemeDirectory;
  33. protected $pThemeThumbnail;
  34. protected $pThemeHandle;
  35. protected $pThemeURL;
  36. protected $pThemeIsPreview = false;
  37. protected $pkgID;
  38. protected $stylesheetCachePath;
  39. protected $stylesheetCacheRelativePath = REL_DIR_FILES_CACHE;
  40. public function __construct()
  41. {
  42. $this->setStylesheetCachePath(Config::get('concrete.cache.directory'));
  43. }
  44. public static function getGlobalList()
  45. {
  46. return static::getList('pkgID > 0');
  47. }
  48. public static function getLocalList()
  49. {
  50. return static::getList('pkgID = 0');
  51. }
  52. public static function getListByPackage($pkg)
  53. {
  54. return static::getList('pkgID = '.$pkg->getPackageID());
  55. }
  56. public static function getList($where = null)
  57. {
  58. if ($where != null) {
  59. $where = ' where '.$where;
  60. }
  61. $db = Loader::db();
  62. $r = $db->query('select pThemeID from PageThemes'.$where);
  63. $themes = [];
  64. while ($row = $r->fetchRow()) {
  65. $pl = static::getByID($row['pThemeID']);
  66. $themes[] = $pl;
  67. }
  68. return $themes;
  69. }
  70. public static function getInstalledHandles()
  71. {
  72. $db = Loader::db();
  73. return $db->GetCol('select pThemeHandle from PageThemes');
  74. }
  75. public function providesAsset($assetType, $assetHandle = null)
  76. {
  77. $r = ResponseAssetGroup::get();
  78. $r->markAssetAsIncluded($assetType, $assetHandle);
  79. }
  80. public function requireAsset()
  81. {
  82. $r = ResponseAssetGroup::get();
  83. $args = func_get_args();
  84. call_user_func_array([$r, 'requireAsset'], $args);
  85. }
  86. /**
  87. * scans the directory for available themes. For those who don't want to go through the hassle of uploading.
  88. *
  89. * @param bool $filterInstalled
  90. *
  91. * @return static[]
  92. */
  93. public static function getAvailableThemes($filterInstalled = true)
  94. {
  95. $db = Loader::db();
  96. $dh = Loader::helper('file');
  97. $themes = $dh->getDirectoryContents(DIR_FILES_THEMES);
  98. if ($filterInstalled) {
  99. // strip out themes we've already installed
  100. $handles = $db->GetCol('select pThemeHandle from PageThemes');
  101. $themesTemp = [];
  102. foreach ($themes as $t) {
  103. if (!in_array($t, $handles)) {
  104. $themesTemp[] = $t;
  105. }
  106. }
  107. $themes = $themesTemp;
  108. }
  109. if (count($themes) > 0) {
  110. $themesTemp = [];
  111. // get theme objects from the file system
  112. foreach ($themes as $t) {
  113. $th = static::getByFileHandle($t);
  114. if (!empty($th)) {
  115. $themesTemp[] = $th;
  116. }
  117. }
  118. $themes = $themesTemp;
  119. }
  120. return $themes;
  121. }
  122. public static function getByFileHandle($handle, $dir = DIR_FILES_THEMES, $pkgHandle = '')
  123. {
  124. $dirt = $dir.'/'.$handle;
  125. if (is_dir($dirt)) {
  126. $res = static::getThemeNameAndDescription($dirt, $handle, $pkgHandle);
  127. $th = new static();
  128. $th->pThemeHandle = $handle;
  129. $th->pThemeDirectory = $dirt;
  130. $th->pThemeName = $res->pThemeName;
  131. $th->pThemeDescription = $res->pThemeDescription;
  132. if (strlen($res->pError) > 0) {
  133. $th->error = $res->pError;
  134. }
  135. switch ($dir) {
  136. case DIR_FILES_THEMES:
  137. $th->pThemeURL = DIR_REL.'/'.DIRNAME_APPLICATION.'/'.DIRNAME_THEMES.'/'.$handle;
  138. break;
  139. }
  140. return $th;
  141. }
  142. }
  143. /**
  144. * Checks the theme for a styles.xml file (which is how customizations happen.).
  145. *
  146. * @return bool
  147. */
  148. public function isThemeCustomizable()
  149. {
  150. $env = Environment::get();
  151. $r = $env->getRecord(
  152. DIRNAME_THEMES.'/'.$this->getThemeHandle().'/'.DIRNAME_CSS.'/'.FILENAME_STYLE_CUSTOMIZER_STYLES,
  153. $this->getPackageHandle()
  154. );
  155. return $r->exists();
  156. }
  157. /**
  158. * Gets the style list object for this theme.
  159. *
  160. * @return \Concrete\Core\StyleCustomizer\StyleList
  161. */
  162. public function getThemeCustomizableStyleList()
  163. {
  164. if (!isset($this->styleList)) {
  165. $env = Environment::get();
  166. $r = $env->getRecord(
  167. DIRNAME_THEMES.'/'.$this->getThemeHandle(
  168. ).'/'.DIRNAME_CSS.'/'.FILENAME_STYLE_CUSTOMIZER_STYLES,
  169. $this->getPackageHandle()
  170. );
  171. $this->styleList = \Concrete\Core\StyleCustomizer\StyleList::loadFromXMLFile($r->file);
  172. }
  173. return $this->styleList;
  174. }
  175. /**
  176. * Gets a preset for this theme by handle.
  177. */
  178. public function getThemeCustomizablePreset($handle)
  179. {
  180. $env = Environment::get();
  181. if ($this->isThemeCustomizable()) {
  182. $file = $env->getRecord(
  183. DIRNAME_THEMES.'/'.$this->getThemeHandle(
  184. ).'/'.DIRNAME_CSS.'/'.DIRNAME_STYLE_CUSTOMIZER_PRESETS.'/'.$handle.static::THEME_CUSTOMIZABLE_STYLESHEET_EXTENSION,
  185. $this->getPackageHandle()
  186. );
  187. if ($file->exists()) {
  188. $urlroot = $env->getURL(
  189. DIRNAME_THEMES.'/'.$this->getThemeHandle().'/'.DIRNAME_CSS,
  190. $this->getPackageHandle()
  191. );
  192. $preset = Preset::getFromFile($file->file, $urlroot);
  193. return $preset;
  194. }
  195. }
  196. }
  197. /**
  198. * Gets all presets available to this theme.
  199. */
  200. public function getThemeCustomizableStylePresets()
  201. {
  202. $presets = [];
  203. $env = Environment::get();
  204. if ($this->isThemeCustomizable()) {
  205. $directory = $env->getPath(
  206. DIRNAME_THEMES.'/'.$this->getThemeHandle(
  207. ).'/'.DIRNAME_CSS.'/'.DIRNAME_STYLE_CUSTOMIZER_PRESETS,
  208. $this->getPackageHandle()
  209. );
  210. $urlroot = $env->getURL(
  211. DIRNAME_THEMES.'/'.$this->getThemeHandle().'/'.DIRNAME_CSS,
  212. $this->getPackageHandle()
  213. );
  214. $dh = Loader::helper('file');
  215. $files = $dh->getDirectoryContents($directory);
  216. foreach ($files as $f) {
  217. if (strrchr($f, '.') == static::THEME_CUSTOMIZABLE_STYLESHEET_EXTENSION) {
  218. $preset = Preset::getFromFile($directory.'/'.$f, $urlroot);
  219. if (is_object($preset)) {
  220. $presets[] = $preset;
  221. }
  222. }
  223. }
  224. }
  225. usort(
  226. $presets,
  227. function ($a, $b) {
  228. if ($a->isDefaultPreset()) {
  229. return -1;
  230. } else {
  231. return strcasecmp($a->getPresetDisplayName('text'), $b->getPresetDisplayName('text'));
  232. }
  233. }
  234. );
  235. return $presets;
  236. }
  237. public function enablePreviewRequest()
  238. {
  239. $this->setStylesheetCacheRelativePath(REL_DIR_FILES_CACHE.'/preview');
  240. $this->setStylesheetCachePath(Config::get('concrete.cache.directory').'/preview');
  241. $this->pThemeIsPreview = true;
  242. }
  243. public function resetThemeCustomStyles()
  244. {
  245. $db = Loader::db();
  246. $db->delete('PageThemeCustomStyles', ['pThemeID' => $this->getThemeID()]);
  247. $sheets = $this->getThemeCustomizableStyleSheets();
  248. foreach ($sheets as $sheet) {
  249. $sheet->clearOutputFile();
  250. }
  251. }
  252. public function isThemePreviewRequest()
  253. {
  254. return $this->pThemeIsPreview;
  255. }
  256. public function getThemeCustomizableStyleSheets()
  257. {
  258. $sheets = [];
  259. $env = Environment::get();
  260. if ($this->isThemeCustomizable()) {
  261. $directory = $env->getPath(
  262. DIRNAME_THEMES.'/'.$this->getThemeHandle().'/'.DIRNAME_CSS,
  263. $this->getPackageHandle()
  264. );
  265. $dh = Loader::helper('file');
  266. $files = $dh->getDirectoryContents($directory);
  267. foreach ($files as $f) {
  268. if (strrchr($f, '.') == static::THEME_CUSTOMIZABLE_STYLESHEET_EXTENSION) {
  269. $sheets[] = $this->getStylesheetObject($f);
  270. }
  271. }
  272. }
  273. return $sheets;
  274. }
  275. public function getStylesheetObject($stylesheet)
  276. {
  277. $env = Environment::get();
  278. $output = $this->getStylesheetCachePath().'/'.DIRNAME_CSS.'/'.$this->getThemeHandle();
  279. $relative = $this->getStylesheetCacheRelativePath().'/'.DIRNAME_CSS.'/'.$this->getThemeHandle();
  280. $r = $env->getRecord(
  281. DIRNAME_THEMES.'/'.$this->getThemeHandle().'/'.DIRNAME_CSS.'/'.$stylesheet,
  282. $this->getPackageHandle()
  283. );
  284. $stylesheet = new \Concrete\Core\StyleCustomizer\Stylesheet($stylesheet, $r->file, $r->url, $output, $relative);
  285. return $stylesheet;
  286. }
  287. /**
  288. * Looks into the current CSS directory and returns a fully compiled stylesheet
  289. * when passed a LESS stylesheet. Also serves up custom value list values for the stylesheet if they exist.
  290. *
  291. * @param string $stylesheet The LESS stylesheet to compile
  292. *
  293. * @return string The path to the stylesheet
  294. */
  295. public function getStylesheet($stylesheet)
  296. {
  297. $stylesheet = $this->getStylesheetObject($stylesheet);
  298. $styleValues = $this->getThemeCustomStyleObjectValues();
  299. if (!is_null($styleValues)) {
  300. $stylesheet->setValueList($styleValues);
  301. }
  302. if (!$this->isThemePreviewRequest()) {
  303. if (!$stylesheet->outputFileExists() || !Config::get('concrete.cache.theme_css')) {
  304. $stylesheet->output();
  305. }
  306. }
  307. $path = $stylesheet->getOutputRelativePath();
  308. if ($this->isThemePreviewRequest()) {
  309. $path .= '?ts='.time();
  310. } else {
  311. $path .= '?ts='.filemtime($stylesheet->getOutputPath());
  312. }
  313. return $path;
  314. }
  315. /**
  316. * Returns a Custom Style Object for the theme if one exists.
  317. */
  318. public function getThemeCustomStyleObject()
  319. {
  320. $db = Loader::db();
  321. $row = $db->FetchAssoc('select * from PageThemeCustomStyles where pThemeID = ?', [$this->getThemeID()]);
  322. if (isset($row['pThemeID'])) {
  323. $o = new \Concrete\Core\Page\CustomStyle();
  324. $o->setThemeID($this->getThemeID());
  325. $o->setValueListID($row['scvlID']);
  326. $o->setPresetHandle($row['preset']);
  327. $o->setCustomCssRecordID($row['sccRecordID']);
  328. return $o;
  329. }
  330. }
  331. /**
  332. * Returns the value list of the custom style object if one exists.
  333. *
  334. * @return ValueList
  335. */
  336. public function getThemeCustomStyleObjectValues()
  337. {
  338. $style = $this->getThemeCustomStyleObject();
  339. if (is_object($style)) {
  340. return $style->getValueList();
  341. }
  342. return null;
  343. }
  344. public function setCustomStyleObject(\Concrete\Core\StyleCustomizer\Style\ValueList $valueList, $selectedPreset = false, CustomCssRecord $customCssRecord = null)
  345. {
  346. $db = Loader::db();
  347. $db->delete('PageThemeCustomStyles', ['pThemeID' => $this->getThemeID()]);
  348. $preset = false;
  349. if ($selectedPreset) {
  350. $preset = $selectedPreset->getPresetHandle();
  351. }
  352. $sccRecordID = 0;
  353. if ($customCssRecord !== null) {
  354. $sccRecordID = $customCssRecord->getRecordID();
  355. }
  356. $db->insert(
  357. 'PageThemeCustomStyles',
  358. [
  359. 'pThemeID' => $this->getThemeID(),
  360. 'sccRecordID' => $sccRecordID,
  361. 'preset' => $preset,
  362. 'scvlID' => $valueList->getValueListID(),
  363. ]
  364. );
  365. // now we reset all cached css files in this theme
  366. $sheets = $this->getThemeCustomizableStyleSheets();
  367. foreach ($sheets as $s) {
  368. $s->clearOutputFile();
  369. }
  370. $scc = new \Concrete\Core\Page\CustomStyle();
  371. $scc->setThemeID($this->getThemeID());
  372. $scc->setValueListID($valueList->getValueListID());
  373. $scc->setPresetHandle($preset);
  374. $scc->setCustomCssRecordID($sccRecordID);
  375. return $scc;
  376. }
  377. /**
  378. * @param string $pThemeHandle
  379. *
  380. * @return PageTheme
  381. */
  382. public static function getByHandle($pThemeHandle)
  383. {
  384. $where = 'pThemeHandle = ?';
  385. $args = [$pThemeHandle];
  386. $pt = static::populateThemeQuery($where, $args);
  387. return $pt;
  388. }
  389. /**
  390. * @param int $ptID
  391. *
  392. * @return PageTheme
  393. */
  394. public static function getByID($pThemeID)
  395. {
  396. $where = 'pThemeID = ?';
  397. $args = [$pThemeID];
  398. $pt = static::populateThemeQuery($where, $args);
  399. return $pt;
  400. }
  401. /**
  402. * @param string $where
  403. * @param array $args
  404. *
  405. * @return \Concrete\Core\Page\Theme\Theme|null
  406. */
  407. protected static function populateThemeQuery($where, $args)
  408. {
  409. $db = Loader::db();
  410. $row = $db->GetRow(
  411. "select pThemeID, pThemeHandle, pThemeDescription, pkgID, pThemeName, pThemeHasCustomClass from PageThemes where {$where}",
  412. $args
  413. );
  414. $env = Environment::get();
  415. $pl = null;
  416. if (!empty($row)) {
  417. $standardClass = '\\Concrete\Core\\Page\\Theme\\Theme';
  418. if ($row['pThemeHasCustomClass']) {
  419. $pkgHandle = PackageList::getHandle($row['pkgID']);
  420. $r = $env->getRecord(DIRNAME_THEMES.'/'.$row['pThemeHandle'].'/'.FILENAME_THEMES_CLASS, $pkgHandle);
  421. $prefix = $r->override ? true : $pkgHandle;
  422. $customClass = core_class(
  423. 'Theme\\'.
  424. Loader::helper('text')->camelcase($row['pThemeHandle']).
  425. '\\PageTheme',
  426. $prefix);
  427. try {
  428. $pl = Core::make($customClass);
  429. } catch (\ReflectionException $e) {
  430. $pl = Core::make($standardClass);
  431. }
  432. } else {
  433. $pl = Core::make($standardClass);
  434. }
  435. $pl->setPropertiesFromArray($row);
  436. $pkgHandle = $pl->getPackageHandle();
  437. $pl->pThemeDirectory = $env->getPath(DIRNAME_THEMES.'/'.$row['pThemeHandle'], $pkgHandle);
  438. $pl->pThemeURL = $env->getURL(DIRNAME_THEMES.'/'.$row['pThemeHandle'], $pkgHandle);
  439. }
  440. return $pl;
  441. }
  442. public static function add($pThemeHandle, $pkg = null)
  443. {
  444. if (is_object($pkg)) {
  445. if (is_dir(DIR_PACKAGES.'/'.$pkg->getPackageHandle())) {
  446. $dir = DIR_PACKAGES.'/'.$pkg->getPackageHandle().'/'.DIRNAME_THEMES.'/'.$pThemeHandle;
  447. } else {
  448. $dir = DIR_PACKAGES_CORE.'/'.$pkg->getPackageHandle().'/'.DIRNAME_THEMES.'/'.$pThemeHandle;
  449. }
  450. $pkgID = $pkg->getPackageID();
  451. } else {
  452. if (is_dir(DIR_FILES_THEMES.'/'.$pThemeHandle)) {
  453. $dir = DIR_FILES_THEMES.'/'.$pThemeHandle;
  454. $pkgID = 0;
  455. } else {
  456. $dir = DIR_FILES_THEMES_CORE.'/'.$pThemeHandle;
  457. $pkgID = 0;
  458. }
  459. }
  460. $l = static::install($dir, $pThemeHandle, $pkgID);
  461. return $l;
  462. }
  463. // grabs all files in theme that are PHP based (or html if we go that route) and then
  464. // lists them out, by type, allowing people to install them as page type, etc...
  465. public function getFilesInTheme()
  466. {
  467. $dh = Loader::helper('file');
  468. $templateList = PageTemplate::getList();
  469. $pts = [];
  470. foreach ($templateList as $pt) {
  471. $pts[] = $pt->getPageTemplateHandle();
  472. }
  473. $files = [];
  474. $filesTmp = $dh->getDirectoryContents($this->pThemeDirectory);
  475. foreach ($filesTmp as $f) {
  476. if (strrchr($f, '.') == static::THEME_EXTENSION) {
  477. $fHandle = substr($f, 0, strpos($f, '.'));
  478. if ($f == FILENAME_THEMES_VIEW) {
  479. $type = PageThemeFile::TFTYPE_VIEW;
  480. } elseif ($f == FILENAME_THEMES_CLASS) {
  481. $type = PageThemeFile::TFTYPE_PAGE_CLASS;
  482. } else {
  483. if ($f == FILENAME_THEMES_DEFAULT) {
  484. $type = PageThemeFile::TFTYPE_DEFAULT;
  485. } else {
  486. if (in_array($f, SinglePage::getThemeableCorePages())) {
  487. $type = PageThemeFile::TFTYPE_SINGLE_PAGE;
  488. } else {
  489. if (in_array($fHandle, $pts)) {
  490. $type = PageThemeFile::TFTYPE_PAGE_TEMPLATE_EXISTING;
  491. } else {
  492. $type = PageThemeFile::TFTYPE_PAGE_TEMPLATE_NEW;
  493. }
  494. }
  495. }
  496. }
  497. $pf = new PageThemeFile();
  498. $pf->setFilename($f);
  499. $pf->setType($type);
  500. $files[] = $pf;
  501. }
  502. }
  503. return $files;
  504. }
  505. private static function getThemeNameAndDescription($dir, $pThemeHandle, $pkgHandle = '')
  506. {
  507. $res = new \stdClass();
  508. $res->pThemeName = '';
  509. $res->pThemeDescription = '';
  510. $res->pError = '';
  511. if (file_exists($dir.'/'.FILENAME_THEMES_DESCRIPTION)) {
  512. $con = file($dir.'/'.FILENAME_THEMES_DESCRIPTION);
  513. $res->pThemeName = trim($con[0]);
  514. $res->pThemeDescription = trim($con[1]);
  515. }
  516. $pageThemeFile = $dir.'/'.FILENAME_THEMES_CLASS;
  517. if (is_file($pageThemeFile)) {
  518. try {
  519. $cn = '\\Theme\\'.camelcase($pThemeHandle).'\\PageTheme';
  520. $classNames = [];
  521. if (strlen($pkgHandle)) {
  522. $classNames[] = '\\Concrete\\Package\\'.camelcase($pkgHandle).$cn;
  523. } else {
  524. $classNames[] = '\\Application'.$cn;
  525. $classNames[] = '\\Concrete'.$cn;
  526. }
  527. $className = null;
  528. foreach ($classNames as $cn) {
  529. if (class_exists($cn, false)) {
  530. $className = $cn;
  531. break;
  532. }
  533. }
  534. if (is_null($className)) {
  535. include_once $pageThemeFile;
  536. foreach ($classNames as $cn) {
  537. if (class_exists($cn, false)) {
  538. $className = $cn;
  539. break;
  540. }
  541. }
  542. }
  543. if (is_null($className)) {
  544. $res->pError = t(/*i18n: %1$s is a filename, %2$s is a PHP class name */'The theme file %1$s does not define the class %2$s', FILENAME_THEMES_CLASS, ltrim($classNames[0], '\\'));
  545. } else {
  546. $instance = new $className();
  547. $extensionOf = '\\Concrete\\Core\\Page\\Theme\\Theme';
  548. if (!is_a($instance, $extensionOf)) {
  549. $res->pError = t(/*i18n: %1$s is a filename, %2$s and %3$s are PHP class names */'The theme file %1$s should define a %2$s class that extends the class %3$s', FILENAME_THEMES_CLASS, ltrim($className, '\\'), ltrim($extensionOf, '\\'));
  550. } else {
  551. if (method_exists($instance, 'getThemeName')) {
  552. $s = $instance->getThemeName();
  553. if (strlen($s) > 0) {
  554. $res->pThemeName = $s;
  555. }
  556. }
  557. if (method_exists($instance, 'getThemeDescription')) {
  558. $s = $instance->getThemeDescription();
  559. if (strlen($s) > 0) {
  560. $res->pThemeDescription = $s;
  561. }
  562. }
  563. }
  564. }
  565. } catch (\Exception $x) {
  566. $res->pError = $x->getMessage();
  567. }
  568. }
  569. return $res;
  570. }
  571. public function export($node)
  572. {
  573. $pst = static::getSiteTheme();
  574. $activated = 0;
  575. if ($pst->getThemeID() == $this->getThemeID()) {
  576. $activated = 1;
  577. }
  578. $type = $node->addChild('theme');
  579. $type->addAttribute('handle', $this->getThemeHandle());
  580. $type->addAttribute('package', $this->getPackageHandle());
  581. $type->addAttribute('activated', $activated);
  582. }
  583. public static function exportList($xml)
  584. {
  585. $nxml = $xml->addChild('themes');
  586. $list = static::getList();
  587. foreach ($list as $pt) {
  588. $pt->export($nxml);
  589. }
  590. }
  591. protected static function install($dir, $pThemeHandle, $pkgID)
  592. {
  593. $result = null;
  594. if (is_dir($dir)) {
  595. $pkg = null;
  596. if ($pkgID) {
  597. $pkg = \Concrete\Core\Package\Package::getByID($pkgID);
  598. }
  599. $db = Loader::db();
  600. $cnt = $db->getOne('select count(pThemeID) from PageThemes where pThemeHandle = ?', [$pThemeHandle]);
  601. if ($cnt > 0) {
  602. throw new \Exception(static::E_THEME_INSTALLED);
  603. }
  604. $loc = Localization::getInstance();
  605. $loc->pushActiveContext(Localization::CONTEXT_SYSTEM);
  606. try {
  607. $res = static::getThemeNameAndDescription($dir, $pThemeHandle, is_object($pkg) ? $pkg->getPackageHandle() : '');
  608. } catch (\Exception $x) {
  609. $loc->popActiveContext();
  610. throw $x;
  611. }
  612. $loc->popActiveContext();
  613. if (strlen($res->pError) === 0) {
  614. $pThemeName = $res->pThemeName;
  615. $pThemeDescription = $res->pThemeDescription;
  616. $db->query(
  617. 'insert into PageThemes (pThemeHandle, pThemeName, pThemeDescription, pkgID) values (?, ?, ?, ?)',
  618. [$pThemeHandle, $pThemeName, $pThemeDescription, $pkgID]
  619. );
  620. $env = Environment::get();
  621. $env->clearOverrideCache();
  622. $pt = static::getByID($db->Insert_ID());
  623. $pt->updateThemeCustomClass();
  624. $result = $pt;
  625. }
  626. }
  627. return $result;
  628. }
  629. public function updateThemeCustomClass()
  630. {
  631. $env = Environment::get();
  632. $db = Loader::db();
  633. $r = $env->getRecord(
  634. DIRNAME_THEMES.'/'.$this->pThemeHandle.'/'.FILENAME_THEMES_CLASS,
  635. $this->getPackageHandle()
  636. );
  637. if ($r->exists()) {
  638. $db->Execute('update PageThemes set pThemeHasCustomClass = 1 where pThemeID = ?', [$this->pThemeID]);
  639. $this->pThemeHasCustomClass = true;
  640. } else {
  641. $db->Execute('update PageThemes set pThemeHasCustomClass = 0 where pThemeID = ?', [$this->pThemeID]);
  642. $this->pThemeHasCustomClass = false;
  643. }
  644. }
  645. public function getThemeID()
  646. {
  647. return $this->pThemeID;
  648. }
  649. public function getThemeName()
  650. {
  651. return $this->pThemeName;
  652. }
  653. /** Returns the display name for this theme (localized and escaped accordingly to $format)
  654. * @param string $format = 'html'
  655. * Escape the result in html format (if $format is 'html').
  656. * If $format is 'text' or any other value, the display name won't be escaped
  657. *
  658. * @return string
  659. */
  660. public function getThemeDisplayName($format = 'html')
  661. {
  662. $value = $this->getThemeName();
  663. if (strlen($value)) {
  664. $value = t($value);
  665. } else {
  666. $value = t('(No Name)');
  667. }
  668. switch ($format) {
  669. case 'html':
  670. return h($value);
  671. case 'text':
  672. default:
  673. return $value;
  674. }
  675. }
  676. public function getPackageID()
  677. {
  678. return $this->pkgID;
  679. }
  680. public function getPackageHandle()
  681. {
  682. return \Concrete\Core\Package\PackageList::getHandle($this->pkgID);
  683. }
  684. /**
  685. * Returns whether a theme has a custom class.
  686. */
  687. public function hasCustomClass()
  688. {
  689. return $this->pThemeHasCustomClass;
  690. }
  691. public function getThemeHandle()
  692. {
  693. return $this->pThemeHandle;
  694. }
  695. public function getThemeDescription()
  696. {
  697. return $this->pThemeDescription;
  698. }
  699. public function getThemeDisplayDescription($format = 'html')
  700. {
  701. $value = $this->getThemeDescription();
  702. if (strlen($value)) {
  703. $value = t($value);
  704. } else {
  705. $value = t('(No Description)');
  706. }
  707. switch ($format) {
  708. case 'html':
  709. return h($value);
  710. case 'text':
  711. default:
  712. return $value;
  713. }
  714. }
  715. public function getThemeDirectory()
  716. {
  717. return $this->pThemeDirectory;
  718. }
  719. public function getThemeURL()
  720. {
  721. return $this->pThemeURL;
  722. }
  723. public function getThemeEditorCSS()
  724. {
  725. return $this->pThemeURL.'/'.static::FILENAME_TYPOGRAPHY_CSS;
  726. }
  727. public function setThemeURL($pThemeURL)
  728. {
  729. $this->pThemeURL = $pThemeURL;
  730. }
  731. public function setThemeDirectory($pThemeDirectory)
  732. {
  733. $this->pThemeDirectory = $pThemeDirectory;
  734. }
  735. public function setThemeHandle($pThemeHandle)
  736. {
  737. $this->pThemeHandle = $pThemeHandle;
  738. }
  739. public function setStylesheetCachePath($path)
  740. {
  741. $this->stylesheetCachePath = $path;
  742. }
  743. public function setStylesheetCacheRelativePath($path)
  744. {
  745. $this->stylesheetCacheRelativePath = $path;
  746. }
  747. public function getStylesheetCachePath()
  748. {
  749. return $this->stylesheetCachePath;
  750. }
  751. public function getStylesheetCacheRelativePath()
  752. {
  753. return $this->stylesheetCacheRelativePath;
  754. }
  755. public function isUninstallable()
  756. {
  757. return $this->pThemeDirectory != DIR_FILES_THEMES_CORE.'/'.$this->getThemeHandle();
  758. }
  759. public function getThemeThumbnail()
  760. {
  761. if (file_exists($this->pThemeDirectory.'/'.FILENAME_THEMES_THUMBNAIL)) {
  762. $src = $this->pThemeURL.'/'.FILENAME_THEMES_THUMBNAIL;
  763. } else {
  764. $src = ASSETS_URL_THEMES_NO_THUMBNAIL;
  765. }
  766. $html = new \HtmlObject\Image();
  767. $img = $html->src($src)
  768. ->width(Config::get('concrete.icons.theme_thumbnail.width'))
  769. ->height(Config::get('concrete.icons.theme_thumbnail.height'))
  770. ->class('ccm-icon-theme');
  771. return $img;
  772. }
  773. public function applyToSite(Site $site = null)
  774. {
  775. if (!is_object($site)) {
  776. $site = \Core::make('site')->getSite();
  777. }
  778. $entityManager = \Database::connection()->getEntityManager();
  779. $site->setThemeID($this->getThemeID());
  780. $entityManager->persist($site);
  781. $entityManager->flush();
  782. $treeIDs = [0];
  783. foreach($site->getLocales() as $locale) {
  784. $tree = $locale->getSiteTree();
  785. if (is_object($tree)) {
  786. $treeIDs[] = $tree->getSiteTreeID();
  787. }
  788. }
  789. $treeIDs = implode(',', $treeIDs);
  790. $db = Loader::db();
  791. $r = $db->query(
  792. "update CollectionVersions inner join Pages on CollectionVersions.cID = Pages.cID left join Packages on Pages.pkgID = Packages.pkgID set CollectionVersions.pThemeID = ? where cIsTemplate = 0 and siteTreeID in ({$treeIDs}) and (Pages.ptID > 0 or CollectionVersions.pTemplateID > 0)",
  793. array($this->pThemeID)
  794. );
  795. }
  796. /**
  797. * @return static
  798. */
  799. public static function getSiteTheme()
  800. {
  801. $site = \Core::make('site')->getSite();
  802. return static::getByID($site->getThemeID());
  803. }
  804. public function uninstall()
  805. {
  806. $db = Loader::db();
  807. $db->query('delete from PageThemes where pThemeID = ?', [$this->pThemeID]);
  808. $env = Environment::get();
  809. $env->clearOverrideCache();
  810. }
  811. /**
  812. * Special items meant to be extended by custom theme classes.
  813. */
  814. protected $pThemeGridFrameworkHandle = false;
  815. public function registerAssets()
  816. {
  817. }
  818. public function supportsGridFramework()
  819. {
  820. return $this->pThemeGridFrameworkHandle != false;
  821. }
  822. /**
  823. * @return GridFramework|null
  824. */
  825. public function getThemeGridFrameworkObject()
  826. {
  827. if ($this->pThemeGridFrameworkHandle) {
  828. $framework = Core::make('manager/grid_framework')->driver($this->pThemeGridFrameworkHandle);
  829. return $framework;
  830. }
  831. }
  832. public function getThemeBlockClasses()
  833. {
  834. return [];
  835. }
  836. public function getThemeAreaClasses()
  837. {
  838. return [];
  839. }
  840. public function getThemeEditorClasses()
  841. {
  842. return [];
  843. }
  844. public function getThemeDefaultBlockTemplates()
  845. {
  846. return [];
  847. }
  848. public function getThemeResponsiveImageMap()
  849. {
  850. return [];
  851. }
  852. public function getThemeGatheringGridItemMargin()
  853. {
  854. return 20;
  855. }
  856. public function getThemeGatheringGridItemWidth()
  857. {
  858. return 150;
  859. }
  860. public function getThemeGatheringGridItemHeight()
  861. {
  862. return 150;
  863. }
  864. }