aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Zotlabs/Module/Webpages.php185
-rw-r--r--Zotlabs/Storage/Directory.php1
-rw-r--r--doc/context/en/webpages/help.html8
-rw-r--r--doc/webpage-element-import.md94
-rw-r--r--doc/webpages.bb3
-rw-r--r--doc/webpages.md (renamed from doc/Webpages.md)1
-rw-r--r--include/attach.php67
-rw-r--r--include/import.php213
-rw-r--r--include/text.php28
-rw-r--r--include/widgets.php14
-rw-r--r--view/css/mod_webpages.css85
-rw-r--r--view/js/mod_webpages.js15
-rw-r--r--view/pdl/mod_webpages.pdl1
-rw-r--r--view/tpl/webpage_import.tpl126
-rw-r--r--view/tpl/website_import_tools.tpl37
15 files changed, 877 insertions, 1 deletions
diff --git a/Zotlabs/Module/Webpages.php b/Zotlabs/Module/Webpages.php
index cc0a01cce..d3199f223 100644
--- a/Zotlabs/Module/Webpages.php
+++ b/Zotlabs/Module/Webpages.php
@@ -45,7 +45,29 @@ class Webpages extends \Zotlabs\Web\Controller {
$observer = \App::get_observer();
$channel = \App::get_channel();
-
+
+ switch ($_SESSION['action']) {
+ case 'import':
+ $_SESSION['action'] = null;
+ $o .= replace_macros(get_markup_template('webpage_import.tpl'), array(
+ '$title' => t('Import Webpage Elements'),
+ '$importbtn' => t('Import selected'),
+ '$action' => 'import',
+ '$pages' => $_SESSION['pages'],
+ '$layouts' => $_SESSION['layouts'],
+ '$blocks' => $_SESSION['blocks'],
+ ));
+ return $o;
+
+ case 'importselected':
+ $_SESSION['action'] = null;
+ break;
+ default :
+ $_SESSION['action'] = null;
+ break;
+ }
+
+
if(\App::$is_sys && is_site_admin()) {
$sys = get_sys_channel();
if($sys && intval($sys['channel_id'])) {
@@ -209,4 +231,165 @@ class Webpages extends \Zotlabs\Web\Controller {
return $o;
}
+ function post() {
+
+ $action = $_REQUEST['action'];
+ if( $action ){
+ switch ($action) {
+ case 'scan':
+
+ // the state of this variable tracks whether website files have been scanned (null, true, false)
+ $cloud = null;
+
+ // Website files are to be imported from an uploaded zip file
+ if(($_FILES) && array_key_exists('zip_file',$_FILES) && isset($_POST['w_upload'])) {
+ $source = $_FILES["zip_file"]["tmp_name"];
+ $type = $_FILES["zip_file"]["type"];
+ $okay = false;
+ $accepted_types = array('application/zip', 'application/x-zip-compressed', 'multipart/x-zip', 'application/x-compressed');
+ foreach ($accepted_types as $mime_type) {
+ if ($mime_type == $type) {
+ $okay = true;
+ break;
+ }
+ }
+ if(!$okay) {
+ notice( t('Invalid file type.') . EOL);
+ return;
+ }
+ $zip = new \ZipArchive();
+ if ($zip->open($source) === true) {
+ $tmp_folder_name = random_string(5);
+ $website = dirname($source) . '/' . $tmp_folder_name;
+ $zip->extractTo($website); // change this to the correct site path
+ $zip->close();
+ @unlink($source); // delete the compressed file now that the content has been extracted
+ $cloud = false;
+ } else {
+ notice( t('Error opening zip file') . EOL);
+ return null;
+ }
+ }
+
+ // Website files are to be imported from the channel cloud files
+ if (($_POST) && array_key_exists('path',$_POST) && isset($_POST['cloudsubmit'])) {
+
+ $channel = \App::get_channel();
+ $dirpath = get_dirpath_by_cloudpath($channel, $_POST['path']);
+ if(!$dirpath) {
+ notice( t('Invalid folder path.') . EOL);
+ return null;
+ }
+ $cloud = true;
+
+ }
+
+ // If the website files were uploaded or specified in the cloud files, then $cloud
+ // should be either true or false
+ if ($cloud !== null) {
+ require_once('include/import.php');
+ $elements = [];
+ if($cloud) {
+ $path = $_POST['path'];
+ } else {
+ $path = $website;
+ }
+ $elements['pages'] = scan_webpage_elements($path, 'page', $cloud);
+ $elements['layouts'] = scan_webpage_elements($path, 'layout', $cloud);
+ $elements['blocks'] = scan_webpage_elements($path, 'block', $cloud);
+ $_SESSION['blocks'] = $elements['blocks'];
+ $_SESSION['layouts'] = $elements['layouts'];
+ $_SESSION['pages'] = $elements['pages'];
+ if(!(empty($elements['pages']) && empty($elements['blocks']) && empty($elements['layouts']))) {
+ //info( t('Webpages elements detected.') . EOL);
+ $_SESSION['action'] = 'import';
+ } else {
+ notice( t('No webpage elements detected.') . EOL);
+ $_SESSION['action'] = null;
+ }
+
+ }
+
+ // If the website elements were imported from a zip file, delete the temporary decompressed files
+ if ($cloud === false && $website && $elements) {
+ rrmdir($website); // Delete the temporary decompressed files
+ }
+
+ break;
+
+ case 'importselected':
+ require_once('include/import.php');
+ $channel = \App::get_channel();
+
+ // Import layout first so that pages that reference new layouts will find
+ // the mid of layout items in the database
+
+ // Obtain the user-selected layouts to import and import them
+ $checkedlayouts = $_POST['layout'];
+ $layouts = [];
+ if (!empty($checkedlayouts)) {
+ foreach ($checkedlayouts as $name) {
+ foreach ($_SESSION['layouts'] as &$layout) {
+ if ($layout['name'] === $name) {
+ $layout['import'] = 1;
+ $layoutstoimport[] = $layout;
+ }
+ }
+ }
+ foreach ($layoutstoimport as $elementtoimport) {
+ $layouts[] = import_webpage_element($elementtoimport, $channel, 'layout');
+ }
+ }
+ $_SESSION['import_layouts'] = $layouts;
+
+ // Obtain the user-selected blocks to import and import them
+ $checkedblocks = $_POST['block'];
+ $blocks = [];
+ if (!empty($checkedblocks)) {
+ foreach ($checkedblocks as $name) {
+ foreach ($_SESSION['blocks'] as &$block) {
+ if ($block['name'] === $name) {
+ $block['import'] = 1;
+ $blockstoimport[] = $block;
+ }
+ }
+ }
+ foreach ($blockstoimport as $elementtoimport) {
+ $blocks[] = import_webpage_element($elementtoimport, $channel, 'block');
+ }
+ }
+ $_SESSION['import_blocks'] = $blocks;
+
+ // Obtain the user-selected pages to import and import them
+ $checkedpages = $_POST['page'];
+ $pages = [];
+ if (!empty($checkedpages)) {
+ foreach ($checkedpages as $pagelink) {
+ foreach ($_SESSION['pages'] as &$page) {
+ if ($page['pagelink'] === $pagelink) {
+ $page['import'] = 1;
+ $pagestoimport[] = $page;
+ }
+ }
+ }
+ foreach ($pagestoimport as $elementtoimport) {
+ $pages[] = import_webpage_element($elementtoimport, $channel, 'page');
+ }
+ }
+ $_SESSION['import_pages'] = $pages;
+ if(!(empty($_SESSION['import_pages']) && empty($_SESSION['import_blocks']) && empty($_SESSION['import_layouts']))) {
+ info( t('Import complete.') . EOL);
+ }
+ break;
+
+ default :
+ break;
+ }
+ }
+
+
+
+
+ }
+
}
diff --git a/Zotlabs/Storage/Directory.php b/Zotlabs/Storage/Directory.php
index 0860f99a1..15e06e28f 100644
--- a/Zotlabs/Storage/Directory.php
+++ b/Zotlabs/Storage/Directory.php
@@ -371,6 +371,7 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota {
);
if ($r) {
+ require_once('include/attach.php');
$result = attach_mkdir($r[0], $this->auth->observer, array('filename' => $name, 'folder' => $this->folder_hash));
if($result['success']) {
diff --git a/doc/context/en/webpages/help.html b/doc/context/en/webpages/help.html
new file mode 100644
index 000000000..af57ee88a
--- /dev/null
+++ b/doc/context/en/webpages/help.html
@@ -0,0 +1,8 @@
+<dl class="dl-horizontal">
+ <dt>General</dt>
+ <dd>You can create modular, identity-aware websites composed of shareable elements. </dd>
+ <dt>Pages</dt>
+ <dd>This page lists your "pages", which are assigned URLs where people can visit your site. The structure of pages are typically described by an associated <b>layout</b>, and their content is constructed from a collection of <b>blocks</b>.</dd>
+ <dt><a href='#' onclick='contextualHelpFocus("#website-import-tools", 1); return false;' title="Click to highlight element...">Website import tool</a></dt>
+ <dd>The website import tool allows you import multiple webpage elements (pages, layouts, blocks) either from an uploaded zip file or from an existing cloud files folder. <a target="_blank" href="help/webpages">Read more...</a></dd>
+</dl> \ No newline at end of file
diff --git a/doc/webpage-element-import.md b/doc/webpage-element-import.md
new file mode 100644
index 000000000..4330227c2
--- /dev/null
+++ b/doc/webpage-element-import.md
@@ -0,0 +1,94 @@
+## <a href="#webpage-element-import"></a>Webpage element import
+
+There are two methods of importing webpage elements: uploading a zip file or
+referencing a local cloud files folder. Both methods require that the webpage
+elements are specified using a specific folder structure. The import tool makes
+it possible to import all the elements necessary to construct an entire website
+or set of websites. The goal is to accommodate external development of webpages
+as well as tools to simplify and automate deployment on a hub.
+
+### Folder structure
+Element definitions must be stored in the repo root under folders called
+
+ /pages/
+ /blocks/
+ /layouts/
+
+
+Each element of these types must be defined in an individual subfolder using two files: one JSON-formatted file for the metadata and one plain text file for the element content.
+
+### Page elements
+Page element metadata is specified in a JSON-formatted file called `page.json` with the following properties:
+
+ * title
+ * pagelink
+ * mimetype
+ * layout
+ * contentfile
+
+**Example**
+
+Files:
+
+ /pages/my-page/page.json
+ /pages/my-page/my-page.bbcode
+
+Content of `page.json`:
+
+ {
+ "title": "My Page",
+ "pagelink": "mypage",
+ "mimetype": "text/bbcode",
+ "layout": "my-layout",
+ "contentfile": "my-page.bbcode"
+ }
+
+
+### Layout elements
+Layout element metadata is specified in a JSON-formatted file called `layout.json` with the following properties:
+
+ * name
+ * description
+ * contentfile
+
+**Example**
+
+Files:
+
+ /layouts/my-layout/layout.json
+ /layouts/my-layout/my-layout.bbcode
+
+Content of `layout.json`:
+
+ {
+ "name": "my-layout",
+ "description": "Layout for my project page",
+ "contentfile": "my-layout.bbcode"
+ }
+
+
+### Block elements
+Block element metadata is specified in a JSON-formatted file called `block.json` with the following properties:
+
+ * name
+ * title
+ * mimetype
+ * contentfile
+
+**Example**
+
+Files:
+
+ /blocks/my-block/block.json
+ /blocks/my-block/my-block.html
+
+Content of `block.json`:
+
+
+ {
+ "name": "my-block",
+ "title": "",
+ "mimetype": "text/html",
+ "contentfile": "my-block.html"
+ }
+ \ No newline at end of file
diff --git a/doc/webpages.bb b/doc/webpages.bb
index 6b3a800cb..2b909dc63 100644
--- a/doc/webpages.bb
+++ b/doc/webpages.bb
@@ -9,6 +9,9 @@ The &quot;page link title&quot; box allows a user to specify the &quot;pagelinkt
Beneath the page creation box, a list of existing pages will appear with an &quot;edit&quot; link. Clicking this will take you to an editor, similar to that of the post editor, where you can make changes to your webpages.
+See also:
+
+[zrl=[baseurl]/help/webpage-element-import]Webpage element import tool[/zrl]
[b]Using Blocks[/b]
diff --git a/doc/Webpages.md b/doc/webpages.md
index 801a9a3a0..05c16f2bb 100644
--- a/doc/Webpages.md
+++ b/doc/webpages.md
@@ -13,4 +13,5 @@ Beneath the page creation box, a list of existing pages will appear with an "edi
If you are the admin of a site, you can specify a channel whose webpages we will use at key points around the site. Presently, the only place this is implemented is the home page. If you specify the channel "admin" and then the channel called "admin" creates a webpage called "home", we will display it's content on your websites home page. We expect this functionality to be extended to other areas in future.
+#include doc/webpage-element-import.md;
#include doc/macros/main_footer.bb;
diff --git a/include/attach.php b/include/attach.php
index 7123d59fe..f3fb12293 100644
--- a/include/attach.php
+++ b/include/attach.php
@@ -1911,3 +1911,70 @@ function get_attach_binname($s) {
}
return $p;
}
+
+
+function get_dirpath_by_cloudpath($channel, $path) {
+
+ // Warning: Do not edit the following line. The first symbol is UTF-8 &#65312;
+ $path = str_replace('@','@',notags(trim($path)));
+
+ $h = @parse_url($path);
+
+ if(! $h || !x($h, 'path')) {
+ return null;
+ }
+ if(substr($h['path'],-1,1) === '/') {
+ $h['path'] = substr($h['path'],0,-1);
+ }
+ if(substr($h['path'],0,1) === '/') {
+ $h['path'] = substr($h['path'],1);
+ }
+ $folders = explode('/', $h['path']);
+ $f = array_shift($folders);
+
+ $nick = $channel['channel_address'];
+ //check to see if the absolute path was provided (/cloud/channelname/path/to/folder)
+ if($f === 'cloud' ) {
+ $g = array_shift($folders);
+ if( $g !== $nick) {
+ // if nick does not follow "cloud", then the top level folder must be called "cloud"
+ // and the given path must be relative to "/cloud/channelname/".
+ $folders = array_unshift(array_unshift($folders, $g), $f);
+ }
+ } else {
+ array_unshift($folders, $f);
+ }
+ $clouddir = 'store/' . $nick . '/' ;
+ $subdir = '/';
+ $valid = true;
+ while($folders && $valid && is_dir($clouddir . $subdir) && is_readable($clouddir . $subdir)) {
+ $valid = false;
+ $f = array_shift($folders);
+ $items = array_diff(scandir($clouddir . $subdir), array('.', '..')); // hashed names
+ foreach($items as $item) {
+ $filename = find_filename_by_hash($channel['channel_id'], $item);
+ if($filename === $f) {
+ $subdir .= $item . '/';
+ $valid = true;
+ }
+ }
+ }
+ if(!$valid) {
+ return null;
+ } else {
+ return $clouddir . $subdir;
+ }
+
+
+}
+
+function get_filename_by_cloudname($cloudname, $channel, $storepath) {
+ $items = array_diff(scandir($storepath), array('.', '..')); // hashed names
+ foreach($items as $item) {
+ $filename = find_filename_by_hash($channel['channel_id'], $item);
+ if($filename === $cloudname) {
+ return $item;
+ }
+ }
+ return null;
+} \ No newline at end of file
diff --git a/include/import.php b/include/import.php
index 42c902a0a..0b2cd38df 100644
--- a/include/import.php
+++ b/include/import.php
@@ -1246,3 +1246,216 @@ function convert_oldfields(&$arr,$old,$new) {
unset($arr[$old]);
}
}
+
+function scan_webpage_elements($path, $type, $cloud = false) {
+ $channel = \App::get_channel();
+ $dirtoscan = $path;
+ switch ($type) {
+ case 'page':
+ $dirtoscan .= '/pages/';
+ $json_filename = 'page.json';
+ break;
+ case 'layout':
+ $dirtoscan .= '/layouts/';
+ $json_filename = 'layout.json';
+ break;
+ case 'block':
+ $dirtoscan .= '/blocks/';
+ $json_filename = 'block.json';
+ break;
+ default :
+ return array();
+ }
+ if($cloud) {
+ $dirtoscan = get_dirpath_by_cloudpath($channel, $dirtoscan);
+ }
+ $elements = [];
+ if (is_dir($dirtoscan)) {
+ $dirlist = scandir($dirtoscan);
+ if ($dirlist) {
+ foreach ($dirlist as $element) {
+ if ($element === '.' || $element === '..') {
+ continue;
+ }
+ $folder = $dirtoscan . '/' . $element;
+ if (is_dir($folder)) {
+ if($cloud) {
+ $jsonfilepath = $folder . '/' . get_filename_by_cloudname($json_filename, $channel, $folder);
+ } else {
+ $jsonfilepath = $folder . '/' . $json_filename;
+ }
+ if (is_file($jsonfilepath)) {
+ $metadata = json_decode(file_get_contents($jsonfilepath), true);
+ if($cloud) {
+ $contentfilename = get_filename_by_cloudname($metadata['contentfile'], $channel, $folder);
+ $metadata['path'] = $folder . '/' . $contentfilename;
+ } else {
+ $contentfilename = $metadata['contentfile'];
+ $metadata['path'] = $folder . '/' . $contentfilename;
+ }
+ if ($metadata['contentfile'] === '') {
+ logger('Invalid ' . $type . ' content file');
+ return false;
+ }
+ $content = file_get_contents($folder . '/' . $contentfilename);
+ if (!$content) {
+ logger('Failed to get file content for ' . $metadata['contentfile']);
+ return false;
+ }
+ $elements[] = $metadata;
+ }
+ }
+ }
+ }
+ }
+ return $elements;
+ }
+
+
+ function import_webpage_element($element, $channel, $type) {
+
+ $arr = array(); // construct information for the webpage element item table record
+
+ switch ($type) {
+ //
+ // PAGES
+ //
+ case 'page':
+ $arr['item_type'] = ITEM_TYPE_WEBPAGE;
+ $namespace = 'WEBPAGE';
+ $name = $element['pagelink'];
+ if($name) {
+ require_once('library/urlify/URLify.php');
+ $name = strtolower(\URLify::transliterate($name));
+ }
+ $arr['title'] = $element['title'];
+ $arr['term'] = $element['term'];
+ $arr['layout_mid'] = ''; // by default there is no layout associated with the page
+ // If a layout was specified, find it in the database and get its info. If
+ // it does not exist, leave layout_mid empty
+ if($element['layout'] !== '') {
+ $liid = q("select iid from iconfig where k = 'PDL' and v = '%s' and cat = 'system'",
+ dbesc($element['layout'])
+ );
+ if($liid) {
+ $linfo = q("select mid from item where id = %d",
+ intval($liid[0]['iid'])
+ );
+ $arr['layout_mid'] = $linfo[0]['mid'];
+ }
+ }
+ break;
+ //
+ // LAYOUTS
+ //
+ case 'layout':
+ $arr['item_type'] = ITEM_TYPE_PDL;
+ $namespace = 'PDL';
+ $name = $element['name'];
+ $arr['title'] = $element['description'];
+ $arr['term'] = $element['term'];
+ break;
+ //
+ // BLOCKS
+ //
+ case 'block':
+ $arr['item_type'] = ITEM_TYPE_BLOCK;
+ $namespace = 'BUILDBLOCK';
+ $name = $element['name'];
+ $arr['title'] = $element['title'];
+
+ break;
+ default :
+ return null; // return null if invalid element type
+ }
+
+ $arr['uid'] = $channel['channel_id'];
+ $arr['aid'] = $channel['channel_account_id'];
+
+ // Check if an item already exists based on the name
+ $iid = q("select iid from iconfig where k = '" . $namespace . "' and v = '%s' and cat = 'system'",
+ dbesc($name)
+ );
+ if($iid) { // If the item does exist, get the item metadata
+ $iteminfo = q("select mid,created,edited from item where id = %d",
+ intval($iid[0]['iid'])
+ );
+ $arr['mid'] = $arr['parent_mid'] = $iteminfo[0]['mid'];
+ $arr['created'] = $iteminfo[0]['created'];
+ $arr['edited'] = (($element['edited']) ? datetime_convert('UTC', 'UTC', $element['edited']) : datetime_convert());
+ } else { // otherwise, generate the creation times and unique id
+ $arr['created'] = (($element['created']) ? datetime_convert('UTC', 'UTC', $element['created']) : datetime_convert());
+ $arr['edited'] = datetime_convert('UTC', 'UTC', '0000-00-00 00:00:00');
+ $arr['mid'] = $arr['parent_mid'] = item_message_id();
+ }
+ // Import the actual element content
+ $arr['body'] = file_get_contents($element['path']);
+ // The element owner is the channel importing the elements
+ $arr['owner_xchan'] = get_observer_hash();
+ // The author is either the owner or whomever was specified
+ $arr['author_xchan'] = (($element['author_xchan']) ? $element['author_xchan'] : get_observer_hash());
+ // Import mimetype if it is a valid mimetype for the element
+ $mimetypes = [ 'text/bbcode',
+ 'text/html',
+ 'text/markdown',
+ 'text/plain',
+ 'application/x-pdl',
+ 'application/x-php'
+ ];
+ // Blocks and pages can have any mimetype, but layouts must be text/bbcode
+ if((in_array($element['mimetype'], $mimetypes)) && ($type === 'page' || $type === 'block') ) {
+ $arr['mimetype'] = $element['mimetype'];
+ } else {
+ $arr['mimetype'] = 'text/bbcode';
+ }
+
+ // Verify ability to use html or php!!!
+ $execflag = false;
+ if ($arr['mimetype'] === 'application/x-php') {
+ $z = q("select account_id, account_roles, channel_pageflags from account "
+ . "left join channel on channel_account_id = account_id where channel_id = %d limit 1",
+ intval(local_channel())
+ );
+
+ if ($z && (($z[0]['account_roles'] & ACCOUNT_ROLE_ALLOWCODE) || ($z[0]['channel_pageflags'] & PAGE_ALLOWCODE))) {
+ $execflag = true;
+ }
+ }
+
+ $z = q("select * from iconfig where v = '%s' and k = '%s' and cat = 'service' limit 1",
+ dbesc($name),
+ dbesc($namespace)
+ );
+
+ $i = q("select id, edited, item_deleted from item where mid = '%s' and uid = %d limit 1",
+ dbesc($arr['mid']),
+ intval(local_channel())
+ );
+ $remote_id = 0;
+ if ($z && $i) {
+ $remote_id = $z[0]['id'];
+ $arr['id'] = $i[0]['id'];
+ // don't update if it has the same timestamp as the original
+ if ($arr['edited'] > $i[0]['edited'])
+ $x = item_store_update($arr, $execflag);
+ } else {
+ if (($i) && (intval($i[0]['item_deleted']))) {
+ // was partially deleted already, finish it off
+ q("delete from item where mid = '%s' and uid = %d",
+ dbesc($arr['mid']),
+ intval(local_channel())
+ );
+ }
+ $x = item_store($arr, $execflag);
+ }
+ if ($x['success']) {
+ $item_id = $x['item_id'];
+ update_remote_id($channel, $item_id, $arr['item_type'], $name, $namespace, $remote_id, $arr['mid']);
+ $element['import_success'] = 1;
+ } else {
+ $element['import_success'] = 0;
+ }
+
+ return $element;
+
+}
diff --git a/include/text.php b/include/text.php
index 1eec2ba0a..d508f8ab3 100644
--- a/include/text.php
+++ b/include/text.php
@@ -2242,6 +2242,34 @@ function design_tools() {
));
}
+/**
+ * @brief Creates website import tools menu
+ *
+ * @return string
+ */
+function website_import_tools() {
+
+ $channel = App::get_channel();
+ $sys = false;
+
+ if(App::$is_sys && is_site_admin()) {
+ require_once('include/channel.php');
+ $channel = get_sys_channel();
+ $sys = true;
+ }
+
+ return replace_macros(get_markup_template('website_import_tools.tpl'), array(
+ '$title' => t('Import'),
+ '$import_label' => t('Import website...'),
+ '$import_placeholder' => t('Select folder to import'),
+ '$file_upload_text' => t('Import from a zipped folder:'),
+ '$file_import_text' => t('Import from cloud files:'),
+ '$desc' => t('/cloud/channel/path/to/folder'),
+ '$hint' => t('Enter path to website files'),
+ '$select' => t('Select folder'),
+ ));
+}
+
/* case insensitive in_array() */
function in_arrayi($needle, $haystack) {
diff --git a/include/widgets.php b/include/widgets.php
index 3516e82da..5477dc1e4 100644
--- a/include/widgets.php
+++ b/include/widgets.php
@@ -779,6 +779,20 @@ function widget_design_tools($arr) {
return design_tools();
}
+function widget_website_import_tools($arr) {
+
+ // mod menu doesn't load a profile. For any modules which load a profile, check it.
+ // otherwise local_channel() is sufficient for permissions.
+
+ if(App::$profile['profile_uid'])
+ if((App::$profile['profile_uid'] != local_channel()) && (! App::$is_sys))
+ return '';
+
+ if(! local_channel())
+ return '';
+
+ return website_import_tools();
+}
function widget_findpeople($arr) {
return findpeople_widget();
diff --git a/view/css/mod_webpages.css b/view/css/mod_webpages.css
index 1c6ec4aea..f72f632dd 100644
--- a/view/css/mod_webpages.css
+++ b/view/css/mod_webpages.css
@@ -34,3 +34,88 @@
.webpage-list-tool {
padding: 7px 10px;
}
+
+.webpage-import-button {
+ background-color: green;
+ color: white;
+}
+
+.webpage-import-button:hover {
+ background-color: darkgreen;
+}
+
+
+/* SQUARED TWO */
+.squaredTwo {
+ width: 28px;
+ height: 28px;
+ background: #fcfff4;
+
+ background: -webkit-linear-gradient(top, #fcfff4 0%, #dfe5d7 40%, #b3bead 100%);
+ background: -moz-linear-gradient(top, #fcfff4 0%, #dfe5d7 40%, #b3bead 100%);
+ background: -o-linear-gradient(top, #fcfff4 0%, #dfe5d7 40%, #b3bead 100%);
+ background: -ms-linear-gradient(top, #fcfff4 0%, #dfe5d7 40%, #b3bead 100%);
+ background: linear-gradient(top, #fcfff4 0%, #dfe5d7 40%, #b3bead 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fcfff4', endColorstr='#b3bead',GradientType=0 );
+ margin: 20px auto;
+
+ -webkit-box-shadow: inset 0px 1px 1px white, 0px 1px 3px rgba(0,0,0,0.5);
+ -moz-box-shadow: inset 0px 1px 1px white, 0px 1px 3px rgba(0,0,0,0.5);
+ box-shadow: inset 0px 1px 1px white, 0px 1px 3px rgba(0,0,0,0.5);
+ position: relative;
+}
+
+.squaredTwo label {
+ cursor: pointer;
+ position: absolute;
+ width: 20px;
+ height: 20px;
+ left: 4px;
+ top: 4px;
+
+ -webkit-box-shadow: inset 0px 1px 1px rgba(0,0,0,0.5), 0px 1px 0px rgba(255,255,255,1);
+ -moz-box-shadow: inset 0px 1px 1px rgba(0,0,0,0.5), 0px 1px 0px rgba(255,255,255,1);
+ box-shadow: inset 0px 1px 1px rgba(0,0,0,0.5), 0px 1px 0px rgba(255,255,255,1);
+
+ background: -webkit-linear-gradient(top, #222 0%, #45484d 100%);
+ background: -moz-linear-gradient(top, #222 0%, #45484d 100%);
+ background: -o-linear-gradient(top, #222 0%, #45484d 100%);
+ background: -ms-linear-gradient(top, #222 0%, #45484d 100%);
+ background: linear-gradient(top, #222 0%, #45484d 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#222', endColorstr='#45484d',GradientType=0 );
+}
+
+.squaredTwo label:after {
+ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
+ filter: alpha(opacity=0);
+ opacity: 0;
+ content: '';
+ position: absolute;
+ width: 9px;
+ height: 5px;
+ background: transparent;
+ top: 4px;
+ left: 4px;
+ border: 3px solid #fcfff4;
+ border-top: none;
+ border-right: none;
+
+ -webkit-transform: rotate(-45deg);
+ -moz-transform: rotate(-45deg);
+ -o-transform: rotate(-45deg);
+ -ms-transform: rotate(-45deg);
+ transform: rotate(-45deg);
+}
+
+.squaredTwo label:hover::after {
+ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=30)";
+ filter: alpha(opacity=30);
+ opacity: 0.3;
+}
+
+.squaredTwo input[type=checkbox]:checked + label:after {
+ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
+ filter: alpha(opacity=100);
+ opacity: 1;
+}
+
diff --git a/view/js/mod_webpages.js b/view/js/mod_webpages.js
new file mode 100644
index 000000000..7cfe297e1
--- /dev/null
+++ b/view/js/mod_webpages.js
@@ -0,0 +1,15 @@
+$(document).ready(function() {
+ $("input[type=\"checkbox\"]").hide();
+});
+
+window.isChecked = true;
+
+function checkedAll(isChecked) {
+ window.isChecked = !window.isChecked ;
+ var c = document.getElementsByTagName('input');
+ for (var i = 0; i < c.length; i++){
+ if (c[i].type == 'checkbox'){
+ c[i].checked = isChecked;
+ }
+ }
+} \ No newline at end of file
diff --git a/view/pdl/mod_webpages.pdl b/view/pdl/mod_webpages.pdl
index cef69f194..b62ec6e7c 100644
--- a/view/pdl/mod_webpages.pdl
+++ b/view/pdl/mod_webpages.pdl
@@ -1,3 +1,4 @@
[region=aside]
[widget=design_tools][/widget]
+[widget=website_import_tools][/widget]
[/region] \ No newline at end of file
diff --git a/view/tpl/webpage_import.tpl b/view/tpl/webpage_import.tpl
new file mode 100644
index 000000000..fdc983932
--- /dev/null
+++ b/view/tpl/webpage_import.tpl
@@ -0,0 +1,126 @@
+<div class="generic-content-wrapper">
+ <form action="" method="post" autocomplete="on" >
+ <input type="hidden" name="action" value="importselected">
+ <div class="section-title-wrapper">
+ <div class="pull-right">
+ <button class="btn btn-md btn-success" type="submit" name="submit" value="{{$importbtn}}">{{$importbtn}}</button>
+ </div>
+ <h2>{{$title}}</h2>
+ <div class="clear"></div>
+ </div>
+ <div id="import-website-content-wrapper" class="section-content-wrapper">
+ <div class="pull-left">
+ <button id="toggle-select-all" class="btn btn-xs btn-primary" onclick="checkedAll(window.isChecked); return false;"><i class="fa fa-check"></i>&nbsp;Toggle Select All</button>
+ </div>
+ <div class="clear"></div>
+ <h4>Scanned Pages</h4>
+ <div>
+ <table class="table-striped table-responsive table-hover" style="width: 100%;">
+ <tr><td>Import?</td><td>Page</td><!--<td>Existing Page</td>--></tr>
+ {{foreach $pages as $page}}
+ <tr>
+ <td>
+ <div class='squaredTwo'>
+ <input type="checkbox" id="page_{{$page.pagelink}}" name="page[]" value="{{$page.pagelink}}">
+ <label for="page_{{$page.pagelink}}"></label>
+ </div>
+ </td>
+ <td>
+ <div class='desc'>
+ Page Link: {{$page.pagelink}}<br>
+ Layout: {{$page.layout}}<br>
+ Title: {{$page.title}}<br>
+ Content File: {{$page.contentfile}}<br>
+ Type: {{$page.type}}<br>
+ </div>
+ </td>
+ <!-- TODO: Retrieve existing element information to avoid accidental overwriting
+ <td>
+ <div class='desc'>
+ Name: {{$page.curpage.pagelink}}<br>
+ Layout: {{$page.curpage.layout}}<br>
+ Title: {{$page.curpage.title}}<br>
+ Last edit: {{$page.curpage.edited}}<br>
+ Type: {{$page.curpage.type}}<br>
+ </div>
+ </td>
+ -->
+ </tr>
+ {{/foreach}}
+ </table>
+ </div>
+
+
+ <h4>Scanned Layouts</h4>
+ <div>
+ <table class="table-striped table-responsive table-hover" style="width: 100%;">
+ <tr><td>Import?</td><td>Layout</td><!--<td>Existing Layout</td>--></tr>
+ {{foreach $layouts as $layout}}
+ <tr>
+ <td>
+ <div class='squaredTwo'>
+ <input type="checkbox" id="layout_{{$layout.name}}" name="layout[]" value="{{$layout.name}}">
+ <label for="layout_{{$layout.name}}"></label>
+ </div>
+ </td>
+ <td>
+ <div class='desc'>
+ Name: {{$layout.name}}<br>
+ Description: {{$layout.description}}<br>
+ Content File: {{$layout.contentfile}}<br>
+ </div>
+ </td>
+ <!-- TODO: Retrieve existing element information to avoid accidental overwriting
+ <td>
+ <div class='desc'>
+ Name: {{$layout.curblock.name}}<br>
+ Title: {{$layout.curblock.description}}<br>
+ Last edit: {{$layout.curblock.edited}}<br>
+ </div>
+ </td>
+ -->
+ </tr>
+ {{/foreach}}
+ </table>
+ </div>
+
+ <h4>Scanned Blocks</h4>
+ <div>
+ <table class="table-striped table-responsive table-hover" style="width: 100%;">
+ <tr><td>Import?</td><td>Block</td><!--<td>Existing Block</td>--></tr>
+ {{foreach $blocks as $block}}
+ <tr>
+ <td>
+ <div class='squaredTwo'>
+ <input type="checkbox" id="block_{{$block.name}}" name="block[]" value="{{$block.name}}">
+ <label for="block_{{$block.name}}"></label>
+ </div>
+ </td>
+ <td>
+ <div class='desc'>
+ Name: {{$block.name}}<br>
+ Title: {{$block.title}}<br>
+ Content File: {{$block.contentfile}}<br>
+ Type: {{$block.type}}<br>
+ </div>
+ </td>
+ <!-- TODO: Retrieve existing element information to avoid accidental overwriting
+ <td>
+ <div class='desc'>
+ Name: {{$block.curblock.name}}<br>
+ Title: {{$block.curblock.title}}<br>
+ Last edit: {{$block.curblock.edited}}<br>
+ Type: {{$block.curblock.type}}<br>
+ </div>
+ </td>
+ -->
+ </tr>
+ {{/foreach}}
+ </table>
+ </div>
+
+ </div>
+ <div class="clear"></div>
+ </form>
+</div>
+
diff --git a/view/tpl/website_import_tools.tpl b/view/tpl/website_import_tools.tpl
new file mode 100644
index 000000000..cb3e6b524
--- /dev/null
+++ b/view/tpl/website_import_tools.tpl
@@ -0,0 +1,37 @@
+<div id="website-import-tools" class="widget">
+ <h3>{{$title}}</h3>
+ <ul class="nav nav-pills nav-stacked">
+ <li>
+ <a href="#" onclick="openClose('import-form');
+ return false;"><i class="fa fa-cloud-upload generic-icons"></i> {{$import_label}}</a>
+ </li>
+ <li>
+ <form id="import-form" enctype="multipart/form-data" method="post" action="" style="display: none;" class="sub-menu">
+
+ <input type="hidden" name="action" value="scan">
+
+ <p style="margin-top: 20px;" class="descriptive-text">{{$file_import_text}}</p>
+ <div class="form-group">
+ <div class="input-group">
+ <input class="widget-input" type="text" name="path" title="{{$hint}}" placeholder="{{$desc}}" />
+ <div class="input-group-btn">
+ <button class="btn btn-default btn-sm" type="submit" name="cloudsubmit" value="{{$select}}"><i class="fa fa-folder-open generic-icons"></i></button>
+ </div>
+ </div>
+ </div>
+
+ <!-- Or upload a zipped file containing the website -->
+ <p class="descriptive-text">{{$file_upload_text}}</p>
+ <div class="form-group">
+
+ <div class="input-group">
+ <input class="widget-input" type="file" name="zip_file" />
+ <div class="input-group-btn">
+ <button class="btn btn-default btn-sm" type="submit" name="w_upload" value="w_upload"><i class="fa fa-file-archive-o generic-icons"></i></button>
+ </div>
+ </div>
+ </div>
+ </form>
+ </li>
+ </ul>
+</div>