manager.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  1. <?php
  2. namespace Elementor\TemplateLibrary;
  3. use Elementor\Api;
  4. use Elementor\Core\Settings\Manager as SettingsManager;
  5. use Elementor\TemplateLibrary\Classes\Import_Images;
  6. use Elementor\Plugin;
  7. if ( ! defined( 'ABSPATH' ) ) {
  8. exit; // Exit if accessed directly.
  9. }
  10. /**
  11. * Elementor template library manager.
  12. *
  13. * Elementor template library manager handler class is responsible for
  14. * initializing the template library.
  15. *
  16. * @since 1.0.0
  17. */
  18. class Manager {
  19. /**
  20. * Registered template sources.
  21. *
  22. * Holds a list of all the supported sources with their instances.
  23. *
  24. * @access protected
  25. *
  26. * @var Source_Base[]
  27. */
  28. protected $_registered_sources = [];
  29. /**
  30. * Imported template images.
  31. *
  32. * Holds an instance of `Import_Images` class.
  33. *
  34. * @access private
  35. *
  36. * @var Import_Images
  37. */
  38. private $_import_images = null;
  39. /**
  40. * Template library manager constructor.
  41. *
  42. * Initializing the template library manager by registering default template
  43. * sources and initializing ajax calls.
  44. *
  45. * @since 1.0.0
  46. * @access public
  47. */
  48. public function __construct() {
  49. $this->register_default_sources();
  50. $this->init_ajax_calls();
  51. }
  52. /**
  53. * Get `Import_Images` instance.
  54. *
  55. * Retrieve the instance of the `Import_Images` class.
  56. *
  57. * @since 1.0.0
  58. * @access public
  59. *
  60. * @return Import_Images Imported images instance.
  61. */
  62. public function get_import_images_instance() {
  63. if ( null === $this->_import_images ) {
  64. $this->_import_images = new Import_Images();
  65. }
  66. return $this->_import_images;
  67. }
  68. /**
  69. * Register template source.
  70. *
  71. * Used to register new template sources displayed in the template library.
  72. *
  73. * @since 1.0.0
  74. * @access public
  75. *
  76. * @param string $source_class The name of source class.
  77. * @param array $args Optional. Class arguments. Default is an
  78. * empty array.
  79. *
  80. * @return \WP_Error|true True if the source was registered, `WP_Error`
  81. * otherwise.
  82. */
  83. public function register_source( $source_class, $args = [] ) {
  84. if ( ! class_exists( $source_class ) ) {
  85. return new \WP_Error( 'source_class_name_not_exists' );
  86. }
  87. $source_instance = new $source_class( $args );
  88. if ( ! $source_instance instanceof Source_Base ) {
  89. return new \WP_Error( 'wrong_instance_source' );
  90. }
  91. $this->_registered_sources[ $source_instance->get_id() ] = $source_instance;
  92. return true;
  93. }
  94. /**
  95. * Unregister template source.
  96. *
  97. * Remove an existing template sources from the list of registered template
  98. * sources.
  99. *
  100. * @since 1.0.0
  101. * @access public
  102. *
  103. * @param string $id The source ID.
  104. *
  105. * @return bool Whether the source was unregistered.
  106. */
  107. public function unregister_source( $id ) {
  108. if ( ! isset( $this->_registered_sources[ $id ] ) ) {
  109. return false;
  110. }
  111. unset( $this->_registered_sources[ $id ] );
  112. return true;
  113. }
  114. /**
  115. * Get registered template sources.
  116. *
  117. * Retrieve registered template sources.
  118. *
  119. * @since 1.0.0
  120. * @access public
  121. *
  122. * @return Source_Base[] Registered template sources.
  123. */
  124. public function get_registered_sources() {
  125. return $this->_registered_sources;
  126. }
  127. /**
  128. * Get template source.
  129. *
  130. * Retrieve single template sources for a given template ID.
  131. *
  132. * @since 1.0.0
  133. * @access public
  134. *
  135. * @param string $id The source ID.
  136. *
  137. * @return false|Source_Base Template sources if one exist, False otherwise.
  138. */
  139. public function get_source( $id ) {
  140. $sources = $this->get_registered_sources();
  141. if ( ! isset( $sources[ $id ] ) ) {
  142. return false;
  143. }
  144. return $sources[ $id ];
  145. }
  146. /**
  147. * Get templates.
  148. *
  149. * Retrieve all the templates from all the registered sources.
  150. *
  151. * @since 1.0.0
  152. * @access public
  153. *
  154. * @return array Templates array.
  155. */
  156. public function get_templates() {
  157. $templates = [];
  158. foreach ( $this->get_registered_sources() as $source ) {
  159. $templates = array_merge( $templates, $source->get_items() );
  160. }
  161. return $templates;
  162. }
  163. /**
  164. * Get library data.
  165. *
  166. * Retrieve the library data.
  167. *
  168. * @since 1.9.0
  169. * @access public
  170. *
  171. * @param array $args Library arguments.
  172. *
  173. * @return array Library data.
  174. */
  175. public function get_library_data( array $args ) {
  176. $library_data = Api::get_library_data( ! empty( $args['sync'] ) );
  177. return [
  178. 'templates' => $this->get_templates(),
  179. 'config' => [
  180. 'categories' => $library_data['categories'],
  181. ],
  182. ];
  183. }
  184. /**
  185. * Save template.
  186. *
  187. * Save new or update existing template on the database.
  188. *
  189. * @since 1.0.0
  190. * @access public
  191. *
  192. * @param array $args Template arguments.
  193. *
  194. * @return \WP_Error|int The ID of the saved/updated template.
  195. */
  196. public function save_template( array $args ) {
  197. $validate_args = $this->ensure_args( [ 'post_id', 'source', 'content', 'type' ], $args );
  198. if ( is_wp_error( $validate_args ) ) {
  199. return $validate_args;
  200. }
  201. $source = $this->get_source( $args['source'] );
  202. if ( ! $source ) {
  203. return new \WP_Error( 'template_error', 'Template source not found.' );
  204. }
  205. $args['content'] = json_decode( stripslashes( $args['content'] ), true );
  206. if ( 'page' === $args['type'] ) {
  207. $page = SettingsManager::get_settings_managers( 'page' )->get_model( $args['post_id'] );
  208. $args['page_settings'] = $page->get_data( 'settings' );
  209. }
  210. $template_id = $source->save_item( $args );
  211. if ( is_wp_error( $template_id ) ) {
  212. return $template_id;
  213. }
  214. return $source->get_item( $template_id );
  215. }
  216. /**
  217. * Update template.
  218. *
  219. * Update template on the database.
  220. *
  221. * @since 1.0.0
  222. * @access public
  223. *
  224. * @param array $template_data New template data.
  225. *
  226. * @return \WP_Error|Source_Base Template sources instance if the templates
  227. * was updated, `WP_Error` otherwise.
  228. */
  229. public function update_template( array $template_data ) {
  230. $validate_args = $this->ensure_args( [ 'source', 'content', 'type' ], $template_data );
  231. if ( is_wp_error( $validate_args ) ) {
  232. return $validate_args;
  233. }
  234. $source = $this->get_source( $template_data['source'] );
  235. if ( ! $source ) {
  236. return new \WP_Error( 'template_error', 'Template source not found.' );
  237. }
  238. $template_data['content'] = json_decode( stripslashes( $template_data['content'] ), true );
  239. $update = $source->update_item( $template_data );
  240. if ( is_wp_error( $update ) ) {
  241. return $update;
  242. }
  243. return $source->get_item( $template_data['id'] );
  244. }
  245. /**
  246. * Update templates.
  247. *
  248. * Update template on the database.
  249. *
  250. * @since 1.0.0
  251. * @access public
  252. *
  253. * @param array $args Template arguments.
  254. *
  255. * @return \WP_Error|true True if templates updated, `WP_Error` otherwise.
  256. */
  257. public function update_templates( array $args ) {
  258. foreach ( $args['templates'] as $template_data ) {
  259. $result = $this->update_template( $template_data );
  260. if ( is_wp_error( $result ) ) {
  261. return $result;
  262. }
  263. }
  264. return true;
  265. }
  266. /**
  267. * Get template data.
  268. *
  269. * Retrieve the template data.
  270. *
  271. * @since 1.5.0
  272. * @access public
  273. *
  274. * @param array $args Template arguments.
  275. *
  276. * @return \WP_Error|bool|array ??
  277. */
  278. public function get_template_data( array $args ) {
  279. $validate_args = $this->ensure_args( [ 'source', 'template_id' ], $args );
  280. if ( is_wp_error( $validate_args ) ) {
  281. return $validate_args;
  282. }
  283. if ( isset( $args['edit_mode'] ) ) {
  284. Plugin::$instance->editor->set_edit_mode( $args['edit_mode'] );
  285. }
  286. $source = $this->get_source( $args['source'] );
  287. if ( ! $source ) {
  288. return new \WP_Error( 'template_error', 'Template source not found.' );
  289. }
  290. do_action( 'elementor/template-library/before_get_source_data', $args, $source );
  291. $data = $source->get_data( $args );
  292. do_action( 'elementor/template-library/after_get_source_data', $args, $source );
  293. return $data;
  294. }
  295. /**
  296. * Delete template.
  297. *
  298. * Delete template from the database.
  299. *
  300. * @since 1.0.0
  301. * @access public
  302. *
  303. * @param array $args Template arguments.
  304. *
  305. * @return \WP_Post|\WP_Error|false|null Post data on success, false or null
  306. * or 'WP_Error' on failure.
  307. */
  308. public function delete_template( array $args ) {
  309. $validate_args = $this->ensure_args( [ 'source', 'template_id' ], $args );
  310. if ( is_wp_error( $validate_args ) ) {
  311. return $validate_args;
  312. }
  313. $source = $this->get_source( $args['source'] );
  314. if ( ! $source ) {
  315. return new \WP_Error( 'template_error', 'Template source not found.' );
  316. }
  317. return $source->delete_template( $args['template_id'] );
  318. }
  319. /**
  320. * Export template.
  321. *
  322. * Export template to a file.
  323. *
  324. * @since 1.0.0
  325. * @access public
  326. *
  327. * @param array $args Template arguments.
  328. *
  329. * @return mixed Whether the export succeeded or failed.
  330. */
  331. public function export_template( array $args ) {
  332. $validate_args = $this->ensure_args( [ 'source', 'template_id' ], $args );
  333. if ( is_wp_error( $validate_args ) ) {
  334. return $validate_args;
  335. }
  336. $source = $this->get_source( $args['source'] );
  337. if ( ! $source ) {
  338. return new \WP_Error( 'template_error', 'Template source not found.' );
  339. }
  340. // If you reach this line, the export was not successful.
  341. return $source->export_template( $args['template_id'] );
  342. }
  343. /**
  344. * Import template.
  345. *
  346. * Import template from a file.
  347. *
  348. * @since 1.0.0
  349. * @access public
  350. *
  351. * @return mixed Whether the export succeeded or failed.
  352. */
  353. public function import_template() {
  354. /** @var Source_Local $source */
  355. $source = $this->get_source( 'local' );
  356. return $source->import_template( $_FILES['file']['name'], $_FILES['file']['tmp_name'] );
  357. }
  358. /**
  359. * Mark template as favorite.
  360. *
  361. * Add the template to the user favorite templates.
  362. *
  363. * @since 1.9.0
  364. * @access public
  365. *
  366. * @param array $args Template arguments.
  367. *
  368. * @return mixed Whether the template marked as favorite.
  369. */
  370. public function mark_template_as_favorite( $args ) {
  371. $validate_args = $this->ensure_args( [ 'source', 'template_id', 'favorite' ], $args );
  372. if ( is_wp_error( $validate_args ) ) {
  373. return $validate_args;
  374. }
  375. $source = $this->get_source( $args['source'] );
  376. return $source->mark_as_favorite( $args['template_id'], filter_var( $args['favorite'], FILTER_VALIDATE_BOOLEAN ) );
  377. }
  378. /**
  379. * On successful template import.
  380. *
  381. * Redirect the user to the template library after template import was
  382. * successful finished.
  383. *
  384. * @since 1.0.0
  385. * @access public
  386. */
  387. public function on_import_template_success() {
  388. wp_redirect( admin_url( 'edit.php?post_type=' . Source_Local::CPT ) );
  389. }
  390. /**
  391. * On failed template import.
  392. *
  393. * Echo the error messages after template import was failed.
  394. *
  395. * @since 1.0.0
  396. * @access public
  397. *
  398. * @param \WP_Error $error WordPress error instance.
  399. */
  400. public function on_import_template_error( \WP_Error $error ) {
  401. echo $error->get_error_message();
  402. }
  403. /**
  404. * On failed template export.
  405. *
  406. * Kill WordPress execution and display HTML error messages after template
  407. * export was failed.
  408. *
  409. * @since 1.0.0
  410. * @access public
  411. *
  412. * @param \WP_Error $error WordPress error instance.
  413. */
  414. public function on_export_template_error( \WP_Error $error ) {
  415. _default_wp_die_handler( $error->get_error_message(), 'Elementor Library' );
  416. }
  417. /**
  418. * Register default template sources.
  419. *
  420. * Register the 'local' and 'remote' template sources that Elementor use by
  421. * default.
  422. *
  423. * @since 1.0.0
  424. * @access private
  425. */
  426. private function register_default_sources() {
  427. $sources = [
  428. 'local',
  429. 'remote',
  430. ];
  431. foreach ( $sources as $source_filename ) {
  432. $class_name = ucwords( $source_filename );
  433. $class_name = str_replace( '-', '_', $class_name );
  434. $this->register_source( __NAMESPACE__ . '\Source_' . $class_name );
  435. }
  436. }
  437. /**
  438. * Handle ajax request.
  439. *
  440. * Fire authenticated ajax actions for any given ajax request.
  441. *
  442. * @since 1.0.0
  443. * @access private
  444. *
  445. * @param string $ajax_request Ajax request.
  446. */
  447. private function handle_ajax_request( $ajax_request ) {
  448. Plugin::$instance->editor->verify_ajax_nonce();
  449. if ( ! empty( $_REQUEST['editor_post_id'] ) ) {
  450. $editor_post_id = absint( $_REQUEST['editor_post_id'] );
  451. if ( ! get_post( $editor_post_id ) ) {
  452. wp_send_json_error( __( 'Post not found.', 'elementor' ) );
  453. }
  454. Plugin::$instance->db->switch_to_post( $editor_post_id );
  455. }
  456. $result = call_user_func( [ $this, $ajax_request ], $_REQUEST );
  457. $request_type = ! empty( $_SERVER['HTTP_X_REQUESTED_WITH'] ) && strtolower( $_SERVER['HTTP_X_REQUESTED_WITH'] ) === 'xmlhttprequest' ? 'ajax' : 'direct';
  458. if ( 'direct' === $request_type ) {
  459. $callback = 'on_' . $ajax_request;
  460. if ( method_exists( $this, $callback ) ) {
  461. $this->$callback( $result );
  462. }
  463. }
  464. if ( is_wp_error( $result ) ) {
  465. if ( 'ajax' === $request_type ) {
  466. wp_send_json_error( $result );
  467. }
  468. $callback = "on_{$ajax_request}_error";
  469. if ( method_exists( $this, $callback ) ) {
  470. $this->$callback( $result );
  471. }
  472. die;
  473. }
  474. if ( 'ajax' === $request_type ) {
  475. wp_send_json_success( $result );
  476. }
  477. $callback = "on_{$ajax_request}_success";
  478. if ( method_exists( $this, $callback ) ) {
  479. $this->$callback( $result );
  480. }
  481. die;
  482. }
  483. /**
  484. * Init ajax calls.
  485. *
  486. * Initialize template library ajax calls for allowed ajax requests.
  487. *
  488. * @since 1.0.0
  489. * @access private
  490. */
  491. private function init_ajax_calls() {
  492. $allowed_ajax_requests = [
  493. 'get_library_data',
  494. 'get_template_data',
  495. 'save_template',
  496. 'update_templates',
  497. 'delete_template',
  498. 'export_template',
  499. 'import_template',
  500. 'mark_template_as_favorite',
  501. ];
  502. foreach ( $allowed_ajax_requests as $ajax_request ) {
  503. add_action( 'wp_ajax_elementor_' . $ajax_request, function() use ( $ajax_request ) {
  504. $this->handle_ajax_request( $ajax_request );
  505. } );
  506. }
  507. }
  508. /**
  509. * Ensure arguments exist.
  510. *
  511. * Checks whether the required arguments exist in the specified arguments.
  512. *
  513. * @since 1.0.0
  514. * @access private
  515. *
  516. * @param array $required_args Required arguments to check whether they
  517. * exist.
  518. * @param array $specified_args The list of all the specified arguments to
  519. * check against.
  520. *
  521. * @return \WP_Error|true True on success, 'WP_Error' otherwise.
  522. */
  523. private function ensure_args( array $required_args, array $specified_args ) {
  524. $not_specified_args = array_diff( $required_args, array_keys( array_filter( $specified_args ) ) );
  525. if ( $not_specified_args ) {
  526. return new \WP_Error( 'arguments_not_specified', sprintf( 'The required argument(s) "%s" not specified.', implode( ', ', $not_specified_args ) ) );
  527. }
  528. return true;
  529. }
  530. }