Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation on 06/07/2015 in all areas

  1. When it comes to output, options fields are designed to mimic the behavior of Page fields. So $option->title is the primary field you would access from each option when doing output, i.e. echo $page->myfield->title; Or if a multi-value field: foreach($page->myfield as $item) { echo "<li>$item->title</li>"; } Other properties you can access are 'id' and 'value'. In most cases, you will just be accessing the 'title' though. The 'value' property is only used if you defined separate values for your options, i.e. "id=value|title". When it comes to manipulating values, it's really simple. Just make sure that output formatting is OFF ahead of time (like Kixe mentioned above). This is true for any ProcessWire field where you intend to manipulate values. If output formatting is ON, then it's simply not going to work because the Page is not in a state to have it's values manipulated. So for those of you above where it didn't work, chances are your $page output formatting was on. Turn it off like this: $page->of(false); Assign the selected option title you want to to field directly (as a string value). This is just like previous examples on this page: $page->myfield = 'Yes'; If you want multiple selected options, then assign an array of option titles, i.e. $page->myfield = array('Yes', 'Maybe'); You can also use a string to set multiple titles (separated by a pipe "|"): $page->myfield = "Yes|Maybe"; You can also set by the option's 'id' or 'value' properties, using the same syntax as above. Note that when assigning positive numbers, they are always assumed to be the 'id'. So if your option 'title' or 'value' (if used) needs to be numeric, you might want to specifically assign your 'id' properties to be that same number to reduce confusion, i.e. "1=1". If using separate values and titles, it will look for a matching title first. If/when you want to save the changes, then follow it up with: $page->save(); // this $page->save('myfield'); // or this, if you don't need to save anything else
    3 points
  2. Antispambee sounds like a good one! Does it use some external library that is separate from the Wordpress Plugin? We actually do have a means of integrating more types of spam filters into comments, and it's via extending an abstract module class called CommentFilter. This is what CommentFilterAkismet extends, and we could add additional modules that also extend CommentFilter and implement the abstract methods. What's missing right now is for FieldtypeComments to check for additional CommentFilter modules. However, that was always the intention to expand it, and I'd be happy to add this capability, and maybe someone else can implement a new CommentFilterAntispambee or other(s) so that we've got more options here.
    2 points
  3. You can detect whether the current page was loaded from ajax by checking the value of $config->ajax from your template file: <?php if($config->ajax) { // page was requested from ajax } Following that, you will likely want to render the page differently to accommodate whatever you are doing from the javascript side. For instance, you might want do one of these: 1. Deliver alternate or reduced markup when loaded from ajax 2. Deliver a JSON or XML string for parsing from javascript Below are examples of each of these scenarios. 1. Deliver alternate or reduced markup when loaded from ajax You might find checking for ajax helpful when you want portions of pages to load in your site without re-rendering the entire page for each request. As a simple example, we'll use the default ProcessWire site and make it repopulate it's #bodycopy area when you click a page in the top navigation. (To use this example, you'll need the default ProcessWire site templates, though you can easily adapt the example to another situation.) To accomplish this, we'll update our main page template to only include the header and footer markup if the page is NOT being loaded from ajax: /site/templates/page.php <?php if(!$config->ajax) include("./head.inc"); echo $page->body; if(!$config->ajax) include("./foot.inc"); Next we'll update the top navigation to do ajax loads of the pages when the client has javascript (and leave as-is when they don't). Paste this javascript snippet before the closing </head> tag in the header markup file: /site/templates/head.inc: <script type="text/javascript"> $(document).ready(function() { $("#topnav a").click(function() { $("#topnav a.on").removeClass('on'); // unhighlight selected nav item... $(this).addClass('on'); // ...and highlight new nav item $("#bodycopy").html("<p>Loading...</p>"); $.get($(this).attr('href'), function(data) { $("#bodycopy").html(data); }); return false; }); }); </script> Now when you click on any page in the top navigation, it pops into the bodycopy area without a page load visible from your browser. And all pages remain accessible from their URL as well. Note that this is just a test scenario, and I probably wouldn't use this approach for the entire bodycopy area on a production site (it would make bookmarking difficult). But this approach can be very useful in the right places. 2. Deliver a JSON or XML string for parsing from javascript Lets say that you want pages in your site to return a JSON string with the page's id, title, and number of children when it is requested from ajax. When not requested from ajax, they will return their content as normal. To handle the ajax requests, you'd want to add something like this at the top of your template file before any other output. <?php if($config->ajax) { // this is an ajax request, return basic page information in a JSON string $json = array( 'id' => $page->id, 'title' => $page->title, 'numChildren' => $page->numChildren ); echo json_encode($json); return; } // not ajax, continue with regular page output And here is some markup and inline javascript you might use to test the ajax call on some other page (or the same one if you prefer). You would paste this snippet right in your site's markup where you want that info to appear. <ul id='info'></ul> <script type='text/javascript'> var url = '/'; // this is homepage, so replace '/' with page URL you want to load JSON from $(document).ready(function() { $.getJSON(url, function(data) { $.each(data, function(key, value) { $("#info").append("<li>" + key + ": " + value + "</li>"); }); }); }); </script> The above snippet would output something like this: • id: 1 • title: Home • numChildren: 5 To take this example further, you could build an ajax-driven sitemap or any number of web services. Conclusion Hope this helps you to see how simple it is to use ProcessWire to deliver output for ajax. These are just contrived examples, but hopefully examples that might lead to more ideas. In addition, much of what you see in these examples is also applicable to building web services in ProcessWire.
    1 point
  4. Get the last version: from PW modules directory: http://modules.processwire.com/modules/fieldtype-select-ext-option/ from Github: Github: https://github.com/kixe/FieldtypeSelectExtOption From the first view a simple select dropdown Fieldtype. But what is different? Rather than the option field in the modul settings you will find some setting fields to define a source (datatable or field) from where you can pull the options (value and label). I found this very useful when I had to put 250 options in a select field (which I provide in frontend too) and needed this connected and congruent to another more complex table. So now I can make changes in the main table and the select field will take over these changes. You can define the following in the settings: datatable tablecolumn for (value) should be integer tablecolumn for (label) dependance (value or label) order ascending or descending Here we go: https://github.com/k...electExtOption/ (updated 10.03.15 Version 1.0.2 with Multiselect Option) (updated 11.03.15 Version 1.0.3 with Select Inputfields in Settings)
    1 point
  5. Hi Guys, So after reading multiple threads and searching for a solution required for a project, I decided to build a module to enable users to login to the CMS via their gmail account and hopefully the module can also be altered for LinkedIn and other Social accounts. Unfortunately the Facebook module does give you a Facebook ID and assigns the Facebook Role however I would like to assign the role, get the users email and create the user if its not in the system. I have basically taken the Facebook Login Module created by apeisa and manipulated the module to somewhat work with GMAIL. What it does now: It allows you to login but doesn't pull any data from GMAIL. Therefore it automatically logins in to the first account created in ProcessWire, but oddly changes the password, Im sure its due to the line of code in the module that is set to generate a random password via sha1. What I need it to do: If the user isn't in the system, get the gmail email, add the gmail email to the email field in ProcessWire, assign the gmail role or whatever role I want to auto assign it and log them in. ​My resources from Google: https://developers.google.com/accounts/docs/OAuth2 Please note, I will be updating the code as I make progress but please give input or help if you can. As always, thank you so much to everyone for their input and help, especially apeisa and craig a rodway for giving some direction initially. Here is what I have so far: <?php class GoogleLogin extends WireData implements Module, ConfigurableModule { const name = 'google-login'; //the google ID isn't used in Gmail so I am sure this will go away. const fieldName = 'google_id'; public static function getModuleInfo() { return array( "title" => "Google Login for Website", "version" => 001, "summary" => "Allows users to authenticate through their GMAIL account.", "autoload" => false ); } public function init() { } public function execute() { // Prevent association of Google Account to an existing user if ($this->user->isLoggedin()) { echo "Already logged in."; return; } $client_id = $this->googleAppId; $app_secret = $this->googleAppSecret; $redirect_uri = $this->page->httpUrl; $code = $_REQUEST["code"]; if(empty($code)) { $_SESSION['state'] = md5(uniqid(rand(), TRUE)); //CSRF protection $dialog_url = "https://accounts.google.com/o/oauth2/auth?client_id=" . $client_id . "&redirect_uri=" . $redirect_uri . "&state=" . $_SESSION['state'] . "&scope=profile email&response_type=code"; echo("<script> top.location.href='" . $dialog_url . "'</script>"); } if($_SESSION['state'] && ($_SESSION['state'] === $_REQUEST['state'])) { $token_url = "https://accounts.google.com/o/oauth2/auth/access_token?" . "client_id=" . $client_id . "&redirect_uri=" . urlencode($redirect_uri) . "&client_secret=" . $app_secret . "&code=" . $code; $response = file_get_contents($token_url); $params = null; parse_str($response, $params); $access_url = "https://accounts.google.com/o/oauth2/auth/user?access_token=" . $params['access_token']; // Add stream context $options = array('http' => array('user_agent'=> $_SERVER['HTTP_USER_AGENT'])); $context = stream_context_create($options); $ghUserData = json_decode(file_get_contents($access_url, false, $context)); $this->processLogin($ghUserData); } else { echo("The state does not match. You may be a victim of CSRF."); } } public function processLogin($ghUserData) { $id = $ghUserData->id; $u = $this->users->get("google_id=$id"); // First we create random pass to use in login $uniqid = uniqid(); $pass = sha1($uniqid . $id . $ghUserData->updated_at); // User has logged in earlier with Google id, great news let's login if ($u->id) { $u->of(false); $u->pass = $pass; $u->addRole(self::name); $u->save(); } // User has not logged in before and autogenerate is on else if (!$this->disableAutogenerate) { $name = $this->sanitizer->pageName($ghUserData->name, true); $u = $this->users->get("name=$name"); // All seems to be fine, let's create the user $u = new User; $u->name = $name; $u->google_id = $ghUserData->id; $u->pass = $pass; $u->addRole(self::name); $u->save(); } else { echo $this->renderCreateUserForm(); } $this->session->login($u->name, $pass); $p = $this->pages->get($this->redirectPage); if (!$p->id) { $p = $this->pages->get(1); } $this->session->redirect($p->httpUrl); } public function renderCreateUserForm() { } static public function getModuleConfigInputfields(Array $data) { $fields = new InputfieldWrapper(); // since this is a static function, we can't use $this->modules, so get them from the global wire() function $modules = wire('modules'); $field = $modules->get("InputfieldText"); $field->attr('name', 'googleAppId'); $field->attr('value', $data['googleAppId']); $field->label = "Google App Id"; $field->description = 'Client Id for your project. You can create one from here: https://console.developers.google.com'; $fields->add($field); $field = $modules->get("InputfieldText"); $field->attr('name', 'googleAppSecret'); $field->attr('value', $data['googleAppSecret']); $field->label = "Google App Secret"; $field->description = 'Client Secret for your project. Available in your project console here: https://console.developers.google.com'; $fields->add($field); /* $field = $modules->get("InputfieldCheckbox"); $field->attr('name', 'disableAutogenerate'); $field->attr('value', 1); $field->attr('checked', empty($data['disableAutogenerate']) ? '' : 'checked'); $field->label = "Don't set username automatically, but let the user choose username when doing the first login"; $fields->add($field); */ $field = $modules->get("InputfieldPageListSelect"); $field->attr('name', 'redirectPage'); $field->attr('value', $data['redirectPage']); $field->label = "Page where user is redirected after succesful login"; $fields->add($field); return $fields; } public function install() { $name = self::name; $fieldName = self::fieldName; $page = $this->pages->get("/$name/"); if($page->id) throw new WireException("There is already a page installed called '/$name/'"); $template = $this->templates->get($name); if($template) throw new WireException("There is already a template installed called '$name'"); $fieldgroup = $this->fieldgroups->get($name); if($fieldgroup) throw new WireException("There is already a fieldgroup installed called '$name'"); $field = $this->fields->get($fieldName); if($field) throw new WireException("There is already a field installed called '$fieldName'"); $role = $this->roles->get($name); if (!$role->id) { $this->roles->add($name); $this->message("Create role called $name"); } $fieldgroup = new Fieldgroup(); $fieldgroup->name = $name; $title = $this->fields->get('title'); if($title) $fieldgroup->add($title); $fieldgroup->save(); $template = new Template(); $template->name = $name; $template->fieldgroup = $fieldgroup; $template->allowPageNum = 1; $template->save(); $this->message("Installed template $name"); $page = new Page(); $page->template = $template; $page->parent = '/'; $page->name = $name; $page->title = "Google Login"; $page->addStatus(Page::statusHidden); $page->save(); $this->message("Installed page $page->path"); $basename = $name . ".php"; $src = $this->config->paths->SessionGoogleLogin . $basename; $dst = $this->config->paths->templates . $basename; if(@copy($src, $dst)) { $this->message("Installed template file $basename"); } else { $this->error("Templates directory is not writable so we were unable to auto-install the $basename template file."); $this->error("To complete the installation please copy $basename from $src to $dst"); } // Create hidden inputfield $input = new InputfieldText; $input->set('collapsed', Inputfield::collapsedHidden); // Create field called Google and set details and inputfield $f = new Field(); $f->type = $this->modules->get("FieldtypeText"); $f->name = $fieldName; $f->label = 'Google ID'; $f->description = 'Stores Google id for user'; $f->inputfield = $input; $f->save(); // Add the field to user fieldgroup (basically means user template in this context) $fg = $this->fieldgroups->get('user'); $fg->add($f); $fg->save(); } public function uninstall() { } }
    1 point
  6. Hi Ryan, i would like an alternative Spamprotection for the Commentfield. Akismat is expensive and criticle for german users. I use the Plugin Antispambee on my WordPress Blogs, which protects the comments very well! On my projekt i get more than 300 spam comments per day and not one gets published but the real comments are published without any effort on my part. Thats a great work and i found it is one of the best way to protect the comments from spam. All of this works without an external service like Akismet and without JavaScript. The Functions: trust authorized comments can trust comments with gravatar classify BBcode as spam validate the IP-Adress use regular expression use a locale spam database optional: can use a public spam database block or allow certain countries Allow comments only in one language Delete existing spam after X days delete immediately according to defined spam reasons all this is optional and can be activated by the user manually. Is there a API for the Comment Module to implement this functions?
    1 point
  7. Thank you Ryan for your instruction. It deliverd me the solution for the problem. It pointed out that there was an Apache error 406, due to my hosting providers settings. The mentioned Ajax errors were non visible in Chrome Developer Tools (all the way logical, because there are no real Jquery errors, although the console in IE shows them, but that should be incorrect). I found the solution for the Apache error 406 here: https://www.tipsandtricks-hq.com/apache-mod-security-update-how-to-fix-error-406-or-not-acceptable-issue-259 Coding solution 1 in my .htaccess file resolved all problems with uploading files or images: <IfModule mod_security.c> SecFilterEngine Off SecFilterScanPOST Off </IfModule> I am very very happy, and can go on with processwire.
    1 point
  8. @Adrian, Thanks for the suggestions. I am trying to create some time this week to look at these and other pending issues RE my other modules....I know I have been saying this a lot lately but other things/distractions beyond my control keep cropping up.....
    1 point
  9. That something that shouldn't be done before drafting will find it's way into the core. In my opinion content should never be overwritten automatically.
    1 point
  10. You should take a look at this: https://processwire.com/talk/topic/3579-tutorial-approaches-to-categorising-site-content/
    1 point
  11. OR operators do work in 2.6, but I'm not sure about the compatibility with repeaters.
    1 point
  12. http://processwire.com/blog/posts/language-access-control-and-more-special-permissions/ I guess this thread can be marked as solved
    1 point
  13. The Field Value of Type FieldtypeOptions is a WireArray. Easy to manipulate as Soma described above. Furthermore you can use all the methods supported by WireArray as described here: http://processwire.com/api/modules/select-options-fieldtype/#manipulating-options-on-a-page and here http://processwire.com/api/arrays/ In case of updating existing page don't forget to set of(false) $page->of(false); $page->form_type = 'Yes'; $page->save();
    1 point
  14. ok found the missing thing...deeper look in your html shows: <a href="#"> <img src="unfallservice.png"> </a> so the imagename is there....not the path/url...so it was wrong example from me...since i load the image always in a var like: //get the first image from the array of the repeater field $myimage = $col->images->first(); and then use it like: //use the $myimage var to call all needed things like url, name, description... <a href="#"> <img src="<?php echo $myimage->url; ?>" alt="<?php echo $myimage->description; ?>" /> </a> you can set the size, too with this API calls have a good read here: https://processwire.com/api/fieldtypes/images/ there is my example, too for a fast look you could quick change: <a href="#"> <img src="<?php echo $col->images->first(); ?>" /> </a> //to <a href="#"> <img src="<?php echo $col->images->first()->url; ?>" /> </a> best regards mr-fan
    1 point
  15. This is the solution to remove unwanted table attributes from CKEditor. Go to /wire/modules/Inputfield/InputfieldCKEditor/ckeditor-4.4.6/config.js and insert this piece of code into the config.js: CKEDITOR.on( 'dialogDefinition', function( ev ) { var dialogName = ev.data.name; var dialogDefinition = ev.data.definition; if (dialogName == 'table') { // Get the advanced tab reference var infoTab2 = dialogDefinition.getContents('advanced'); //Set the default // Remove the 'Advanced' tab completely dialogDefinition.removeContents('advanced'); // Get the properties tab reference var infoTab = dialogDefinition.getContents('info'); // Remove unnecessary bits from this tab infoTab.remove('txtBorder'); infoTab.remove('cmbAlign'); infoTab.remove('txtWidth'); infoTab.remove('txtHeight'); infoTab.remove('txtCellSpace'); infoTab.remove('txtCellPad'); infoTab.remove('txtCaption'); infoTab.remove('txtSummary'); } }); After that you have only a few attributes left. This is the best way to prevent customers from filling out useless table attributes. This works for other plugins (images, links and so on) too. A big thanks to Lostkobrakai for pointing me into the right direction.
    1 point
  16. Hi all, My team has built a few ProcessWire sites, and we LOVE it - it is now our go to CMS. However, we have come up against the same issue on each site and wonder if/how any of you are managing it. I don't think the issue is entirely specific to ProcessWire, but due to the structure of the way PW stores data it may require an answer that only applies to PW. It's the issue of keeping a staging site database in synch with a live site. To illustrate: we start developing a new section, or modifying the functionality of a current section. All the while the client is making content changes on the current site, and in so doing changing the data in many different tables. Is there an approach, other than manually noting which tables have changed during our update, that people are using to manage this situation? With WordPress, for example, all of the content ends up in one big table, the structure of which doesn't change, so updates aren't that tricky. With PW data and structure being intermingled, it makes it a bit more tricky to manage. I'd really appreciate knowing more about how people manage this, even if it's just confirming that there isn't a better way than doing it manually. Thanks.
    1 point
  17. Link to the module in the modules directory: Modules Directory: http://mods.pw/8s
    1 point
  18. Thank you for your fast response. It works perfectly... Perhaps my following simple solution of your suggestion will help someone with an similar problem. This is the login.inc which is included at the top of every concerning template. <?php // return if is the user currently logged in function loggedIn() { // check, if a gallery is set if(wire("session")->gallery) return true; else return false; } // perform login if($input->post->userLogin) login($input->post->login, $input->post->password); // login user and set the corresponding gallery function login($login, $password) { if($login != '' && $password != '') { // get page to login/password // gc = Gallery Customer $gc = wire('pages')->find("galleryLogin=$login, galleryPassword=$password"); // check exactly if only one login/password combination fits if(count($gc) == 1) { // set gallery in session wire("session")->gallery = $gc->first; return loggedIn(); } else // login data incorrect (or bad configuration) return false; } else // login and/or password are not set return false; } // perform logout if($input->post->userLogout) logout(); // logout user function logout() { // unset gallery in session wire("session")->remove("gallery"); // return current login status to proof success return loggedIn(); } // redirect if(!loggedIn() && $page->name != 'kundengalerie') // redirect to loginform if not logged in, but only if not already on loginform $session->redirect($pages->get("/kundengalerie/")->url, false); elseif(loggedIn() && $page->id != $session->gallery->id) // redirect to gallery if logged in and not the correct gallery page $session->redirect($session->gallery->url, false);
    1 point
  19. Good points. The #1 idea sounds interesting and I like where you are going with that, though I don't think it's safe to consider the meta data independent from the content to the point where it would be safe to version them separately. The recorder modes (#2 & #3) would be great if it weren't for the issue of primary keys you mentioned, but maybe there are places where it could still work. I think that anything that involves merging two versions of the same site has to treat the other's data as totally foreign. Things like IDs have to be thrown out unless you are giving all ID generation authority to one or the other. Personally, I have never seen this as a particular problem to be solved because I've never seen the ability to do it, never expected to be able to, and not sure I could ever trust it if it existed. Nevertheless, I'm enthusiastic about exploring the possibility. But I'd be scared to death of having to provide support for such a feature. Something that I've got a higher comfort level with are specific import/export tools. For instance, a tool that enables me to connect my PW install with another, and browse the fields, templates and pages. Then I can click on "import" and have that field, template or page(s) created on my system too. If there's some matter of meta data to be resolved (like a new page reference with non-existing parent), then I know it then and there and can adjust my import sequence accordingly. If I'm modifying data rather than importing, then I can have the opportunity to see and confirm what's going to happen before it takes place. If we're talking about going between two servers that can't talk to each other, then copy/paste of JSON data from one to the other in the developer's browser is another way to accomplish it. This process seems more supportable because the individual site developer still has some ownership over what's happening.
    1 point
  20. Good question. I'm not sure there's a simple answer though. What you mentioned about migrating changes directly from the database of WordPress doesn't sound like a safe thing to do with any database-driven CMS. The nature of a relational database is that there will be records in different tables referencing each other, so when you've got two versions of the same database, they would be challenging to merge. As a result, it's a problem if you have two databases going off on different paths of changes if they need to come back together at some point. I use my staging servers for staging things like new template files, modules, new PW versions, etc. I don't usually stage actual content from one server to another. Or if I do, I'm copying and pasting it (in PW admin) from one to the other. But the way I usually stage major content changes is to make them on the live server, but in a staging part of the site (unpublished, hidden or otherwise inaccessible pages). When ready to migrate, I publish or drag the relevant pages to their live home. Note that this is for content, and not for anything that could interfere with the operation of the live site. Anything that could interfere with the live site (templates already in use, modules, versions, etc.) ideally should be staged on a different server. I don't know of a better way than that. It's not perfect, but it's always worked well for me. Longer term, I want ProcessWire to support a workflow that lets a staging server talk to a live server and publish from one to the other (using web services). I already use this workflow on a lot of sites with custom modules, but they are specific enough to the individual sites that they aren't suitable as a broader solution. However, I do think we'll have a broader solution like this in the next 6 months to a year.
    1 point
×
×
  • Create New...