-\r
-recommended packages to install in cygwin's setup\r
-if you're going to use cygwin on windows:\r
-\r
-first, keep all the standard packages that cygwin will enable.\r
-second, add these packages to get the full recommended set...\r
-\r
-===========\r
-\r
-using apt-cyg (https://github.com/transcode-open/apt-cyg),\r
-this is the only command needed:\r
-\r
-apt-cyg install bc crypt emacs email expect gcc-g++ git gitk gvim inetutils \\r
- libcrypt-devel libcurl-devel libgnutls-devel make mutt ncftp openssh \\r
- openssl-devel perl python subversion time unzip util-linux vim xinit \\r
- xterm zip\r
-\r
-\r
-===========\r
-\r
-list broken out by category:\r
-\r
-shells:\r
- python\r
- perl\r
-\r
-network:\r
- ncftp\r
- openssh\r
-\r
-editors:\r
- vim\r
- gvim\r
- emacs\r
-\r
-revision control:\r
- git\r
- gitk\r
- subversion\r
-\r
-general tools:\r
- bc\r
- expect\r
- util-linux\r
- inetutils\r
- email\r
- mutt\r
- unzip\r
- zip\r
- crypt\r
- time\r
-\r
-libraries:\r
- libcurl-devel\r
- libgnutls-devel \r
- openssl-devel\r
-\r
-build tools:\r
- gcc4\r
- make\r
-\r
-x window support:\r
- xterm\r
- xinit\r
-\r
+
+The cygwin setup app is available at: http://cygwin.com
+
+The default packages selected by Cygwin are a good starting point for running
+Feisty Meow on windows. If you supplement this set with a few additional
+packages, you can rely on the apt-cyg tool rather than having to run the
+Cygwin setup program (which can be a little fiddly). Here is a step by step
+process to getting going with apt-cyg:
+
+1) Install Cygwin.
+
+Run the Cygwin setup exe from their website. Keep all the default packages
+that the installer suggests, but add the following additional ones (the
+easiest way to add additional packages is to switch to the "Full" view for the
+package list and then search for the terms below):
+
+ + lynx
+ + wget
+ + subversion
+
+2) Install apt-cyg.
+
+The apt-cyg program brings the convenience of the Debian and Ubuntu installer
+application (apt) to Cygwin. This program does require a couple of additional
+setup steps.
+
+This material is drawn from the apt-cyg home page:
+ https://github.com/transcode-open/apt-cyg
+
+Start the cygwin bash prompt (there should be a desktop icon or start menu
+icon for it called something like "cygwin64") and run the following
+commands (omitting the '#' in front):
+
+ # lynx -source rawgit.com/transcode-open/apt-cyg/master/apt-cyg > apt-cyg
+ # install apt-cyg /bin
+
+3) Install the basic set of cygwin apps and libraries.
+
+These tools are not necessarily needed by the Feisty Meow scripts, but they
+are all required to create a sane and useful Unix or Linux environment on
+MS-Windows. You may find you will want additional packages installed in the
+future, and you can use this same approach.
+
+In the cygwin bash prompt, type this command:
+
+# apt-cyg install bc crypt cygutils emacs email expect gcc-g++ git gitk \
+ gvim inetutils less lynx make mutt ncftp openssh perl procps python \
+ sharutils shutdown subversion time unzip util-linux vim wget xinit \
+ xterm zip
+
+You may have cleverly spotted that we repeated some package names that were
+already installed using the Cygwin setup program in step 1. That is fine and
+should just fetch the latest versions.
+
+Later, to update the apt-cyg package datebase to the latest available on the
+internet, you can run this command:
+
+# apt-cyg update
+
+We don't currently know of an analog in apt-cyg of the "apt dist-upgrade"
+command, which fetches all updated versions of the installed packages. We
+think the install command above will upgrade packages when there are new ones
+available. Also, the cygwin setup tool (bless its heart) may automatically
+update packages if you run it again (you don't need to select anything again,
+but just run through the install process to get the latest).
+
+==> hmmm: verify above claims.
+
+
--- /dev/null
+
+Welcome, adventurous script user.
+
+These are the handiest commands available in the Feisty Meow scripts.
+Note that each script is expected to be self-documenting. Try running it
+with a "--help" flag (or with no parameters in some cases) to print the
+built-in docs. At worst, you may have to read the script (that is a
+"documentation fail" on our part; please let us know).
+
+setup and loading commands
+==========================
+
++ read "readme.txt" in the top of the feisty meow codebase, or
++ read it online at: https://feistymeow.org/feisty_meow/readme.txt
+
+generally useful commands
+=========================
+
+ pwd:
+ reports similarly to the good old system "pwd", but translates the $HOME
+ variable into the '~' name. e.g., if you're fred in /home/fred/turnips
+ and you run 'pwd', then it will print: ~/turnips
+
+ i:
+ take inventory. prints out some time and relative dimension in space
+ information and shows the current directory's contents.
+
+ dir or l (lower-case L):
+ show the directory with a "summing" feature that calculates the full size
+ consumed by all files in the listing, with somewhat esthetic output.
+
+ ls:
+ the standard ls command (not the summing directory), but with ls colors
+ enabled.
+
+ del or rm:
+ invoke "safedel" feature to remove the files specified. this archives the
+ deleted files in "$TMP/zz_safedel_keep" and writes a report of the deletion
+ history in "$TMP/zz_safedel_report.txt".
+
+note: currently there is no "empty the trash" function aside from running a
+command such as:
+ # \rm -rf $TMP/zz_safedel*
+the backslash forces bash to run the "rm" tool from the path rather than
+using the feisty meow alias. a trash flushing feature is planned for the
+somewhat near future.
+
+revision control commands
+=========================
+
+all revision control commands bring up the editor in the EDITOR environment
+variable when creating commit messages. you need to actually save and quit
+from that editor when you're done writing your commit message.
+
+ here's a guide to writing good commit messages:
+ + https://robots.thoughtbot.com/5-useful-tips-for-a-better-commit-message
+
+========
+the first suite of commands takes a list of directory names as parameters and
+then operates on those names.
+========
+
+ rgetem:
+ does a simple update (or pull) of the repository paths provided on the
+ command line. this will only get things from the main origin that the
+ repository is hooked up with, so it is super quick compared to the next
+ couple commands.
+
+ rpuffer:
+ update the repositories provided on the command line by "puffing them out",
+ which means that the upstream repositories that feed the local one will be
+ synched up with it. this is quite important to do when a git repository has
+ multiple branches, since unmerged changes upstream can really snarl up your
+ checkin. this is basically a heavyweight version of rgetem.
+
+ rcheckin:
+ checks in the list of repositories passed on the command line. in git
+ parlance, this adds all modified or untracked files, then commits all
+ changes in the repository, and finally pushes up the changes to the remote
+ online repository. before doing the checkin, this will do a full "rpuffer"
+ update on the repository to ensure that there are no unmerged upstream
+ changes that could cause problems later.
+
+========
+the next suite of commands uses the REPOSITORY_LIST environment variable as
+the set of revision controlled folders to operate on. the feisty meow scripts
+automatically add the feisty meow top-level (the apex) to this list to ensure
+that updates are received when available.
+========
+
+ getem:
+ update all repositories in the REPOSITORY_LIST from their upstream remote
+ counterparts. fast.
+
+ puffer:
+ puffs out the REPOSITORY_LIST items to merge upstream changes.
+
+ checkin:
+ checks in all changes in the REPOSITORY_LIST to their remote repositories.
+
+========
+some assorted other revision control commands:
+========
+
+ feisty_branch:
+ shows the current branch that is checked out.
+
+ this command will move your feisty meow codebase to the development branch:
+ pushd $FEISTY_MEOW_APEX; git checkout dev; popd
+
+ and this command will get you back onto the mainline branch:
+ pushd $FEISTY_MEOW_APEX; git checkout master; popd
+
+=============================
+the site avenger script suite
+=============================
+
+the site avenger tools (inherited from the avbash project) are commands for
+managing web sites. these scripts offer a lot of power to the developer, and
+of course that comes with great responsibility...
+
+the site avenger scripts are configured by "app" files stored in the "config"
+directory (in $FEISTY_MEOW_SCRIPTS/scripts/site_avenger/config). the scripts
+seek out a config file named after the application, e.g. they look for
+"winterportlibrary.app" if the application name is "winterportlibrary".
+the basic config file "default.app" is used for any application that is unknown
+in the config directory. any of the variable definitions provided in
+default.app can be overridden to change how the applications, and associated
+web site and domain, are configured. see "mapsdemo.app" for an example of
+overriding the domain name for the mapsdemo application.
+
+ revamp_cakelampvm:
+ establishes permissions and ownership to make the virtual machine and its
+ services behave properly. if something goes wonky, try running this script.
+ this script is also the main vehicle for delivering configuration changes
+ to the cakelampvm. we are trying really hard to never release a version 2
+ of the vm, since we can patch it as needed using the revamp script. let's
+ see how well that works out...
+
+ standup:
+ brings up an application or web site from scratch (potentially) by creating
+ an appropriate domain name, writing a basic apache site config file, pulling
+ the application from a git repository, and "powering up" the application via
+ composer. this is most powerful and effective on php sites, but can also be
+ used for other types of websites. note that this, and all of the scripts
+ here, are heavily biased for site avenger based development at saco designs.
+ to make these scripts truly your own, write configuration files (see above)
+ that define the proper folders and repository for your applications.
+
+ teardown:
+ takes down a site previously brought up by the standup command. this just
+ eliminates the domain and the apache site though; the code is left in place
+ to prevent disaster.
+
+ powerup:
+ similar to standup, but just gets the application source out and powers it
+ up with composer.
+
+(note: automatic database configuration and inflation is in the pipeline for
+the powerup command, but is not ready yet.)
+
+ avcoreup:
+ updates the avcore portion of a site avenger application. this command can
+ accept an application name within which to update, or it can auto-pick the
+ applicatin for you from the available checked out ones in ~/apps (the default
+ storage folder for all site avenger style sites).
+
+ siteup:
+ updates the entire checked out repository for a site avenger application.
+ supports app name on the command line, or auto-picks the app.
+
+ sitepush:
+ checks in the source code and other site assets for a site avenger app.
+ supports passing an app name on the command line, or auto-picks the app.
+
+ satis-refresh:
+ updates satis for a site(?).
+
+note: satis-refresh is the one site avenger command that hasn't been "feisty meowicized" yet.
+
+lower level scripts used by site avenger scripts
+------------------------------------------------
+
+ add_domain and remove_domain: (from system script collection)
+ adds (or removes) a DNS domain to the bind9 configuration. the domain
+ tools, are very sensitive to any edits within the chunks of code they have
+ written. when it comes time to remove the domain again, the script will eat
+ the number of lines it expects to find after the beginning of the domain
+ definition that it added. to avoid any issues, if you need to edit the bind
+ config files, be sure to do it way above or way below the auto-generated
+ domain chunks.
+
+ add_apache_site and remove_apache_site: (from system script collection)
+ creates (or removes) an apache compatible site definition. this will rely
+ on the site's domain previously having been added to the DNS.
+
+note: currently we only implement the http site, but we're planning to add https support via self-signed certificates soon.
+
+
# specifies the version of the code that is being constructed here.
major=2
minor=140
-revision=98
+revision=100
build=420
# specifies the remainder of the version record info.
Configuration and Usage</h1>
<h2 style=" text-align: center;">By Chris Koeritz</h2>
<h3 style=" text-align: center;"> Vintage: cakelampvm v002
- Updated: 2017-11-10</h3>
- <h6> </h6>
- <h2>Basic info for the guest VM</h2>
+ Updated: 2017-11-16</h3>
+ <p>The cakelampvm project provides a Virtualbox VM that acts as an "internet
+ in a bottle". The virtual machine provides DNS services (<a title="dns server"
+ href="http://www.bind9.net/">bind9</a>), a Web server (<a title="patchy"
+ href="https://httpd.apache.org/">Apache2</a>), a full <a title="ubuntu means compassion and humanity"
+ href="https://www.ubuntu.com/">Ubuntu</a> <a title="it's pronounced leenoox"
+ href="https://www.linuxfoundation.org/">Linux</a> desktop environment,
+ the <a title="flux is change" href="http://fluxbox.org/">Fluxbox</a> <a
+ title="a better windows" href="https://www.x.org/">X window manager</a>,
+ and a suite of tools called the <a title="feisty meow® concerns ltd. website"
+ href="https://feistymeow.org/">Feisty Meow® codebase</a> .
+ Together, these services provide you with a very flexible and powerful
+ testbed for web development, especially suited for <a title="it's cake" href="https://cakephp.org/">CakePHP</a>.</p>
+ <p>Commands preceded by an octothorpe ('#') below are intended to be typed
+ into a bash shell running on the cakelampvm virtual machine. The
+ bash shell can be obtained either by logging into the VM through ssh or by
+ logging in directly to the Virtualbox VM console. You may find the
+ ssh session more convenient, because copy & paste features work as
+ expected.</p>
+ <p>Commands preceded by a greater-than symbol ('>') are intended to be
+ run on the Host PC in a Windows command prompt (or in a bash prompt running
+ on the Host PC).</p>
+ <h2> Guest VM Configuration<a id="#config" name="#config"></a></h2>
<ul>
- <li>hostname: cakelampvm.com</li>
- <li>local IP address: 10.28.42.20</li>
- <li>services: DNS (bind9), apache2, fluxbox X windowing system, gnome
- display manager</li>
- <li>main user: developer (password distributed separately)</li>
- <li>mysql root password: (password distributed separately)</li>
+ <li>Hostname: <a title="the vm's website, when configured properly" href="https://cakelampvm.com/">cakelampvm.com</a></li>
+ <li>Local IP Address: 10.28.42.20</li>
+ <li>Services Included: DNS (bind9), apache2, fluxbox X windowing system, <a
+ title="not just in the garden" href="https://www.gnome.org/">gnome
+ display manager</a></li>
+ <li>Main VM User: developer (password distributed separately)</li>
+ <li>Database Access: mysql root account, password: (password distributed
+ separately)</li>
</ul>
- <h2>How to set up virtualbox for your host PC</h2>
+ <h2>Powering up with the Feisty Meow® scripts<a id="#powerup" name="#powerup"></a></h2>
+ The feisty meow scripts are a cohesive bash scripting environment for
+ getting a variety of tasks done. The feisty meow scripts recently
+ incorporated the "avbash" collection from Saco Designs and added those
+ scripts to a new "site_avenger" collection of scripts. The site
+ avenger scripts provide tools for bringing up CakePHP web sites and managing
+ the collection of repositories for those sites. Each website is
+ considered an "application", and the application name itself (e.g.
+ "winterportlibrary") can often provide all the details for "powering up" the
+ site. The feisty meow team has added additional scripts for managing
+ DNS domains and Apache websites that provide the capability to "stand up" an
+ entire website around an application, with an accompanying DNS domain and
+ Apache2 site definition.
+ <p>The site avenger scripts are documented separately within the feisty meow
+ codebase. Consult the <span style="text-decoration: underline;">f</span><a
+ title="quickstart" href="https://feistymeow.org/feisty_meow/readme.txt">eisty
+meow
+ readme</a> file first, as it provides some valuable information on
+ configuring the codebase initially. The site avenger script commands
+ are documented in the <a title="useful commands" href="https://feistymeow.org/feisty_meow/documentation/feisty_meow_command_reference.txt">feisty
+ meow command reference</a> file.</p>
+ <p>(The feisty meow codebase is already configured for the developer account
+ on the cakelampvm virtual machine.)</p>
+ <h2>How to set up virtualbox for your host PC<a id="#virtualbox-setup" name="#virtualbox-setup"></a></h2>
<ol>
<li>Download and install virtualbox:
https://www.virtualbox.org/wiki/Downloads</li>
- <li>Install the extension pack for virtualbox: This provides USB drivers
- and other features. This is installed on virtualbox itself, not on
- the guests.</li>
+ <li>Install the extension pack for Virtualbox: This provides USB drivers
+ and other features. This is installed on Virtualbox itself (on the
+ Host PC), not on the guests.</li>
<ol>
<li>Download the extension pack at
https://www.virtualbox.org/wiki/Downloads</li>
- <li>Stop any running virtualbox vms.</li>
- <li>Close virtualbox control panel.</li>
+ <li>Stop any running Virtualbox VMs.</li>
+ <li>Close the Virtualbox control panel.</li>
<li>Double-click on the downloaded extensions package (in a file
- explorer) and virtualbox should install it.</li>
+ explorer) and Virtualbox should be launched to install it.</li>
</ol>
- <li>Run the virtualbox control panel.</li>
+ <li>Run the Virtualbox control panel.</li>
<li>Download the cakelampvm guest vm package and unzip it. Store the
unzipped version in some appropriate place where you want the virtual
machine to reside on your host's hard drive.</li>
- <li>Add the guest VM to your list of VMs. From the virtualbox menus,
+ <li>Add the guest VM to your list of VMs. From the Virtualbox menus,
choose the "Machine" menu and select "Add". Point the selector
dialog at the cakelampvm folder you created above and open the
cakelampvm.vbox file.</li>
machines. Before starting it, perform the following network
configuration sections.</li>
</ol>
- <h3>Configure the Host-Only network on virtualbox</h3>
+ <h3>Configure the Host-Only network on Virtualbox<a id="#host-only" name="#host-only"></a></h3>
<p>Configuring host-only networking for the VM makes the VM completely local
to your machine. The cakelampvm will not be accessible on the
internet or from the LAN, and can only be accessed by your host PC.</p>
+ <p>Note: If the host-only or NAT network exist ahead of time, Virtualbox may
+ complain about them even if they have the correct configuration.
+ This can be corrected simply by opening the VM settings and selecting the
+ appropriate network names again.</p>
+ <p>To configure the host-only network, follow these steps:</p>
<ol>
<li> Go to virtual box "Preferences" (global preferences, not for a
specific vm).</li>
<li> Click on the "Network" tab.</li>
<li> Choose the "Host-only Networks" tab from within "Network".</li>
- <li> Click the plus icon to add a new host-only network.</li>
+ <li> Click the plus icon to add a new host-only network, or if there is
+ already a Host-only network, then edit it.</li>
<li>Set the "Adapter" parameters:<br>
IPv4 Address: 10.28.42.1<br>
IPv4 Network Mask: 255.255.255.0<br>
for convenience and stability.</li>
</ol>
<p>Additional information on host-only (and other) network adapter types is
- at: https://www.virtualbox.org/manual/ch06.html#network_nat_service<br>
- </p>
- <h3>Configure the Nat Network on virtualbox</h3>
+ at: https://www.virtualbox.org/manual/ch06.html#network_nat_service</p>
+ <h3>Configure the NAT Network on Virtualbox<a id="#nat-network" name="#nat-network"></a></h3>
+ <p>The NAT (Network Address Translation) network allows the VM to get off of
+ the machine and onto the internet safely. It will use this interface
+ for any communication off of the host machine. Since the real IP
+ address of the VM is hidden behind the NAT firewall on Virtualbox, this
+ keeps the VM safe from attackers, and hence your machine stays safe as
+ well.</p>
+ <p>To set up the NAT network, follow these steps:</p>
<ol>
<li> Go to virtual box "Preferences" (global preferences, not for a
specific vm).</li>
These are my settings, with IPv6 left disabled:<br>
<img alt="nat net config" src="images/nat_network_config.png"></li>
</ol>
- <h2>Using the guest VM's DNS services</h2>
+ <h2>Starting up the VM<a id="#start-vm" name="#start-vm"></a></h2>
+ <p>Using the Virtualbox interface, you should now be able to start your
+ virtual machine. Virtualbox will complain if it detects any
+ remaining configuration problems in the VM, but it should start
+ normally. The Linux boot sequence will show many lines of text,
+ before bringing up a black console window with a login dialog.</p>
+ <p>If Windows complains about the Virtualbox application slamming into its
+ firewall, then allow the Virtualbox to get through. Usually, telling
+ Windows that once is enough, but if any odd network access problems result,
+ edit the Windows firewall settings and allow Virtualbox to use both
+ "Public" and "Private" networks.</p>
+ <p>You can log in directly on the VM console with the developer account, but
+ it is generally more useful to connect to the cakelampvm over ssh.
+ If the networking has been established properly, you should be able to do
+ this with:</p>
+ <pre>ssh developer@cakelampvm.com (or equivalent with your ssh client)</pre>
+ <p>And then provide the password to log in.</p>
+ <p>If a feature called "X forwarding" is enabled in your ssh client, then
+ you can start graphical applications on the VM and display them on your
+ local machine. This works right away on most Linux hosts, but can
+ also work on PCs with X window system installed. The section below
+ describes how to set up Cygwin to run X server, which enable X forwarding
+ to your local display.</p>
+ <p>...{insert that info}...</p>
+ <h2>Updating cakelampvm to the Latest Model<a id="#update-vm" name="#update-vm"></a></h2>
+ <p>The cakelampvm is released with the intention to not be released
+ again. Version 001 was not built with that explicit intention, which
+ then required the release of Version 002. We hope to not need a v003
+ release.</p>
+ <p>There is an update feature built into the VM that is quite easy to
+ use. The updates are driven by the feisty meow script repository in
+ conjunction with a local scripted command. To activate the "update
+ process" for your VM, run the following commands (without the initial '#'
+ symbol):</p>
+ <p># rpuffer $FEISTY_MEOW_APEX # updates to the latest version
+ of feisty meow<br>
+ # revamp_cakelampvm # enacts any configuration changes
+ needed, plus fixes web folder and other permissions.</p>
+ <p>These two commands can be run at any time to patch up your VM to the
+ latest.</p>
+ <p>The first command ("rpuffer ...") is also useful on its own for getting
+ the latest version of the feisty meow code. If there are bug fixes
+ you need for the scripts or you want updated cakelampvm documentation,
+ that is the command to use.</p>
+ <h2>Using the guest VM's DNS services<a id="#dns-from-vm" name="#dns-from-vm"></a></h2>
<p>The cakelampvm has been set up to provide a DNS server which will answer
- name requests for all of the sites that the VM hosts.</p>
+ name lookup requests on any of the sites that the cakelampvm is hosting
+ for you. It will also serve as a general DNS server for any other
+ domains that need to be looked up.</p>
<p>To use the cakelampvm DNS, modify your host operating system network
configuration by adding or changing the DNS server to use the guest VM's
- DNS service. This is available at the local address
- 10.28.42.20. The DNS server can be tested with nslookup, dig and
- other tools.</p>
+ DNS service. The cakelampvm is available at the local IP address
+ 10.28.42.20. (The DNS server can be tested with nslookup, dig and
+ other tools.)</p>
<p>Note that the cakelampvm DNS should be listed first, if one intends to
- override any DNS names that actually exist out on the internet.</p>
- <p>If the DNS server is properly set up, then these ping commands should get
- answering responses:</p>
- <pre>ping cakelampvm.com</pre>
- <pre>ping defaultcake.cakelampvm.com</pre>
- <pre>ping mapsdemo.cakelampvm.com</pre>
- <h2>Editing files on the guest VM from the host</h2>
+ override any DNS names that actually exist out on the internet. We
+ have also found it most effective to have only the cakelampvm as your DNS
+ server, because a secondary DNS server can "take over" providing the name
+ lookups, and thus foul up DNS requests that should succeed for your
+ VM-hosted sites.</p>
+ <p>If your Host PC is running Windows, see the DNS configuration section
+ below that is tailored to that operating system.</p>
+ <p>Important Note: It behooves you to remember to switch back to a normal
+ DNS server configuration when you shut off the cakelampvm, or your machine
+ will not know the names of any sites on the internet any more!</p>
+ <p>Once the DNS server is properly set up (by whatever means necessary),
+ these ping commands should get answering responses (from 10.28.42.20) on
+ both the cakelampvm VM and on your host PC. Note: ping on Linux
+ keeps going forever, so hit control-C when you are tired of seeing the pings:</p>
+ <pre># ping cakelampvm.com</pre>
+ <pre># ping mapsdemo.cakelampvm.com</pre>
+ <p>Note that any other answer than 10.28.42.20 for the address is *bzzzt*
+ wrong, and means something needs to be fixed.</p>
+ <p>If these pings succeed (which hopefully they will!), then try accessing
+ the websites of each domain:</p>
+ <pre>(browse to) http://cakelampvm.com</pre>
+ <pre>(browse to) http://mapsdemo.cakelampvm.com</pre>
+ <p>These should show local sites on the VM rather than sites on the
+ internet. If you instead get failures to find the domains, or if the
+ "real internet" site comes up for cakelampvm.com (the page covered with
+ red X marks and complaining), then the DNS is not hooked up properly yet.</p>
+ <h4>Troubleshooting the DNS</h4>
+ <p>If your pings are getting the wrong answers and you're certain the DNS
+ settings on your Host PC are right, then you may need to flush your DNS
+ cache, and that might be sufficient. On Windows, the command for
+ flushing DNS is:</p>
+ <pre>> ipconfig /flushdns</pre>
+ <p>and on Linux the flush DNS command can be many different things, but try
+ these two most common options:</p>
+ <pre># sudo service dns-clean restart # restarts the client side DNS cache.</pre>
+ <p>or</p>
+ <pre># sudo service nscd restart # restarts the nscd caching server.</pre>
+ After, this try the pings again. If they still fail, please go back
+ over your DNS configuration very carefully. The cakelampvm's DNS
+ feature *does* actually work, but operating systems sometimes do their best
+ to deny this.<br>
+ <h4>Troubleshooting the Apache Sites</h4>
+ <p>If your DNS pings and lookups are functioning properly, but you're just
+ not getting the right websites, then try clearing your browser's cache and
+ shutting the browser application down. Then, start the browser up
+ and try the address again. Often this cache dumping is enough to fix
+ the browser so that you start seeing the local website versions on
+ cakelampvm.com.</p>
+ <h3>Setting up DNS on Windows<a id="#windoze-dns" name="#windoze-dns"></a></h3>
+ <p>The ipconfig tool will provide helpful information about your current
+ networking and DNS configuration:</p>
+ <pre>ipconfig --all</pre>
+ <p>The DNS configuration on Windows is somewhat byzantine. The pipe
+ characters ('|') below are used to separate the menus or tabs or dialogs
+ to traverse. Follow this path to get to the DNS config:</p>
+ <pre>Control Panel | Network & Sharing | click WiFI or Ethernet link near top right | click Adapter Settings on left | click on specific network device to modify | select Properties</pre>
+ <p><br>
+ </p>
+ <p>{fill in rest}<br>
+ </p>
+ <p><br>
+ </p>
+ <h2>Editing files on the guest VM from the host<a id="#editing-files-on-vm"
+ name="#editing-files-on-vm"></a></h2>
<p>On the host computer, look for the guest vm as a networked computer
called cakelampvm. This should provide some network shares using
Microsoft SMB protocol, and they can be attached to using the "developer"
for easier access.</p>
<p>Currently, the root of all web servers is exposed as "www". Editing
the files in those folders requires ownership by the developer user.
- Currently the defaultcake server is owned by developer.</p>
- <p>One should be able to create a new directory in the www folder owned by
- the developer user over the network also, which can be used for creating
- new projects. However, there is a config issue in the current vm
- (v001) about this; to fix, run this command on the guest vm as the
- developer user:</p>
- <pre>sudo chmod g+w /var/www</pre>
- <p>Afterwards, the www folder should allow the developer user to create new
- folders at will.</p>
- <h2>Accessing files on the host PC from the guest VM</h2>
+ The existing mapsdemo site is owned by a different user ("fred") rather
+ than developer, mostly as a test case. The "fred", "developer", and
+ "www-data" accounts on the VM have all been put into each others Unix
+ "groups" so that they can access each other's files, and thus you may not
+ notice any issues editing fred's files.</p>
+ <p>One should be able to create a new directory over the network also.
+ Try creating a junk folder in the "www" folder, and then deleting it
+ again. That should succeed, and this approach can be used to create
+ folders (from the Host PC) that are owned by the developer user (on the
+ VM). You should be able to create folders or copy files within the
+ developer's home folder also ("/home/developer").</p>
+ <p>If you run into any permission problems that prevent file access, either
+ remotely or within the VM itself, then try running this command to fix
+ them:</p>
+ <pre># revamp_cakelampvm</pre>
+ <p>Afterwards, the www folder and others should allow the developer user to
+ create new folders at will.</p>
+ <p>The revamp command above is also used to deliver new configuration to the
+ VM from the feisty meow script environment; running it after any update of
+ the feisty meow codebase is a good idea.</p>
+ <h2>Accessing files on the host PC from the guest VM<a id="#samba-shares" name="#samba-shares"></a></h2>
<p>If you want to share a folder from the host to the guest, perhaps for
driver updates or other conveniences, then make the share with these
steps:</p>
<ol>
<li>Create a folder on the host that is to be shared.</li>
- <li>Right-click on the vm in virtualbox manager and choose "Settings".</li>
+ <li>Right-click on the vm in Virtualbox manager and choose "Settings".</li>
<li>In the "Shared Folders" tab of the settings, go to "Machine Folders".</li>
<li>Click the folder plus icon to create a new share.</li>
<li>Fill in the "Folder Path" on the host PC to the folder that will be
shared, and give it a name for the guest. We assume the folder
name will be "myshare".</li>
<li>On the guest vm, run the following commands to mount the share:<br>
- <pre>mkdir ~/shared # for the guest's version of the shared folder</pre>
- <pre>sudo mount -t vboxsf myshare ~/shared # mount the vm's share name onto the folder on the vm.</pre>
+ <pre># mkdir ~/shared # for the guest's version of the shared folder<br># sudo mount -t vboxsf myshare ~/shared # mount the vm's share name onto the folder on the vm.</pre>
</li>
</ol>
<h2>Adding a new website and domain on the guest VM</h2>
- <p>To add a new website, you will need to pick one of the DNS options below
- (A or B) depending on how you want to name the site. After the DNS
- is updated, then follow the section after for creating a new apache conf
- file.</p>
- <p>Assuming one has created a new folder in "www" called "greatsite", then
- the new web site can be brought online on the vm with one of the following
- options.</p>
- <h3>DNS Option A: Using a sub-domain in the cakelampvm.com domain</h3>
- Connect to the cakelampvm via ssh as the developer user, e.g.: ssh
- developer@cakelampvm.com
- <p>Execute the following command to edit the DNS file for the cakelampvm
- domain:</p>
- <pre>sudo vi /etc/bind/cakelampvm.com.conf</pre>
+ <p>Note: these instructions, even the quick approaches below, pale in
+ comparison to the ease of use of the "standup" command in feisty meow's
+ site avenger scripts. The standup command is detailed in the <a
+ title="useful commands" href="https://feistymeow.org/feisty_meow/documentation/feisty_meow_command_reference.txt">feisty
+ meow command reference</a> document. These instructions are for
+ situations when the domain or site is idiosyncratic in some way that
+ standup doesn't support.</p>
+ <p>To add a new website, you will first need to pick one of the DNS options
+ below (A or B) depending on how you want to name the site. If the
+ DNS name of the site is contained within another existing domain (e.g.,
+ "A.B.C" has subdomain A contained in domain B.C), use Option A. If
+ the DNS name is a so-called "Second Level Domain" (SLD), then it stands on
+ its own (e.g., "B.C" is an SLD).</p>
+ <p>Once the DNS option has been picked and implemented, continue to the next
+ section of "Creating a New Apache Site".</p>
+ <p>For either Option A or Option B, first connect to the cakelampvm via ssh
+ as the developer user, e.g.: ssh developer@cakelampvm.com </p>
+ <h3>DNS Option A: Adding a sub-domain in an existing domain</h3>
+ <p>Let us say a customer needs an application called "excalibur". It
+ will be a new subdomain within an existing domain, such as the
+ "cakelampvm.com" domain, meaning we want the VM to start answering
+ requests for "excalibur.cakelampvm.com".</p>
+ Note that this option requires the containing domain "cakelampvm.com" to
+ already exist before adding the subdomain; see DNS Option B below for
+ details on how to add a containing domain for the first time.
+ <h4>Quick approach: Use the feisty meow "add_domain" command.</h4>
+ <p>Run this command in a bash shell on the VM:</p>
+ <pre># add_domain excalibur.cakelampvm.com</pre>
+ <p>Done.</p>
+ <h4>Manual approach: Edit the bind9 configuration.</h4>
+ <p>Note: the manual approach is not compatible with later use of feisty
+ meow's "remove_domain".</p>
+ Execute the following command to edit the DNS file for the cakelampvm
+ domain:
+ <pre># sudo vi /etc/bind/cakelampvm.com.conf</pre>
<p>Add a stanza for the new site at the end of this file:</p>
- <pre>greatsite.cakelampvm.com IN A 10.28.42.20<br> IN HINFO "linux server" "ubuntu"</pre>
- <p>Restart the DNS server: sudo service bind9 restart</p>
- <p>Afterwards, pinging greatsite.cakelampvm.com should work from either the
- guest or the host.</p>
+ <pre>excalibur.cakelampvm.com. IN A 10.28.42.20<br> IN HINFO "linux server" "ubuntu"</pre>
+ <p>Restart the DNS server:</p>
+ <pre># sudo service bind9 restart</pre>
+ <p>Afterwards, pinging excalibur.cakelampvm.com should work from both the
+ guest VM and the host PC.</p>
<h3>DNS Option B: Using an entirely new domain for the site</h3>
- <p>Similar procedure to above, but we will create a new file for the new
- domain and add it to the bind directory. For this example, we will
- create a file called /etc/bind/greatsite.tv.conf for our new domain
- greatsite.tv with these contents:</p>
- <pre>$TTL 1W<br>@ IN SOA @ fred.cakelampvm.com. (<br> 2017100801 ; serial<br> 2H ; refresh<br> 8M ; retry<br> 14D ; expiry<br> 6H ) ; minimum<br><br> IN NS ns.cakelampvm.com.<br> IN MX 10 mail.cakelampvm.com.<br><br># main domain for machine.<br>greatsite.tv. IN A 10.28.42.20<br> IN HINFO "linux server" "ubuntu"</pre>
- The gnarly prefix stuff above the "greatsite.tv." listing establishes
+ <p>This is a similar procedure to Option A, but we will create a totally new
+ config file for the new domain and add it to the bind directory. For
+ this example, we need to add the site "excalibur.tv" into the DNS.</p>
+ <h4>Quick approach: Use the feisty meow "add_domain" command.</h4>
+ Run this command in a bash shell on the VM:
+ <pre># add_domain excalibur.tv</pre>
+ <p>Done.</p>
+ <h4>Manual approach: Edit a new DNS config file</h4>
+ <p>Note: the manual approach is not compatible with later use of feisty
+ meow's "remove_domain".</p>
+ Create a file called /etc/bind/excalibur.tv.conf for our new domain
+ excalibur.tv with these contents:
+ <pre>$TTL 1W<br>@ IN SOA @ fred.cakelampvm.com. (<br> 2017100801 ; serial<br> 2H ; refresh<br> 8M ; retry<br> 14D ; expiry<br> 6H ) ; minimum<br><br> IN NS ns.cakelampvm.com.<br> IN MX 10 mail.cakelampvm.com.<br><br># new SLD for our excalibur site.<br>excalibur.tv. IN A 10.28.42.20<br> IN HINFO "linux server" "ubuntu"</pre>
+ The gnarly prefix stuff above the "excalibur.tv." listing establishes
configuration info for the new domain. This file relies on the
existing cakelampvm.com infrastructure in DNS, such as the "ns" host, which
- is the domain's name server.
- <p>Now that the config file is in place, edit "named.conf.local" to add the
- new file by adding this bit of configuration at the end:</p>
- <pre>zone "greatsite.tv" in {<br> file "/etc/bind/greatsite.tv.conf";<br> type master;<br> allow-query { any; };<br>};</pre>
- <p>Restart the DNS server: sudo service bind9 restart</p>
- <p>Afterwards, pinging greatsite.tv should work from either the guest or the
+ is the domain's name server. However, the new domain does <span style="text-decoration: underline;">not</span>
+ live inside the cakelampvm.com domain.<br>
+ <p>Now that the config file is in place, edit "/etc/bind/named.conf.local"
+ to add the new file by adding this bit of configuration at the end:</p>
+ <pre>zone "excalibur.tv" in {<br> file "/etc/bind/excalibur.tv.conf";<br> type master;<br> allow-query { any; };<br>};</pre>
+ <p>Restart the DNS server:</p>
+ <pre># sudo service bind9 restart</pre>
+ <p>Afterwards, pinging excalibur.tv should work from both the guest and the
host.</p>
- <h3>Create a new apache configuration file and load it</h3>
+ <h3>Creating a New Apache site</h3>
+ <p>First, connect to the cakelampvm via ssh as the developer user, e.g.: ssh
+ developer@cakelampvm.com </p>
+ <h4>Quick approach: Use the feisty meow "add_apache_site" command.</h4>
+ <p>Run this command in a bash shell on the VM:</p>
+ <pre># add_apache_site excalibur excalibur.tv</pre>
+ <p>(The first parameter is the application name, the second is the domain
+ name.)</p>
+ <p>Done.</p>
+ <h4>Manual approach: Edit an Apache config file</h4>
+ <p>Note: the manual approach is not compatible with later use of feisty
+ meow's "remove_apache_site".</p>
+ <p>For Apache, the choice of DNS Option A or B, subdomain or SLD does not
+ matter. The site configuration file just has to accurately specify
+ the domain in question.</p>
<p>Start with the following template file for the new website, and modify it
for the appropriate host name:</p>
- <pre><VirtualHost *:80><br> ServerName greatsite.cakelampvm.com<br> ServerAlias greatsite.cakelampvm.com *.greatsite.cakelampvm.com<br> DocumentRoot /var/www/greatsite<br> ErrorLog ${APACHE_LOG_DIR}/greatsite.cakelampvm.com-error.log<br> CustomLog ${APACHE_LOG_DIR}/greatsite.cakelampvm.com-access.log combined<br> Alias /statistics "/var/www/webwork.repository/webwork/maps_demo/webroot/statistics"<br> Include /etc/apache2/conf-library/basic-options.conf<br> Include /etc/apache2/conf-library/rewrite-enabling.conf<br></VirtualHost></pre>
- <p>The above example is pre-modified for DNS Option A above, the
- greatsite.cakelampvm.com name. Switching all of those to
- "greatsite.tv" instead would support DNS option B.</p>
- <p>Copy that file into /etc/apache/available-sites under an appropriate
- name, which here we will call "greatsite.conf".</p>
- <p>Tell apache to use the new file:</p>
- <pre>a2ensite greatsite.conf</pre>
+ <pre><VirtualHost *:80><br> ServerName excalibur.tv
+ DocumentRoot /home/apps/excalibur<br> ErrorLog ${APACHE_LOG_DIR}/excalibur.tv-error.log<br> CustomLog ${APACHE_LOG_DIR}/excalibur.tv-access.log combined<br> Include /etc/apache2/conf-library/basic-options.conf<br> Include /etc/apache2/conf-library/rewrite-enabling.conf<br></VirtualHost></pre>
+ <p>The above example is appropriate for our excalibur app in the
+ excalibur.tv domain (using DNS Option B). Modifying the excalibur.tv
+ references in it is sufficient to retarget it for any domain you want.</p>
+ <p>Copy the new site config file into "/etc/apache2/sites-available" with an
+ appropriate file name that includes the site's domain name. We will
+ call our config file "excalibur.tv.conf". If you developed the file
+ in your home folder, this would be the command to move it up to Apache:</p>
+ <pre># sudo cp ~/excalibur.tv.conf /etc/apache2/sites-available</pre>
+ <p>Then tell apache to use the new file:</p>
+ <pre># sudo a2ensite excalibur.tv # the '.conf' portion of the filename is unnecessary for this command.
+</pre>
<p>Finally, restart apache to get it to begin serving the site:</p>
- <pre>sudo service apache2 restart</pre>
+ <pre># sudo service apache2 restart</pre>
<h3>Test the new web site</h3>
<p>Given the configuration above, your host PC should now be able to access
- the new website.</p>
- <p>To test this, first try pinging the hostname, e.g.: ping
- greatsite.cakelampvm.com or ping greatsite.tv</p>
- <p>Then, if there are responses to the ping, it means the DNS is
- working. If there are no responses, check the instructions in the
- above DNS option section.</p>
- <p>Once the DNS is working, one can try browsing to the site at:
- http://greatsite.cakelampvm.com or http://greatsite.tv (depending on the
- DNS option chosen).</p>
- <p>If the site is not showing up properly, try examining the apache logs for
- error messages that can be corrected. The log files are stored in
- /var/log/apache2 and are generally named after the website.</p>
+ the new website on the domain "excalibur.tv".</p>
+ <p>To test this, first try pinging the new DNS name:</p>
+ <pre># ping excalibur.tv</pre>
+ <p>If there are responses to the ping *and* the answer is 10.28.42.20, then
+ it means the DNS is working. If there are no responses or it's some
+ other IP address talking back, check the instructions in the above DNS
+ sections.</p>
+ <p>Once the DNS is working, try browsing to the site at "http://excalibur.tv".
+ That should at least bring up the configured site storage path, even if
+ nothing is being served from that folder yet.</p>
+ <p>If the new site is not showing up properly, try examining the apache logs
+ for error messages that can be corrected. The log files are stored
+ in "/var/log/apache2" and are named after the website (if configured as
+ shown above).</p>
+ <h2>Handy Techniques</h2>
+ <h3>Assorted Guides and Cheat-Sheets</h3>
+ <p>Cheat sheet for Vim: <a title="vim commands" href="https://vim.rtorr.com/">https://vim.rtorr.com/</a></p>
+ <p>Git branching model that seems to work well: <a title="release and patch process"
+ href="http://nvie.com/posts/a-successful-git-branching-model/">http://nvie.com/posts/a-successful-git-branching-model/</a></p>
+ <h3>Get the network address on the guest vm</h3>
+ <p>Run this command: ifconfig</p>
+ <p>In the results, look for "inet addr". There may be more than one,
+ if there are multiple network interfaces.</p>
+ <h3>How to cleanly reboot or shut down the guest VM</h3>
+ <p>When you've got the DNS and everything integrated, these commands will
+ manage the vm's state:</p>
+ <p>First, log into the guest VM: ssh developer@cakelampvm.com</p>
+ <p>Then, reboot the guest VM: sudo reboot</p>
+ <p>Or, halt the guest VM: sudo shutdown -h now</p>
+ <p>Using these commands is better than just cycling the power from the
+ Virtualbox control panel.</p>
+ <p><br>
+ </p>
+ <p><br>
+ </p>
+ <p><br>
+ </p>
+ <h1>Gritty Details</h1>
+ <p>This is the lowest level of plumbing for your VM. Hopefully you
+ will not need to engage with this section. The most useful area here
+ is the one below about the "Virtualbox guest additions", which you will
+ probably need at some future point. Oracle releases updates to the
+ guest additions fairly regularly.</p>
<h2>Configuring the guest VM</h2>
<p>The guest VM should already be set up appropriately. These steps
are provided for reference and updates.</p>
- <h3>Set up virtualbox guest additions for the VM</h3>
- <p>** note for v001 of cakelampvm: the below steps are still needed on the
- shipped image.</p>
- <p>This procedure is needed if the guest provides an older or incompatible
- version of the guest additions (which have already been installed on the
- guest vm). It may also be necessary when a new version of the guest
- additions becomes available.</p>
+ <h3>Set up Virtualbox guest additions for the VM</h3>
+ This procedure is needed if the guest provides an older or incompatible
+ version of the guest additions (which have already been installed on the
+ guest vm). It may also be necessary when a new version of the guest
+ additions becomes available.
<ol>
<li>To install the guest additions, open the guest VM and have its window
in focus.</li>
Image". This will mount the CD's ISO image on the VM.</li>
<li>On the guest VM, it may be necessary to mount the CD image that's now
available:<br>
- sudo mount /dev/sr0 /media/cdrom</li>
+ <pre># sudo mount /dev/sr0 /media/cdrom</pre>
+ <p>Linux will mention that the device is mounted "read-only".</p>
+ </li>
<li>Since the VM currently has no windowing system installed, one must
start the Guest Additions install manually:<br>
- cd /media/cdrom<br>
- sudo sh VBoxLinuxAdditions.run</li>
- <li>This should install the guest additions.</li>
+ <pre># cd /media/cdrom<br># sudo sh VBoxLinuxAdditions.run</pre>
+ </li>
+ <li>The latest Virtualbox guest additions should now be installed.</li>
</ol>
<h3>Set up network adapters on guest VM</h3>
<p>The network interfaces should already be configured on the guest within
- the virtualbox configuration. This is available by clicking on the
- VM in the virtualbox manager and selecting "Settings". These are the
+ the Virtualbox configuration. This is available by clicking on the
+ VM in the Virtualbox manager and selecting "Settings". These are the
configuration settings used:</p>
Adapter 1:<br>
Attached to: Host-only Adapter<br>
<pre>auto enp0s8</pre>
<pre>iface enp0s8 inet dhcp</pre>
<p> </p>
- <h2>Handy Techniques</h2>
- <h3>Get the network address on the guest vm</h3>
- <p>Run this command: ifconfig</p>
- <p>In the results, look for "inet addr". There may be more than one,
- if there are multiple network interfaces.</p>
- <h3>How to cleanly reboot or shut down the guest VM</h3>
- <p>When you've got the DNS and everything integrated, these commands will
- manage the vm's state:</p>
- <p>First, log into the guest VM: ssh developer@cakelampvm.com</p>
- <p>Then, reboot the guest VM: sudo reboot</p>
- <p>Or, halt the guest VM: sudo shutdown -h now</p>
- <p>Using these commands is better than just cycling the power from the
- virtualbox control panel.</p>
<h2>Notes on building the Cake Lamp VM</h2>
<p>This is all work that should already have been done. It is
mentioned here just as breadcrumbs for a future vm builder.</p>
<ul>
- <li>Downloaded and installed virtualbox for host computer (where the vm
+ <li>Downloaded and installed Virtualbox for host computer (where the vm
image will be built).</li>
<li>Downloaded ubuntu server 16.04 iso.
(https://www.ubuntu.com/download/server)</li>
- <li>Created a new vm in virtualbox, telling it to start from the ubuntu
+ <li>Created a new vm in Virtualbox, telling it to start from the ubuntu
server iso.</li>
<li>Installed LAMP stack on guest VM. Some help here:
http://howtoubuntu.org/how-to-install-lamp-on-ubuntu</li>
https://askubuntu.com/questions/628938/how-to-install-cakephp-in-ubuntu-14-04</li>
<li>Configured the two network adapters as needed (one for host-only
network and one for nat network). Here's some info about
- virtualbox networking with two adapters similar to our setup:
+ Virtualbox networking with two adapters similar to our setup:
https://askubuntu.com/questions/293816/in-virtualbox-how-do-i-set-up-host-only-virtual-machines-that-can-access-the-in<br>
</li>
<li>Installed and configured Samba service for the guest VM. The
</ul>
<p><br>
</p>
+ <ul>
+ </ul>
+ <h6> </h6>
+ <p> </p>
</body>
</html>
--- /dev/null
+<?php
+namespace Geo\View\Helper;
+
+use Cake\Core\Configure;
+use Cake\Core\Exception\Exception;
+use Cake\Routing\Router;
+use Cake\Utility\Hash;
+use Cake\View\Helper;
+use Cake\View\View;
+use Geo\View\Helper\JsBaseEngineTrait;
+
+/**
+ * This is a CakePHP helper that helps users to integrate GoogleMap v3
+ * into their application by only writing PHP code. This helper depends on jQuery.
+ *
+ * Capable of resetting itself (full or partly) for multiple maps on a single view.
+ *
+ * CodeAPI: http://code.google.com/intl/de-DE/apis/maps/documentation/javascript/basics.html
+ * Icons/Images: http://gmapicons.googlepages.com/home
+ *
+ * @author Rajib Ahmed
+ * @author Mark Scherer
+ * @link http://www.dereuromark.de/2010/12/21/googlemapsv3-cakephp-helper/
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ * @property \Cake\View\Helper\HtmlHelper $Html
+ */
+class GoogleMapHelper extends Helper {
+
+ use JsBaseEngineTrait;
+
+ const API = 'maps.google.com/maps/api/js';
+
+ const STATIC_API = 'maps.google.com/maps/api/staticmap';
+
+ /**
+ * @var int
+ */
+ public static $mapCount = 0;
+
+ /**
+ * @var int
+ */
+ public static $markerCount = 0;
+
+ /**
+ * @var int
+ */
+ public static $iconCount = 0;
+
+ /**
+ * @var int
+ */
+ public static $infoWindowCount = 0;
+
+ /**
+ * @var int
+ */
+ public static $infoContentCount = 0;
+
+ const TYPE_ROADMAP = 'R';
+
+ const TYPE_HYBRID = 'H';
+
+ const TYPE_SATELLITE = 'S';
+
+ const TYPE_TERRAIN = 'T';
+
+ /**
+ * @var array
+ */
+ public $types = [
+ self::TYPE_ROADMAP => 'ROADMAP',
+ self::TYPE_HYBRID => 'HYBRID',
+ self::TYPE_SATELLITE => 'SATELLITE',
+ self::TYPE_TERRAIN => 'TERRAIN'
+ ];
+
+ const TRAVEL_MODE_DRIVING = 'D';
+
+ const TRAVEL_MODE_BICYCLING = 'B';
+
+ const TRAVEL_MODE_TRANSIT = 'T';
+
+ const TRAVEL_MODE_WALKING = 'W';
+
+ /**
+ * @var array
+ */
+ public $travelModes = [
+ self::TRAVEL_MODE_DRIVING => 'DRIVING',
+ self::TRAVEL_MODE_BICYCLING => 'BICYCLING',
+ self::TRAVEL_MODE_TRANSIT => 'TRANSIT',
+ self::TRAVEL_MODE_WALKING => 'WALKING'
+ ];
+
+ /**
+ * Needed helpers
+ *
+ * @var array
+ */
+ public $helpers = ['Html'];
+
+ /**
+ * Google maker config instance variable
+ *
+ * @var array
+ */
+ public $markers = [];
+
+ /**
+ * @var array
+ */
+ public $infoWindows = [];
+
+ /**
+ * @var array
+ */
+ public $infoContents = [];
+
+ /**
+ * @var array
+ */
+ public $icons = [];
+
+ /**
+ * @var array
+ */
+ public $matching = [];
+
+ /**
+ * @var string
+ */
+ public $map = '';
+
+ /**
+ * @var array
+ */
+ protected $_mapIds = []; // Remember already used ones (valid xhtml contains ids not more than once)
+
+ /**
+ * Default settings
+ *
+ * @var array
+ */
+ protected $_defaultConfig = [
+ 'zoom' => null, // global, both map and staticMap
+ 'lat' => null, // global, both map and staticMap
+ 'lng' => null, // global, both map and staticMap
+ 'api' => '3',
+ 'type' => self::TYPE_ROADMAP,
+ 'map' => [
+ 'api' => null,
+ 'zoom' => null,
+ 'lat' => null,
+ 'lng' => null,
+ 'type' => null,
+ 'streetViewControl' => false,
+ 'navigationControl' => true,
+ 'mapTypeControl' => true,
+ 'scaleControl' => true,
+ 'scrollwheel' => false,
+ 'keyboardShortcuts' => true,
+ 'typeOptions' => [],
+ 'navOptions' => [],
+ 'scaleOptions' => [],
+ 'defaultLat' => 51, // only last fallback, use Configure::write('Google.lat', ...); to define own one
+ 'defaultLng' => 11, // only last fallback, use Configure::write('Google.lng', ...); to define own one
+ 'defaultZoom' => 5,
+ ],
+ 'staticMap' => [
+ 'size' => '300x300',
+ 'format' => 'png',
+ 'mobile' => false,
+ //'shadow' => true // for icons
+ ],
+ 'geolocate' => false,
+ 'language' => null,
+ 'region' => null,
+ 'showMarker' => true,
+ //'showInfoWindow' => true,
+ 'infoWindow' => [
+ 'content' => '',
+ 'useMultiple' => false, // Using single infowindow object for all
+ 'maxWidth' => 300,
+ 'lat' => null,
+ 'lng' => null,
+ 'pixelOffset' => 0,
+ 'zIndex' => 200,
+ 'disableAutoPan' => false
+ ],
+ 'marker' => [
+ //'autoCenter' => true,
+ 'animation' => null, // BOUNCE or DROP https://developers.google.com/maps/documentation/javascript/3.exp/reference#Animation
+ 'icon' => null, // => default (red marker) //http://google-maps-icons.googlecode.com/files/home.png
+ 'title' => null,
+ 'shadow' => null,
+ 'shape' => null,
+ 'zIndex' => null,
+ 'draggable' => false,
+ 'cursor' => null,
+ 'directions' => false, // add form with directions
+ 'open' => false, // New in 1.5
+ ],
+ 'div' => [
+ 'id' => 'map_canvas',
+ 'width' => '100%',
+ 'height' => '400px',
+ 'class' => 'map',
+ 'escape' => true
+ ],
+ 'event' => [
+ ],
+ 'animation' => [
+ //TODO
+ ],
+ 'polyline' => [
+ 'color' => '#FF0000',
+ 'opacity' => 1.0,
+ 'weight' => 2,
+ ],
+ 'directions' => [
+ 'travelMode' => self::TRAVEL_MODE_DRIVING,
+ 'unitSystem' => 'METRIC',
+ 'directionsDiv' => null,
+ ],
+ 'callbacks' => [
+ 'geolocate' => null //TODO
+ ],
+ 'plugins' => [
+ 'keydragzoom' => false, // http://google-maps-utility-library-v3.googlecode.com/svn/tags/keydragzoom/
+ 'markermanager' => false, // http://google-maps-utility-library-v3.googlecode.com/svn/tags/markermanager/
+ 'markercluster' => false, // http://google-maps-utility-library-v3.googlecode.com/svn/tags/markerclusterer/
+ ],
+ 'autoCenter' => false, // try to fit all markers in (careful, all zooms values are omitted)
+ 'autoScript' => false, // let the helper include the necessary js script links
+ 'block' => true, // for scripts
+ 'localImages' => false,
+ 'https' => null, // auto detect
+ 'key' => null,
+ ];
+
+ /**
+ * @var array
+ */
+ protected $_runtimeConfig = [];
+
+ /**
+ * @var bool
+ */
+ protected $_apiIncluded = false;
+
+ /**
+ * @var bool
+ */
+ protected $_gearsIncluded = false;
+
+ /**
+ * @var bool
+ */
+ protected $_located = false;
+
+ /**
+ * @param \Cake\View\View|null $View
+ * @param array $config
+ */
+ public function __construct(View $View, array $config = []) {
+ parent::__construct($View, $config);
+ }
+
+ /**
+ * @param array $config
+ * @return void
+ */
+ public function initialize(array $config) {
+ parent::initialize($config);
+
+ $defaultConfig = Hash::merge($this->_defaultConfig, (array)Configure::read('GoogleMap'));
+ $config = Hash::merge($defaultConfig, $config);
+
+ if (isset($config['api']) && !isset($config['map']['api'])) {
+ $config['map']['api'] = $config['api'];
+ }
+ if (isset($config['zoom']) && !isset($config['map']['zoom'])) {
+ $config['map']['zoom'] = $config['zoom'];
+ }
+ if (isset($config['lat']) && !isset($config['map']['lat'])) {
+ $config['map']['lat'] = $config['lat'];
+ }
+ if (isset($config['lng']) && !isset($config['map']['lng'])) {
+ $config['map']['lng'] = $config['lng'];
+ }
+ if (isset($config['type']) && !isset($config['map']['type'])) {
+ $config['map']['type'] = $config['type'];
+ }
+ if (isset($config['size'])) {
+ $config['div']['width'] = $config['size']['width'];
+ $config['div']['height'] = $config['size']['height'];
+ }
+ if (isset($config['staticSize'])) {
+ $config['staticMap']['size'] = $config['staticSize'];
+ }
+ // the following are convenience defaults - if not available the map lat/lng/zoom defaults will be used
+ if (isset($config['staticZoom'])) {
+ $config['staticMap']['zoom'] = $config['staticZoom'];
+ }
+ if (isset($config['staticLat'])) {
+ $config['staticMap']['lat'] = $config['staticLat'];
+ }
+ if (isset($config['staticLng'])) {
+ $config['staticMap']['lng'] = $config['staticLng'];
+ }
+ if (isset($config['localImages'])) {
+ if ($config['localImages'] === true) {
+ $config['localImages'] = Router::url('/img/google_map/', true);
+ }
+ }
+
+ // BC
+ if (!empty($config['inline'])) {
+ trigger_error('Deprecated inline option, use block instead.', E_USER_DEPRECATED);
+ $config['block'] = null;
+ }
+
+ $this->_config = $config;
+ $this->_runtimeConfig = $this->_config;
+ }
+
+ /**
+ * JS maps.google API url.
+ *
+ * Options read via configs
+ * - key
+ * - api
+ * - language (iso2: en, de, ja, ...)
+ *
+ * You can adds more after the URL like "&key=value&..." via
+ * - query string array: additional query strings (e.g. callback for deferred execution - not supported yet by this helper)
+ *
+ * @param array $query
+ * @return string Full URL
+ */
+ public function apiUrl(array $query = []) {
+ $url = $this->_protocol() . static::API;
+
+ if ($this->_runtimeConfig['map']['api']) {
+ $query['v'] = $this->_runtimeConfig['map']['api'];
+ }
+ if ($this->_runtimeConfig['key']) {
+ $query['key'] = $this->_runtimeConfig['key'];
+ }
+
+ if ($this->_runtimeConfig['language']) {
+ $query['language'] = $this->_runtimeConfig['language'];
+ }
+
+ if ($query) {
+ $query = http_build_query($query);
+
+ $url .= '?' . $query;
+ }
+
+ return $url;
+ }
+
+ /**
+ * @deprecated
+ * @return string
+ */
+ public function gearsUrl() {
+ $this->_gearsIncluded = true;
+ $url = $this->_protocol() . 'code.google.com/apis/gears/gears_init.js';
+ return $url;
+ }
+
+ /**
+ * @return string currentMapObject
+ */
+ public function name() {
+ return 'map' . static::$mapCount;
+ }
+
+ /**
+ * @return string currentContainerId
+ */
+ public function id() {
+ return $this->_runtimeConfig['div']['id'];
+ }
+
+ /**
+ * Make it possible to include multiple maps per page
+ * resets markers, infoWindows etc
+ *
+ * @param bool $full true=optionsAsWell
+ * @return void
+ */
+ public function reset($full = true) {
+ static::$markerCount = static::$infoWindowCount = 0;
+ $this->markers = $this->infoWindows = [];
+ if ($full) {
+ $this->_runtimeConfig = $this->_config;
+ }
+ }
+
+ /**
+ * Set the controls of current map
+ *
+ * Control options
+ * - zoom, scale, overview: TRUE/FALSE
+ *
+ * - map: FALSE, small, large
+ * - type: FALSE, normal, menu, hierarchical
+ * TIP: faster/shorter by using only the first character (e.g. "H" for "hierarchical")
+ *
+ * @param array $options
+ * @return void
+ */
+ public function setControls(array $options = []) {
+ if (isset($options['streetView'])) {
+ $this->_runtimeConfig['map']['streetViewControl'] = $options['streetView'];
+ }
+ if (isset($options['zoom'])) {
+ $this->_runtimeConfig['map']['scaleControl'] = $options['zoom'];
+ }
+ if (isset($options['scrollwheel'])) {
+ $this->_runtimeConfig['map']['scrollwheel'] = $options['scrollwheel'];
+ }
+ if (isset($options['keyboardShortcuts'])) {
+ $this->_runtimeConfig['map']['keyboardShortcuts'] = $options['keyboardShortcuts'];
+ }
+ if (isset($options['type'])) {
+ $this->_runtimeConfig['map']['type'] = $options['type'];
+ }
+ }
+
+ /**
+ * This the initialization point of the script
+ * Returns the div container you can echo on the website
+ *
+ * @param array $options associative array of settings are passed
+ * @return string divContainer
+ */
+ public function map(array $options = []) {
+ $this->reset();
+ $this->_runtimeConfig = Hash::merge($this->_runtimeConfig, $options);
+ $this->_runtimeConfig['map'] = $options + $this->_runtimeConfig['map'];
+
+ if (!isset($this->_runtimeConfig['map']['lat']) || !isset($this->_runtimeConfig['map']['lng'])) {
+ $this->_runtimeConfig['map']['lat'] = $this->_runtimeConfig['map']['defaultLat'];
+ $this->_runtimeConfig['map']['lng'] = $this->_runtimeConfig['map']['defaultLng'];
+ }
+ if (!isset($this->_runtimeConfig['map']['zoom'])) {
+ $this->_runtimeConfig['map']['zoom'] = $this->_runtimeConfig['map']['defaultZoom'];
+ }
+
+ $result = '';
+
+ // autoinclude js?
+ if ($this->_runtimeConfig['autoScript'] && !$this->_apiIncluded) {
+ $res = $this->Html->script($this->apiUrl(), ['block' => $this->_runtimeConfig['block']]);
+ $this->_apiIncluded = true;
+
+ if (!$this->_runtimeConfig['block']) {
+ $result .= $res . PHP_EOL;
+ }
+ // usually already included
+ //http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js
+ }
+ // still not very common: http://code.google.com/intl/de-DE/apis/maps/documentation/javascript/basics.html
+ if (false && !empty($this->_runtimeConfig['autoScript']) && !$this->_gearsIncluded) {
+ $res = $this->Html->script($this->gearsUrl(), ['block' => $this->_runtimeConfig['block']]);
+ if (!$this->_runtimeConfig['block']) {
+ $result .= $res . PHP_EOL;
+ }
+ }
+
+ $map = "
+ var initialLocation = " . $this->_initialLocation() . ";
+ var browserSupportFlag = new Boolean();
+ var myOptions = " . $this->_mapOptions() . ";
+
+ // deprecated
+ gMarkers" . static::$mapCount . " = new Array();
+ gInfoWindows" . static::$mapCount . " = new Array();
+ gWindowContents" . static::$mapCount . " = new Array();
+ ";
+
+ #rename "map_canvas" to "map_canvas1", ... if multiple maps on one page
+ while (in_array($this->_runtimeConfig['div']['id'], $this->_mapIds)) {
+ $this->_runtimeConfig['div']['id'] .= '-1'; //TODO: improve
+ }
+ $this->_mapIds[] = $this->_runtimeConfig['div']['id'];
+
+ $map .= "
+ var " . $this->name() . ' = new google.maps.Map(document.getElementById("' . $this->_runtimeConfig['div']['id'] . "\"), myOptions);
+ ";
+ $this->map = $map;
+
+ $this->_runtimeConfig['div']['style'] = '';
+ if (is_numeric($this->_runtimeConfig['div']['width'])) {
+ $this->_runtimeConfig['div']['width'] .= 'px';
+ }
+ if (is_numeric($this->_runtimeConfig['div']['height'])) {
+ $this->_runtimeConfig['div']['height'] .= 'px';
+ }
+
+ $this->_runtimeConfig['div']['style'] .= 'width: ' . $this->_runtimeConfig['div']['width'] . ';';
+ $this->_runtimeConfig['div']['style'] .= 'height: ' . $this->_runtimeConfig['div']['height'] . ';';
+ unset($this->_runtimeConfig['div']['width']);
+ unset($this->_runtimeConfig['div']['height']);
+
+ $defaultText = isset($this->_runtimeConfig['content']) ? $this->_runtimeConfig['content'] : __('Map cannot be displayed!');
+ $result .= $this->Html->tag('div', $defaultText, $this->_runtimeConfig['div']);
+
+ return $result;
+ }
+
+ /**
+ * Generate a new LatLng object with the current lat and lng.
+ *
+ * @return string
+ */
+ protected function _initialLocation() {
+ if ($this->_runtimeConfig['map']['lat'] && $this->_runtimeConfig['map']['lng']) {
+ return 'new google.maps.LatLng(' . $this->_runtimeConfig['map']['lat'] . ', ' . $this->_runtimeConfig['map']['lng'] . ')';
+ }
+ $this->_runtimeConfig['autoCenter'] = true;
+ return 'false';
+ }
+
+ /**
+ * Add a marker to the map.
+ *
+ * Options:
+ * - lat and lng or address (to geocode on demand, not recommended, though)
+ * - title, content, icon, directions, maxWidth, open (optional)
+ *
+ * Note, that you can only set one marker to "open" for single window mode.
+ * If you declare multiple ones, the last one will be the one shown as open.
+ *
+ * @param array $options
+ * @return mixed Integer marker count or boolean false on failure
+ * @throws \Cake\Core\Exception\Exception
+ */
+ public function addMarker($options) {
+ $defaults = $this->_runtimeConfig['marker'];
+ if (isset($options['icon']) && is_array($options['icon'])) {
+ $defaults = $options['icon'] + $defaults;
+ unset($options['icon']);
+ }
+ $options += $defaults;
+
+ $params = [];
+ $params['map'] = $this->name();
+
+ if (isset($options['title'])) {
+ $params['title'] = json_encode($options['title']);
+ }
+ if (isset($options['icon'])) {
+ $params['icon'] = $options['icon'];
+ if (is_int($params['icon'])) {
+ $params['icon'] = 'gIcons' . static::$mapCount . '[' . $params['icon'] . ']';
+ } else {
+ $params['icon'] = json_encode($params['icon']);
+ }
+ }
+ if (isset($options['shadow'])) {
+ $params['shadow'] = $options['shadow'];
+ if (is_int($params['shadow'])) {
+ $params['shadow'] = 'gIcons' . static::$mapCount . '[' . $params['shadow'] . ']';
+ } else {
+ $params['shadow'] = json_encode($params['shadow']);
+ }
+ }
+ if (isset($options['shape'])) {
+ $params['shape'] = $options['shape'];
+ }
+ if (isset($options['zIndex'])) {
+ $params['zIndex'] = $options['zIndex'];
+ }
+ if (isset($options['animation'])) {
+ $params['animation'] = 'google.maps.Animation.' . strtoupper($options['animation']);
+ }
+
+ // geocode if necessary
+ if (!isset($options['lat']) || !isset($options['lng'])) {
+ $this->map .= "
+var geocoder = new google.maps.Geocoder();
+
+function geocodeAddress(address) {
+ geocoder.geocode({'address': address}, function(results, status) {
+ if (status == google.maps.GeocoderStatus.OK) {
+
+ x" . static::$markerCount . " = new google.maps.Marker({
+ position: results[0].geometry.location,
+ " . $this->_toObjectParams($params, false, false) . "
+ });
+ gMarkers" . static::$mapCount . " .push(
+ x" . static::$markerCount . "
+ );
+ return results[0].geometry.location;
+ } else {
+ //alert('Geocoding was not successful for the following reason: ' + status);
+ return null;
+ }
+ });
+}";
+ if (!isset($options['address'])) {
+ throw new Exception('Either use lat/lng or address to add a marker');
+ }
+ $position = 'geocodeAddress("' . h($options['address']) . '")';
+ } else {
+ $position = 'new google.maps.LatLng(' . $options['lat'] . ',' . $options['lng'] . ')';
+ }
+
+ $marker = "
+ var x" . static::$markerCount . " = new google.maps.Marker({
+ position: " . $position . ",
+ " . $this->_toObjectParams($params, false, false) . "
+ });
+ gMarkers" . static::$mapCount . " .push(
+ x" . static::$markerCount . "
+ );
+ ";
+ $this->map .= $marker;
+
+ if (!empty($options['directions'])) {
+ $options['content'] .= $this->_directions($options['directions'], $options);
+ }
+
+ // Fill popup windows
+ if (!empty($options['content']) && $this->_runtimeConfig['infoWindow']['useMultiple']) {
+ $x = $this->addInfoWindow(['content' => $options['content']]);
+ $this->addEvent(static::$markerCount, $x, $options['open']);
+
+ } elseif (!empty($options['content'])) {
+ if (!isset($this->_runtimeConfig['marker']['infoWindow'])) {
+ $this->_runtimeConfig['marker']['infoWindow'] = $this->addInfoWindow();
+ }
+
+ $x = $this->addInfoContent($options['content']);
+ $event = "
+ gInfoWindows" . static::$mapCount . '[' . $this->_runtimeConfig['marker']['infoWindow'] . ']. setContent(gWindowContents' . static::$mapCount . '[' . $x . "]);
+ gInfoWindows" . static::$mapCount . '[' . $this->_runtimeConfig['marker']['infoWindow'] . '].open(' . $this->name() . ', gMarkers' . static::$mapCount . '[' . $x . "]);
+ ";
+ $this->addCustomEvent(static::$markerCount, $event);
+
+ if (!empty($options['open'])) {
+ $this->addCustom($event);
+ }
+ }
+
+ // Custom matching event?
+ if (isset($options['id'])) {
+ $this->matching[$options['id']] = static::$markerCount;
+ }
+
+ return static::$markerCount++;
+ }
+
+ /**
+ * Build directions form (type get) for directions inside infoWindows
+ *
+ * Options for directions (if array)
+ * - label
+ * - submit
+ * - escape: defaults to true
+ *
+ * @param mixed $directions
+ * - bool TRUE for autoDirections (using lat/lng)
+ * @param array $markerOptions
+ * - options array of marker for autoDirections etc (optional)
+ * @return string HTML
+ */
+ protected function _directions($directions, array $markerOptions = []) {
+ $options = [
+ 'from' => null,
+ 'to' => null,
+ 'label' => __('Enter your address'),
+ 'submit' => __('Get directions'),
+ 'escape' => true,
+ 'zoom' => null, // auto
+ ];
+ if ($directions === true) {
+ $options['to'] = $markerOptions['lat'] . ',' . $markerOptions['lng'];
+ } elseif (is_array($directions)) {
+ $options = $directions + $options;
+ }
+ if (empty($options['to']) && empty($options['from'])) {
+ return '';
+ }
+ $form = '<form action="http://maps.google.com/maps" method="get" target="_blank">';
+ $form .= $options['escape'] ? h($options['label']) : $options['label'];
+ if (!empty($options['from'])) {
+ $form .= '<input type="hidden" name="saddr" value="' . $options['from'] . '" />';
+ } else {
+ $form .= '<input type="text" name="saddr" />';
+ }
+ if (!empty($options['to'])) {
+ $form .= '<input type="hidden" name="daddr" value="' . $options['to'] . '" />';
+ } else {
+ $form .= '<input type="text" name="daddr" />';
+ }
+ if (isset($options['zoom'])) {
+ $form .= '<input type="hidden" name="z" value="' . $options['zoom'] . '" />';
+ }
+ $form .= '<input type="submit" value="' . $options['submit'] . '" />';
+ $form .= '</form>';
+
+ return '<div class="directions">' . $form . '</div>';
+ }
+
+ /**
+ * @param string $content
+ * @return int Current marker counter
+ */
+ public function addInfoContent($content) {
+ $this->infoContents[static::$markerCount] = $this->escapeString($content);
+ $event = "
+ gWindowContents" . static::$mapCount . '.push(' . $this->escapeString($content) . ");
+ ";
+ $this->addCustom($event);
+
+ //TODO: own count?
+ return static::$markerCount;
+ }
+
+ /**
+ * @var array
+ */
+ public $setIcons = [
+ 'color' => 'http://www.google.com/mapfiles/marker%s.png',
+ 'alpha' => 'http://www.google.com/mapfiles/marker%s%s.png',
+ 'numeric' => 'http://google-maps-icons.googlecode.com/files/%s%s.png',
+ 'special' => 'http://google-maps-icons.googlecode.com/files/%s.png'
+ ];
+
+ /**
+ * Get a custom icon set
+ *
+ * @param string $color Color: green, red, purple, ... or some special ones like "home", ...
+ * @param string|null $char Char: A...Z or 0...20/100 (defaults to none)
+ * @param string $size Size: s, m, l (defaults to medium)
+ * NOTE: for special ones only first parameter counts!
+ * @return array Array(icon, shadow, shape, ...)
+ */
+ public function iconSet($color, $char = null, $size = 'm') {
+ $colors = ['red', 'green', 'yellow', 'blue', 'purple', 'white', 'black'];
+ if (!in_array($color, $colors)) {
+ $color = 'red';
+ }
+
+ if (!empty($this->_runtimeConfig['localImages'])) {
+ $this->setIcons['color'] = $this->_runtimeConfig['localImages'] . 'marker%s.png';
+ $this->setIcons['alpha'] = $this->_runtimeConfig['localImages'] . 'marker%s%s.png';
+ $this->setIcons['numeric'] = $this->_runtimeConfig['localImages'] . '%s%s.png';
+ $this->setIcons['special'] = $this->_runtimeConfig['localImages'] . '%s.png';
+ }
+
+ if (!empty($char)) {
+ if ($color === 'red') {
+ $color = '';
+ } else {
+ $color = '_' . $color;
+ }
+ $url = sprintf($this->setIcons['alpha'], $color, $char);
+ } else {
+ if ($color === 'red') {
+ $color = '';
+ } else {
+ $color = '_' . $color;
+ }
+ $url = sprintf($this->setIcons['color'], $color);
+ }
+
+ /*
+ var iconImage = new google.maps.MarkerImage('images/' + images[0] + ' .png',
+ new google.maps.Size(iconData[images[0]].width, iconData[images[0]].height),
+ new google.maps.Point(0,0),
+ new google.maps.Point(0, 32)
+ );
+
+ var iconShadow = new google.maps.MarkerImage('images/' + images[1] + ' .png',
+ new google.maps.Size(iconData[images[1]].width, iconData[images[1]].height),
+ new google.maps.Point(0,0),
+ new google.maps.Point(0, 32)
+ );
+
+ var iconShape = {
+ coord: [1, 1, 1, 32, 32, 32, 32, 1],
+ type: 'poly'
+ };
+ */
+
+ $shadow = 'http://www.google.com/mapfiles/shadow50.png';
+ $res = [
+ 'url' => $url,
+ 'icon' => $this->icon($url, ['size' => ['width' => 20, 'height' => 34]]),
+ 'shadow' => $this->icon($shadow, ['size' => ['width' => 37, 'height' => 34], 'shadow' => ['width' => 10, 'height' => 34]])
+ ];
+ return $res;
+ }
+
+ /**
+ * Generate icon array.
+ *
+ * custom icon: http://thydzik.com/thydzikGoogleMap/markerlink.php?text=?&color=FFFFFF
+ * custom icons: http://code.google.com/p/google-maps-icons/wiki/NumericIcons#Lettered_Balloons_from_A_to_Z,_in_10_Colors
+ * custom shadows: http://www.cycloloco.com/shadowmaker/shadowmaker.htm
+ *
+ * @param string $image Image Url (http://...)
+ * @param string|null $shadow ShadowImage Url (http://...)
+ * @param array $imageOptions Image options
+ * @param array $shadowOptions Shadow image options
+ * @return array Resulting array
+ */
+ public function addIcon($image, $shadow = null, array $imageOptions = [], array $shadowOptions = []) {
+ $res = ['url' => $image];
+ $res['icon'] = $this->icon($image, $imageOptions);
+ if ($shadow) {
+ $last = $this->_iconRemember[$res['icon']];
+ if (!isset($shadowOptions['anchor'])) {
+ $shadowOptions['anchor'] = [];
+ }
+ $shadowOptions['anchor'] = $last['options']['anchor'] + $shadowOptions['anchor'];
+
+ $res['shadow'] = $this->icon($shadow, $shadowOptions);
+ }
+ return $res;
+ }
+
+ /**
+ * @var array
+ */
+ protected $_iconRemember = [];
+
+ /**
+ * Generate icon object
+ *
+ * @param string $url (required)
+ * @param array $options (optional):
+ * - size: array(width=>x, height=>y)
+ * - origin: array(width=>x, height=>y)
+ * - anchor: array(width=>x, height=>y)
+ * @return int Icon count
+ */
+ public function icon($url, array $options = []) {
+ // The shadow image is larger in the horizontal dimension
+ // while the position and offset are the same as for the main image.
+ if (empty($options['size'])) {
+ if (substr($url, 0, 1) === '/') {
+ // patch local paths to use the document root. otherwise getimagesize fails filesystem lookup.
+ // paths with http or other protocol in front will be handled more simply in 'else' below.
+ $canonicalPath = realpath(WWW_ROOT . $url);
+ if (! $canonicalPath) {
+ // failed to resolve the path, so just fall back to the url provided.
+ $canonicalPath = "$url";
+ }
+ $data = getimagesize($canonicalPath);
+ } else {
+ $data = getimagesize($url);
+ }
+ if ($data) {
+ $options['size']['width'] = $data[0];
+ $options['size']['height'] = $data[1];
+ } else {
+ $options['size']['width'] = $options['size']['height'] = 0;
+ }
+ }
+ if (empty($options['anchor'])) {
+ $options['anchor']['width'] = (int)($options['size']['width'] / 2);
+ $options['anchor']['height'] = $options['size']['height'];
+ }
+ if (empty($options['origin'])) {
+ $options['origin']['width'] = $options['origin']['height'] = 0;
+ }
+ if (isset($options['shadow'])) {
+ $options['anchor'] = $options['shadow'];
+ }
+
+ $icon = 'new google.maps.MarkerImage("' . $url . '",
+ new google.maps.Size(' . $options['size']['width'] . ', ' . $options['size']['height'] . '),
+ new google.maps.Point(' . $options['origin']['width'] . ', ' . $options['origin']['height'] . '),
+ new google.maps.Point(' . $options['anchor']['width'] . ', ' . $options['anchor']['height'] . ')
+)';
+ $this->icons[static::$iconCount] = $icon;
+ $this->_iconRemember[static::$iconCount] = ['url' => $url, 'options' => $options, 'id' => static::$iconCount];
+ return static::$iconCount++;
+ }
+
+ /**
+ * Creates a new InfoWindow.
+ *
+ * @param array $options
+ * - lat, lng, content, maxWidth, pixelOffset, zIndex
+ * @return int windowCount
+ */
+ public function addInfoWindow(array $options = []) {
+ $defaults = $this->_runtimeConfig['infoWindow'];
+ $options += $defaults;
+
+ if (!empty($options['lat']) && !empty($options['lng'])) {
+ $position = 'new google.maps.LatLng(' . $options['lat'] . ', ' . $options['lng'] . ')';
+ } else {
+ $position = ' ' . $this->name() . ' .getCenter()';
+ }
+
+ $windows = "
+ gInfoWindows" . static::$mapCount . ".push(new google.maps.InfoWindow({
+ position: {$position},
+ content: " . $this->escapeString($options['content']) . ",
+ maxWidth: {$options['maxWidth']},
+ pixelOffset: {$options['pixelOffset']}
+ /*zIndex: {$options['zIndex']},*/
+ }));
+ ";
+ $this->map .= $windows;
+ return static::$infoWindowCount++;
+ }
+
+ /**
+ * Add event to open marker on click.
+ *
+ * @param int $marker
+ * @param int $infoWindow
+ * @param bool $open Also open it right away.
+ * @return void
+ */
+ public function addEvent($marker, $infoWindow, $open = false) {
+ $this->map .= "
+ google.maps.event.addListener(gMarkers" . static::$mapCount . "[{$marker}], 'click', function() {
+ gInfoWindows" . static::$mapCount . "[$infoWindow].open(" . $this->name() . ", this);
+ });
+ ";
+ if ($open) {
+ $event = 'gInfoWindows' . static::$mapCount . "[$infoWindow].open(" . $this->name() .
+ ', gMarkers' . static::$mapCount . '[' . $marker . ']);';
+ $this->addCustom($event);
+ }
+ }
+
+ /**
+ * Add a custom event for a marker on click.
+ *
+ * @param int $marker
+ * @param string $event (js)
+ * @return void
+ */
+ public function addCustomEvent($marker, $event) {
+ $this->map .= "
+ google.maps.event.addListener(gMarkers" . static::$mapCount . "[{$marker}], 'click', function() {
+ $event
+ });
+ ";
+ }
+
+ /**
+ * Add custom JS.
+ *
+ * @param string $js Custom JS
+ * @return void
+ */
+ public function addCustom($js) {
+ $this->map .= $js;
+ }
+
+ /**
+ * Add directions to the map.
+ *
+ * @param array|string $from Location as array(fixed lat/lng pair) or string (to be geocoded at runtime)
+ * @param array|string $to Location as array(fixed lat/lng pair) or string (to be geocoded at runtime)
+ * @param array $options
+ * - directionsDiv: Div to place directions in text form
+ * - travelMode: TravelMode,
+ * - transitOptions: TransitOptions,
+ * - unitSystem: UnitSystem (IMPERIAL, METRIC, AUTO),
+ * - waypoints[]: DirectionsWaypoint,
+ * - optimizeWaypoints: Boolean,
+ * - provideRouteAlternatives: Boolean,
+ * - avoidHighways: Boolean,
+ * - avoidTolls: Boolean
+ * - region: String
+ * @see https://developers.google.com/maps/documentation/javascript/3.exp/reference#DirectionsRequest
+ * @return void
+ */
+ public function addDirections($from, $to, array $options = []) {
+ $id = 'd' . static::$markerCount++;
+ $defaults = $this->_runtimeConfig['directions'];
+ $options += $defaults;
+ $travelMode = $this->travelModes[$options['travelMode']];
+
+ $directions = "
+ var {$id}Service = new google.maps.DirectionsService();
+ var {$id}Display;
+ {$id}Display = new google.maps.DirectionsRenderer();
+ {$id}Display. setMap(" . $this->name() . ");
+ ";
+
+ if (!empty($options['directionsDiv'])) {
+ $directions .= "{$id}Display. setPanel(document.getElementById('" . $options['directionsDiv'] . "'));";
+ }
+
+ if (is_array($from)) {
+ $from = 'new google.maps.LatLng(' . (float)$from['lat'] . ', ' . (float)$from['lng'] . ')';
+ } else {
+ $from = '"' . h($from) . '"';
+ }
+ if (is_array($to)) {
+ $to = 'new google.maps.LatLng(' . (float)$to['lat'] . ', ' . (float)$to['lng'] . ')';
+ } else {
+ $to = '"' . h($to) . '"';
+ }
+
+ $directions .= "
+ var request = {
+ origin: $from,
+ destination: $to,
+ unitSystem: google.maps.UnitSystem." . $options['unitSystem'] . ",
+ travelMode: google.maps.TravelMode. $travelMode
+ };
+ {$id}Service.route(request, function(result, status) {
+ if (status == google.maps.DirectionsStatus.OK) {
+ {$id}Display. setDirections(result);
+ }
+ });
+ ";
+ $this->map .= $directions;
+ }
+
+ /**
+ * Add a polyline
+ *
+ * This method adds a line between 2 points
+ *
+ * @param array|string $from Location as array(fixed lat/lng pair) or string (to be geocoded at runtime)
+ * @param array|string $to Location as array(fixed lat/lng pair) or string (to be geocoded at runtime)
+ * @param array $options
+ * - color (#FFFFFF ... #000000)
+ * - opacity (0.1 ... 1, defaults to 1)
+ * - weight in pixels (defaults to 2)
+ * @see https://developers.google.com/maps/documentation/javascript/3.exp/reference#Polyline
+ * @return void
+ */
+ public function addPolyline($from, $to, array $options = []) {
+ if (is_array($from)) {
+ $from = 'new google.maps.LatLng(' . (float)$from['lat'] . ', ' . (float)$from['lng'] . ')';
+ } else {
+ throw new Exception('not implemented yet, use array of lat/lng');
+ //$from = '\'' . h($from) . '\'';
+ }
+ if (is_array($to)) {
+ $to = 'new google.maps.LatLng(' . (float)$to['lat'] . ', ' . (float)$to['lng'] . ')';
+ } else {
+ throw new Exception('not implemented yet, use array of lat/lng');
+ //$to = '\'' . h($to) . '\'';
+ }
+
+ $defaults = $this->_runtimeConfig['polyline'];
+ $options += $defaults;
+
+ $id = 'p' . static::$markerCount++;
+
+ $polyline = "var start = $from;";
+ $polyline .= "var end = $to;";
+ $polyline .= "
+ var poly = [
+ start,
+ end
+ ];
+ var {$id}Polyline = new google.maps.Polyline({
+ path: poly,
+ strokeColor: '" . $options['color'] . "',
+ strokeOpacity: " . $options['opacity'] . ",
+ strokeWeight: " . $options['weight'] . "
+ });
+ {$id}Polyline.setMap(" . $this->name() . ");
+ ";
+ $this->map .= $polyline;
+ }
+
+ /**
+ * @param string $content (html/text)
+ * @param int $index infoWindowCount
+ * @return void
+ */
+ public function setContentInfoWindow($content, $index) {
+ $this->map .= "
+ gInfoWindows" . static::$mapCount . "[$index]. setContent(" . $this->escapeString($content) . ');';
+ }
+
+ /**
+ * Json encode string
+ *
+ * @param mixed $content
+ * @return string JSON
+ */
+ public function escapeString($content) {
+ return json_encode($content);
+ }
+
+ /**
+ * This method returns the javascript for the current map container.
+ * Including script tags.
+ * Just echo it below the map container. New: Alternativly, use finalize() directly.
+ *
+ * @return string
+ */
+ public function script() {
+ $script = '<script>
+ ' . $this->finalize(true) . '
+</script>';
+ return $script;
+ }
+
+ /**
+ * Finalize the map and write the javascript to the buffer.
+ * Make sure that your view does also output the buffer at some place!
+ *
+ * @param bool $return If the output should be returned instead
+ * @return null|string Javascript if $return is true
+ */
+ public function finalize($return = false) {
+ $script = $this->_arrayToObject('matching', $this->matching, false, true) . PHP_EOL;
+ $script .= $this->_arrayToObject('gIcons' . static::$mapCount, $this->icons, false, false) . '
+
+ jQuery(document).ready(function() {
+ ';
+
+ $script .= $this->map;
+ if ($this->_runtimeConfig['geolocate']) {
+ $script .= $this->_geolocate();
+ }
+
+ if ($this->_runtimeConfig['showMarker'] && !empty($this->markers) && is_array($this->markers)) {
+ $script .= implode($this->markers, ' ');
+ }
+
+ if ($this->_runtimeConfig['autoCenter']) {
+ $script .= $this->_autoCenter();
+ }
+ $script .= '
+
+ });';
+ static::$mapCount++;
+ if ($return) {
+ return $script;
+ }
+ $this->Html->scriptBlock($script, ['block' => true]);
+ }
+
+ /**
+ * Set a custom geolocate callback
+ *
+ * @param string|bool $js Custom JS
+ * false: no callback at all
+ * @return void
+ */
+ public function geolocateCallback($js) {
+ if ($js === false) {
+ $this->_runtimeConfig['callbacks']['geolocate'] = false;
+ return;
+ }
+ $this->_runtimeConfig['callbacks']['geolocate'] = $js;
+ }
+
+ /**
+ * Experimental - works in cutting edge browsers like chrome10
+ *
+ * @return string
+ */
+ protected function _geolocate() {
+ return '
+ // Try W3C Geolocation (Preferred)
+ if (navigator.geolocation) {
+ browserSupportFlag = true;
+ navigator.geolocation.getCurrentPosition(function(position) {
+ geolocationCallback(position.coords.latitude, position.coords.longitude);
+ }, function() {
+ handleNoGeolocation(browserSupportFlag);
+ });
+ // Try Google Gears Geolocation
+ } else if (google.gears) {
+ browserSupportFlag = true;
+ var geo = google.gears.factory.create("beta.geolocation");
+ geo.getCurrentPosition(function(position) {
+ geolocationCallback(position.latitude, position.longitude);
+ }, function() {
+ handleNoGeoLocation(browserSupportFlag);
+ });
+ // Browser doesn\'t support Geolocation
+ } else {
+ browserSupportFlag = false;
+ handleNoGeolocation(browserSupportFlag);
+ }
+
+ function geolocationCallback(lat, lng) {
+ ' . $this->_geolocationCallback() . '
+ }
+
+ function handleNoGeolocation(errorFlag) {
+ if (errorFlag == true) {
+ //alert("Geolocation service failed.");
+ } else {
+ //alert("Your browser doesn\'t support geolocation. We\'ve placed you in Siberia.");
+ }
+ //' . $this->name() . ' . setCenter(initialLocation);
+ }
+ ';
+ }
+
+ /**
+ * @return string
+ */
+ protected function _geolocationCallback() {
+ if (($js = $this->_runtimeConfig['callbacks']['geolocate']) === false) {
+ return '';
+ }
+ if ($js === null) {
+ $js = 'initialLocation = new google.maps.LatLng(lat, lng);
+ ' . $this->name() . ' . setCenter(initialLocation);
+';
+ }
+ return $js;
+ }
+
+ /**
+ * Auto center map
+ * careful: with only one marker this can result in too high zoom values!
+ *
+ * @return string autoCenterCommands
+ */
+ protected function _autoCenter() {
+ return '
+ var bounds = new google.maps.LatLngBounds();
+ $.each(gMarkers' . static::$mapCount . ',function (index, marker) { bounds.extend(marker.position);});
+ ' . $this->name() . ' .fitBounds(bounds);
+ ';
+ }
+
+ /**
+ * @return string JSON like js string
+ */
+ protected function _mapOptions() {
+ $options = $this->_runtimeConfig['map'] + $this->_runtimeConfig;
+
+ $mapOptions = array_intersect_key($options, [
+ 'streetViewControl' => null,
+ 'navigationControl' => null,
+ 'mapTypeControl' => null,
+ 'scaleControl' => null,
+ 'scrollwheel' => null,
+ 'zoom' => null,
+ 'keyboardShortcuts' => null,
+ 'styles' => null,
+ ]);
+ $res = [];
+ foreach ($mapOptions as $key => $mapOption) {
+ $res[] = $key . ': ' . $this->value($mapOption);
+ }
+ if (empty($options['autoCenter'])) {
+ $res[] = 'center: initialLocation';
+ }
+ if (!empty($options['navOptions'])) {
+ $res[] = 'navigationControlOptions: ' . $this->_controlOptions('nav', $options['navOptions']);
+ }
+ if (!empty($options['typeOptions'])) {
+ $res[] = 'mapTypeControlOptions: ' . $this->_controlOptions('type', $options['typeOptions']);
+ }
+ if (!empty($options['scaleOptions'])) {
+ $res[] = 'scaleControlOptions: ' . $this->_controlOptions('scale', $options['scaleOptions']);
+ }
+
+ if (array_key_exists($options['type'], $this->types)) {
+ $type = $this->types[$options['type']];
+ } else {
+ $type = $options['type'];
+ }
+ $res[] = 'mapTypeId: google.maps.MapTypeId.' . $type;
+
+ return '{' . implode(', ', $res) . '}';
+ }
+
+ /**
+ * @param string $type
+ * @param array $options
+ * @return string JSON like js string
+ */
+ protected function _controlOptions($type, $options) {
+ $mapping = [
+ 'nav' => 'NavigationControlStyle',
+ 'type' => 'MapTypeControlStyle',
+ 'scale' => ''
+ ];
+ $res = [];
+ if (!empty($options['style']) && ($m = $mapping[$type])) {
+ $res[] = 'style: google.maps.' . $m . '.' . $options['style'];
+ }
+ if (!empty($options['pos'])) {
+ $res[] = 'position: google.maps.ControlPosition.' . $options['pos'];
+ }
+
+ return '{' . implode(', ', $res) . '}';
+ }
+
+ /**
+ * Returns a maps.google link
+ *
+ * @param string $title Link title
+ * @param array $mapOptions
+ * @param array $linkOptions
+ * @return string HTML link
+ */
+ public function mapLink($title, $mapOptions = [], $linkOptions = []) {
+ return $this->Html->link($title, $this->mapUrl($mapOptions + ['escape' => false]), $linkOptions);
+ }
+
+ /**
+ * Returns a maps.google url
+ *
+ * Options:
+ * - from: necessary (address or lat,lng)
+ * - to: 1x necessary (address or lat,lng - can be an array of multiple destinations: array('dest1', 'dest2'))
+ * - zoom: optional (defaults to none)
+ * - query: Additional query strings as array
+ * - escape: defaults to true
+ *
+ * @param array $options Options
+ * @return string link: http://...
+ */
+ public function mapUrl(array $options = []) {
+ $url = $this->_protocol() . 'maps.google.com/maps?';
+
+ $urlArray = !empty($options['query']) ? $options['query'] : [];
+ if (!empty($options['from'])) {
+ $urlArray['saddr'] = $options['from'];
+ }
+
+ if (!empty($options['to']) && is_array($options['to'])) {
+ $to = array_shift($options['to']);
+ foreach ($options['to'] as $key => $value) {
+ $to .= '+to:' . $value;
+ }
+ $urlArray['daddr'] = $to;
+ } elseif (!empty($options['to'])) {
+ $urlArray['daddr'] = $options['to'];
+ }
+
+ if (isset($options['zoom']) && $options['zoom'] !== false) {
+ $urlArray['z'] = (int)$options['zoom'];
+ }
+ //$urlArray[] = 'f=d';
+ //$urlArray[] = 'hl=de';
+ //$urlArray[] = 'ie=UTF8';
+
+ $options += [
+ 'escape' => true,
+ ];
+
+ $query = http_build_query($urlArray);
+ if ($options['escape']) {
+ $query = h($query);
+ }
+
+ return $url . $query;
+ }
+
+ /**
+ * Creates a plain image map.
+ *
+ * @link http://code.google.com/intl/de-DE/apis/maps/documentation/staticmaps
+ * @param array $options Options
+ * - string $size [necessary: VALxVAL, e.g. 500x400 - max 640x640]
+ * - string $center: x,y or address [necessary, if no markers are given; else tries to take defaults if available] or TRUE/FALSE
+ * - int $zoom [optional; if no markers are given, default value is used; if set to "auto" and ]*
+ * - array $markers [optional, @see staticPaths() method]
+ * - string $type [optional: roadmap/hybrid, ...; default:roadmap]
+ * - string $mobile TRUE/FALSE
+ * - string $visible: $area (x|y|...)
+ * - array $paths [optional, @see staticPaths() method]
+ * - string $language [optional]
+ * @param array $attributes HTML attributes for the image
+ * - title
+ * - alt (defaults to 'Map')
+ * - url (tip: you can pass $this->link(...) and it will create a link to maps.google.com)
+ * @return string imageTag
+ */
+ public function staticMap(array $options = [], array $attributes = []) {
+ $defaultAttributes = ['alt' => __d('tools', 'Map')];
+ $attributes += $defaultAttributes;
+
+ // This was fixed in 3.5.1 to auto-escape URL query strings for security reasons
+ $escape = version_compare(Configure::version(), '3.5.1') < 0 ? true : false;
+ return $this->Html->image($this->staticMapUrl($options + ['escape' => $escape]), $attributes);
+ }
+
+ /**
+ * Create a link to a plain image map
+ *
+ * @param string $title Link title
+ * @param array $mapOptions
+ * @param array $linkOptions
+ * @return string HTML link
+ */
+ public function staticMapLink($title, array $mapOptions = [], array $linkOptions = []) {
+ return $this->Html->link($title, $this->staticMapUrl($mapOptions + ['escape' => false]), $linkOptions);
+ }
+
+ /**
+ * Creates a URL to a plain image map.
+ *
+ * Options:
+ * - escape: defaults to true (Deprecated as of CakePHP 3.5.1 and now has to be always false)
+ *
+ * @param array $options
+ * - see staticMap() for details
+ * @return string urlOfImage: http://...
+ */
+ public function staticMapUrl(array $options = []) {
+ $mapUrl = $this->_protocol() . static::STATIC_API;
+ /*
+ $params = array(
+ 'mobile' => 'false',
+ 'format' => 'png',
+ //'center' => false
+ );
+
+ if (!empty($options['mobile'])) {
+ $params['mobile'] = 'true';
+ }
+ */
+
+ $defaults = $this->_config['staticMap'] + $this->_config;
+
+ $mapOptions = $options + $defaults;
+
+ $params = array_intersect_key($mapOptions, [
+ 'mobile' => null,
+ 'format' => null,
+ 'size' => null,
+ //'zoom' => null,
+ //'lat' => null,
+ //'lng' => null,
+ //'visible' => null,
+ //'type' => null,
+ ]);
+
+ // add API key to parameters.
+ if ($this->_runtimeConfig['key']) {
+ $params['key'] = $this->_runtimeConfig['key'];
+ }
+
+ // do we want zoom to auto-correct itself?
+ if (!isset($options['zoom']) && !empty($mapOptions['markers']) || !empty($mapOptions['paths']) || !empty($mapOptions['visible'])) {
+ $options['zoom'] = 'auto';
+ }
+
+ // a position on the map that is supposed to stay visible at all cost
+ if (!empty($mapOptions['visible'])) {
+ $params['visible'] = urlencode($mapOptions['visible']);
+ }
+
+ // center and zoom are not necessary if path, visible or markers are given
+ if (!isset($options['center']) || $options['center'] === false) {
+ // dont use it
+ } elseif ($options['center'] === true && $mapOptions['lat'] !== null && $mapOptions['lng'] !== null) {
+ $params['center'] = urlencode((string)$mapOptions['lat'] . ',' . (string)$mapOptions['lng']);
+ } elseif (!empty($options['center'])) {
+ $params['center'] = urlencode($options['center']);
+ } /*else {
+ // try to read from markers array???
+ if (isset($options['markers']) && count($options['markers']) == 1) {
+ //pr ($options['markers']);
+ }
+ }*/
+
+ if (!isset($options['zoom']) || $options['zoom'] === false) {
+ // dont use it
+ } else {
+ if ($options['zoom'] === 'auto') {
+ if (!empty($options['markers']) && strpos($options['zoom'], '|') !== false) {
+ // let google find the best zoom value itself
+ } else {
+ // do something here?
+ }
+ } else {
+ $params['zoom'] = $options['zoom'];
+ }
+ }
+
+ if (array_key_exists($mapOptions['type'], $this->types)) {
+ $params['maptype'] = $this->types[$mapOptions['type']];
+ } else {
+ $params['maptype'] = $mapOptions['type'];
+ }
+ $params['maptype'] = strtolower($params['maptype']);
+
+ // old: {latitude},{longitude},{color}{alpha-character}
+ // new: @see staticMarkers()
+ if (!empty($options['markers'])) {
+ $params['markers'] = $options['markers'];
+ }
+
+ if (!empty($options['paths'])) {
+ $params['path'] = $options['paths'];
+ }
+
+ // valXval
+ if (!empty($options['size'])) {
+ $params['size'] = $options['size'];
+ }
+
+ $pieces = [];
+ foreach ($params as $key => $value) {
+ if (is_array($value)) {
+ $value = implode('&' . $key . '=', $value);
+ } elseif ($value === true) {
+ $value = 'true';
+ } elseif ($value === false) {
+ $value = 'false';
+ } elseif ($value === null) {
+ continue;
+ }
+ $pieces[] = $key . '=' . $value;
+ }
+
+ $options += [
+ 'escape' => true,
+ ];
+ $query = implode('&', $pieces);
+ if ($options['escape']) {
+ $query = h($query);
+ }
+
+ return $mapUrl . '?' . $query;
+ }
+
+ /**
+ * Prepare paths for staticMap
+ *
+ * @param array $pos PathElementArrays
+ * - elements: [required] (multiple array(lat=>x, lng=>y) or just a address strings)
+ * - color: red/blue/green (optional, default blue)
+ * - weight: numeric (optional, default: 5)
+ * @return array Array of paths: e.g: color:0x0000FF80|weight:5|37.40303,-122.08334|37.39471,-122.07201|37.40589,-122.06171{|...}
+ */
+ public function staticPaths(array $pos = []) {
+ $defaults = [
+ 'color' => 'blue',
+ 'weight' => 5 // pixel
+ ];
+
+ // not a 2-level array? make it one
+ if (!isset($pos[0])) {
+ $pos = [$pos];
+ }
+
+ $res = [];
+ foreach ($pos as $p) {
+ $options = $p + $defaults;
+
+ $markers = $options['path'];
+ unset($options['path']);
+
+ // prepare color
+ if (!empty($options['color'])) {
+ $options['color'] = $this->_prepColor($options['color']);
+ }
+
+ $path = [];
+ foreach ($options as $key => $value) {
+ $path[] = $key . ':' . urlencode($value);
+ }
+ foreach ($markers as $key => $pos) {
+ if (is_array($pos)) {
+ // lat/lng?
+ $pos = $pos['lat'] . ',' . $pos['lng'];
+ }
+ $path[] = $pos;
+ }
+ $res[] = implode('|', $path);
+ }
+ return $res;
+ }
+
+ /**
+ * Prepare markers for staticMap
+ *
+ * @param array $pos markerArrays
+ * - lat: xx.xxxxxx (necessary)
+ * - lng: xx.xxxxxx (necessary)
+ * - address: (instead of lat/lng)
+ * - color: red/blue/green (optional, default blue)
+ * - label: a-z or numbers (optional, default: s)
+ * - icon: custom icon (png, gif, jpg - max 64x64 - max 5 different icons per image)
+ * - shadow: TRUE/FALSE
+ * @param array $style (global) (overridden by custom marker styles)
+ * - color
+ * - label
+ * - icon
+ * - shadow
+ * @return array markers: color:green|label:Z|48,11|Berlin
+ *
+ * NEW: size:mid|color:red|label:E|37.400465,-122.073003|37.437328,-122.159928&markers=size:small|color:blue|37.369110,-122.096034
+ * OLD: 40.702147,-74.015794,blueS|40.711614,-74.012318,greenG{|...}
+ */
+ public function staticMarkers(array $pos = [], array $style = []) {
+ $markers = [];
+ $verbose = false;
+
+ $defaults = [
+ 'shadow' => 'true',
+ 'color' => 'blue',
+ 'label' => '',
+ 'address' => '',
+ 'size' => ''
+ ];
+
+ // not a 2-level array? make it one
+ if (!isset($pos[0])) {
+ $pos = [$pos];
+ }
+
+ // new in staticV2: separate styles! right now just merged
+ foreach ($pos as $p) {
+ $p += $style + $defaults;
+
+ // adress or lat/lng?
+ if (!empty($p['lat']) && !empty($p['lng'])) {
+ $p['address'] = $p['lat'] . ',' . $p['lng'];
+ }
+ $p['address'] = urlencode($p['address']);
+
+ $values = [];
+
+ // prepare color
+ if (!empty($p['color'])) {
+ $p['color'] = $this->_prepColor($p['color']);
+ $values[] = 'color:' . $p['color'];
+ }
+ // label? A-Z0-9
+ if (!empty($p['label'])) {
+ $values[] = 'label:' . strtoupper($p['label']);
+ }
+ if (!empty($p['size'])) {
+ $values[] = 'size:' . $p['size'];
+ }
+ if (!empty($p['shadow'])) {
+ $values[] = 'shadow:' . $p['shadow'];
+ }
+ if (!empty($p['icon'])) {
+ $values[] = 'icon:' . urlencode($p['icon']);
+ }
+ $values[] = $p['address'];
+
+ //TODO: icons
+ $markers[] = implode('|', $values);
+ }
+
+ //TODO: shortcut? only possible if no custom params!
+ if ($verbose) {
+
+ }
+ // long: markers=styles1|address1&markers=styles2|address2&...
+ // short: markers=styles,address1|address2|address3|...
+
+ return $markers;
+ }
+
+ /**
+ * Ensure that we stay on the appropriate protocol
+ *
+ * @return string protocol base (including ://)
+ */
+ protected function _protocol() {
+ $https = $this->_runtimeConfig['https'];
+ if ($https === null) {
+ $https = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on';
+ }
+ return ($https ? 'https' : 'http') . '://';
+ }
+
+ /**
+ * // to 0x
+ * or // added
+ *
+ * @param string $color Color: FFFFFF, #FFFFFF, 0xFFFFFF or blue
+ * @return string Color
+ */
+ protected function _prepColor($color) {
+ if (strpos($color, '#') !== false) {
+ return str_replace('#', '0x', $color);
+ }
+ if (is_numeric($color)) {
+ return '0x' . $color;
+ }
+ return $color;
+ }
+
+ /**
+ * @param string $name
+ * @param array $array
+ * @param bool $asString
+ * @param bool $keyAsString
+ * @return string
+ */
+ protected function _arrayToObject($name, $array, $asString = true, $keyAsString = false) {
+ $res = 'var ' . $name . ' = {' . PHP_EOL;
+ $res .= $this->_toObjectParams($array, $asString, $keyAsString);
+ $res .= '};';
+ return $res;
+ }
+
+ /**
+ * @param array $array
+ * @param bool $asString
+ * @param bool $keyAsString
+ * @return string
+ */
+ protected function _toObjectParams($array, $asString = true, $keyAsString = false) {
+ $pieces = [];
+ foreach ($array as $key => $value) {
+ $e = ($asString && strpos($value, 'new ') !== 0 ? '"' : '');
+ $ke = ($keyAsString ? '"' : '');
+ $pieces[] = $ke . $key . $ke . ': ' . $e . $value . $e;
+ }
+ return implode(',' . PHP_EOL, $pieces);
+ }
+
+}
--- /dev/null
+#!/bin/bash
+
+
+if [ ! -d "$HOME/apps/mapsdemo/avenger5" ]; then
+ echo Not seeing the mapsdemo checked out man.
+ exit 1
+fi
+
+meld $(find ~/apps/mapsdemo/avenger5/ -iname goog*map*helper.php) ./Goog*Map*Help*p
+
+
--- /dev/null
+Welcome to the CakePHP LAMP VM.
+
+Please refer to the built-in documentation available at: http://cakelampvm.com
+
+Some first steps to make this vm your own:
+
+####
+
+1) change your password for the developer account.
+(may eventually be automatically required)
+
+####
+
+2) change your git configuration for user and email. this is how we've
+configured it so far:
+
+ # git config --global user.email "developer@cakelampvm.com"
+ # git config --global user.name "Developer J. Cakemo"
+
+if you're developing on a real project, you probably don't want the bogus
+email and even more bogus name above attached to your commits.
+Just run the two commands again but with proper values.
+
+####
+
&backup_hierarchy($snarf_file_base, $number, "$root", "production/assign_bases");
&backup_hierarchy($snarf_file_base, $number, "$root", "production/check_versions");
&backup_hierarchy($snarf_file_base, $number, "$root", "production/setup_src");
+&backup_hierarchy($snarf_file_base, $number, "$root", "production/sites");
# now rename the file so only the unpacker can access it.
&rename_archive($snarf_file);
# some aliases that are just generally nice to have.
define_yeti_alias aliases=alias
-define_yeti_alias calc='kcalc'
+define_yeti_alias calc='galculator'
define_yeti_alias cd..='\cd ..'
define_yeti_alias cd...='\cd ../..'
define_yeti_alias cd....='\cd ../../..'
# some important retreads on aliases that provide a sudo-ized version of other scripts.
define_yeti_alias snarf_linux_config="sudo -E PERLLIB=\$PERLLIB perl \$FEISTY_MEOW_SCRIPTS/archival/snarf_linux_config.pl"
-#no, does its own sudo wrangling.edefine_yeti_alias standup="sudo bash \"$FEISTY_MEOW_SCRIPTS/site_avenger/standup.sh\""
+define_yeti_alias add_domain="sudo bash \$FEISTY_MEOW_SCRIPTS/system/add_domain.sh"
+define_yeti_alias remove_domain="sudo bash \$FEISTY_MEOW_SCRIPTS/system/remove_domain.sh"
+define_yeti_alias add_apache_site="sudo bash \$FEISTY_MEOW_SCRIPTS/system/add_apache_site.sh"
+define_yeti_alias remove_apache_site="sudo bash \$FEISTY_MEOW_SCRIPTS/system/remove_apache_site.sh"
#hmmm: some magma intrusions from the fred customizations...
define_yeti_alias revamp_cakelampvm="sudo bash \"$FEISTY_MEOW_SCRIPTS/site_avenger/revamp_cakelampvm.sh\""
function ssh()
{
local args=($*)
- save_terminal_title
# we remember the old terminal title, then force the TERM variable to a more generic
# version for the other side (just 'linux'); we don't want the remote side still
# thinking it's running xterm.
- export TERM=linux
+ save_terminal_title
+#hmmm: why were we doing this? it scorches the user's logged in session, leaving it without proper terminal handling.
+# # we save the value of TERM; we don't want to leave the user's terminal
+# # brain dead once we come back from this function.
+# local oldterm="$TERM"
+# export TERM=linux
/usr/bin/ssh -X -C "${args[@]}"
+# # restore the terminal variable also.
+# TERM="$oldterm"
restore_terminal_title
}
done
}
+#hmmm: not really doing anything yet; ubuntu seems to have changed from pulseaudio in 17.04?
# restarts the sound driver.
function fix_sound_driver() {
-#if something
+ # stop bash complaining about blank function body.
+ local nothing=
+#if alsa something
# sudo service alsasound restart
+#elif pulse something
+# sudo pulseaudio -k
+# sudo pulseaudio -D
#else
- sudo service pulseaudio restart
+# something else...?
#fi
}
echo "but that folder does not exist. Skipping customization."
return 1
fi
+
+ # prevent permission foul-ups.
+#hmmm: save error output here instead of muting it.
+#hmmm: better yet actually, just don't complain on freaking cygwin, since that's where this happens
+ chown -R "$(logname):$(logname)" \
+ "$FEISTY_MEOW_LOADING_DOCK"/* "$FEISTY_MEOW_GENERATED_STORE"/* 2>/dev/null
+ test_or_continue "chowning to $(logname) didn't happen."
+
regenerate >/dev/null
pushd "$FEISTY_MEOW_LOADING_DOCK/custom" &>/dev/null
incongruous_files="$(bash "$FEISTY_MEOW_SCRIPTS/files/list_non_dupes.sh" "$FEISTY_MEOW_SCRIPTS/customize/$custom_user" "$FEISTY_MEOW_LOADING_DOCK/custom")"
echo
regenerate
+ # prevent permission foul-ups, again.
+ chown -R "$(logname):$(logname)" \
+ "$FEISTY_MEOW_LOADING_DOCK" "$FEISTY_MEOW_GENERATED_STORE" 2>/dev/null
+ test_or_continue "chowning to $(logname) didn't happen."
+
restore_terminal_title
}
##############
+ # given a filename and a string to seek and a number of lines, then this
+ # function will remove the first occurrence of a line in the file that
+ # matches the string, and it will also axe the next N lines as specified.
+ function create_chomped_copy_of_file()
+ {
+ local filename="$1"; shift
+ local seeker="$1"; shift
+ local numlines=$1; shift
+
+#echo into create_chomped_copy...
+#var filename seeker numlines
+
+ # make a backup first, oy.
+ \cp -f "$filename" "/tmp/$(basename ${filename}).bkup-${RANDOM}"
+ test_or_die "backing up file: $filename"
+
+ # make a temp file to write to before we move file into place in bind.
+ local new_version="/tmp/$(basename ${filename}).bkup-${RANDOM}"
+ \rm -f "$new_version"
+ test_or_die "cleaning out new version of file from: $new_version"
+
+ local line
+ local skip_count=0
+ local found_any=
+ while read line; do
+ # don't bother looking at the lines if we're already in skip mode.
+ if [[ $skip_count == 0 ]]; then
+ # find the string they're seeking.
+ if [[ ! "$line" =~ .*${seeker}.* ]]; then
+ # no match.
+ echo "$line" >> "$new_version"
+ else
+ # a match! start skipping. we will delete this line and the next N lines.
+ ((skip_count++))
+#echo first skip count is now $skip_count
+ found_any=yes
+ fi
+ else
+ # we're already skipping. let's keep going until we hit the limit.
+ ((skip_count++))
+#echo ongoing skip count is now $skip_count
+ if (( $skip_count > $numlines )); then
+ echo "Done skipping, and back to writing output file."
+ skip_count=0
+ fi
+ fi
+ done < "$filename"
+
+#echo file we created looks like this:
+#cat "$new_version"
+
+ if [ ! -z "$found_any" ]; then
+ # put the file back into place under the original name.
+ \mv "$new_version" "$filename"
+ test_or_die "moving the new version into place in: $filename"
+ else
+ # cannot always be considered an error, but we can at least gripe.
+ echo "Did not find any matches for seeker '$seeker' in file: $filename"
+ fi
+ }
+
+ ##############
+
# NOTE: no more function definitions are allowed after this point.
function function_sentinel()
unset FEISTY_MEOW_SHOW_LAUNCH_GREETING
fi
+ # only run this hello file if the core feisty meow support haven't been loaded already. this
+ # hopefully guarantees we show the info at most once in one shell continuum.
+ # this can also be disabled if the NO_HELLO variable has a non-empty value.
+ type CORE_VARIABLES_LOADED &>/dev/null
+ if [ $? -ne 0 -a -z "$NO_HELLO" ]; then
+ # print out a personalized hello file if we find one.
+ if [ -f ~/hello.txt ]; then
+ echo
+ sep 28
+ perl $FEISTY_MEOW_SCRIPTS/*/filedump.pl ~/hello.txt
+ sep 28
+ echo
+ fi
+ # from now on there should be no extra helloing.
+ export NO_HELLO=true
+ fi
+
# load the last bits we do here.
source "$FEISTY_MEOW_LOADING_DOCK/fmc_ending_sentinel.sh"
unset CORE_VARIABLES_LOADED
# repetitive bit stolen from variables. should make a file out of this somehow.
-IS_DOS=$(uname | grep -i ming)
-if [ -z "$IS_DOS" ]; then IS_DOS=$(uname | grep -i cygwin); fi
-# now if we're stuck in DOS, then fix the feisty meow variable name.
-if [ ! -z "$IS_DOS" ]; then
- FEISTY_MEOW_APEX="$(cmd /c chdir | tr A-Z a-z | sed -e 's/\\/\//g')"
-echo feisty meow dos is: $FEISTY_MEOW_APEX
- FEISTY_MEOW_APEX="$(dos_to_unix_path "$FEISTY_MEOW_APEX")"
-echo new feisty meow fixed dir is: $FEISTY_MEOW_APEX
-fi
+#IS_DOS=$(uname | grep -i ming)
+#if [ -z "$IS_DOS" ]; then IS_DOS=$(uname | grep -i cygwin); fi
+## now if we're stuck in DOS, then fix the feisty meow variable name.
+#if [ ! -z "$IS_DOS" ]; then
+# FEISTY_MEOW_APEX="$(cmd /c chdir | tr A-Z a-z | sed -e 's/\\/\//g')"
+#echo feisty meow dos is: $FEISTY_MEOW_APEX
+# FEISTY_MEOW_APEX="$(dos_to_unix_path "$FEISTY_MEOW_APEX")"
+#echo new feisty meow fixed dir is: $FEISTY_MEOW_APEX
+#fi
popd &>/dev/null
##############
-# pull in the custom overrides for feisty_meow scripts. this is done last,
-# because we want to set everything up as expected, then let the user
+# pull in the custom overrides for feisty_meow scripts. this is done almost
+# last, because we want to set everything up as expected, then let the user
# override individual variables and definitions. we also don't guard this
# to avoid running it again, because we don't know what mix of functions and
# aliases they want to define in there.
source "$i"
done
+##############
+
+# a late breaking action is to set the editor, if we can.
+# we will fallback to whatever we can find on the host.
+export EDITOR
+if [ ! -z "$DISPLAY" ]; then
+ # only try to add bluefish, a gui editor, if there is an X display for it.
+ if [ -z "$EDITOR" ]; then
+ EDITOR="$(which bluefish)"
+ fi
+fi
+if [ -z "$EDITOR" ]; then
+ EDITOR="$(which gvim)"
+ if [ ! -z "$EDITOR" ]; then
+ # if we found gvim, then add in the no forking flag.
+ EDITOR+=" --nofork"
+ fi
+fi
+if [ -z "$EDITOR" ]; then
+ EDITOR="$(which vim)"
+fi
+if [ -z "$EDITOR" ]; then
+ EDITOR="$(which vi)"
+fi
+##
+# out of ideas about editors at this point.
+##
+# set the VISUAL variable from EDITOR if we found an editor to use.
+if [ ! -z "$EDITOR" ]; then
+ VISUAL="$EDITOR"
+fi
+
+##############
+
--- /dev/null
+#!/bin/bash
+
+# puffer: "puffs out" all of the folders present in the REPOSITORY_LIST
+# variable, which causes the repo to be merged with its remote versions.
+# this enables a clean check-in; after puffer runs, there will be no secret
+# upstream changes that could mess up the git push (svn and cvs are not
+# supported in this script, since they branch differently than git).
+
+source "$FEISTY_MEOW_SCRIPTS/core/launch_feisty_meow.sh"
+source "$FEISTY_MEOW_SCRIPTS/rev_control/version_control.sh"
+
+##############
+
+echo "puffing out repositories at: $(date)"
+echo
+
+FULL_LIST=" $(dirname $FEISTY_MEOW_APEX) $HOME "
+if [ "$OS" == "Windows_NT" ]; then
+ FULL_LIST+=" c:/ d:/ e:/ "
+fi
+
+puff_out_list $FULL_LIST
+test_or_die "puffing out list: $FULL_LIST"
+
+##############
+
+# regenerate the scripts after puffing out, since this could mean a modified version
+# of feisty meow is present.
+regenerate
+
+##############
+
export MAX_DEPTH=5
# use our splitter tool for lengthy output if it's available.
-if [ ! -z "$(which splitter)" ]; then
+if [ ! -z "$(which splitter 2>/dev/null)" ]; then
TO_SPLITTER="$(which splitter)"
# calculate the number of columsn in the terminal.
cols=$(get_maxcols)
# see if there are any changes in the local repository.
if ! git diff-index --quiet HEAD --; then
# tell git about all the files and get a check-in comment.
+#hmmm: begins to look like, you guessed it, a reusable bit that all commit actions could enjoy.
git commit .
- test_or_die "git commit"
+ retval=$?
+ test_or_continue "git commit"
+ if [ $retval -ne 0 ]; then
+ echo -e -n "Commit failed or was aborted:\nShould we continue with other check-ins? [y/N] "
+ local line
+ read line
+ if [[ "${line:0:1}" != "y" ]]; then
+ echo "Stopping check-in process due to missing commit and user request."
+ exit 1
+ fi
+ fi
fi
# a new set of steps we have to take to make sure the branch integrity is good.
# there could already be committed changes that haven't been pushed yet.
# upload any changes to the upstream repo so others can see them.
- git push origin "$(my_branch_name)" 2>&1 | grep -v "X11 forwarding request failed" | $TO_SPLITTER
+ git push --tags origin "$(my_branch_name)" 2>&1 | grep -v "X11 forwarding request failed" | $TO_SPLITTER
promote_pipe_return 0
test_or_die "git push"
return 0
}
-# checks in all the folders in a specified list.
+# checks in all the folders in the specified list.
function checkin_list()
{
# make the list of directories unique.
if [[ $outer =~ /.* ]]; then
# yep, this path is absolute. just handle it directly.
if [ ! -d "$outer" ]; then continue; fi
- do_checkin $outer
+ do_checkin "$outer"
test_or_die "running check-in (absolute) on path: $outer"
sep 28
else
# add in the directory component to see if we can find the folder.
local path="$inner/$outer"
if [ ! -d "$path" ]; then continue; fi
- do_checkin $path
+ do_checkin "$path"
test_or_die "running check-in (relative) on path: $path"
sep 28
done
restore_terminal_title
}
-# takes out the first few carriage returns that are in the input.
-function squash_first_few_crs()
+# does a careful git update on all the folders in the specified list.
+function puff_out_list()
{
- i=0
- while read input_text; do
- i=$((i+1))
- if [ $i -le 5 ]; then
- echo -n "$input_text "
+ # make the list of directories unique.
+ local list="$(uniquify $*)"
+
+ save_terminal_title
+
+ # turn repo list back into an array.
+ eval "repository_list=( ${REPOSITORY_LIST[*]} )"
+
+ local outer inner
+
+#hmmm: once again, seeing some reusable code in this loop...
+ for outer in "${repository_list[@]}"; do
+ # check the repository first, since it might be an absolute path.
+ if [[ $outer =~ /.* ]]; then
+ # yep, this path is absolute. just handle it directly.
+ if [ ! -d "$outer" ]; then continue; fi
+ do_careful_git_update "$outer"
+ test_or_die "running puff-out (absolute) on path: $outer"
+ sep 28
else
- echo $input_text
+ for inner in $list; do
+ # add in the directory component to see if we can find the folder.
+ local path="$inner/$outer"
+ if [ ! -d "$path" ]; then continue; fi
+ do_careful_git_update "$path"
+ test_or_die "running puff-out (relative) on path: $path"
+ sep 28
+ done
fi
done
- if [ $i -le 3 ]; then
- # if we're still squashing eols, make sure we don't leave them hanging.
- echo
- fi
+
+ restore_terminal_title
}
+#hmmm: to go below.
+### takes out the first few carriage returns that are in the input.
+##function squash_first_few_crs()
+##{
+ ##i=0
+ ##while read input_text; do
+ ##i=$((i+1))
+ ##if [ $i -le 5 ]; then
+ ##echo -n "$input_text "
+ ##else
+ ##echo $input_text
+ ##fi
+ ##done
+ ##if [ $i -le 3 ]; then
+ ### if we're still squashing eols, make sure we don't leave them hanging.
+ ##echo
+ ##fi
+##}
+
#hmmm: the below are git specific and should be named that way.
function all_branch_names()
local remote_branch=$(git rev-parse "$branch")
local merge_base=$(git merge-base @ "$branch")
+ local to_echo=
if [ "$local_branch" == "$remote_branch" ]; then
- echo "okay"
+ to_echo="okay"
elif [ "$local_branch" == "$merge_base" ]; then
- echo "needs_pull"
+ to_echo="needs_pull"
elif [ "$remote_branch" == "$merge_base" ]; then
- echo "needs_push"
+ to_echo="needs_push"
else
- echo "diverged"
+ to_echo="diverged"
fi
+ echo -n "$to_echo"
+
return $to_return
}
{
local this_branch="$1"; shift
- state=$(check_branch_state "$this_branch")
+ local state=$(check_branch_state "$this_branch")
if [ "$state" != "okay" ]; then
echo "=> branch '$this_branch' state is not clean: $state"
fi
# we are pretty sure the remote branch does exist.
git pull --no-ff origin "$bran" | $TO_SPLITTER
promote_pipe_return 0
-
- echo "=> branch '$bran' state after pull is: $state"
fi
test_or_die "git pull of remote branch: $bran"
done
sep
-check_application_dir "$APPLICATION_DIR"
+check_application_dir "$BASE_APPLICATION_PATH"
# find proper webroot where the site will be initialized.
if [ -z "$app_dirname" ]; then
# no dir was passed, so guess it.
- find_app_folder "$APPLICATION_DIR"
+ find_app_folder "$BASE_APPLICATION_PATH"
else
- test_app_folder "$APPLICATION_DIR" "$app_dirname"
+ test_app_folder "$BASE_APPLICATION_PATH" "$app_dirname"
fi
# where we expect to find our checkout folder underneath.
-full_app_dir="$APPLICATION_DIR/$app_dirname"
+full_app_dir="$BASE_APPLICATION_PATH/$app_dirname"
# simplistic approach here; just go to the folder and pull the changes.
# basic information that is constant for all site avenger sites.
-export APPLICATION_DIR="$HOME/apps"
+# the top level of the user's application storage.
+export BASE_APPLICATION_PATH="$HOME/apps"
+# where the code should come from.
export DEFAULT_REPOSITORY_ROOT="git@github.com:kwentworth"
+# we checkout the git repository to a directory underneath the
+# app storage directory named this:
export CHECKOUT_DIR_NAME="avenger5"
+# the subfolder that the web browser will look for the site in,
+# underneath the application's specific path.
+export STORAGE_SUFFIX="/public"
+
+####
+
+# constants within our cakelampvm machine.
+
+# in our scheme, the single IP address that all our domains map to.
+export IP_ADDRESS="10.28.42.20"
+# the email address (where first dot is replaced by @) for the administrator of the domain.
+export SERVER_ADMIN="developer.cakelampvm.com"
+# the name of the name server for the new domains (should already be configured).
+export MAIN_NAME_SERVER="ns.cakelampvm.com"
+# the name of the mail server for a new domain (should already be configured).
+export MAIL_SERVER="mail.cakelampvm.com"
+# the distribution name to be listed in info for the new domain or subdomain.
+export DISTRO="ubuntu"
####
sep
-check_application_dir "$APPLICATION_DIR"
+check_application_dir "$BASE_APPLICATION_PATH"
# find proper webroot where the site will be initialized.
if [ -z "$app_dirname" ]; then
# no dir was passed, so guess it.
- find_app_folder "$APPLICATION_DIR"
+ find_app_folder "$BASE_APPLICATION_PATH"
else
- test_app_folder "$APPLICATION_DIR" "$app_dirname"
+ test_app_folder "$BASE_APPLICATION_PATH" "$app_dirname"
fi
# where we expect to find our checkout folder underneath.
-full_app_dir="$APPLICATION_DIR/$app_dirname"
+full_app_dir="$BASE_APPLICATION_PATH/$app_dirname"
# use our default values for the repository and theme if they're not provided.
if [ -z "$repo_name" ]; then
#if [ ! -z "$user_name" ]; then
# echo "Chowning the apps folder to be owned by: $user_name"
##hmmm: have to hope for now for standard group named after user
-# chown -R "$user_name:$user_name" "$APPLICATION_DIR"
-# test_or_die "Chowning $APPLICATION_DIR to be owned by $user_name"
+# chown -R "$user_name:$user_name" "$BASE_APPLICATION_PATH"
+# test_or_die "Chowning $BASE_APPLICATION_PATH to be owned by $user_name"
#fi
sep
export WORKDIR="$( \cd "$(\dirname "$0")" && \pwd )" # obtain the script's working directory.
export FEISTY_MEOW_APEX="$( \cd "$WORKDIR/../.." && \pwd )"
+export NO_HELLO=right
source "$FEISTY_MEOW_APEX/scripts/core/launch_feisty_meow.sh"
##############
# fix up the main web storage.
chown -R www-data:www-data /var/www
+test_or_die "chown www-data"
group_perm /var/www
+test_or_die "group_perm www-data"
##############
# set up access on some important folders for the developer user.
chown -R developer:developer /home/developer
+test_or_die "chown developer home"
harsh_perm /home/developer/.ssh
-chown -R developer:developer /opt/feistymeow.org
-group_perm /opt/feistymeow.org
+test_or_die "harsh_perm setting on developer .ssh"
chown -R developer:developer /etc/apache2 /etc/bind
+test_or_die "chown apache2 and bind to developer"
group_perm /etc/apache2 /etc/bind
+test_or_die "group perms on apache2 and bind"
##############
# fix perms for fred user.
chown -R fred:fred /home/fred /home/archives/stuffing
+test_or_die "chown fred home"
harsh_perm /home/fred/.ssh
+test_or_die "harsh_perm setting on fred .ssh"
+chown -R fred:fred /opt/feistymeow.org
+test_or_die "chown feisty meow to fred"
+group_perm /opt/feistymeow.org
+test_or_die "group perms on feisty meow"
+
+##############
+#
+# some slightly tricky bits start here. we want to massage the vm into the
+# best possible shape without needing to re-release it.
+#
+##############
+
+# only update hello if they've still got the file there. we don't want to
+# keep forcing our hellos at people.
+if [ -f "$HOME/hello.txt" ]; then
+ # copy the most recent hello file into place for the user.
+ \cp -f "$FEISTY_MEOW_APEX/production/sites/cakelampvm.com/hello.txt" "$HOME"
+ test_or_continue "copying hello file for user"
+fi
+
+##############
+
+# install a better editor app.
+
+echo "
+The script is about to install the bluefish editor and some dependencies.
+If the app is not already installed, then this process takes only about a
+minute on a slower home DSL internet connection...
+
+"
+
+apt-get install -y bluefish &> "/tmp/install_bluefish-$(logname).log"
+test_or_continue "failed to install bluefish editor. not good."
##############
+
+#hmmm: todo
+# deploy the site updater here to fix the local cakelampvm.com site...
+
+
+##############
+
+# sequel--tell them they're great and show the hello again also.
+
+regenerate
+
+echo "
+
+
+Thanks for revamping your cakelampvm. :-)
+"
+
+##############
+
+
local app_dirname="$1"; shift
local configfile="$WORKDIR/config/${app_dirname}.app"
-echo hoping config file would be: $configfile
+ echo "config file?: $configfile"
if [ ! -f "$configfile" ]; then
# this is not a good config file. we can't auto-guess the config.
echo -e "
sep
-check_application_dir "$APPLICATION_DIR"
+check_application_dir "$BASE_APPLICATION_PATH"
# find proper webroot where the site will be initialized.
if [ -z "$app_dirname" ]; then
# no dir was passed, so guess it.
- find_app_folder "$APPLICATION_DIR"
+ find_app_folder "$BASE_APPLICATION_PATH"
else
- test_app_folder "$APPLICATION_DIR" "$app_dirname"
+ test_app_folder "$BASE_APPLICATION_PATH" "$app_dirname"
fi
# where we expect to find our checkout folder underneath.
-full_app_dir="$APPLICATION_DIR/$app_dirname"
+full_app_dir="$BASE_APPLICATION_PATH/$app_dirname"
# use our default values for the repository and theme if they're not provided.
if [ -z "$repo_name" ]; then
sep
-check_application_dir "$APPLICATION_DIR"
+check_application_dir "$BASE_APPLICATION_PATH"
# find proper webroot where the site will be initialized.
if [ -z "$app_dirname" ]; then
# no dir was passed, so guess it.
- find_app_folder "$APPLICATION_DIR"
+ find_app_folder "$BASE_APPLICATION_PATH"
else
- test_app_folder "$APPLICATION_DIR" "$app_dirname"
+ test_app_folder "$BASE_APPLICATION_PATH" "$app_dirname"
fi
# where we expect to find our checkout folder underneath.
-full_app_dir="$APPLICATION_DIR/$app_dirname"
+full_app_dir="$BASE_APPLICATION_PATH/$app_dirname"
# use our default values for the repository and theme if they're not provided.
if [ -z "$repo_name" ]; then
print_instructions
fi
-#we will require sudo later.
-#if [[ $EUID != 0 ]]; then
-# echo "This script must be run as root or sudo."
-# exit 1
-#fi
+# force the sudo at the start of the script, rather than waiting halfway
+# through to ask for access.
+sudo bash -c 'echo sudo permissions acquired.'
source "$WORKDIR/shared_site_mgr.sh"
sep
-check_application_dir "$APPLICATION_DIR"
+check_application_dir "$BASE_APPLICATION_PATH"
# find proper webroot where the site will be initialized.
if [ -z "$app_dirname" ]; then
# no dir was passed, so guess it.
- find_app_folder "$APPLICATION_DIR"
+ find_app_folder "$BASE_APPLICATION_PATH"
else
- test_app_folder "$APPLICATION_DIR" "$app_dirname"
+ test_app_folder "$BASE_APPLICATION_PATH" "$app_dirname"
fi
#echo "!! domain being added is: $DOMAIN_NAME"
#!/bin/bash
-echo "sorry--this script is not implemented yet."
+# this performs the inverse operation of standup, by relying on the
+# remove_domain and remove_apache_site scripts.
+#
+# Author: Chris Koeritz
+export WORKDIR="$( \cd "$(\dirname "$0")" && \pwd )" # obtain the script's working directory.
+export FEISTY_MEOW_APEX="$( \cd "$WORKDIR/../.." && \pwd )"
+source "$FEISTY_MEOW_APEX/scripts/core/launch_feisty_meow.sh"
-# need the inverse of add_domain and add_apache_site.
-# can use the same machinery as standup, just need to invoke these two new removal methods.
+############################
-# the decommissioning of the app is a question though. we don't want to delete it, i'm pretty sure.
-# so how about that part is to do nothing?
+function print_instructions()
+{
+ echo
+ echo "$(basename $0 .sh) {app name}"
+ echo
+ echo "
+$(basename $0 .sh) will drop a web site out of apache server and out of the
+DNS server, as if it never existed. The site storage is left untouched; we
+don't know what valuable assets lurk there.
+This script must be run as sudo or root; it makes changes to system files.
+"
+ exit 0
+}
+
+############################
+
+# main body of script.
+
+# check for parameters.
+app_dirname="$1"; shift
+
+if [ "$app_dirname" == "-help" -o "$app_dirname" == "--help" ]; then
+ print_instructions
+elif [ -z "$app_dirname" ]; then
+ print_instructions
+fi
+
+# force the sudo at the start of the script, rather than waiting halfway
+# through to ask for access.
+sudo bash -c 'echo sudo permissions acquired.'
+
+source "$WORKDIR/shared_site_mgr.sh"
+
+sep
+
+check_application_dir "$BASE_APPLICATION_PATH"
+
+# find proper webroot where the site will be initialized.
+if [ -z "$app_dirname" ]; then
+ # no dir was passed, so guess it.
+ find_app_folder "$BASE_APPLICATION_PATH"
+else
+ test_app_folder "$BASE_APPLICATION_PATH" "$app_dirname"
+fi
+
+sep
+
+sudo bash "$FEISTY_MEOW_SCRIPTS/system/remove_apache_site.sh" "$DOMAIN_NAME"
+test_or_die "dropping apache site for: $DOMAIN_NAME"
+
+sep
+
+#echo "!! domain being removed is: $DOMAIN_NAME"
+
+sudo bash "$FEISTY_MEOW_SCRIPTS/system/remove_domain.sh" "$DOMAIN_NAME"
+test_or_die "dropping domain: $DOMAIN_NAME"
+
+sep
+
+echo "
+Finished tearing down the domain name and apache site for:
+ $DOMAIN_NAME
+"
export FEISTY_MEOW_APEX="$( \cd "$WORKDIR/../.." && \pwd )"
source "$FEISTY_MEOW_APEX/scripts/core/launch_feisty_meow.sh"
+source "$FEISTY_MEOW_SCRIPTS/system/common_sysadmin.sh"
# some convenient defaults for our current usage.
-BASE_PATH="$HOME/apps"
-STORAGE_SUFFIX="/public"
-
-# this function writes out the new configuration file for the site.
-function write_apache_config()
-{
- local appname="$1"; shift
- local sitename="$1"; shift
- local site_path="$1"; shift
-
- local site_config="/etc/apache2/sites-available/${sitename}.conf"
-
- # check if config file already exists and bail if so.
- if [ -f "$site_config" ]; then
- echo "The apache configuration file already exists at:"
- echo " $site_config"
- echo "Since apache configuration files can get very complex, we do not want to"
- echo "assume that this file is removable. Calling the site addition done."
- exit 0
- fi
-
- echo "Creating a new apache2 site for $sitename with config file:"
- echo " $site_config"
-
- # if no path, then we default to our standard app storage location. otherwise, we
- # put the site where they told us to.
- if [ -z "$site_path" ]; then
- # path where site gets checked out, in some arcane manner, and which happens to be
- # above the path where we put webroot (in the storage suffix, if defined).
- local path_above="${BASE_PATH}/${appname}"
- # no slash between appname and suffix, in case suffix is empty.
- local full_path="${path_above}${STORAGE_SUFFIX}"
-#echo really full path is $full_path
- else
- # we'll go with their specification for the site storage.
- local full_path="$site_path"
- fi
-
- echo "
-# set up the user's web folder as an apache user web directory.
-
-# set permissions on the actual app folder.
-<Directory \"$full_path\">
- Options +ExecCGI +Indexes +FollowSymLinks +Includes +MultiViews
- Require all granted
-</Directory>
-
-<VirtualHost *:80>
- ServerName ${sitename}
- DocumentRoot ${full_path}
- ErrorLog \${APACHE_LOG_DIR}/${sitename}-error.log
- CustomLog \${APACHE_LOG_DIR}/${sitename}-access.log combined
- Include /etc/apache2/conf-library/basic-options.conf
- Include /etc/apache2/conf-library/rewrite-enabling.conf
-</VirtualHost>
-" >"$site_config"
-
- chown "$(logname):$(logname)" "$site_config"
- test_or_die "setting ownership on: $site_config"
-}
-
-# turns on the config file we create above for apache.
-function enable_site()
-{
- local sitename="$1"; shift
- local site_config="/etc/apache2/sites-available/${sitename}.conf"
-
- outfile="$TMP/apacheout.$RANDOM"
- a2ensite "$(basename $site_config)" &>$outfile
- if [ $? -ne 0 ]; then
- # an error happened, so we show the command's output at least.
- cat $outfile
- echo
- echo "There was a problem enabling the apache config file in:"
- echo " $site_config"
- echo "Please consult the apache error logs for more details."
- exit 1
- fi
- \rm "$outfile"
-}
-
-# restarts the apache2 service.
-function restart_apache()
-{
- service apache2 restart
- if [ $? -ne 0 ]; then
- echo "There was a problem restarting the apache2 service."
- echo "Please consult the apache error logs for more details."
- exit 1
- fi
-}
-
-# sets up the serverpilot storage location for a user hosted web site.
-function maybe_create_site_storage()
-{
- local our_app="$1"; shift
- # make sure the base path for storage of all the apps for this user exists.
- local full_path="$BASE_PATH/$our_app"
- if [ ! -d "$full_path" ]; then
- mkdir -p $full_path
- test_or_die "The app storage path could not be created.\n Path in question is: $full_path"
- fi
-
- # now give the web server some access to the folder. this is crucial since the folders
- # can be hosted in any user folder, and the group permissions will not necessarily be correct already.
- local chow_path="$full_path"
- # only the first chmod is recursive; the rest just apply to the specific folder of interest.
- chmod -R g+rx "$chow_path"
- # walk backwards up the path and fix perms.
- while [[ $chow_path != $HOME ]]; do
-echo chow path is now $chow_path
- chmod g+rx "$chow_path"
- test_or_die "Failed to add group permissions on the path: $chow_path"
- # reassert the user's ownership of any directories we might have just created.
- chown $(logname) "$chow_path"
- test_or_die "changing ownership to user failed on the path: $chow_path"
- chow_path="$(dirname "$chow_path")"
- done
-}
+if [ -z "$BASE_APPLICATION_PATH" ]; then
+ BASE_APPLICATION_PATH="$HOME/apps"
+fi
+if [ -z "$STORAGE_SUFFIX" ]; then
+ STORAGE_SUFFIX="/public"
+fi
# main body of script.
appropriate name for a file-system compatible folder name. There is an
optional third parameter (3) the path for site storage. If the site path
is not provided, we'll use this path:
- $BASE_PATH/{app name}/$STORAGE_SUFFIX"
+ $BASE_APPLICATION_PATH/{app name}$STORAGE_SUFFIX"
exit 1
fi
#!/bin/bash
-# this set of functions serve the main purpose of adding new domains or subdomains to the bind9 DNS server on the current host.
-# it is currently highly specific to running a bunch of domains on a linux VM, where the VM has one IP address.
-# note that bind 'named' must already be configured.
-# also, it is assumed that if a subdomain is being added, then the containing domain has already been configured and is
-# configured in a file similar to "blah.com.conf" in /etc/bind.
+# this set of functions serve the main purpose of adding new domains or
+# subdomains to the bind9 DNS server on the current host. it is currently
+# highly specific to running a bunch of domains on a linux VM, where the VM
+# has one IP address. note that the bind 'named' must already be configured.
+# also, it is assumed that, if a subdomain is being added, then the containing
+# domain has already been configured and is configured in a file similar to
+# "blah.com.conf" in /etc/bind.
#
# Author: Chris Koeritz
export FEISTY_MEOW_APEX="$( \cd "$WORKDIR/../.." && \pwd )"
source "$FEISTY_MEOW_APEX/scripts/core/launch_feisty_meow.sh"
+source "$FEISTY_MEOW_SCRIPTS/system/common_sysadmin.sh"
# some defaults that are convenient for current purposes.
+# existing values will be respected over our defaults.
-# hmmm: !!! these would need to be parameterized somehow for this script to become really general.
-
-# in our scheme, the single IP address that all our domains map to.
-IP_ADDRESS="10.28.42.20"
-# the email address (where first dot is replaced by @) for the administrator of the domain.
-SERVER_ADMIN="developer.cakelampvm.com"
-# the name of the name server for the new domains (should already be configured).
-MAIN_NAME_SERVER="ns.cakelampvm.com"
-# the name of the mail server for a new domain (should already be configured).
-MAIL_SERVER="mail.cakelampvm.com"
-# the distribution name to be listed in info for the new domain or subdomain.
-DISTRO="ubuntu"
-
-# creates a totally new domain config file for DNS.
-function write_new_domain_file()
-{
- local domain_name="$1"; shift
-
- local domain_file="/etc/bind/${domain_name}.conf"
-
- echo "adding a totally new domain called $domain_name"
- echo "using the config file: $domain_file"
-
- if [ -f $domain_file ]; then
- echo
- echo "The domain configuration file already exists at:"
- echo " $domain_file"
- echo "Since we don't want to tear that down if it has specialized configuration"
- echo "data in it, we will just leave it in place and consider our job done."
- echo
- exit 0
- fi
-
- echo "
-\$TTL 1W
-@ IN SOA @ ${SERVER_ADMIN}. (
- 2017100801 ; serial
- 2H ; refresh
- 8M ; retry
- 14D ; expiry
- 6H ) ; minimum
-
- IN NS ${MAIN_NAME_SERVER}.
- IN MX 10 ${MAIL_SERVER}.
-
-${domain_name}. IN A ${IP_ADDRESS}
- IN HINFO \"linux server\" \"${DISTRO}\"
-" >"$domain_file"
-
- # our personalized configuration approach wants the real owner to own the file.
- chown "$(logname):$(logname)" $domain_file
- test_or_die "setting ownership on: $domain_file"
-}
-
-# hooks up a new config file into bind's list of zones.
-function add_zone_for_new_domain()
-{
- local domain_name="$1"; shift
-
- local domain_file="/etc/bind/${domain_name}.conf"
-
- echo "adding a new domain configured by ${domain_file} into"
- echo "the named.conf.local configuration file."
-
- # append the reference to the new conf file in the zone list.
- echo "
-zone \"${domain_name}\" in {
- file \"${domain_file}\";
- type master;
- allow-query { any; };
-};
-
-////////////////////////////////////////////////////////////////////////////
-
-" >> /etc/bind/named.conf.local
-
- # keep ownership for the real user.
- chown "$(logname):$(logname)" /etc/bind/named.conf.local
- test_or_die "setting ownership on: /etc/bind/named.conf.local"
-
-}
-
-# adds a new subdomain under a containing domain.
-function add_new_subdomain()
-{
- local new_domain="$1"; shift
-
- # split up the full domain name into subdomain portion and containing domain.
- local subdomain="${new_domain%.*.*}"
- local containing_domain="${new_domain#*.}"
-
- echo "adding a subdomain $subdomain to containing domain $containing_domain"
-
- local domain_file="/etc/bind/${containing_domain}.conf"
- # see if config file already exists; if not, complain.
- if [ ! -f "$domain_file" ]; then
- echo "The domain configuration file for $new_domain is missing."
- echo "It should already be present in: $domain_file"
- echo "Please add the containing domain before trying to add a subdomain."
- exit 1
- fi
-
- # see if subdomain already present in config.
- if [ $(grep -q "$new_domain" "$domain_file") ]; then
- echo "The subdomain $subdomain already seems to exist in the domain"
- echo "configuration file: $domain_file"
- echo "Please edit the config file to remove the subdomain before trying"
- echo "to re-add the subdomain."
- exit 1
- fi
-
- # append the new subdomain into the config file.
- echo "
-${subdomain}.${containing_domain}. IN A ${IP_ADDRESS}
- IN HINFO \"linux server\" \"${DISTRO}\"
-" >> /etc/bind/${containing_domain}.conf
-
- # keep ownership for real user.
- chown "$(logname):$(logname)" "/etc/bind/${containing_domain}.conf"
- test_or_die "setting ownership on: /etc/bind/${containing_domain}.conf"
-}
-
-function restart_bind()
-{
- echo restarting DNS server.
- service bind9 restart
- if [ $? -ne 0 ]; then
- echo "The bind service did not restart properly. Please check the error logs."
- exit 1
- fi
- echo DNS server restarted.
-}
+if [ -z "$IP_ADDRESS" ]; then
+ # in our scheme, the single IP address that all our domains map to.
+ IP_ADDRESS="10.28.42.20"
+fi
+if [ -z "$SERVER_ADMIN" ]; then
+ # the email address (where first dot is replaced by @) for the administrator of the domain.
+ SERVER_ADMIN="developer.cakelampvm.com"
+fi
+if [ -z "$MAIN_NAME_SERVER" ]; then
+ # the name of the name server for the new domains (should already be configured).
+ MAIN_NAME_SERVER="ns.cakelampvm.com"
+fi
+if [ -z "$MAIL_SERVER" ]; then
+ # the name of the mail server for a new domain (should already be configured).
+ MAIL_SERVER="mail.cakelampvm.com"
+fi
+if [ -z "$DISTRO" ]; then
+ # the distribution name to be listed in info for the new domain or subdomain.
+ DISTRO="ubuntu"
+fi
# main body of script.
--- /dev/null
+#!/bin/bash
+
+# this is a library of functions shared by scripts in the system folder.
+#
+# Author: Chris Koeritz
+
+############################################################################
+
+# bind9 methods...
+
+# removes a full domain from the DNS.
+function remove_domain_file()
+{
+ local domain_name="$1"; shift
+
+ local domain_file="/etc/bind/${domain_name}.conf"
+ if [ -f "$domain_file" ]; then
+ # don't destroy, just shuffle.
+ \mv -f "$domain_file" "/tmp/$(basename ${domain_file})-old-${RANDOM}"
+ test_or_die "removing domain file: $domain_file"
+ else
+ echo "Did not see a domain file to remove: $domain_file"
+ fi
+}
+
+# creates a totally new domain config file for DNS.
+function write_new_domain_file()
+{
+ local domain_name="$1"; shift
+
+ local domain_file="/etc/bind/${domain_name}.conf"
+
+ echo "adding a totally new domain called $domain_name"
+ echo "using the config file: $domain_file"
+
+ if [ -f $domain_file ]; then
+ echo
+ echo "The domain configuration file already exists at:"
+ echo " $domain_file"
+ echo "Since we don't want to tear that down if it has specialized configuration"
+ echo "data in it, we will just leave it in place and consider our job done."
+ echo
+ exit 0
+ fi
+
+ echo "
+\$TTL 1W
+@ IN SOA @ ${SERVER_ADMIN}. (
+ 2017100801 ; serial
+ 2H ; refresh
+ 8M ; retry
+ 14D ; expiry
+ 6H ) ; minimum
+
+ IN NS ${MAIN_NAME_SERVER}.
+ IN MX 10 ${MAIL_SERVER}.
+
+${domain_name}. IN A ${IP_ADDRESS}
+ IN HINFO \"linux server\" \"${DISTRO}\"
+" >"$domain_file"
+
+ # our personalized configuration approach wants the real owner to own the file.
+ chown "$(logname):$(logname)" $domain_file
+ test_or_die "setting ownership on: $domain_file"
+}
+
+# takes a zone back out of the local conf file for bind
+function remove_zone_for_domain()
+{
+ local domain_name="$1"; shift
+
+ local domain_file="/etc/bind/${domain_name}.conf"
+
+ # eat the zone file definition. this will botch up badly if more text was added
+ # or the zone info shrank.
+ create_chomped_copy_of_file "/etc/bind/named.conf.local" "zone.*${domain_name}" 6
+}
+
+# hooks up a new config file into bind's list of zones.
+function add_zone_for_new_domain()
+{
+ local domain_name="$1"; shift
+
+ local domain_file="/etc/bind/${domain_name}.conf"
+
+ echo "adding a new domain configured by ${domain_file} into"
+ echo "the named.conf.local configuration file."
+
+ # append the reference to the new conf file in the zone list.
+ echo "
+zone \"${domain_name}\" in {
+ file \"${domain_file}\";
+ type master;
+ allow-query { any; };
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+" >> /etc/bind/named.conf.local
+
+ # keep ownership for the real user.
+ chown "$(logname):$(logname)" /etc/bind/named.conf.local
+ test_or_die "setting ownership on: /etc/bind/named.conf.local"
+}
+
+# zaps a subdomain out of the containing domain file.
+function remove_subdomain()
+{
+ local old_domain="$1"; shift
+
+ # split up the full domain name into subdomain portion and containing domain.
+ local subdomain="${old_domain%.*.*}"
+ local containing_domain="${old_domain#*.}"
+
+ echo "removing subdomain $subdomain from containing domain $containing_domain"
+
+ local domain_file="/etc/bind/${containing_domain}.conf"
+ # see if config file already exists; if not, complain.
+ if [ ! -f "$domain_file" ]; then
+ echo "The domain configuration file for $old_domain is missing."
+ echo "It should already be present in: $domain_file"
+ echo "We cannot remove a subdomain if the containing domain isn't there."
+ exit 1
+ fi
+
+ # see if subdomain already present in config.
+ if ! grep -q "$old_domain" "$domain_file"; then
+ echo "The subdomain $subdomain is already missing from the domain"
+ echo "configuration file: $domain_file"
+ echo "Our work is apparently done for removing it."
+ return 0
+ fi
+
+ create_chomped_copy_of_file "$domain_file" "${old_domain}" 2
+}
+
+# adds a new subdomain under a containing domain.
+function add_new_subdomain()
+{
+ local new_domain="$1"; shift
+
+ # split up the full domain name into subdomain portion and containing domain.
+ local subdomain="${new_domain%.*.*}"
+ local containing_domain="${new_domain#*.}"
+
+ echo "adding a subdomain $subdomain to containing domain $containing_domain"
+
+ local domain_file="/etc/bind/${containing_domain}.conf"
+ # see if config file already exists; if not, complain.
+ if [ ! -f "$domain_file" ]; then
+ echo "The domain configuration file for $new_domain is missing."
+ echo "It should already be present in: $domain_file"
+ echo "Please add the containing domain before trying to add a subdomain."
+ exit 1
+ fi
+
+ # see if subdomain already present in config.
+ if grep -q "$new_domain" "$domain_file"; then
+ echo "The subdomain $subdomain already seems to exist in the domain"
+ echo "configuration file: $domain_file"
+ echo "We are considering our work done; if you want to modify the subdomain,"
+ echo "then please call remove_domain on it first."
+ return 0
+ fi
+
+ # append the new subdomain into the config file.
+ echo "${subdomain}.${containing_domain}. IN A ${IP_ADDRESS}
+ IN HINFO \"linux server\" \"${DISTRO}\"
+" >> /etc/bind/${containing_domain}.conf
+
+ # keep ownership for real user.
+ chown "$(logname):$(logname)" "/etc/bind/${containing_domain}.conf"
+ test_or_die "setting ownership on: /etc/bind/${containing_domain}.conf"
+}
+
+function restart_bind()
+{
+ echo restarting DNS server.
+ service bind9 restart
+ if [ $? -ne 0 ]; then
+ echo "The bind service did not restart properly. Please check the error logs."
+ exit 1
+ fi
+ echo DNS server restarted.
+}
+
+############################################################################
+
+# apache2 methods...
+
+# removes a config file for apache given the app name and site name.
+function remove_apache_config()
+{
+ local sitename="$1"; shift
+
+ local site_config="/etc/apache2/sites-available/${sitename}.conf"
+
+ if [ -f "$site_config" ]; then
+ # don't destroy, just shuffle.
+ \mv -f "$site_config" "/tmp/$(basename ${site_config})-old-${RANDOM}"
+ test_or_die "removing site config: $site_config"
+ else
+ echo "Did not see a site config to remove: $site_config"
+ fi
+}
+
+# this function writes out the new configuration file for the site.
+function write_apache_config()
+{
+ local appname="$1"; shift
+ local sitename="$1"; shift
+ local site_path="$1"; shift
+
+ local site_config="/etc/apache2/sites-available/${sitename}.conf"
+
+ # check if config file already exists and bail if so.
+ if [ -f "$site_config" ]; then
+ echo "The apache configuration file already exists at:"
+ echo " $site_config"
+ echo "Since apache configuration files can get very complex, we do not want to"
+ echo "assume that this file is removable. Calling the site addition done."
+ exit 0
+ fi
+
+ echo "Creating a new apache2 site for $sitename with config file:"
+ echo " $site_config"
+
+ # if no path, then we default to our standard app storage location. otherwise, we
+ # put the site where they told us to.
+ if [ -z "$site_path" ]; then
+ # path where site gets checked out, in some arcane manner, and which happens to be
+ # above the path where we put webroot (in the storage suffix, if defined).
+ local path_above="${BASE_APPLICATION_PATH}/${appname}"
+ # no slash between appname and suffix, in case suffix is empty.
+ local full_path="${path_above}${STORAGE_SUFFIX}"
+#echo really full path is $full_path
+ else
+ # we'll go with their specification for the site storage.
+ local full_path="$site_path"
+ fi
+
+ echo "
+# set up the user's web folder as an apache user web directory.
+
+# set permissions on the actual app folder.
+<Directory \"$full_path\">
+ Options +ExecCGI +Indexes +FollowSymLinks +Includes +MultiViews
+ Require all granted
+</Directory>
+
+<VirtualHost *:80>
+ ServerName ${sitename}
+ DocumentRoot ${full_path}
+ ErrorLog \${APACHE_LOG_DIR}/${sitename}-error.log
+ CustomLog \${APACHE_LOG_DIR}/${sitename}-access.log combined
+ Include /etc/apache2/conf-library/basic-options.conf
+ Include /etc/apache2/conf-library/rewrite-enabling.conf
+</VirtualHost>
+" >"$site_config"
+
+ chown "$(logname):$(logname)" "$site_config"
+ test_or_die "setting ownership on: $site_config"
+}
+
+# stops apache from serving up the site.
+function disable_site()
+{
+ local sitename="$1"; shift
+ local site_config="/etc/apache2/sites-available/${sitename}.conf"
+
+ if [ ! -f "$site_config" ]; then
+ echo "The site config did not exist and could not be disabled: $site_config"
+ return 0
+ fi
+
+#hmmm: repeated pattern of hidden output file, very useful. abstract it...
+ local outfile="$TMP/apacheout.$RANDOM"
+ a2dissite "$(basename $site_config)" &>$outfile
+ if [ $? -ne 0 ]; then
+ # an error happened, so we show the command's output at least.
+ cat $outfile
+ echo
+ echo "There was a problem disabling the apache config file in:"
+ echo " $site_config"
+ echo "Please consult the apache error logs for more details."
+ exit 1
+ fi
+ \rm "$outfile"
+}
+
+# turns on the config file we create above for apache.
+function enable_site()
+{
+ local sitename="$1"; shift
+ local site_config="/etc/apache2/sites-available/${sitename}.conf"
+
+ local outfile="$TMP/apacheout.$RANDOM"
+ a2ensite "$(basename $site_config)" &>$outfile
+ if [ $? -ne 0 ]; then
+ # an error happened, so we show the command's output at least.
+ cat $outfile
+ echo
+ echo "There was a problem enabling the apache config file in:"
+ echo " $site_config"
+ echo "Please consult the apache error logs for more details."
+ exit 1
+ fi
+ \rm "$outfile"
+}
+
+# restarts the apache2 service.
+function restart_apache()
+{
+ service apache2 restart
+ if [ $? -ne 0 ]; then
+ echo "There was a problem restarting the apache2 service."
+ echo "Please consult the apache error logs for more details."
+ exit 1
+ fi
+}
+
+# sets up the serverpilot storage location for a user hosted web site.
+function maybe_create_site_storage()
+{
+ local our_app="$1"; shift
+ # make sure the path for storage this app exists for the user.
+ local full_path="$BASE_APPLICATION_PATH/$our_app"
+ if [ ! -d "$full_path" ]; then
+ mkdir -p $full_path
+ test_or_die "The app storage path could not be created.\n Path in question is: $full_path"
+ fi
+
+ # now give the web server some access to the folder. this is crucial since the folders
+ # can be hosted in any user folder, and the group permissions will not necessarily be correct already.
+ local chow_path="$full_path"
+ # only the first chmod is recursive; the rest just apply to the specific folder of interest.
+ chmod -R g+rx "$chow_path"
+ # walk backwards up the path and fix perms.
+ while [[ $chow_path != $HOME ]]; do
+#echo chow path is now $chow_path
+ chmod g+rx "$chow_path"
+ test_or_die "Failed to add group permissions on the path: $chow_path"
+ # reassert the user's ownership of any directories we might have just created.
+ chown $(logname) "$chow_path"
+ test_or_die "changing ownership to user failed on the path: $chow_path"
+ chow_path="$(dirname "$chow_path")"
+ done
+}
+
+############################################################################
+
+
--- /dev/null
+#!/bin/bash
+
+# uninstalls the apache website for a specified domain.
+
+# auto-find the scripts, since we might want to run this as sudo.
+export WORKDIR="$( \cd "$(\dirname "$0")" && /bin/pwd )" # obtain the script's working directory.
+export FEISTY_MEOW_APEX="$( \cd "$WORKDIR/../.." && \pwd )"
+
+source "$FEISTY_MEOW_APEX/scripts/core/launch_feisty_meow.sh"
+source "$FEISTY_MEOW_SCRIPTS/system/common_sysadmin.sh"
+
+# some convenient defaults for our current usage.
+
+if [ -z "$BASE_APPLICATION_PATH" ]; then
+ BASE_APPLICATION_PATH="$HOME/apps"
+fi
+if [ -z "$STORAGE_SUFFIX" ]; then
+ STORAGE_SUFFIX="/public"
+fi
+
+# main body of script.
+
+if [[ $EUID != 0 ]]; then
+ echo "This script must be run as root or sudo."
+ exit 1
+fi
+
+site="$1"; shift
+
+if [ -z "$site" ]; then
+#hmmm: move to a print_instructions function.
+ echo "
+$(basename $0): {dns name}
+
+This script needs to know (1) the DNS name for the apache virtual host.
+The script will uninstall that site's configuration files for apache2.
+"
+ exit 1
+fi
+
+disable_site "$site"
+remove_apache_config "$site"
+restart_apache
+
--- /dev/null
+#!/bin/bash
+
+# performs the inverse function of add_domain by deconfiguring a domain
+# in bind. the domain needs to have been set up by add_domain, or this will
+# not succeed.
+#
+# Author: Chris Koeritz
+
+export WORKDIR="$( \cd "$(\dirname "$0")" && \pwd )" # obtain the script's working directory.
+export FEISTY_MEOW_APEX="$( \cd "$WORKDIR/../.." && \pwd )"
+
+source "$FEISTY_MEOW_APEX/scripts/core/launch_feisty_meow.sh"
+source "$FEISTY_MEOW_SCRIPTS/system/common_sysadmin.sh"
+
+# some defaults that are convenient for current purposes.
+# existing values will be respected over our defaults.
+
+#if [ -z "$IP_ADDRESS" ]; then
+# # in our scheme, the single IP address that all our domains map to.
+# IP_ADDRESS="10.28.42.20"
+#fi
+#if [ -z "$SERVER_ADMIN" ]; then
+# # the email address (where first dot is replaced by @) for the administrator of the domain.
+# SERVER_ADMIN="developer.cakelampvm.com"
+#fi
+#if [ -z "$MAIN_NAME_SERVER" ]; then
+# # the name of the name server for the new domains (should already be configured).
+# MAIN_NAME_SERVER="ns.cakelampvm.com"
+#fi
+#if [ -z "$MAIL_SERVER" ]; then
+# # the name of the mail server for a new domain (should already be configured).
+# MAIL_SERVER="mail.cakelampvm.com"
+#fi
+#if [ -z "$DISTRO" ]; then
+# # the distribution name to be listed in info for the new domain or subdomain.
+# DISTRO="ubuntu"
+#fi
+
+# main body of script.
+
+if [[ $EUID != 0 ]]; then
+ echo "This script must be run as root or sudo."
+ exit 1
+fi
+
+old_domain="$1"; shift
+
+if [ -z "$old_domain" ]; then
+ echo "This script needs a domain name to remove from DNS."
+ exit 1
+fi
+
+# if domain name has three or more components, then remove a subdomain.
+# otherwise, remove a full domain.
+if [[ $old_domain == *"."*"."* ]]; then
+ # remove a subdomain from the containing domain.
+ remove_subdomain "$old_domain"
+ restart_bind
+else
+ # remove the full domain in DNS.
+ remove_domain_file "$old_domain"
+ remove_zone_for_domain "$old_domain"
+ restart_bind
+fi
+
+
title="$(hostname)"
fi
- if [ "${TERM}" != "dumb" -a -z "$PBS_ENVIRONMENT" -a ! -z "$PS1" ]; then
+ if [ "${TERM}" != "dumb" -a -z "$PBS_ENVIRONMENT" -a \
+ ! -z "$PS1" -a "${TERM}" != "linux" ]; then
echo -n -e "\033]0;${title}\007"
else
# not running interactively, so just echo the title.
function set_terminal_title()
{
apply_title_to_terminal $*
+
+#tricky tries to get it to be available when we ask for it in get_terminal_title
+ sync
+# echo -n
+
+# # we're enforcing a new title from here on.
+# unset PRIOR_TERMINAL_TITLE
save_terminal_title
}
-# reads the current terminal title, if possible, and saves it to our record.
-function save_terminal_title()
+# echoes back the current title on the terminal window, if we can acquire it.
+function get_terminal_title()
{
+ # this is an important value now; it is checked for in save_terminal_title.
+ local term_title_found="unknown"
# save the former terminal title if we're running in X with xterm.
which xprop &>/dev/null
if [ $? -eq 0 ]; then
# make sure we're actually using xterm *and* that we have a window ID.
if [[ "$TERM" =~ .*"xterm".* && ! -z "$WINDOWID" ]]; then
- local prior_title="$(xprop -id $WINDOWID | perl -nle 'print $1 if /^WM_NAME.+= \"(.*)\"$/')"
- if [ ! -z "$prior_title" ]; then
- if [ ! -z "$DEBUG_TERM_TITLE" ]; then
- echo "saving prior terminal title as '$prior_title'"
- fi
- export PRIOR_TERMINAL_TITLE="$prior_title"
- else
- if [ ! -z "$DEBUG_TERM_TITLE" ]; then
- echo "not saving prior terminal title which was empty"
- fi
- fi
+ term_title_found="$(xprop -id $WINDOWID | perl -nle 'print $1 if /^WM_NAME.+= \"(.*)\"$/')"
+ fi
+ fi
+ echo -n "$term_title_found"
+}
+
+# reads the current terminal title, if possible, and saves it to our record.
+function save_terminal_title()
+{
+ local title="$(get_terminal_title)"
+ if [ "$title" != "unknown" ]; then
+ # there was a title, so save it.
+ if [ ! -z "$DEBUG_TERM_TITLE" ]; then
+ echo "saving prior terminal title as '$title'"
+ fi
+ export PRIOR_TERMINAL_TITLE="$title"
+ else
+ # the terminal had no title, or we couldn't access it, or there's no terminal.
+ if [ ! -z "$DEBUG_TERM_TITLE" ]; then
+ echo "not saving prior terminal title which was empty"
fi
fi
}
fi
pruned_host=$(echo $HOSTNAME | sed -e 's/^\([^\.]*\)\..*$/\1/')
date_string=$(date +"%Y %b %e @ %T")
- user=$USER
+ user=$(logname)
if [ -z "$user" ]; then
# try snagging the windoze name.
user=$USERNAME